@tracehound/fastify 1.3.0 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,17 +13,18 @@ npm install @tracehound/fastify @tracehound/core
13
13
  ```ts
14
14
  import fastify from 'fastify'
15
15
  import { tracehoundPlugin } from '@tracehound/fastify'
16
- import { createAgent, createQuarantine, createRateLimiter } from '@tracehound/core'
16
+ import { createTracehound } from '@tracehound/core'
17
17
 
18
18
  const app = fastify()
19
19
 
20
- // Create Tracehound components
21
- const quarantine = createQuarantine({ maxCount: 10000, maxBytes: 100_000_000 })
22
- const rateLimiter = createRateLimiter({ windowMs: 60_000, maxRequests: 100 })
23
- const agent = createAgent({ quarantine, rateLimiter })
20
+ // Create Tracehound instance
21
+ const th = createTracehound({
22
+ quarantine: { maxCount: 10000, maxBytes: 100_000_000 },
23
+ rateLimit: { windowMs: 60_000, maxRequests: 100 },
24
+ })
24
25
 
25
26
  // Register plugin
26
- app.register(tracehoundPlugin, { agent })
27
+ app.register(tracehoundPlugin, { agent: th.agent })
27
28
 
28
29
  app.get('/', async (req, reply) => {
29
30
  return { message: 'Protected by Tracehound' }
@@ -34,11 +35,12 @@ app.listen({ port: 3000 })
34
35
 
35
36
  ## Options
36
37
 
37
- | Option | Type | Required | Description |
38
- | -------------- | ------------------------------ | -------- | ------------------------- |
39
- | `agent` | `IAgent` | Yes | Tracehound Agent instance |
40
- | `extractScent` | `(req) => Scent` | No | Custom scent extraction |
41
- | `onIntercept` | `(result, req, reply) => void` | No | Custom response handler |
38
+ | Option | Type | Required | Description |
39
+ | ------------------------- | ------------------------------ | -------- | -------------------------------------------------- |
40
+ | `agent` | `IAgent` | Yes | Tracehound Agent instance |
41
+ | `emitSignatureInResponse` | `boolean` | No | If true, returns signature in 403 (default: false) |
42
+ | `extractScent` | `(req) => Scent` | No | Custom scent extraction |
43
+ | `onIntercept` | `(result, req, reply) => void` | No | Custom response handler |
42
44
 
43
45
  ## Response Codes
44
46
 
package/dist/index.d.ts CHANGED
@@ -14,9 +14,14 @@ export interface TracehoundPluginOptions {
14
14
  * Required - must be created via createAgent() from @tracehound/core.
15
15
  */
16
16
  agent: IAgent;
17
+ /**
18
+ * If true, includes the Tracehound `signature` in the HTTP 403 Forbidden body
19
+ * for quarantined requests. This is false by default to prevent correlation attacks.
20
+ */
21
+ emitSignatureInResponse?: boolean;
17
22
  /**
18
23
  * Custom scent extraction function.
19
- * Default extracts IP, path, method, and headers.
24
+ * Default extracts IP, path, method, and headers safely.
20
25
  */
21
26
  extractScent?: (req: FastifyRequest) => Scent;
22
27
  /**
@@ -32,12 +37,12 @@ export interface TracehoundPluginOptions {
32
37
  * ```ts
33
38
  * import fastify from 'fastify'
34
39
  * import { tracehoundPlugin } from '@tracehound/fastify'
35
- * import { createAgent } from '@tracehound/core'
40
+ * import { createTracehound } from '@tracehound/core'
36
41
  *
37
42
  * const app = fastify()
38
- * const agent = createAgent({ ... })
43
+ * const th = createTracehound({ }) // options here
39
44
  *
40
- * app.register(tracehoundPlugin, { agent })
45
+ * app.register(tracehoundPlugin, { agent: th.agent })
41
46
  * ```
42
47
  */
43
48
  export declare const tracehoundPlugin: FastifyPluginCallback<TracehoundPluginOptions>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAoB,KAAK,MAAM,EAAE,KAAK,eAAe,EAAE,KAAK,KAAK,EAAE,MAAM,kBAAkB,CAAA;AAClG,OAAO,KAAK,EAAE,qBAAqB,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAElF;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAA;IAEb;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,KAAK,CAAA;IAE7C;;;OAGG;IACH,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,KAAK,IAAI,CAAA;CAC1F;AAsED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,gBAAgB,EAAE,qBAAqB,CAAC,uBAAuB,CAqB3E,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,YAAY,gDAAmB,CAAA;AAG5C,eAAe,gBAAgB,CAAA;AAG/B,YAAY,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAoB,KAAK,MAAM,EAAE,KAAK,eAAe,EAAE,KAAK,KAAK,EAAE,MAAM,kBAAkB,CAAA;AAClG,OAAO,KAAK,EAAE,qBAAqB,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAElF;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAA;IAEb;;;OAGG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAA;IAEjC;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,KAAK,CAAA;IAE7C;;;OAGG;IACH,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,KAAK,IAAI,CAAA;CAC1F;AAoFD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,gBAAgB,EAAE,qBAAqB,CAAC,uBAAuB,CAgC3E,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,YAAY,gDAAmB,CAAA;AAG5C,eAAe,gBAAgB,CAAA;AAG/B,YAAY,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAA"}
package/dist/index.js CHANGED
@@ -4,6 +4,20 @@
4
4
  * Fastify plugin for Tracehound security buffer.
5
5
  */
6
6
  import { generateSecureId } from '@tracehound/core';
7
+ /**
8
+ * Defensive clone for safely copying deeply nested or cyclical external payloads
9
+ * without crashing the process.
10
+ */
11
+ function safeClone(obj) {
12
+ if (obj === undefined)
13
+ return undefined;
14
+ try {
15
+ return JSON.parse(JSON.stringify(obj));
16
+ }
17
+ catch {
18
+ return undefined; // Unsafe to clone or cyclical, omit silently
19
+ }
20
+ }
7
21
  /**
8
22
  * Default scent extraction from Fastify request.
9
23
  */
@@ -16,19 +30,19 @@ function defaultExtractScent(req) {
16
30
  payload: {
17
31
  method: req.method,
18
32
  path: req.url,
19
- query: JSON.parse(JSON.stringify(req.query)),
33
+ query: safeClone(req.query) ?? {},
20
34
  headers: {
21
35
  'user-agent': req.headers['user-agent'] || '',
22
36
  'content-type': req.headers['content-type'] || '',
23
37
  },
24
- body: req.body ? JSON.parse(JSON.stringify(req.body)) : undefined,
38
+ body: safeClone(req.body),
25
39
  },
26
40
  };
27
41
  }
28
42
  /**
29
43
  * Default intercept result handler.
30
44
  */
31
- function defaultOnIntercept(result, _req, reply) {
45
+ function defaultOnIntercept(result, _req, reply, options) {
32
46
  switch (result.status) {
33
47
  case 'rate_limited':
34
48
  reply
@@ -48,7 +62,7 @@ function defaultOnIntercept(result, _req, reply) {
48
62
  case 'quarantined':
49
63
  reply.status(403).send({
50
64
  error: 'Forbidden',
51
- signature: result.handle.signature,
65
+ ...(options?.emitSignatureInResponse ? { signature: result.handle.signature } : {}),
52
66
  });
53
67
  break;
54
68
  case 'error':
@@ -68,16 +82,16 @@ function defaultOnIntercept(result, _req, reply) {
68
82
  * ```ts
69
83
  * import fastify from 'fastify'
70
84
  * import { tracehoundPlugin } from '@tracehound/fastify'
71
- * import { createAgent } from '@tracehound/core'
85
+ * import { createTracehound } from '@tracehound/core'
72
86
  *
73
87
  * const app = fastify()
74
- * const agent = createAgent({ ... })
88
+ * const th = createTracehound({ }) // options here
75
89
  *
76
- * app.register(tracehoundPlugin, { agent })
90
+ * app.register(tracehoundPlugin, { agent: th.agent })
77
91
  * ```
78
92
  */
79
93
  export const tracehoundPlugin = (fastify, options, done) => {
80
- const { agent, extractScent = defaultExtractScent, onIntercept = defaultOnIntercept } = options;
94
+ const { agent, extractScent = defaultExtractScent, onIntercept } = options;
81
95
  fastify.addHook('onRequest', (req, reply, hookDone) => {
82
96
  const scent = extractScent(req);
83
97
  const result = agent.intercept(scent);
@@ -85,7 +99,14 @@ export const tracehoundPlugin = (fastify, options, done) => {
85
99
  hookDone();
86
100
  return;
87
101
  }
88
- onIntercept(result, req, reply);
102
+ if (onIntercept) {
103
+ onIntercept(result, req, reply);
104
+ }
105
+ else {
106
+ defaultOnIntercept(result, req, reply, options.emitSignatureInResponse !== undefined
107
+ ? { emitSignatureInResponse: options.emitSignatureInResponse }
108
+ : {});
109
+ }
89
110
  // Don't call hookDone() - response is already sent
90
111
  });
91
112
  done();
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tracehound/fastify",
3
- "version": "1.3.0",
3
+ "version": "1.4.1",
4
4
  "description": "Fastify plugin for Tracehound security buffer",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Erdem Arslan <me@erdem.work>",
@@ -32,7 +32,7 @@
32
32
  "dist"
33
33
  ],
34
34
  "dependencies": {
35
- "@tracehound/core": "1.3.0"
35
+ "@tracehound/core": "1.4.3"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/node": "^20.0.0",