@tracehound/fastify 1.2.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 +13 -11
- package/dist/index.d.ts +9 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -9
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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 {
|
|
16
|
+
import { createTracehound } from '@tracehound/core'
|
|
17
17
|
|
|
18
18
|
const app = fastify()
|
|
19
19
|
|
|
20
|
-
// Create Tracehound
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
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
|
|
38
|
-
|
|
|
39
|
-
| `agent`
|
|
40
|
-
| `
|
|
41
|
-
| `
|
|
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 {
|
|
40
|
+
* import { createTracehound } from '@tracehound/core'
|
|
36
41
|
*
|
|
37
42
|
* const app = fastify()
|
|
38
|
-
* const
|
|
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>;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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;
|
|
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:
|
|
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:
|
|
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 {
|
|
85
|
+
* import { createTracehound } from '@tracehound/core'
|
|
72
86
|
*
|
|
73
87
|
* const app = fastify()
|
|
74
|
-
* const
|
|
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
|
|
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
|
|
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":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,gBAAgB,EAAiD,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,gBAAgB,EAAiD,MAAM,kBAAkB,CAAA;AAgClG;;;GAGG;AACH,SAAS,SAAS,CAAC,GAAQ;IACzB,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,SAAS,CAAA;IACvC,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAA;IACxC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAA,CAAC,6CAA6C;IAChE,CAAC;AACH,CAAC;AAED;;GAEG;AACH,SAAS,mBAAmB,CAAC,GAAmB;IAC9C,MAAM,EAAE,GAAG,GAAG,CAAC,EAAE,IAAI,SAAS,CAAA;IAE9B,OAAO;QACL,EAAE,EAAE,gBAAgB,EAAE;QACtB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;QACrB,MAAM,EAAE,EAAE;QACV,OAAO,EAAE;YACP,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,IAAI,EAAE,GAAG,CAAC,GAAG;YACb,KAAK,EAAE,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE;YACjC,OAAO,EAAE;gBACP,YAAY,EAAE,GAAG,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,EAAE;gBAC7C,cAAc,EAAE,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,IAAI,EAAE;aAClD;YACD,IAAI,EAAE,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC;SAC1B;KACF,CAAA;AACH,CAAC;AAED;;GAEG;AACH,SAAS,kBAAkB,CACzB,MAAuB,EACvB,IAAoB,EACpB,KAAmB,EACnB,OAAkE;IAElE,QAAQ,MAAM,CAAC,MAAM,EAAE,CAAC;QACtB,KAAK,cAAc;YACjB,KAAK;iBACF,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC,CAAC;iBAClE,MAAM,CAAC,GAAG,CAAC;iBACX,IAAI,CAAC;gBACJ,KAAK,EAAE,mBAAmB;gBAC1B,UAAU,EAAE,MAAM,CAAC,UAAU;aAC9B,CAAC,CAAA;YACJ,MAAK;QAEP,KAAK,mBAAmB;YACtB,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBACrB,KAAK,EAAE,mBAAmB;gBAC1B,KAAK,EAAE,MAAM,CAAC,KAAK;aACpB,CAAC,CAAA;YACF,MAAK;QAEP,KAAK,aAAa;YAChB,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBACrB,KAAK,EAAE,WAAW;gBAClB,GAAG,CAAC,OAAO,EAAE,uBAAuB,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACpF,CAAC,CAAA;YACF,MAAK;QAEP,KAAK,OAAO;YACV,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBACrB,KAAK,EAAE,uBAAuB;aAC/B,CAAC,CAAA;YACF,MAAK;QAEP;YACE,yCAAyC;YACzC,MAAK;IACT,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAmD,CAC9E,OAAO,EACP,OAAO,EACP,IAAI,EACJ,EAAE;IACF,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,mBAAmB,EAAE,WAAW,EAAE,GAAG,OAAO,CAAA;IAE1E,OAAO,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE;QACpD,MAAM,KAAK,GAAG,YAAY,CAAC,GAAG,CAAC,CAAA;QAC/B,MAAM,MAAM,GAAG,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,CAAA;QAErC,IAAI,MAAM,CAAC,MAAM,KAAK,OAAO,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAC7D,QAAQ,EAAE,CAAA;YACV,OAAM;QACR,CAAC;QAED,IAAI,WAAW,EAAE,CAAC;YAChB,WAAW,CAAC,MAAM,EAAE,GAAG,EAAE,KAAK,CAAC,CAAA;QACjC,CAAC;aAAM,CAAC;YACN,kBAAkB,CAChB,MAAM,EACN,GAAG,EACH,KAAK,EACL,OAAO,CAAC,uBAAuB,KAAK,SAAS;gBAC3C,CAAC,CAAC,EAAE,uBAAuB,EAAE,OAAO,CAAC,uBAAuB,EAAE;gBAC9D,CAAC,CAAC,EAAE,CACP,CAAA;QACH,CAAC;QACD,mDAAmD;IACrD,CAAC,CAAC,CAAA;IAEF,IAAI,EAAE,CAAA;AACR,CAAC,CAAA;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,gBAAgB,CAAA;AAE5C,0CAA0C;AAC1C,eAAe,gBAAgB,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tracehound/fastify",
|
|
3
|
-
"version": "1.
|
|
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.
|
|
35
|
+
"@tracehound/core": "1.4.3"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/node": "^20.0.0",
|