ccppccpp home

Profiles

Fastify 5

Fastify 5.12.5+ services and plugins without Fastify 3/4 APIs, with explicit encapsulation, one completion model per hook and handler, trusted schemas, bounded inputs and package-manager-owned dependencies. Rule IDs F5-V/P/H/S/C/T/X; NEED TO blocks.

Set this profile

ccpp cpo profile set fastify-5

Inside Claude Code, prefix the command with ! to run it in the session. Downloading a profile needs a Builder or Team plan; listing them does not.

Policies

110 policies, grouped by what they guard.

Untrusted input 5

Requests, downloads and tool output are validated before they reach SQL, a shell, eval or a deserializer.

  • f5-c06-request-derived-paths

    F5-C06 · File paths and authorities built from request input are contained (no traversal, no Host-header URLs)

  • f5-c06-request-derived-redirect

    F5-C06 · Redirect targets never come straight from request input (open redirect): validate against an allow-list or keep them relative

  • f5-s01-unvalidated-input

    F5-S01 · A route that consumes body, query or params declares a runtime contract for them (schema or an explicit validator)

  • f5-s05-untrusted-schema

    F5-S05 · Schemas compiled by Fastify or Ajv never come from request data

  • f5-s15-cast-as-validation

    F5-S15 · TypeScript casts of request.body/query/params are not validation: a route without a schema does not assert their type

Network & TLS 1

Outbound data, verified certificates, destinations taken from requests and services exposed to others.

  • f5-c05-trust-all-proxies

    F5-C05 · The user confirms trusting every proxy (trustProxy: true): X-Forwarded-* then decides request.ip, host and protocol

Weakened checks 3

Suppressions, disabled lint or CI steps and relaxed strictness need a reason or the user's approval.

  • f5-t06-noop-scripts

    F5-T06 · package.json test, lint and typecheck scripts keep running real checks (no echo/exit 0, no --passWithNoTests added)

  • f5-t06-weakened-checks

    F5-T06 · Checks are not weakened to make a change pass: no blanket ts-ignore/eslint-disable, no focused tests, no no-op test or lint scripts

  • f5-t07-warning-suppression

    F5-T07 · Fastify warnings (FSTDEP, FSTWRN, FSTSEC) are fixed at their source, not suppressed process-wide

Error handling 3

Failures are propagated or handled, never swallowed or turned into fake success.

  • f5-c15-error-handler-override

    F5-C15 · One intentional error handler per scope: no second setErrorHandler on the same instance (FSTWRN004)

  • f5-c16-error-handler-contract

    F5-C16 · Custom error handlers keep error statuses and never expose stacks or internal error data

  • f5-c17-404-in-error-handler

    F5-C17 · Not-found responses are customized with setNotFoundHandler, not by testing 404 in setErrorHandler

Async & concurrency 1

Owned tasks, awaited promises, preserved cancellation and no blocking on event loops.

  • f5-c04-cancellation

    F5-C04 · Under handlerTimeout, outbound calls of a handler follow request.signal (the timeout does not stop work by itself)

Performance & resource bounds 2

Timeouts, bounded retries and fan-out, reused clients, closed resources, no N+1 queries.

  • f5-c02-route-body-limit

    F5-C02 · Route and parser bodyLimit overrides stay finite (no Infinity / MAX_SAFE_INTEGER)

  • f5-s06-compile-on-hot-path

    F5-S06 · Validators, serializers and Ajv instances are built at boot, not per request inside handlers or hooks

Architecture & boundaries 5

Declared layers and dependency directions, no import cycles, small modules, explicit dependencies.

  • f5-p12-import-side-effects

    F5-P12 · A module that exports the app or a plugin does not listen or install process handlers at import (startup owns sockets and signals)

  • f5-p13-transport-objects-in-services

    F5-P13 · Business operations receive their inputs, not Fastify's request or reply objects

  • f5-p14-repeated-cross-cutting

    F5-P14 · Authentication and authorization checks repeated across handlers of a module move into one scoped hook

  • f5-p15-import-cycle

    F5-P15 · A new import does not close a cycle of value imports between modules (up to three hops)

  • f5-p16-handler-budget

    F5-P16 · Route handlers and hooks stay within the project's line budget (maxHandlerLines; off by default)

HTTP & APIs 13

Request and response contracts, schemas, status codes, error bodies and published API surfaces.

  • f5-c08-manual-body-accumulation

    F5-C08 · Bodies are parsed by Fastify's parsers (built-in or addContentTypeParser with parseAs and bodyLimit), not by reading the raw request stream

  • f5-c09-content-type-matching

    F5-C09/C10 · Content-type parsers match precise media types (anchored regexes, no catch-all) and keep the built-in ones

  • f5-c18-version-vary

    F5-C18 · Routes constrained by version also send Vary: Accept-Version (caches must keep variants apart)

  • f5-c20-swagger-legacy-options

    F5-C20 · @fastify/swagger is configured with current options: UI options belong to @fastify/swagger-ui

  • f5-c20-swagger-order

    F5-C20 · @fastify/swagger is registered before the routes it documents, and the spec is generated after ready()

  • f5-s02-response-contract

    F5-S02 · A route with a request schema that returns structured data also declares its response schema

  • f5-s04-duplicate-schema-id

    F5-S04 · Shared schemas registered with addSchema have unique $id values in a scope

  • f5-s07-async-validation

    F5-S07 · Schema validation stays synchronous: no $async schemas or async Ajv keywords for database or network lookups

  • f5-s08-async-validator-envelope

    F5-S08 · An asynchronous custom validator rejects on failure instead of resolving an { error } envelope

  • f5-s10-attach-validation

    F5-S10 · A route with attachValidation: true branches on request.validationError before using the input

  • f5-s14-header-schema-ref

    F5-S14 · Header schemas are inline (case-normalized), not external $ref (FSTSEC002)

  • f5-s16-type-provider-scope

    F5-S16 · A plugin of a project using a type provider keeps it: provider-specific plugin types or withTypeProvider, not a plain FastifyPluginAsync

  • f5-s17-legacy-type-provider

    F5-S17 · Custom type providers implement Fastify 5's validator/serializer members, not the v4 input/output interface

Framework conventions 27

Routing, server and client boundaries, hooks and plugins the way the framework expects them.

  • f5-h01-mixed-completion

    F5-H01 · Plugins and hooks use one completion model: async functions take no done/next callback and never call it

  • f5-h02-hook-names

    F5-H02 · addHook receives a hook Fastify 5 knows; beforeHandler and other legacy names are not hooks

  • f5-h03-late-registration

    F5-H03 · onReady, onListen and onClose hooks do not register routes, hooks, plugins or decorators (the instance has booted)

  • f5-h03-onroute-after-routes

    F5-H03/H17 · onRoute observers are added before the routes they must see, and an onRoute hook adding routes guards against itself

  • f5-h04-body-before-parsing

    F5-H04 · request.body is read only after parsing (preValidation or later), never in onRequest or preParsing

  • f5-h06-preparsing-stream

    F5-H06 · A preParsing hook that replaces the payload returns a stream (not a string, Buffer or parsed object)

  • f5-h07-early-response-continues

    F5-H07 · A hook or handler that answers stops the chain: no done() after reply.send, and a deferred send returns reply

  • f5-h08-send-and-return

    F5-H08 · An async handler completes once: it returns the payload or sends and returns reply, never both

  • f5-h09-async-fallthrough

    F5-H09 · Every path of an async handler returns a payload, sends, throws or hijacks (no silent undefined fall-through)

  • f5-h10-onerror-response

    F5-H10 · onError hooks observe errors: no reply.send and no error passed to done (setErrorHandler maps responses)

  • f5-h11-onsend-payload

    F5-H11 · onSend replaces the payload with a serialized value (string, Buffer, stream or null), never an object

  • f5-h12-response-after-completion

    F5-H12 · onResponse, onTimeout and onRequestAbort hooks never try to send or modify the response

  • f5-h13-raw-response-without-hijack

    F5-H13 · Raw response writes (reply.raw.write/end/writeHead) happen only after reply.hijack(), in an isolated adapter

  • f5-h14-resources-without-onclose

    F5-H14 · A plugin that opens pools, clients, timers or listeners releases them in an onClose (or preClose) hook

  • f5-h15-unhandled-start-stop

    F5-H15 · listen/ready failures are handled and close completes before the process exits

  • f5-h16-readiness-in-onlisten

    F5-H16 · Readiness-critical work (connections, migrations, warm-up) runs in plugin boot or onReady, not in onListen

  • f5-p03-plugin-metadata

    F5-P03 · A fastify-plugin that provides decorators declares its name, and a published plugin declares the Fastify range it supports

  • f5-p05-routes-in-fastify-plugin

    F5-P04/P05 · Routes are not registered directly inside a fastify-plugin wrapper (it breaks encapsulation and register prefixes do not apply)

  • f5-p06-hidden-scope-switch

    F5-P06 · Plugins share scope through fastify-plugin, not by setting Symbol.for('skip-override') or importing Fastify's private lib/ modules

  • f5-p07-detached-decoration

    F5-P07 · Decorations happen during plugin boot (awaited), never later from a .then, timer or event callback

  • f5-p08-shared-request-decoration

    F5-P08 · decorateRequest/decorateReply never share an object, array, Map or Set across requests

  • f5-p09-arrow-this

    F5-P09 · Handlers, hooks and decorators that use Fastify's this are regular functions, not arrow functions

  • f5-p10-duplicate-decorators

    F5-P10 · One decorator name is defined once per scope: no duplicate decorate calls, no hasDecorator guards hiding a second implementation

  • f5-p17-dirname-in-esm

    F5-P17 · ES modules locate plugin and route directories with import.meta.dirname, not the CommonJS __dirname/__filename

  • f5-x09-generator-hint

    F5-X09 · Starting a new Fastify app or plugin by hand gets a hint about the official generators (once per session)

  • f5-x10-generator-overwrite

    F5-X10 · The user confirms a Fastify generator run that integrates into or targets an existing, non-empty directory

  • f5-x12-handrolled-substitutes

    F5-X12 · Fastify code uses the maintained native plugins instead of Express middleware or hand-written CORS, body parsing and security headers

Versions & deprecated APIs 20

Code matches the installed version, without removed, renamed or deprecated APIs.

  • f5-v01-installed-version

    F5-V01/V02/X05 · At session start, Claude learns the installed Fastify version, its bundled docs and plugins that do not match Fastify 5

  • f5-v04-listen-options

    F5-V04 · listen takes an options object ({ port, host }): the positional port/host signature was removed

  • f5-v05-full-json-schema

    F5-V05 · Route schemas given to the default compilers are full JSON Schemas (no v4 shorthand property maps for body, querystring, params, headers or responses)

  • f5-v06-removed-server-options

    F5-V06/V07/V12/C05 · Fastify factory options hold no option Fastify 5 removed or rejects: jsonShortHand, versioning, a logger instance in logger, hop-count trustProxy

  • f5-v08-removed-request-reply-members

    F5-V08/V11 · Route metadata comes from routeOptions, the socket from request.socket and timing from reply.elapsedTime (request.context, routerPath, connection, getResponseTime… were removed)

  • f5-v09-redirect-signature

    F5-V09 · reply.redirect takes the destination first and the status second

  • f5-v10-reply-sent-assignment

    F5-V10 · reply.sent is read-only: the response is taken over with reply.hijack()

  • f5-v12-route-version-option

    F5-V12 · Route versions are constraints (constraints: { version }), not the removed version route option

  • f5-v13-params-prototype

    F5-V13 · request.params has no prototype: no params.hasOwnProperty/toString, use Object.hasOwn

  • f5-v14-trailer-completion

    F5-V14 · reply.trailer producers are async or take the done callback (direct return was removed)

  • f5-v15-head-after-get

    F5-V15 · A custom HEAD route is registered before the GET of the same URL, or that GET sets exposeHeadRoutes: false

  • f5-v16-has-route-definition

    F5-V16 · hasRoute receives a route definition (method + pattern), not a concrete request URL

  • f5-v17-unsupported-methods

    F5-V17 · Routes use HTTP methods Fastify 5 supports, or register the WebDAV ones with addHttpMethod first

  • f5-v18-add-existing-method

    F5-V18 · addHttpMethod for a method Fastify already supports passes overrideExisting: true deliberately (FSTDEP025)

  • f5-v19-hostname-port

    F5-V19 · request.hostname has no port since Fastify 5: the authority is request.host, the port request.port

  • f5-v20-default-route

    F5-V20 · No setDefaultRoute/getDefaultRoute (removed): setNotFoundHandler, or routerOptions.defaultRoute for low-level routing

  • f5-v21-server-option-choices

    F5-V21/V22/V25/C02/C03/C07/C14/C19/S11/S12 · Factory options use routerOptions and logController, keep safe parsing and bounded inputs, and make Ajv and request-ID choices deliberate

  • f5-v23-middleware-adapter

    F5-V23 · fastify.use needs a middleware adapter (@fastify/middie or @fastify/express) installed and registered; native hooks and plugins come first

  • f5-v24-middleware-raw-objects

    F5-V24 · Middleware under @fastify/middie gets raw Node req/res: no Fastify or Express reply methods there

  • f5-v25-wire-format

    F5-V25 · New date-time/time formats are checked against Fastify 5's timezone requirement (hint)

Configuration 1

Settings read once at a boundary, validated, with units and without unsafe defaults.

  • f5-c01-env-in-route-modules

    F5-C01 · Route and plugin modules receive configuration through options or a decorator; process.env is read at the startup/config boundary

Logging & observability 2

Structured logs, bounded metric labels, no debug prints left in production code.

  • f5-c11-console-logging

    F5-C11 · Request work logs through request.log / fastify.log (structured, correlated), not console

  • f5-c12-whole-object-logging

    F5-C12 · Logs do not dump whole requests, headers, bodies or queries (secrets reach the log store)

Dependencies & toolchain 19

Packages change through the package manager, lockfiles stay consistent and the project's tools are used.

  • f5-v02-install-versions

    F5-V02 · Fastify is installed within 5.x at or above the security floor: no 3/4/6 specs, prerelease tags or ranges that cannot reach the floor

  • f5-v02-manifest-range

    F5-V02 · package.json keeps fastify on a 5.x range that can resolve at or above the security floor

  • f5-v03-node-runtime

    F5-V03 · A declared Node.js runtime of a Fastify project meets Fastify 5's minimum (Node 20): engines, .nvmrc, Dockerfile, CI matrices

  • f5-x01-package-manager

    F5-X01 · Dependencies are installed with the project's package manager (packageManager field, lockfile)

  • f5-x01-second-lockfile

    F5-X01 · No second lockfile next to another package manager's lockfile

  • f5-x02-hand-written-manifest

    F5-X02 · A new package.json without dependencies is initialized with the package manager

  • f5-x02-new-manifest

    F5-X02/X03 · A new package.json comes from the package-manager initializer (or a generator), and gets its dependencies from the package manager

  • f5-x03-manual-dependency-edit

    F5-X03 · Dependencies change through the package manager (add/remove/update), never by editing package.json by hand

  • f5-x03-package-manager-call

    Records the tool calls that run a package manager (they may rewrite package.json and lockfiles; F5-X03/X05/X06)

  • f5-x04-version-lookups

    F5-X04 · Registry version lookups are not a step before installing

  • f5-x04-version-pins

    F5-X04 · Packages are installed by name: no guessed exact version, no --save-exact without a reason

  • f5-x05-forced-installs

    F5-X05/X06 · Dependency conflicts are repaired, not masked with --force, --legacy-peer-deps or broad overrides

  • f5-x05-install-plugins

    F5-X05 · Installs use the maintained @fastify/* package names and plugin majors compatible with Fastify 5

  • f5-x05-installed-compat

    F5-V02/X05 · After a package-manager run, the Fastify and plugins it touched resolve to Fastify-5-compatible releases

  • f5-x05-plugin-fastify-range

    F5-X05 · A fastify-plugin wrapper declares a Fastify range that includes 5 (a 3.x/4.x range fails at boot)

  • f5-x06-hand-edited-lockfile

    F5-X06 · Lockfiles are written by the package manager only (no hand edit, no script rewriting them)

  • f5-x06-stale-lockfile

    F5-X06 · A turn that changes package.json dependencies also updates the lockfile next to it (once per stop)

  • f5-x07-ci-frozen-install

    F5-X07 · CI and image builds of a project with a lockfile install from it (npm ci, frozen/immutable installs)

  • f5-x08-pkg-set-hint

    F5-X08 · Hand edits of package.json metadata or scripts get a once-per-session hint about npm pkg set/delete

Tests 2

Tests assert behavior, stay deterministic and are not removed or skipped silently.

  • f5-t01-mocked-framework

    F5-T01 · Route tests run real Fastify registration through inject, not a mocked fastify module or hand-made reply objects

  • f5-t03-unclosed-test-app

    F5-T03 · A test that injects into or listens on a Fastify instance closes it (teardown hook or finally), even when the test fails

Completion evidence 6

A turn ends with its checks run and passing; claims of success need proof after the last edit.

  • f5-t06-check-call

    Records which checks (lint, typecheck, build, test) a shell call runs, from the parsed commands (F5-T06 ledger)

  • f5-t06-completion-claims

    F5-T06 · A final message claiming passing lint, types, build or tests needs a successful run after Claude's last edit

  • f5-t06-evidence-ledger

    F5-T06 · Records, per session, Claude's edits (file tools and shell writes) and the lint, typecheck, build and test runs that succeeded after them

  • f5-t06-new-type-errors

    F5-T06/S15 · A turn adds no TypeScript errors to the changed files or their importers (language server, once per stop)

  • f5-t06-required-checks

    F5-T06 · A turn that changed Fastify code ends only after the configured lint, typecheck and tests succeeded on the final files (once per stop)

  • f5-turn-baseline

    Records the work tree when a prompt starts: the baseline of the Fastify end-of-turn checks (F5-X06, F5-T06)