@tracehound/express 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,18 +13,19 @@ npm install @tracehound/express @tracehound/core
13
13
  ```ts
14
14
  import express from 'express'
15
15
  import { tracehound } from '@tracehound/express'
16
- import { createAgent, createQuarantine, createRateLimiter } from '@tracehound/core'
16
+ import { createTracehound } from '@tracehound/core'
17
17
 
18
18
  const app = express()
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
  // Apply middleware
26
27
  app.use(express.json())
27
- app.use(tracehound({ agent }))
28
+ app.use(tracehound({ agent: th.agent }))
28
29
 
29
30
  app.get('/', (req, res) => {
30
31
  res.json({ message: 'Protected by Tracehound' })
@@ -35,11 +36,12 @@ app.listen(3000)
35
36
 
36
37
  ## Options
37
38
 
38
- | Option | Type | Required | Description |
39
- | -------------- | ---------------------------- | -------- | ------------------------- |
40
- | `agent` | `IAgent` | Yes | Tracehound Agent instance |
41
- | `extractScent` | `(req) => Scent` | No | Custom scent extraction |
42
- | `onIntercept` | `(result, req, res) => void` | No | Custom response handler |
39
+ | Option | Type | Required | Description |
40
+ | ------------------------- | ---------------------------- | -------- | -------------------------------------------------- |
41
+ | `agent` | `IAgent` | Yes | Tracehound Agent instance |
42
+ | `emitSignatureInResponse` | `boolean` | No | If true, returns signature in 403 (default: false) |
43
+ | `extractScent` | `(req) => Scent` | No | Custom scent extraction |
44
+ | `onIntercept` | `(result, req, res) => void` | No | Custom response handler |
43
45
 
44
46
  ## Response Codes
45
47
 
package/dist/index.d.ts CHANGED
@@ -14,9 +14,14 @@ export interface TracehoundMiddlewareOptions {
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: Request) => Scent;
22
27
  /**
@@ -32,12 +37,12 @@ export interface TracehoundMiddlewareOptions {
32
37
  * ```ts
33
38
  * import express from 'express'
34
39
  * import { tracehound } from '@tracehound/express'
35
- * import { createAgent } from '@tracehound/core'
40
+ * import { createTracehound } from '@tracehound/core'
36
41
  *
37
42
  * const app = express()
38
- * const agent = createAgent({ ... })
43
+ * const th = createTracehound({ }) // options here
39
44
  *
40
- * app.use(tracehound({ agent }))
45
+ * app.use(tracehound({ agent: th.agent }))
41
46
  * ```
42
47
  */
43
48
  export declare function tracehound(options: TracehoundMiddlewareOptions): RequestHandler;
@@ -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,EAAgB,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAE9E;;GAEG;AACH,MAAM,WAAW,2BAA2B;IAC1C;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAA;IAEb;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,KAAK,CAAA;IAEtC;;;OAGG;IACH,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,KAAK,IAAI,CAAA;CAC7E;AAgED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,2BAA2B,GAAG,cAAc,CAc/E;AAED;;GAEG;AACH,eAAO,MAAM,gBAAgB,mBAAa,CAAA;AAG1C,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,EAAgB,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAE9E;;GAEG;AACH,MAAM,WAAW,2BAA2B;IAC1C;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAA;IAEb;;;OAGG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAA;IAEjC;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,KAAK,CAAA;IAEtC;;;OAGG;IACH,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,KAAK,IAAI,CAAA;CAC7E;AAkFD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,2BAA2B,GAAG,cAAc,CA0B/E;AAED;;GAEG;AACH,eAAO,MAAM,gBAAgB,mBAAa,CAAA;AAG1C,YAAY,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAA"}
package/dist/index.js CHANGED
@@ -4,6 +4,20 @@
4
4
  * Express middleware 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 Express request.
9
23
  */
@@ -16,19 +30,19 @@ function defaultExtractScent(req) {
16
30
  payload: {
17
31
  method: req.method,
18
32
  path: req.path,
19
- query: JSON.parse(JSON.stringify(req.query)),
33
+ query: safeClone(req.query) ?? {},
20
34
  headers: {
21
35
  'user-agent': req.get('user-agent') || '',
22
36
  'content-type': req.get('content-type') || '',
23
37
  },
24
- body: req.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, res) {
45
+ function defaultOnIntercept(result, _req, res, options) {
32
46
  switch (result.status) {
33
47
  case 'rate_limited':
34
48
  res.set('Retry-After', String(Math.ceil(result.retryAfter / 1000)));
@@ -46,7 +60,7 @@ function defaultOnIntercept(result, _req, res) {
46
60
  case 'quarantined':
47
61
  res.status(403).json({
48
62
  error: 'Forbidden',
49
- signature: result.handle.signature,
63
+ ...(options?.emitSignatureInResponse ? { signature: result.handle.signature } : {}),
50
64
  });
51
65
  break;
52
66
  case 'error':
@@ -66,16 +80,20 @@ function defaultOnIntercept(result, _req, res) {
66
80
  * ```ts
67
81
  * import express from 'express'
68
82
  * import { tracehound } from '@tracehound/express'
69
- * import { createAgent } from '@tracehound/core'
83
+ * import { createTracehound } from '@tracehound/core'
70
84
  *
71
85
  * const app = express()
72
- * const agent = createAgent({ ... })
86
+ * const th = createTracehound({ }) // options here
73
87
  *
74
- * app.use(tracehound({ agent }))
88
+ * app.use(tracehound({ agent: th.agent }))
75
89
  * ```
76
90
  */
77
91
  export function tracehound(options) {
78
- const { agent, extractScent = defaultExtractScent, onIntercept = defaultOnIntercept } = options;
92
+ const { agent, extractScent = defaultExtractScent, onIntercept } = options;
93
+ const interceptHandler = onIntercept ||
94
+ ((res, req, resp) => defaultOnIntercept(res, req, resp, options.emitSignatureInResponse !== undefined
95
+ ? { emitSignatureInResponse: options.emitSignatureInResponse }
96
+ : {}));
79
97
  return (req, res, next) => {
80
98
  const scent = extractScent(req);
81
99
  const result = agent.intercept(scent);
@@ -83,7 +101,7 @@ export function tracehound(options) {
83
101
  next();
84
102
  return;
85
103
  }
86
- onIntercept(result, req, res);
104
+ interceptHandler(result, req, res);
87
105
  };
88
106
  }
89
107
  /**
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;AA0BlG;;GAEG;AACH,SAAS,mBAAmB,CAAC,GAAY;IACvC,MAAM,EAAE,GAAG,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,MAAM,CAAC,aAAa,IAAI,SAAS,CAAA;IAE1D,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,IAAI;YACd,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YAC5C,OAAO,EAAE;gBACP,YAAY,EAAE,GAAG,CAAC,GAAG,CAAC,YAAY,CAAC,IAAI,EAAE;gBACzC,cAAc,EAAE,GAAG,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,EAAE;aAC9C;YACD,IAAI,EAAE,GAAG,CAAC,IAAI;SACf;KACF,CAAA;AACH,CAAC;AAED;;GAEG;AACH,SAAS,kBAAkB,CAAC,MAAuB,EAAE,IAAa,EAAE,GAAa;IAC/E,QAAQ,MAAM,CAAC,MAAM,EAAE,CAAC;QACtB,KAAK,cAAc;YACjB,GAAG,CAAC,GAAG,CAAC,aAAa,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC,CAAC,CAAA;YACnE,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBACnB,KAAK,EAAE,mBAAmB;gBAC1B,UAAU,EAAE,MAAM,CAAC,UAAU;aAC9B,CAAC,CAAA;YACF,MAAK;QAEP,KAAK,mBAAmB;YACtB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBACnB,KAAK,EAAE,mBAAmB;gBAC1B,KAAK,EAAE,MAAM,CAAC,KAAK;aACpB,CAAC,CAAA;YACF,MAAK;QAEP,KAAK,aAAa;YAChB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBACnB,KAAK,EAAE,WAAW;gBAClB,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,SAAS;aACnC,CAAC,CAAA;YACF,MAAK;QAEP,KAAK,OAAO;YACV,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBACnB,KAAK,EAAE,uBAAuB;aAC/B,CAAC,CAAA;YACF,MAAK;QAEP;YACE,yCAAyC;YACzC,MAAK;IACT,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,UAAU,CAAC,OAAoC;IAC7D,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,mBAAmB,EAAE,WAAW,GAAG,kBAAkB,EAAE,GAAG,OAAO,CAAA;IAE/F,OAAO,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAQ,EAAE;QAC/D,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,IAAI,EAAE,CAAA;YACN,OAAM;QACR,CAAC;QAED,WAAW,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;IAC/B,CAAC,CAAA;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,UAAU,CAAA"}
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/express",
3
- "version": "1.3.0",
3
+ "version": "1.4.1",
4
4
  "description": "Express middleware 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/express": "^4.17.21",