@tracehound/express 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 +27 -9
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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 {
|
|
16
|
+
import { createTracehound } from '@tracehound/core'
|
|
17
17
|
|
|
18
18
|
const app = express()
|
|
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
|
// 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
|
|
39
|
-
|
|
|
40
|
-
| `agent`
|
|
41
|
-
| `
|
|
42
|
-
| `
|
|
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 {
|
|
40
|
+
* import { createTracehound } from '@tracehound/core'
|
|
36
41
|
*
|
|
37
42
|
* const app = express()
|
|
38
|
-
* const
|
|
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;
|
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,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;
|
|
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:
|
|
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 {
|
|
83
|
+
* import { createTracehound } from '@tracehound/core'
|
|
70
84
|
*
|
|
71
85
|
* const app = express()
|
|
72
|
-
* const
|
|
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
|
|
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
|
-
|
|
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;
|
|
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,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,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE;YACjC,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,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC;SAC1B;KACF,CAAA;AACH,CAAC;AAED;;GAEG;AACH,SAAS,kBAAkB,CACzB,MAAuB,EACvB,IAAa,EACb,GAAa,EACb,OAAsE;IAEtE,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,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,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,EAAE,GAAG,OAAO,CAAA;IAE1E,MAAM,gBAAgB,GACpB,WAAW;QACX,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,CAClB,kBAAkB,CAChB,GAAG,EACH,GAAG,EACH,IAAI,EACJ,OAAO,CAAC,uBAAuB,KAAK,SAAS;YAC3C,CAAC,CAAC,EAAE,uBAAuB,EAAE,OAAO,CAAC,uBAAuB,EAAE;YAC9D,CAAC,CAAC,EAAE,CACP,CAAC,CAAA;IAEN,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,gBAAgB,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;IACpC,CAAC,CAAA;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,UAAU,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tracehound/express",
|
|
3
|
-
"version": "1.
|
|
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.
|
|
35
|
+
"@tracehound/core": "1.4.3"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/express": "^4.17.21",
|