@@ -143,3 +143,3 @@ <h1 align="center">Fastify</h1> | ||
| const fastify = require('fastify')() | ||
| const fastifyPlugin = require('fastify-plugin') | ||
| const fp = require('fastify-plugin') | ||
@@ -165,3 +165,3 @@ fastify.decorateRequest('answer', 42) | ||
| childServer.register(fastifyPlugin(grandchildContext)) | ||
| childServer.register(fp(grandchildContext)) | ||
@@ -197,2 +197,31 @@ async function grandchildContext (grandchildServer) { | ||
| `fastify-plugin` breaks encapsulation only for the plugin it wraps. Plugins | ||
| registered inside it without `fastify-plugin` still create new encapsulated | ||
| contexts: | ||
| ```js | ||
| 'use strict' | ||
| const fastify = require('fastify')() | ||
| const fp = require('fastify-plugin') | ||
| fastify.register(fp(async function sharedContext (childServer) { | ||
| childServer.decorate('foo', 'foo') | ||
| childServer.register(async function encapsulatedContext (grandchildServer) { | ||
| grandchildServer.decorate('bar', 'bar') | ||
| }) | ||
| })) | ||
| await fastify.ready() | ||
| console.log(fastify.foo) // 'foo' | ||
| console.log(fastify.bar) // undefined | ||
| ``` | ||
| The `foo` decorator is available in the root context because it is added | ||
| directly by the plugin wrapped with `fastify-plugin`. The nested `register` | ||
| call still creates a grandchild context, so the `bar` decorator remains | ||
| available only in that context and its children. | ||
| [fastify-plugin]: https://github.com/fastify/fastify-plugin |
@@ -14,2 +14,3 @@ <h1 align="center">Fastify</h1> | ||
| - [Catching Uncaught Errors In Fastify](#catching-uncaught-errors-in-fastify) | ||
| - [What The Default Error Handler Sends](#what-the-default-error-handler-sends) | ||
| - [Errors In Fastify Lifecycle Hooks And A Custom Error Handler](#errors-in-fastify-lifecycle-hooks-and-a-custom-error-handler) | ||
@@ -146,7 +147,79 @@ - [Fastify Error Codes](#fastify-error-codes) | ||
| In both cases, the error will be caught safely and routed to Fastify's default | ||
| error handler, resulting in a generic `500 Internal Server Error` response. | ||
| error handler, resulting in a `500 Internal Server Error` response. | ||
| To customize this behavior, use | ||
| [`setErrorHandler`](./Server.md#seterrorhandler). | ||
| #### What The Default Error Handler Sends | ||
| The default error handler serializes the error into a JSON body with the | ||
| `statusCode`, `error`, and `message` properties, plus `code` when the error | ||
| carries one: | ||
| ```js | ||
| app.get('/', async () => { throw new Error('kaboom') }) | ||
| ``` | ||
| ```json | ||
| { | ||
| "statusCode": 500, | ||
| "error": "Internal Server Error", | ||
| "message": "kaboom" | ||
| } | ||
| ``` | ||
| The `error` property is the generic HTTP status text, but **`message` is | ||
| `error.message` verbatim**. This applies to every status code, including `500`. | ||
| Fastify's built-in error serializer emits only these four properties, so the | ||
| stack trace is not part of the default payload — but a route-level response | ||
| schema replaces that serializer, and one that declares a `stack` property will | ||
| serialize it. | ||
| > Security: | ||
| > Because `message` and `code` are forwarded as-is, errors thrown by libraries | ||
| > deeper in your application are exposed to the client. A database driver | ||
| > error, for example, can leak schema details and query text: | ||
| > | ||
| > ```json | ||
| > { | ||
| > "statusCode": 500, | ||
| > "code": "ER_BAD_FIELD_ERROR", | ||
| > "error": "Internal Server Error", | ||
| > "message": "Unknown column 'username' in 'field list'" | ||
| > } | ||
| > ``` | ||
| > | ||
| > Fastify does not distinguish between development and production here. If | ||
| > unexpected errors must not reach the client, handle this explicitly. | ||
| Register a [`setErrorHandler`](./Server.md#seterrorhandler) to replace the | ||
| message for errors you did not raise deliberately, while letting the ones you | ||
| did pass through: | ||
| ```js | ||
| app.setErrorHandler(function (error, request, reply) { | ||
| // Errors with a statusCode below 500 were raised deliberately by this | ||
| // application, as were validation errors. A status code below 500 is not on | ||
| // its own a guarantee that the message is safe to expose — narrow this | ||
| // condition if any of yours are not. | ||
| if (error.validation || (error.statusCode && error.statusCode < 500)) { | ||
| return reply.send(error) | ||
| } | ||
| // Anything else is unexpected: log it, but do not describe it to the client. | ||
| this.log.error({ err: error }, 'unhandled error') | ||
| reply.status(500).send({ | ||
| statusCode: 500, | ||
| error: 'Internal Server Error', | ||
| message: 'Internal Server Error' | ||
| }) | ||
| }) | ||
| ``` | ||
| The handler above is registered on the root instance, so it applies to every | ||
| route. Error handlers are encapsulated; for how they resolve across plugin | ||
| contexts, see | ||
| [the next section](#errors-in-fastify-lifecycle-hooks-and-a-custom-error-handler). | ||
| Note that a route-level response schema is still applied to whatever the error | ||
| handler sends, and will reshape the payload accordingly — including properties | ||
| the built-in error serializer would have omitted, such as `stack`. See | ||
| [Serialization](./Validation-and-Serialization.md#serialization). | ||
| ### Errors In Fastify Lifecycle Hooks And A Custom Error Handler | ||
@@ -153,0 +226,0 @@ |
@@ -12,3 +12,7 @@ <h1 align="center">Fastify</h1> | ||
| other content types. | ||
| - `params` - The params matching the URL. | ||
| - `params` - The params matching the URL. Values are percent-decoded | ||
| (for example `%20` becomes a space, and `%2f` becomes `/`). Decoded | ||
| values may contain `.`, `..`, `/`, or other characters; treat them as | ||
| untrusted input. See the security note below and | ||
| [Routes - Url building](./Routes.md#url-building). | ||
| - [`headers`](#headers) - The headers getter and setter. | ||
@@ -39,9 +43,17 @@ - `raw` - The incoming HTTP request from Node core. | ||
| > ⚠️ Security: | ||
| > `request.params`, `request.query`, `request.headers`, and `request.body` | ||
| > are untrusted network input. Route parameter values are percent-decoded | ||
| > before your handler runs, so a segment like `..%2ffile` becomes `../file` | ||
| > in `request.params`. Do not use parameter values as filesystem paths, | ||
| > template names, or redirect targets without validating or containing | ||
| > them. Prefer [`@fastify/static`](https://github.com/fastify/fastify-static) | ||
| > (or `reply.sendFile`) when serving files from a root directory. | ||
| > | ||
| > `request.ip`, `request.ips`, `request.host`, `request.hostname`, | ||
| > `request.port`, and `request.protocol` come from request metadata | ||
| > (socket and/or forwarding headers) and should be treated as untrusted input. | ||
| > Fastify does not perform security validation for business logic. | ||
| > If these values are used in security-sensitive decisions, they must | ||
| > be validated explicitly (for example: trusted proxy configuration, | ||
| > allow-lists, strict parsing, and normalization). | ||
| > (socket and/or forwarding headers) and should also be treated as | ||
| > untrusted input. Fastify does not perform security validation for | ||
| > business logic. If these values are used in security-sensitive decisions, | ||
| > they must be validated explicitly (for example: trusted proxy | ||
| > configuration, allow-lists, strict parsing, and normalization). | ||
@@ -48,0 +60,0 @@ - `method` - The method of the incoming request. |
@@ -331,2 +331,15 @@ <h1 align="center">Fastify</h1> | ||
| > ⚠️ Security: | ||
| > Fastify (via find-my-way) percent-decodes route parameters and wildcards | ||
| > before they reach your handler. Encoded separators in a segment are | ||
| > decoded in the parameter value: for a route `/download/:file`, a request | ||
| > to `/download/..%2fsecret.txt` yields | ||
| > `request.params.file === '../secret.txt'`. Parameters are untrusted | ||
| > input. Do not pass them to `path.join`, `fs` APIs, template engines, or | ||
| > redirects without validation or path containment. To serve files from a | ||
| > directory root, use | ||
| > [`@fastify/static`](https://github.com/fastify/fastify-static) instead of | ||
| > joining `request.params` into a filesystem path yourself. See also | ||
| > [Request](./Request.md). | ||
| To include a colon in a path without declaring a parameter, use a double colon. | ||
@@ -333,0 +346,0 @@ For example: |
+1
-1
| 'use strict' | ||
| const VERSION = '5.11.2' | ||
| const VERSION = '5.11.3' | ||
@@ -5,0 +5,0 @@ const Avvio = require('avvio') |
@@ -148,2 +148,6 @@ 'use strict' | ||
| const parserRegExp = this.parserRegExpList[j] | ||
| // A parser registered with a `g`/`y` flagged RegExp keeps a mutable | ||
| // lastIndex between calls, so a plain `.test()` would skip part of the | ||
| // next content type and match inconsistently. Reset it before testing. | ||
| parserRegExp.lastIndex = 0 | ||
| if (parserRegExp.test(ct)) { | ||
@@ -150,0 +154,0 @@ parser = this.customParsers.get(parserRegExp.toString()) |
+1
-1
@@ -124,3 +124,3 @@ 'use strict' | ||
| for (let i = 0; i !== deps.length; ++i) { | ||
| if (!checkExistence(instance, deps[i])) { | ||
| if (!checkExistence(instance, deps[i]) && !hasInstanceProperty(instance, deps[i])) { | ||
| throw new FST_ERR_DEC_MISSING_DEPENDENCY(deps[i]) | ||
@@ -127,0 +127,0 @@ } |
@@ -57,3 +57,3 @@ 'use strict' | ||
| * | ||
| * @param {Error | null} error Error that occurred during the response, if any. | ||
| * @param {Error | null | undefined} error Error that occurred during the response, if any. | ||
| * @param {object} request Fastify request object. | ||
@@ -60,0 +60,0 @@ * @param {object} reply Fastify reply object. |
+7
-3
@@ -598,5 +598,9 @@ 'use strict' | ||
| } | ||
| // it must be chunked for trailer to work | ||
| reply.header('Transfer-Encoding', 'chunked') | ||
| reply.header('Trailer', header.trim()) | ||
| if (header !== '') { | ||
| // it must be chunked for trailer to work | ||
| reply.header('Transfer-Encoding', 'chunked') | ||
| reply.header('Trailer', header.trim()) | ||
| } else { | ||
| reply[kReplyTrailers] = null | ||
| } | ||
| } | ||
@@ -603,0 +607,0 @@ |
+1
-1
| { | ||
| "name": "fastify", | ||
| "version": "5.11.2", | ||
| "version": "5.11.3", | ||
| "description": "Fast and low overhead web framework, for Node.js", | ||
@@ -5,0 +5,0 @@ "main": "fastify.js", |
+7
-7
@@ -76,3 +76,3 @@ <div align="center"> <a href="https://fastify.dev/"> | ||
| Generate a fastify project with `npm init`: | ||
| Generate a Fastify project with `npm init`: | ||
@@ -125,3 +125,3 @@ ```sh | ||
| }) | ||
| // CommonJs | ||
| // CommonJS | ||
| const fastify = require('fastify')({ | ||
@@ -152,3 +152,3 @@ logger: true | ||
| }) | ||
| // CommonJs | ||
| // CommonJS | ||
| const fastify = require('fastify')({ | ||
@@ -187,3 +187,3 @@ logger: true | ||
| - **Highly performant:** as far as we know, Fastify is one of the fastest web | ||
| frameworks in town, depending on the code complexity we can serve up to 76+ | ||
| frameworks in town, depending on the code complexity we can serve more than 76 | ||
| thousand requests per second. | ||
@@ -196,3 +196,3 @@ - **Extensible:** Fastify is fully extensible via its hooks, plugins, and | ||
| function. | ||
| - **Logging:** logs are extremely important but are costly; we chose the best | ||
| - **Logging:** logs are extremely important, but are costly; we chose the best | ||
| logger to almost remove this cost, [Pino](https://github.com/pinojs/pino)! | ||
@@ -220,4 +220,4 @@ - **Developer friendly:** the framework is built to be very expressive and help | ||
| These benchmarks taken using https://github.com/fastify/benchmarks. This is a | ||
| synthetic "hello world" benchmark that aims to evaluate the framework overhead. | ||
| These benchmarks were taken using https://github.com/fastify/benchmarks. This is | ||
| a synthetic "hello world" benchmark that aims to evaluate the framework overhead. | ||
| The overhead that each framework has on your application depends on your | ||
@@ -224,0 +224,0 @@ application. You should __always__ benchmark if performance matters to you. |
@@ -581,2 +581,34 @@ 'use strict' | ||
| test('content-type match - RegExp with global flag', async t => { | ||
| t.plan(4) | ||
| const fastify = Fastify() | ||
| fastify.addContentTypeParser(/^application\/.+\+xml$/g, { parseAs: 'string' }, function (request, body, done) { | ||
| done(null, body) | ||
| }) | ||
| fastify.post('/', async (request) => request.body) | ||
| // Two distinct content types that both match the parser. A RegExp with the | ||
| // `g` flag keeps a mutable lastIndex between test() calls, so the second | ||
| // content type must still match rather than fall through to 415. | ||
| const first = await fastify.inject({ | ||
| method: 'POST', | ||
| path: '/', | ||
| headers: { 'content-type': 'application/vnd.a+xml' }, | ||
| body: '<a/>' | ||
| }) | ||
| const second = await fastify.inject({ | ||
| method: 'POST', | ||
| path: '/', | ||
| headers: { 'content-type': 'application/vnd.b+xml' }, | ||
| body: '<b/>' | ||
| }) | ||
| t.assert.strictEqual(first.statusCode, 200) | ||
| t.assert.strictEqual(first.payload, '<a/>') | ||
| t.assert.strictEqual(second.statusCode, 200) | ||
| t.assert.strictEqual(second.payload, '<b/>') | ||
| }) | ||
| test('content-type fail when parameters not match - string 1', async t => { | ||
@@ -583,0 +615,0 @@ t.plan(1) |
@@ -64,1 +64,50 @@ 'use strict' | ||
| }) | ||
| test('decorateRequest accepts built-in request properties as dependencies', t => { | ||
| t.plan(6) | ||
| const fastify = Fastify() | ||
| for (const name of ['id', 'params', 'raw', 'query', 'log', 'body']) { | ||
| t.assert.doesNotThrow(() => fastify.decorateRequest(`uses_${name}`, null, [name])) | ||
| } | ||
| }) | ||
| test('decorateReply accepts built-in reply properties as dependencies', t => { | ||
| t.plan(3) | ||
| const fastify = Fastify() | ||
| for (const name of ['raw', 'request', 'log']) { | ||
| t.assert.doesNotThrow(() => fastify.decorateReply(`uses_${name}`, null, [name])) | ||
| } | ||
| }) | ||
| test('decorateRequest accepts built-in properties as dependencies inside a plugin', (t, done) => { | ||
| t.plan(2) | ||
| const fastify = Fastify() | ||
| fastify.register(async (instance) => { | ||
| instance.decorateRequest('scopedExtra', null, ['body']) | ||
| t.assert.equal(instance.hasRequestDecorator('scopedExtra'), true) | ||
| }) | ||
| fastify.ready((err) => { | ||
| t.assert.ifError(err) | ||
| done() | ||
| }) | ||
| }) | ||
| test('decorateRequest accepts built-in properties mixed with user decorators as dependencies', t => { | ||
| t.plan(1) | ||
| const fastify = Fastify() | ||
| fastify.decorateRequest('userProp', null) | ||
| t.assert.doesNotThrow(() => fastify.decorateRequest('mixed', null, ['userProp', 'body'])) | ||
| }) | ||
| test('decorateRequest still throws FST_ERR_DEC_MISSING_DEPENDENCY for unknown dependencies', t => { | ||
| t.plan(2) | ||
| const fastify = Fastify() | ||
| t.assert.throws( | ||
| () => fastify.decorateRequest('withUnknown', null, ['nonExistent']), | ||
| (err) => err.code === 'FST_ERR_DEC_MISSING_DEPENDENCY' | ||
| ) | ||
| t.assert.throws( | ||
| () => fastify.decorateReply('withUnknown', null, ['body']), | ||
| (err) => err.code === 'FST_ERR_DEC_MISSING_DEPENDENCY' | ||
| ) | ||
| }) |
@@ -145,2 +145,33 @@ 'use strict' | ||
| test('remove trailer while stream is being consumed', (t, testDone) => { | ||
| t.plan(5) | ||
| const fastify = Fastify() | ||
| fastify.get('/', function (request, reply) { | ||
| const stream = Readable.from((function * () { | ||
| reply.removeTrailer('ETag') | ||
| yield 'hello' | ||
| })()) | ||
| reply.trailer('ETag', function () { | ||
| t.assert.fail('removed trailer should not be called') | ||
| }) | ||
| reply.send(stream) | ||
| }) | ||
| fastify.inject({ | ||
| method: 'GET', | ||
| url: '/' | ||
| }, (error, res) => { | ||
| t.assert.ifError(error) | ||
| t.assert.strictEqual(res.statusCode, 200) | ||
| t.assert.strictEqual(res.payload, 'hello') | ||
| t.assert.strictEqual(res.headers.trailer, 'etag') | ||
| t.assert.strictEqual(res.trailers.etag, undefined) | ||
| testDone() | ||
| }) | ||
| }) | ||
| test('send trailers when using async-await', (t, testDone) => { | ||
@@ -399,6 +430,6 @@ t.plan(5) | ||
| t.assert.strictEqual(res.statusCode, 200) | ||
| t.assert.ok(!res.headers.trailer) | ||
| t.assert.ok(!res.trailers.etag) | ||
| t.assert.ok(!res.trailers['should-not-call']) | ||
| t.assert.ok(!res.headers['content-length']) | ||
| t.assert.strictEqual(res.headers.trailer, undefined) | ||
| t.assert.strictEqual(res.trailers.etag, undefined) | ||
| t.assert.strictEqual(res.trailers['should-not-call'], undefined) | ||
| t.assert.strictEqual(res.headers['content-length'], '0') | ||
| testDone() | ||
@@ -408,2 +439,59 @@ }) | ||
| test('remove some trailers should keep trailer mode for the remaining ones', (t, testDone) => { | ||
| t.plan(6) | ||
| const fastify = Fastify() | ||
| fastify.get('/', function (request, reply) { | ||
| reply.trailer('ETag', function () { | ||
| t.assert.fail('removed trailer should not be called') | ||
| }) | ||
| reply.removeTrailer('ETag') | ||
| reply.trailer('Content-MD5', function (reply, payload, done) { | ||
| done(null, 'custom-md5') | ||
| }) | ||
| reply.send('hello') | ||
| }) | ||
| fastify.inject({ | ||
| method: 'GET', | ||
| url: '/' | ||
| }, (error, res) => { | ||
| t.assert.ifError(error) | ||
| t.assert.strictEqual(res.statusCode, 200) | ||
| t.assert.strictEqual(res.headers.trailer, 'content-md5') | ||
| t.assert.strictEqual(res.headers['transfer-encoding'], 'chunked') | ||
| t.assert.strictEqual(res.headers['content-length'], undefined) | ||
| t.assert.strictEqual(res.trailers['content-md5'], 'custom-md5') | ||
| testDone() | ||
| }) | ||
| }) | ||
| test('remove all trailers should behave like no trailers were registered', (t, testDone) => { | ||
| t.plan(6) | ||
| const fastify = Fastify() | ||
| fastify.get('/', function (request, reply) { | ||
| reply.trailer('ETag', function () { | ||
| t.assert.fail('removed trailer should not be called') | ||
| }) | ||
| reply.removeTrailer('ETag') | ||
| reply.send('hello') | ||
| }) | ||
| fastify.inject({ | ||
| method: 'GET', | ||
| url: '/' | ||
| }, (error, res) => { | ||
| t.assert.ifError(error) | ||
| t.assert.strictEqual(res.statusCode, 200) | ||
| t.assert.strictEqual(res.headers.trailer, undefined) | ||
| t.assert.strictEqual(res.headers['transfer-encoding'], undefined) | ||
| t.assert.strictEqual(res.headers['content-length'], '5') | ||
| t.assert.strictEqual(res.trailers.etag, undefined) | ||
| testDone() | ||
| }) | ||
| }) | ||
| test('hasTrailer', (t, testDone) => { | ||
@@ -410,0 +498,0 @@ t.plan(10) |
@@ -127,3 +127,3 @@ import { FastifyError } from '@fastify/error' | ||
| requestCompleted ( | ||
| error: Error | null, | ||
| error: Error | null | undefined, | ||
| request: FastifyRequest, | ||
@@ -130,0 +130,0 @@ reply: FastifyReply, |
| { | ||
| "entries": [ | ||
| { | ||
| "type": "exact", | ||
| "value": "git fetch --all --prune --quiet && echo FETCH_OK || echo FETCH_FAILED", | ||
| "addedAt": "2026-02-07T10:03:03.909Z", | ||
| "note": "Always accept", | ||
| "source": "user" | ||
| }, | ||
| { | ||
| "type": "exact", | ||
| "value": "npm install --ignore-scripts", | ||
| "addedAt": "2026-02-07T10:21:13.104Z", | ||
| "note": "Always accept", | ||
| "source": "user" | ||
| }, | ||
| { | ||
| "type": "exact", | ||
| "value": "npm run coverage:ci-check-coverage", | ||
| "addedAt": "2026-02-07T15:10:08.412Z", | ||
| "note": "Always accept", | ||
| "source": "user" | ||
| }, | ||
| { | ||
| "type": "exact", | ||
| "value": "node --test test/web-api.test.js", | ||
| "addedAt": "2026-02-07T15:24:46.872Z", | ||
| "note": "Always accept", | ||
| "source": "user" | ||
| }, | ||
| { | ||
| "type": "exact", | ||
| "value": "gpg --list-secret-keys --keyid-format=long || true && git config --get user.signingkey && git config --get commit.gpgsign", | ||
| "addedAt": "2026-02-11T04:42:44.716Z", | ||
| "note": "Always accept", | ||
| "source": "user" | ||
| }, | ||
| { | ||
| "type": "exact", | ||
| "value": "cd /home/matteo/repositories/fastify && npm run test:typescript", | ||
| "addedAt": "2026-02-19T20:22:59.298Z", | ||
| "note": "Always accept", | ||
| "source": "user" | ||
| }, | ||
| { | ||
| "type": "exact", | ||
| "value": "cd /home/matteo/repositories/fastify && git checkout -- types/route.d.ts", | ||
| "addedAt": "2026-02-19T20:23:45.912Z", | ||
| "note": "Always accept", | ||
| "source": "user" | ||
| }, | ||
| { | ||
| "type": "exact", | ||
| "value": "cd /home/matteo/repositories/fastify && git status --short && git diff -- types/route.d.ts test/types/route.test-d.ts | sed -n '1,220p'", | ||
| "addedAt": "2026-02-19T20:25:10.175Z", | ||
| "note": "Always accept", | ||
| "source": "user" | ||
| }, | ||
| { | ||
| "type": "exact", | ||
| "value": "cd /home/matteo/repositories/fastify && git checkout -- types/route.d.ts test/types/route.test-d.ts && npm run test:typescript", | ||
| "addedAt": "2026-02-19T20:25:56.421Z", | ||
| "note": "Always accept", | ||
| "source": "user" | ||
| }, | ||
| { | ||
| "type": "exact", | ||
| "value": "cd /home/matteo/repositories/fastify && npm run lint:eslint -- types/route.d.ts test/types/route.test-d.ts", | ||
| "addedAt": "2026-02-19T20:27:12.547Z", | ||
| "note": "Always accept", | ||
| "source": "user" | ||
| }, | ||
| { | ||
| "type": "exact", | ||
| "value": "cd /home/matteo/repositories/fastify && git checkout -b fix/6513-route-hook-meta-return", | ||
| "addedAt": "2026-02-19T20:27:31.569Z", | ||
| "note": "Always accept", | ||
| "source": "user" | ||
| }, | ||
| { | ||
| "type": "exact", | ||
| "value": "cd /home/matteo/repositories/fastify && git add types/route.d.ts test/types/route.test-d.ts && git commit -m \"fix(types): allow async route hooks in shorthand options\"", | ||
| "addedAt": "2026-02-19T20:27:35.261Z", | ||
| "note": "Always accept", | ||
| "source": "user" | ||
| }, | ||
| { | ||
| "type": "exact", | ||
| "value": "cd /home/matteo/repositories/fastify && npx tsc /tmp/ctx-test2.ts --noEmit --strict", | ||
| "addedAt": "2026-02-19T20:44:45.451Z", | ||
| "note": "Always accept", | ||
| "source": "user" | ||
| }, | ||
| { | ||
| "type": "exact", | ||
| "value": "cd /home/matteo/repositories/fastify && npx tsd --files test/types/hooks.test-d.ts", | ||
| "addedAt": "2026-02-19T20:47:16.815Z", | ||
| "note": "Always accept", | ||
| "source": "user" | ||
| }, | ||
| { | ||
| "type": "exact", | ||
| "value": "cd /home/matteo/repositories/fastify && npx tsd --files test/types/route.test-d.ts", | ||
| "addedAt": "2026-02-19T20:47:37.607Z", | ||
| "note": "Always accept", | ||
| "source": "user" | ||
| }, | ||
| { | ||
| "type": "exact", | ||
| "value": "cd /home/matteo/repositories/fastify && npx tsd", | ||
| "addedAt": "2026-02-19T20:48:17.812Z", | ||
| "note": "Always accept", | ||
| "source": "user" | ||
| }, | ||
| { | ||
| "type": "exact", | ||
| "value": "cd /home/matteo/repositories/fastify && npm run lint:eslint -- types/route.d.ts && npx tsd --files test/types/hooks.test-d.ts && npx tsd --files test/types/route.test-d.ts", | ||
| "addedAt": "2026-02-19T20:51:05.173Z", | ||
| "note": "Always accept", | ||
| "source": "user" | ||
| }, | ||
| { | ||
| "type": "exact", | ||
| "value": "which gh && gh --version && gh auth status", | ||
| "addedAt": "2026-02-20T07:03:43.023Z", | ||
| "note": "Always accept", | ||
| "source": "user" | ||
| }, | ||
| { | ||
| "type": "exact", | ||
| "value": "gh api repos/fastify/fastify/rulesets", | ||
| "addedAt": "2026-02-22T22:29:22.020Z", | ||
| "note": "Always accept", | ||
| "source": "user" | ||
| }, | ||
| { | ||
| "type": "exact", | ||
| "value": "node --test test/fastify-instance.test.js", | ||
| "addedAt": "2026-02-23T07:23:34.147Z", | ||
| "note": "Always accept", | ||
| "source": "user" | ||
| }, | ||
| { | ||
| "type": "exact", | ||
| "value": "nl -ba scripts/validate-ecosystem-links.js | sed -n '1,220p'", | ||
| "addedAt": "2026-02-23T07:23:55.927Z", | ||
| "note": "Always accept", | ||
| "source": "user" | ||
| }, | ||
| { | ||
| "type": "pattern", | ||
| "value": "^npx\\s+eslint\\s+[\\w./~-]+\\s+[\\w./~-]+\\s+[\\w./~-]+\\s+[\\w./~-]+$", | ||
| "addedAt": "2026-03-09T12:41:11.065Z", | ||
| "note": "Always accept generic", | ||
| "source": "ai" | ||
| }, | ||
| { | ||
| "type": "pattern", | ||
| "value": "^node\\s+--test\\s+[\\w./~-]+\\s+[\\w./~-]+$", | ||
| "addedAt": "2026-03-09T12:41:58.425Z", | ||
| "note": "Always accept generic", | ||
| "source": "ai" | ||
| }, | ||
| { | ||
| "type": "pattern", | ||
| "value": "^npm\\s+run\\s+test:typescript$", | ||
| "addedAt": "2026-03-09T12:42:48.283Z", | ||
| "note": "Always accept generic", | ||
| "source": "ai" | ||
| }, | ||
| { | ||
| "type": "exact", | ||
| "value": "gh search code \"request.host org:fastify\" --limit 20 --json repository,path,url", | ||
| "addedAt": "2026-03-09T21:03:22.953Z", | ||
| "note": "Always accept exact", | ||
| "source": "user" | ||
| } | ||
| ], | ||
| "version": 2 | ||
| } |
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
2903868
0.17%361
-0.28%67113
-0.04%