@queno/agent-node 0.1.2
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 +421 -0
- package/dist/agent.d.ts +222 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +591 -0
- package/dist/agent.js.map +1 -0
- package/dist/api-discovery/discovery-buffer.d.ts +27 -0
- package/dist/api-discovery/discovery-buffer.d.ts.map +1 -0
- package/dist/api-discovery/discovery-buffer.js +50 -0
- package/dist/api-discovery/discovery-buffer.js.map +1 -0
- package/dist/api-discovery/endpoint-observer.d.ts +25 -0
- package/dist/api-discovery/endpoint-observer.d.ts.map +1 -0
- package/dist/api-discovery/endpoint-observer.js +127 -0
- package/dist/api-discovery/endpoint-observer.js.map +1 -0
- package/dist/api-discovery/route-normalizer.d.ts +15 -0
- package/dist/api-discovery/route-normalizer.d.ts.map +1 -0
- package/dist/api-discovery/route-normalizer.js +34 -0
- package/dist/api-discovery/route-normalizer.js.map +1 -0
- package/dist/config.d.ts +100 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +101 -0
- package/dist/config.js.map +1 -0
- package/dist/db-hooks/correlate.d.ts +19 -0
- package/dist/db-hooks/correlate.d.ts.map +1 -0
- package/dist/db-hooks/correlate.js +45 -0
- package/dist/db-hooks/correlate.js.map +1 -0
- package/dist/db-hooks/instrument.d.ts +27 -0
- package/dist/db-hooks/instrument.d.ts.map +1 -0
- package/dist/db-hooks/instrument.js +194 -0
- package/dist/db-hooks/instrument.js.map +1 -0
- package/dist/detectors/base.d.ts +61 -0
- package/dist/detectors/base.d.ts.map +1 -0
- package/dist/detectors/base.js +57 -0
- package/dist/detectors/base.js.map +1 -0
- package/dist/detectors/bola.d.ts +60 -0
- package/dist/detectors/bola.d.ts.map +1 -0
- package/dist/detectors/bola.js +108 -0
- package/dist/detectors/bola.js.map +1 -0
- package/dist/detectors/command-injection.d.ts +22 -0
- package/dist/detectors/command-injection.d.ts.map +1 -0
- package/dist/detectors/command-injection.js +41 -0
- package/dist/detectors/command-injection.js.map +1 -0
- package/dist/detectors/custom-rule.d.ts +24 -0
- package/dist/detectors/custom-rule.d.ts.map +1 -0
- package/dist/detectors/custom-rule.js +65 -0
- package/dist/detectors/custom-rule.js.map +1 -0
- package/dist/detectors/index.d.ts +17 -0
- package/dist/detectors/index.d.ts.map +1 -0
- package/dist/detectors/index.js +31 -0
- package/dist/detectors/index.js.map +1 -0
- package/dist/detectors/nosql-injection.d.ts +23 -0
- package/dist/detectors/nosql-injection.d.ts.map +1 -0
- package/dist/detectors/nosql-injection.js +54 -0
- package/dist/detectors/nosql-injection.js.map +1 -0
- package/dist/detectors/path-traversal.d.ts +21 -0
- package/dist/detectors/path-traversal.d.ts.map +1 -0
- package/dist/detectors/path-traversal.js +54 -0
- package/dist/detectors/path-traversal.js.map +1 -0
- package/dist/detectors/prototype-pollution.d.ts +23 -0
- package/dist/detectors/prototype-pollution.d.ts.map +1 -0
- package/dist/detectors/prototype-pollution.js +50 -0
- package/dist/detectors/prototype-pollution.js.map +1 -0
- package/dist/detectors/sql-injection.d.ts +22 -0
- package/dist/detectors/sql-injection.d.ts.map +1 -0
- package/dist/detectors/sql-injection.js +42 -0
- package/dist/detectors/sql-injection.js.map +1 -0
- package/dist/detectors/ssrf.d.ts +26 -0
- package/dist/detectors/ssrf.d.ts.map +1 -0
- package/dist/detectors/ssrf.js +37 -0
- package/dist/detectors/ssrf.js.map +1 -0
- package/dist/detectors/suspicious-headers.d.ts +25 -0
- package/dist/detectors/suspicious-headers.d.ts.map +1 -0
- package/dist/detectors/suspicious-headers.js +87 -0
- package/dist/detectors/suspicious-headers.js.map +1 -0
- package/dist/detectors/template-injection.d.ts +27 -0
- package/dist/detectors/template-injection.d.ts.map +1 -0
- package/dist/detectors/template-injection.js +35 -0
- package/dist/detectors/template-injection.js.map +1 -0
- package/dist/detectors/xss.d.ts +22 -0
- package/dist/detectors/xss.d.ts.map +1 -0
- package/dist/detectors/xss.js +38 -0
- package/dist/detectors/xss.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/express.d.ts +39 -0
- package/dist/integrations/express.d.ts.map +1 -0
- package/dist/integrations/express.js +62 -0
- package/dist/integrations/express.js.map +1 -0
- package/dist/integrations/fastify.d.ts +33 -0
- package/dist/integrations/fastify.d.ts.map +1 -0
- package/dist/integrations/fastify.js +63 -0
- package/dist/integrations/fastify.js.map +1 -0
- package/dist/integrations/nestjs.d.ts +40 -0
- package/dist/integrations/nestjs.d.ts.map +1 -0
- package/dist/integrations/nestjs.js +58 -0
- package/dist/integrations/nestjs.js.map +1 -0
- package/dist/policy/canonical.d.ts +23 -0
- package/dist/policy/canonical.d.ts.map +1 -0
- package/dist/policy/canonical.js +40 -0
- package/dist/policy/canonical.js.map +1 -0
- package/dist/policy/policy-manager.d.ts +43 -0
- package/dist/policy/policy-manager.d.ts.map +1 -0
- package/dist/policy/policy-manager.js +89 -0
- package/dist/policy/policy-manager.js.map +1 -0
- package/dist/policy/types.d.ts +70 -0
- package/dist/policy/types.d.ts.map +1 -0
- package/dist/policy/types.js +2 -0
- package/dist/policy/types.js.map +1 -0
- package/dist/policy/verify.d.ts +11 -0
- package/dist/policy/verify.d.ts.map +1 -0
- package/dist/policy/verify.js +61 -0
- package/dist/policy/verify.js.map +1 -0
- package/dist/redaction/audit-log.d.ts +40 -0
- package/dist/redaction/audit-log.d.ts.map +1 -0
- package/dist/redaction/audit-log.js +110 -0
- package/dist/redaction/audit-log.js.map +1 -0
- package/dist/redaction/engine.d.ts +50 -0
- package/dist/redaction/engine.d.ts.map +1 -0
- package/dist/redaction/engine.js +143 -0
- package/dist/redaction/engine.js.map +1 -0
- package/dist/redaction/patterns.d.ts +24 -0
- package/dist/redaction/patterns.d.ts.map +1 -0
- package/dist/redaction/patterns.js +142 -0
- package/dist/redaction/patterns.js.map +1 -0
- package/dist/runtime-context.d.ts +33 -0
- package/dist/runtime-context.d.ts.map +1 -0
- package/dist/runtime-context.js +46 -0
- package/dist/runtime-context.js.map +1 -0
- package/dist/self-protect.d.ts +34 -0
- package/dist/self-protect.d.ts.map +1 -0
- package/dist/self-protect.js +134 -0
- package/dist/self-protect.js.map +1 -0
- package/dist/transport/buffer.d.ts +52 -0
- package/dist/transport/buffer.d.ts.map +1 -0
- package/dist/transport/buffer.js +57 -0
- package/dist/transport/buffer.js.map +1 -0
- package/dist/transport/client.d.ts +77 -0
- package/dist/transport/client.d.ts.map +1 -0
- package/dist/transport/client.js +178 -0
- package/dist/transport/client.js.map +1 -0
- package/dist/transport/heartbeat.d.ts +86 -0
- package/dist/transport/heartbeat.d.ts.map +1 -0
- package/dist/transport/heartbeat.js +110 -0
- package/dist/transport/heartbeat.js.map +1 -0
- package/dist/transport/secure-request.d.ts +30 -0
- package/dist/transport/secure-request.d.ts.map +1 -0
- package/dist/transport/secure-request.js +95 -0
- package/dist/transport/secure-request.js.map +1 -0
- package/dist/types.d.ts +311 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +12 -0
- package/dist/types.js.map +1 -0
- package/package.json +60 -0
package/README.md
ADDED
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
# @queno/agent-node
|
|
2
|
+
|
|
3
|
+
> Runtime Application Self-Protection for Node.js - Express · Fastify · NestJS
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@queno/agent-node)
|
|
6
|
+
[](https://nodejs.org)
|
|
7
|
+
[](./LICENSE)
|
|
8
|
+
[](#)
|
|
9
|
+
|
|
10
|
+
`@queno/agent-node` is a lightweight RASP agent that installs inside your Node.js application and **detects, redacts, audits, and optionally blocks** runtime attacks - without a proxy, without a WAF, and without crashing your app.
|
|
11
|
+
|
|
12
|
+
It instruments Express, Fastify, and NestJS middleware to inspect each incoming request against 10 built-in attack detectors. Sensitive data is redacted **before leaving the process**, a tamper-evident audit log is written locally, and security events are forwarded to the RASP collector.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Features
|
|
17
|
+
|
|
18
|
+
- **10 built-in detectors** - SQL injection, XSS, Command injection, Path traversal, NoSQL injection, SSRF, Prototype pollution, Template injection, Suspicious headers, BOLA/IDOR
|
|
19
|
+
- **Local redaction engine** - key-based denylist + value-based (email, card, SIN/RAMQ, IP). Redaction happens *before* telemetry leaves the process
|
|
20
|
+
- **Local JSONL audit log** - metadata-only, never raw sensitive values, with automatic rotation
|
|
21
|
+
- **Monitor and block modes** - monitor by default; block must be explicitly enabled or pushed via signed policy
|
|
22
|
+
- **Signed policy distribution** - Ed25519 signed configuration from the dashboard (mode, custom rules, redaction config). Agents verify before applying
|
|
23
|
+
- **Passive API discovery** - observes endpoints at runtime, normalizes routes, flushes inventory to the collector
|
|
24
|
+
- **Self-protection (optional)** - AES-256-GCM in-memory secret store, anti-debug detection, DB hook integrity
|
|
25
|
+
- **Fail-open** - any internal error is swallowed; the agent never crashes the host application
|
|
26
|
+
- **Single runtime dependency** - only `zod`. No axios, no winston, no lodash
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Requirements
|
|
31
|
+
|
|
32
|
+
| Requirement | Version |
|
|
33
|
+
|---|---|
|
|
34
|
+
| Node.js | **≥ 18** |
|
|
35
|
+
| TypeScript | ≥ 5 (optional) |
|
|
36
|
+
| Framework | Express ≥ 4, Fastify ≥ 4, or NestJS ≥ 10 |
|
|
37
|
+
|
|
38
|
+
> Node ≥ 18 is required for native `crypto` (AES-GCM, Ed25519, HMAC), native `fetch`, and `AsyncLocalStorage`.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# npm
|
|
46
|
+
npm install @queno/agent-node
|
|
47
|
+
|
|
48
|
+
# pnpm
|
|
49
|
+
pnpm add @queno/agent-node
|
|
50
|
+
|
|
51
|
+
# yarn
|
|
52
|
+
yarn add @queno/agent-node
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Install the peer dependency for your framework - only what you need:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# Express
|
|
59
|
+
npm install express
|
|
60
|
+
|
|
61
|
+
# Fastify
|
|
62
|
+
npm install fastify
|
|
63
|
+
|
|
64
|
+
# NestJS
|
|
65
|
+
npm install @nestjs/common @nestjs/core
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Quickstart
|
|
71
|
+
|
|
72
|
+
Before you start: create a **Project** and an **Agent** in the RASP dashboard, then copy the `apiKey`, `projectId`, and `agentId`.
|
|
73
|
+
|
|
74
|
+
### Express
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
import express from "express";
|
|
78
|
+
import { RaspAgent, createExpressMiddleware } from "@queno/agent-node";
|
|
79
|
+
|
|
80
|
+
// 1. Create and start the agent
|
|
81
|
+
const agent = new RaspAgent({
|
|
82
|
+
apiKey: process.env.RASP_API_KEY!,
|
|
83
|
+
projectId: process.env.RASP_PROJECT_ID!,
|
|
84
|
+
agentId: process.env.RASP_AGENT_ID!,
|
|
85
|
+
framework: "express",
|
|
86
|
+
mode: "monitor", // "monitor" | "block"
|
|
87
|
+
});
|
|
88
|
+
agent.start();
|
|
89
|
+
|
|
90
|
+
const app = express();
|
|
91
|
+
app.use(express.json());
|
|
92
|
+
app.use(express.urlencoded({ extended: false }));
|
|
93
|
+
|
|
94
|
+
// 2. Mount the RASP middleware AFTER body parsers
|
|
95
|
+
app.use(createExpressMiddleware(agent));
|
|
96
|
+
|
|
97
|
+
// 3. Your routes
|
|
98
|
+
app.get("/api/users/:id", (req, res) => {
|
|
99
|
+
// In block mode, the middleware already replied 403 if an attack was detected
|
|
100
|
+
res.json({ id: req.params.id });
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// 4. Drain on shutdown
|
|
104
|
+
process.on("SIGTERM", async () => {
|
|
105
|
+
await agent.stop(); // flushes event buffer + closes audit log
|
|
106
|
+
process.exit(0);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
app.listen(3000);
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Fastify
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
import Fastify from "fastify";
|
|
116
|
+
import { RaspAgent, createFastifyPlugin } from "@queno/agent-node";
|
|
117
|
+
|
|
118
|
+
const agent = new RaspAgent({
|
|
119
|
+
apiKey: process.env.RASP_API_KEY!,
|
|
120
|
+
projectId: process.env.RASP_PROJECT_ID!,
|
|
121
|
+
agentId: process.env.RASP_AGENT_ID!,
|
|
122
|
+
framework: "fastify",
|
|
123
|
+
});
|
|
124
|
+
agent.start();
|
|
125
|
+
|
|
126
|
+
const fastify = Fastify({ logger: true });
|
|
127
|
+
|
|
128
|
+
// Register as a Fastify plugin
|
|
129
|
+
await fastify.register(createFastifyPlugin(agent));
|
|
130
|
+
|
|
131
|
+
fastify.get("/api/items", async () => ({ items: [] }));
|
|
132
|
+
|
|
133
|
+
await fastify.listen({ port: 3000 });
|
|
134
|
+
|
|
135
|
+
process.on("SIGTERM", async () => {
|
|
136
|
+
await fastify.close();
|
|
137
|
+
await agent.stop();
|
|
138
|
+
});
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
> **Note:** The Fastify plugin hooks into `onRequest` (before body parsing). To inspect request bodies, add a `preHandler` hook after plugin registration.
|
|
142
|
+
|
|
143
|
+
### NestJS
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
// rasp.module.ts
|
|
147
|
+
import { Module, NestModule, MiddlewareConsumer } from "@nestjs/common";
|
|
148
|
+
import { RaspAgent, createNestMiddleware } from "@queno/agent-node";
|
|
149
|
+
|
|
150
|
+
export const raspAgent = new RaspAgent({
|
|
151
|
+
apiKey: process.env.RASP_API_KEY!,
|
|
152
|
+
projectId: process.env.RASP_PROJECT_ID!,
|
|
153
|
+
agentId: process.env.RASP_AGENT_ID!,
|
|
154
|
+
framework: "nestjs",
|
|
155
|
+
});
|
|
156
|
+
raspAgent.start();
|
|
157
|
+
|
|
158
|
+
@Module({ imports: [/* your modules */] })
|
|
159
|
+
export class AppModule implements NestModule {
|
|
160
|
+
configure(consumer: MiddlewareConsumer) {
|
|
161
|
+
consumer
|
|
162
|
+
.apply(createNestMiddleware(raspAgent))
|
|
163
|
+
.forRoutes("*");
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
// main.ts - graceful shutdown
|
|
170
|
+
import { raspAgent } from "./rasp.module";
|
|
171
|
+
|
|
172
|
+
process.on("SIGTERM", async () => {
|
|
173
|
+
await raspAgent.stop();
|
|
174
|
+
});
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Configuration
|
|
180
|
+
|
|
181
|
+
Pass a `RaspConfig` object to the `RaspAgent` constructor. All fields except `apiKey`, `projectId`, and `agentId` are optional with safe defaults.
|
|
182
|
+
|
|
183
|
+
| Option | Type | Default | Min | Description |
|
|
184
|
+
|---|---|---|---|---|
|
|
185
|
+
| `apiKey` * | `string` | - | - | API key generated in the dashboard |
|
|
186
|
+
| `projectId` * | `string` | - | - | Project ID |
|
|
187
|
+
| `agentId` * | `string` | - | - | Agent ID (registered in the dashboard) |
|
|
188
|
+
| `mode` | `"monitor" \| "block"` | `"monitor"` | - | Initial mode. Can be overridden by a signed policy |
|
|
189
|
+
| `channel` | `"stable" \| "early" \| "edge"` | `"stable"` | - | Policy and version distribution channel |
|
|
190
|
+
| `heartbeatIntervalMs` | `number` | `30000` | `5000` | Heartbeat interval |
|
|
191
|
+
| `flushIntervalMs` | `number` | `5000` | `1000` | Event buffer flush interval |
|
|
192
|
+
| `bufferMaxSize` | `number` | `50` | `1` | Flush also triggers at this buffer size |
|
|
193
|
+
| `transportTimeoutMs` | `number` | `5000` | `500` | HTTP timeout to the collector |
|
|
194
|
+
| `auditLog` | `boolean` | `true` | - | Enable local JSONL audit log |
|
|
195
|
+
| `auditLogPath` | `string` | `"./rasp-audit.log"` | - | Local audit log path |
|
|
196
|
+
| `auditLogMaxBytes` | `number` | `10485760` | `1` | Rotation size (10 MB default) |
|
|
197
|
+
| `discoveryFlushIntervalMs` | `number` | `60000` | `5000` | API discovery flush interval |
|
|
198
|
+
| `hmacSecret` | `string` | - | - | HMAC-SHA256 secret if `HMAC_REQUIRED=true` on the collector |
|
|
199
|
+
| `instrumentDb` | `boolean` | `false` | - | Enable DB driver monkey-patching for BOLA correlation (`pg`, `mysql2`, `mongoose`, `knex`, `sequelize`, Prisma) |
|
|
200
|
+
| `selfProtect` | `boolean` | `false` | - | Enable anti-debug, hook integrity checks, extended SecureStore |
|
|
201
|
+
| `policyPublicKey` | `string` | Dev key (built-in) | - | Ed25519 PEM public key. **Override in production** |
|
|
202
|
+
| `agentVersion` | `string` | - | - | Reported in heartbeats |
|
|
203
|
+
| `framework` | `string` | - | - | Free-form tag reported in heartbeats |
|
|
204
|
+
| `tls` | `object` | - | - | TLS/mTLS: `caCert`, `clientCert`, `clientKey`, `collectorFingerprints`, `rejectUnauthorized` |
|
|
205
|
+
|
|
206
|
+
### Environment variables
|
|
207
|
+
|
|
208
|
+
Two environment variables are read from `process.env` by `src/config.ts`:
|
|
209
|
+
|
|
210
|
+
| Variable | Default | Description |
|
|
211
|
+
|---|---|---|
|
|
212
|
+
| `RASP_COLLECTOR_URL` | `https://collector.rasp.dev` | Override the collector URL (dev/staging) |
|
|
213
|
+
| `RASP_POLICY_PUBLIC_KEY` | Built-in dev key | Override the trusted Ed25519 public key |
|
|
214
|
+
|
|
215
|
+
All other options are passed directly via `RaspConfig` - not read from the environment.
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Modes
|
|
220
|
+
|
|
221
|
+
### Monitor (default)
|
|
222
|
+
|
|
223
|
+
All requests pass through. Detections are reported as events to the collector and visible in the dashboard. **No impact on your application's responses.**
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
const agent = new RaspAgent({ ..., mode: "monitor" });
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Block
|
|
230
|
+
|
|
231
|
+
When an attack is detected, the middleware replies with `HTTP 403` and does **not** call `next()`:
|
|
232
|
+
|
|
233
|
+
```json
|
|
234
|
+
{ "error": "Request blocked by RASP", "eventType": "sql_injection" }
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
const agent = new RaspAgent({ ..., mode: "block" });
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
> **Important:** Always read `agent.mode` at runtime - the mode may change via a signed policy pushed from the dashboard. Do not rely on the boot config value.
|
|
242
|
+
|
|
243
|
+
### Kill-switch
|
|
244
|
+
|
|
245
|
+
If the dashboard activates a kill-switch (per-agent or global), the agent's `inspect()` becomes a no-op on the next heartbeat. The agent stops sending telemetry but continues heartbeating to detect when the kill-switch is lifted. **Recovery is automatic.**
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## Detectors
|
|
250
|
+
|
|
251
|
+
| # | Detector | Severity | Targets |
|
|
252
|
+
|---|---|---|---|
|
|
253
|
+
| 1 | SQL injection | critical | query, body, path |
|
|
254
|
+
| 2 | XSS | high | query, body |
|
|
255
|
+
| 3 | Command injection | critical | query, body, path |
|
|
256
|
+
| 4 | Path traversal | high | query, body, path |
|
|
257
|
+
| 5 | NoSQL injection | high | query, body |
|
|
258
|
+
| 6 | SSRF | high | query, body |
|
|
259
|
+
| 7 | Prototype pollution | critical | query, body |
|
|
260
|
+
| 8 | Template injection | high | query, body |
|
|
261
|
+
| 9 | Suspicious headers | medium/high | headers |
|
|
262
|
+
| 10 | BOLA / IDOR | high | path, JWT `sub` |
|
|
263
|
+
|
|
264
|
+
Detection is **synchronous** on the request path. All telemetry side-effects (redaction, audit, buffer, transport) are **async fire-and-forget**.
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Security & Privacy
|
|
269
|
+
|
|
270
|
+
### Redaction before transport
|
|
271
|
+
|
|
272
|
+
The redaction engine runs **before** any event enters the transport buffer. If redaction throws, the event is **dropped** (fail-closed) and logged locally with `dropped: true`. No raw sensitive value ever leaves the process.
|
|
273
|
+
|
|
274
|
+
**Default key redaction:** `authorization`, `cookie`, `password`, `secret`, `token`, `api_key`, `ssn`, `credit_card`, `cvv`, `pin`, `x-api-key` and more.
|
|
275
|
+
|
|
276
|
+
**Value-based redaction:** emails → `[EMAIL:<hash>]`, Luhn-valid cards → `****-****-****-XXXX`, SIN → `[SIN REDACTED]`, RAMQ → `[HEALTH_ID REDACTED]`, SQL literals → `[STRING]`/`[INT]`.
|
|
277
|
+
|
|
278
|
+
### Local audit log
|
|
279
|
+
|
|
280
|
+
Every redaction action is journaled locally as JSONL (`./rasp-audit.log` by default). Contents: timestamp, eventType, severity, redactedFields - **never raw values**. The log never leaves the customer's environment.
|
|
281
|
+
|
|
282
|
+
### Secrets in memory
|
|
283
|
+
|
|
284
|
+
`apiKey` and `hmacSecret` are stored encrypted in memory using `SecureStore` (AES-256-GCM, per-process random key). They never appear in heap dumps or crash logs.
|
|
285
|
+
|
|
286
|
+
### Signed policy
|
|
287
|
+
|
|
288
|
+
The agent verifies every policy with Ed25519 before applying it. Policies are monotonically versioned - replay attacks are rejected. Without a valid signature, the agent keeps its last known-good policy.
|
|
289
|
+
|
|
290
|
+
### Transport hardening
|
|
291
|
+
|
|
292
|
+
- **HMAC-SHA256** payload signing (`X-RASP-Signature` header) - opt-in via `hmacSecret`
|
|
293
|
+
- **TLS certificate pinning** - via `tls.collectorFingerprints` (SHA-256 DER)
|
|
294
|
+
- **mTLS** - via `tls.clientCert` + `tls.clientKey`
|
|
295
|
+
|
|
296
|
+
### Fail-open (never crash your app)
|
|
297
|
+
|
|
298
|
+
Every exception inside the agent - in detectors, transport, audit log - is caught and swallowed. The agent never propagates errors to the host application.
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
## Extension
|
|
303
|
+
|
|
304
|
+
### Custom detector
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
import { RaspAgent, Detector, NormalizedRequest, DetectionResult } from "@queno/agent-node";
|
|
308
|
+
|
|
309
|
+
const myDetector: Detector = {
|
|
310
|
+
name: "my-detector",
|
|
311
|
+
detect(req: NormalizedRequest): DetectionResult | null {
|
|
312
|
+
if (req.path.startsWith("/internal")) {
|
|
313
|
+
return {
|
|
314
|
+
eventType: "unauthorized_internal_access",
|
|
315
|
+
severity: "high",
|
|
316
|
+
detectorName: "my-detector",
|
|
317
|
+
matchedValue: req.path,
|
|
318
|
+
matchedField: "path",
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
return null;
|
|
322
|
+
},
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
const agent = new RaspAgent(
|
|
326
|
+
{ apiKey, projectId, agentId },
|
|
327
|
+
{ extraDetectors: [myDetector] },
|
|
328
|
+
);
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### Prisma instrumentation (BOLA DB correlation)
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
import { PrismaClient } from "@prisma/client";
|
|
335
|
+
import { instrumentPrismaClient } from "@queno/agent-node";
|
|
336
|
+
|
|
337
|
+
const prisma = new PrismaClient({
|
|
338
|
+
log: [{ emit: "event", level: "query" }], // required
|
|
339
|
+
});
|
|
340
|
+
instrumentPrismaClient(prisma);
|
|
341
|
+
|
|
342
|
+
const agent = new RaspAgent({ ..., instrumentDb: true });
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
Also supports `pg`, `mysql2`, `sequelize`, `mongoose`, and `knex` via `instrumentDatabaseDrivers()` (called automatically when `instrumentDb: true`).
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
## Self-protection
|
|
350
|
+
|
|
351
|
+
When `selfProtect: true`:
|
|
352
|
+
|
|
353
|
+
- **Anti-debug** - warns if `--inspect`/`--debug` args or a V8 inspector is detected
|
|
354
|
+
- **Hook integrity** - polls every 30s to detect tampering of installed DB driver hooks
|
|
355
|
+
- **SecureStore** - always active regardless of this flag: `apiKey` and `hmacSecret` are encrypted in-process
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## API discovery
|
|
360
|
+
|
|
361
|
+
The agent passively observes HTTP endpoints at runtime and normalizes routes (`:id` substitution for numeric, UUID, and CUID-like segments). Observations are flushed every 60 seconds (configurable) to `POST /v1/discovery` on the collector.
|
|
362
|
+
|
|
363
|
+
Discover shadow APIs, zombie endpoints (no traffic in 30 days), and get an auth coverage heatmap in the dashboard. Export your live API inventory as an OpenAPI spec.
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
## Development warning
|
|
368
|
+
|
|
369
|
+
> **⚠ The default `policyPublicKey` is a development key** bundled in `src/config.ts`. It works out of the box with the RASP platform seed data. **Before production deployment, override it** with your actual Ed25519 public key via `RaspConfig.policyPublicKey` or `RASP_POLICY_PUBLIC_KEY`.
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
## Architecture overview
|
|
374
|
+
|
|
375
|
+
```
|
|
376
|
+
Host application
|
|
377
|
+
└── RASP middleware (Express / Fastify / NestJS)
|
|
378
|
+
├── inspect(req)
|
|
379
|
+
│ ├── EndpointObserver.observe() ← passive API discovery
|
|
380
|
+
│ ├── Detectors[0..N].detect() ← first-match, sync
|
|
381
|
+
│ └── handleDetection() [async]
|
|
382
|
+
│ ├── RedactionEngine.redact() ← fail-closed on error
|
|
383
|
+
│ ├── AuditLog.write() ← local JSONL, never sent
|
|
384
|
+
│ └── EventBuffer.enqueue() ← batch flush → collector
|
|
385
|
+
└── endRequest(outcome)
|
|
386
|
+
├── EndpointObserver.observeOutcome()
|
|
387
|
+
└── correlateBola() ← DB query correlation (opt-in)
|
|
388
|
+
|
|
389
|
+
EventBuffer → POST /v1/events → rasp-collector → PostgreSQL
|
|
390
|
+
Heartbeat → POST /v1/heartbeat (kill-switch, policy version, upgrade)
|
|
391
|
+
← GET /v1/policy (Ed25519 signed, verified locally)
|
|
392
|
+
Discovery → POST /v1/discovery (batched, 60s)
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
## Testing
|
|
398
|
+
|
|
399
|
+
```bash
|
|
400
|
+
pnpm test # vitest run
|
|
401
|
+
pnpm test:watch # vitest watch
|
|
402
|
+
pnpm typecheck # tsc --noEmit
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
Test coverage areas: redaction engine, redaction patterns (email/card/SIN/IP), policy rejection, canonical bytes, custom rules, self-protect, hook integrity.
|
|
406
|
+
|
|
407
|
+
See `examples/banking-api/` for a full end-to-end test harness (Express app with intentionally vulnerable routes).
|
|
408
|
+
|
|
409
|
+
---
|
|
410
|
+
|
|
411
|
+
## Further reading
|
|
412
|
+
|
|
413
|
+
- [`AGENTS.md`](./AGENTS.md) - architecture rules and security invariants for contributors
|
|
414
|
+
- [`examples/banking-api/README.md`](./examples/banking-api/README.md) - local test lab with attack payloads
|
|
415
|
+
- [Technical documentation](../rasp/docs/dossier-technique.html) - full architecture doc, integration guide, design decisions, and recommendations (French)
|
|
416
|
+
|
|
417
|
+
---
|
|
418
|
+
|
|
419
|
+
## License
|
|
420
|
+
|
|
421
|
+
MIT
|
package/dist/agent.d.ts
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* {@link RaspAgent} - the entry point of the RASP runtime.
|
|
3
|
+
*
|
|
4
|
+
* Wires together five subsystems:
|
|
5
|
+
* 1. Detectors - pattern matchers run on every incoming request.
|
|
6
|
+
* 2. Redaction engine - strips secrets/PII before anything leaves the process.
|
|
7
|
+
* 3. Audit log - local JSONL trace of every redaction action.
|
|
8
|
+
* 4. Event buffer - batches sanitised events to the collector.
|
|
9
|
+
* 5. Heartbeat - liveness signal + kill-switch / policy delivery channel.
|
|
10
|
+
*
|
|
11
|
+
* Invariants enforced here:
|
|
12
|
+
* - {@link RaspAgent.inspect} never throws (fail-open).
|
|
13
|
+
* - A detection is **never** enqueued before passing through the redaction
|
|
14
|
+
* engine; if redaction fails the event is dropped and audit-logged.
|
|
15
|
+
* - When the kill switch is received, inspection is disabled and the buffer
|
|
16
|
+
* is drained.
|
|
17
|
+
*/
|
|
18
|
+
import { type ValidatedRaspConfig } from "./config.js";
|
|
19
|
+
import type { RaspConfig, NormalizedRequest, DetectionResult, AgentMode } from "./types.js";
|
|
20
|
+
import { type Detector } from "./detectors/index.js";
|
|
21
|
+
import { type RequestContext } from "./runtime-context.js";
|
|
22
|
+
import type { RequestOutcome } from "./types.js";
|
|
23
|
+
export declare class RaspAgent {
|
|
24
|
+
/** Fully validated configuration with defaults applied. */
|
|
25
|
+
readonly cfg: ValidatedRaspConfig;
|
|
26
|
+
private redaction;
|
|
27
|
+
private readonly auditLog;
|
|
28
|
+
private readonly client;
|
|
29
|
+
private readonly buffer;
|
|
30
|
+
private readonly heartbeat;
|
|
31
|
+
/** Built-in signature detectors. */
|
|
32
|
+
private readonly baseDetectors;
|
|
33
|
+
/** User-supplied detectors passed to the constructor. */
|
|
34
|
+
private readonly extraDetectors;
|
|
35
|
+
/** Active detector chain, rebuilt when custom rules arrive via policy. */
|
|
36
|
+
private detectors;
|
|
37
|
+
private readonly observer;
|
|
38
|
+
private readonly discoveryBuffer;
|
|
39
|
+
/** Verifies and tracks signed policies distributed by the control plane. */
|
|
40
|
+
private readonly policyManager;
|
|
41
|
+
/** Data residency directives applied in {@link handleDetection}. */
|
|
42
|
+
private dataResidency;
|
|
43
|
+
/** Version the control plane wants this agent to run (canary cohort / pin). */
|
|
44
|
+
private targetVersion;
|
|
45
|
+
/** True when an upgrade is available for this agent. */
|
|
46
|
+
private upgradePending;
|
|
47
|
+
/** Set to true after a kill-switch heartbeat - short-circuits {@link inspect}. */
|
|
48
|
+
private killed;
|
|
49
|
+
/** Stops the self-protection timer (Addendum E.7); null when disabled. */
|
|
50
|
+
private stopSelfProtection;
|
|
51
|
+
/**
|
|
52
|
+
* Enforcement mode in effect. Initialised from `cfg.mode` and updated
|
|
53
|
+
* whenever the collector returns a different mode in a heartbeat response.
|
|
54
|
+
*/
|
|
55
|
+
private currentMode;
|
|
56
|
+
/**
|
|
57
|
+
* Deduplication key for `policy_rejected` telemetry: `"<version>:<reason>"`.
|
|
58
|
+
* Prevents spamming the collector on every heartbeat cycle when the same
|
|
59
|
+
* policy is repeatedly rejected (e.g. a persistent trust-anchor mismatch).
|
|
60
|
+
*/
|
|
61
|
+
private lastRejectedPolicyKey;
|
|
62
|
+
/**
|
|
63
|
+
* Build a new agent.
|
|
64
|
+
*
|
|
65
|
+
* The constructor validates the config (throws on bad input), instantiates
|
|
66
|
+
* the subsystems and registers the default detectors. The heartbeat loop is
|
|
67
|
+
* **not** started until {@link start} is called.
|
|
68
|
+
*
|
|
69
|
+
* @param rawConfig - User-supplied configuration. Validated via
|
|
70
|
+
* {@link validateConfig}; throws if required fields are missing.
|
|
71
|
+
* @param extraDetectors - Optional custom detectors appended after the
|
|
72
|
+
* built-in set. They run in declaration order and the first non-null
|
|
73
|
+
* detection wins.
|
|
74
|
+
*/
|
|
75
|
+
constructor(rawConfig: RaspConfig, extraDetectors?: Detector[]);
|
|
76
|
+
/**
|
|
77
|
+
* Start the heartbeat loop. Idempotent.
|
|
78
|
+
*
|
|
79
|
+
* Call once during application bootstrap, after the framework middleware
|
|
80
|
+
* has been registered. The buffer flush timer is already armed by the
|
|
81
|
+
* constructor.
|
|
82
|
+
*/
|
|
83
|
+
start(): void;
|
|
84
|
+
/**
|
|
85
|
+
* Begin a request's runtime context for DB correlation. Called by the
|
|
86
|
+
* framework integration at the start of the request, before the handler runs,
|
|
87
|
+
* so DB queries issued during the request are attributed to it.
|
|
88
|
+
*
|
|
89
|
+
* @returns The bound context, or `null` when DB instrumentation is disabled.
|
|
90
|
+
*/
|
|
91
|
+
beginRequest(req: NormalizedRequest): RequestContext | null;
|
|
92
|
+
/**
|
|
93
|
+
* Finalise a request: record its outcome for traffic profiling and, when a
|
|
94
|
+
* DB context is present, run BOLA-via-DB correlation and report any finding.
|
|
95
|
+
*/
|
|
96
|
+
endRequest(ctx: RequestContext | null, req: NormalizedRequest, outcome: RequestOutcome): void;
|
|
97
|
+
/**
|
|
98
|
+
* The enforcement mode currently in effect. Reflects remote mode changes
|
|
99
|
+
* (heartbeat) and applied policies, so integrations should consult this
|
|
100
|
+
* rather than the boot config.
|
|
101
|
+
*/
|
|
102
|
+
get mode(): AgentMode;
|
|
103
|
+
/** The policy version currently applied (0 if none). */
|
|
104
|
+
get policyVersion(): number;
|
|
105
|
+
/**
|
|
106
|
+
* Version the control plane wants this agent to run, and whether an upgrade
|
|
107
|
+
* is pending. The Node agent is delivered as an npm package and cannot
|
|
108
|
+
* hot-swap its own binary, so the upgrade is surfaced (logged + queryable)
|
|
109
|
+
* for the host's deployment automation to act on, rather than self-applied.
|
|
110
|
+
*/
|
|
111
|
+
get desiredVersion(): {
|
|
112
|
+
targetVersion: string | null;
|
|
113
|
+
upgradePending: boolean;
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* React to the target version advertised by the heartbeat. Records the target
|
|
117
|
+
* and logs an actionable message when an upgrade is available. Never throws.
|
|
118
|
+
*/
|
|
119
|
+
private handleTargetVersion;
|
|
120
|
+
/**
|
|
121
|
+
* Graceful shutdown.
|
|
122
|
+
*
|
|
123
|
+
* Stops the heartbeat timer, drains the buffer (final flush) and closes
|
|
124
|
+
* the audit log file descriptor. Safe to await during process exit.
|
|
125
|
+
*/
|
|
126
|
+
stop(): Promise<void>;
|
|
127
|
+
/**
|
|
128
|
+
* Run every detector against a normalised request.
|
|
129
|
+
*
|
|
130
|
+
* Detectors run in registration order; the first one returning a non-null
|
|
131
|
+
* result wins. Detector exceptions are swallowed so that a bug in one
|
|
132
|
+
* detector cannot take down the host application.
|
|
133
|
+
*
|
|
134
|
+
* @param req - Framework-agnostic request view.
|
|
135
|
+
* @returns The first {@link DetectionResult} found, or `null` when the
|
|
136
|
+
* request is clean (or when the kill switch is active).
|
|
137
|
+
*
|
|
138
|
+
* @remarks Side-effects (redaction, audit log, buffering) happen in the
|
|
139
|
+
* background via {@link handleDetection}; this method itself returns
|
|
140
|
+
* synchronously.
|
|
141
|
+
*/
|
|
142
|
+
/**
|
|
143
|
+
* Record the response-phase outcome of a request for API-discovery traffic
|
|
144
|
+
* profiling (status code, latency, confirmed auth middleware execution).
|
|
145
|
+
* Called by the framework integration's response hook. Never throws.
|
|
146
|
+
*/
|
|
147
|
+
observeOutcome(req: NormalizedRequest, outcome: import("./types.js").RequestOutcome): void;
|
|
148
|
+
inspect(req: NormalizedRequest): DetectionResult | null;
|
|
149
|
+
/**
|
|
150
|
+
* Build a raw event payload, redact it, audit-log locally and enqueue it.
|
|
151
|
+
*
|
|
152
|
+
* Control flow:
|
|
153
|
+
* 1. Assemble the raw {@link EventPayload} from config + detection + request.
|
|
154
|
+
* 2. Run it through the {@link RedactionEngine}. On failure → drop and
|
|
155
|
+
* audit-log with `dropped: true`.
|
|
156
|
+
* 3. If any field was redacted → write a non-dropped audit-log line.
|
|
157
|
+
* 4. Stamp `auditLoggedLocally` into the event metadata and enqueue.
|
|
158
|
+
*/
|
|
159
|
+
private handleDetection;
|
|
160
|
+
/**
|
|
161
|
+
* Apply data residency directives (Addendum B.3) before an event is queued
|
|
162
|
+
* for transmission.
|
|
163
|
+
*
|
|
164
|
+
* @returns The (possibly trimmed) event to send, or `null` if the event must
|
|
165
|
+
* stay local / be skipped. The local audit log has already been written by
|
|
166
|
+
* the caller, so dropping here still leaves a verifiable local record.
|
|
167
|
+
*/
|
|
168
|
+
private applyDataResidency;
|
|
169
|
+
/**
|
|
170
|
+
* Handle a kill-switch heartbeat response.
|
|
171
|
+
*
|
|
172
|
+
* Disables inspection, drains the buffer and closes the audit log. The
|
|
173
|
+
* heartbeat scheduler has already stopped itself by the time this is
|
|
174
|
+
* called.
|
|
175
|
+
*/
|
|
176
|
+
private handleKillSwitch;
|
|
177
|
+
/**
|
|
178
|
+
* Called when the kill switch transitions from active back to inactive.
|
|
179
|
+
* Re-enables inspection and restarts self-protection if configured.
|
|
180
|
+
*/
|
|
181
|
+
private handleRecover;
|
|
182
|
+
/**
|
|
183
|
+
* React to a policy-version change signalled by the heartbeat.
|
|
184
|
+
*
|
|
185
|
+
* Fetches the latest signed policy, verifies its Ed25519 signature against
|
|
186
|
+
* the pinned trust anchor, and applies it. An untrusted, malformed or stale
|
|
187
|
+
* policy is ignored and the agent keeps its current configuration
|
|
188
|
+
* (self-rollback of policy - Addendum D.4 / E.4.1). Never throws.
|
|
189
|
+
*
|
|
190
|
+
* Rejections are surfaced via a `console.warn` and a `policy_rejected`
|
|
191
|
+
* telemetry event so operators can diagnose trust-anchor mismatches without
|
|
192
|
+
* reading raw application logs.
|
|
193
|
+
*/
|
|
194
|
+
private handlePolicyChange;
|
|
195
|
+
/**
|
|
196
|
+
* Emit a single `policy_rejected` telemetry event so the control plane can
|
|
197
|
+
* surface trust-anchor or signature mismatches in the dashboard.
|
|
198
|
+
*
|
|
199
|
+
* The event is deduplicated by `version:reason` so a persistent rejection
|
|
200
|
+
* (e.g. a misconfigured trust anchor that the operator has not yet fixed)
|
|
201
|
+
* does not flood the collector on every heartbeat cycle.
|
|
202
|
+
*
|
|
203
|
+
* The payload passes through the redaction engine (Addendum B.2 / AGENTS.md
|
|
204
|
+
* rule: "All telemetry must pass through the redaction engine before
|
|
205
|
+
* buffering") and the data-residency filter before being enqueued.
|
|
206
|
+
*/
|
|
207
|
+
private enqueuePolicyRejected;
|
|
208
|
+
/**
|
|
209
|
+
* Apply a verified policy to the live agent: enforcement mode, customer
|
|
210
|
+
* detection rules, redaction configuration and data residency directives.
|
|
211
|
+
*/
|
|
212
|
+
private applyPolicy;
|
|
213
|
+
/** Rebuild the active detector chain from base + custom rules + extras. */
|
|
214
|
+
private rebuildDetectors;
|
|
215
|
+
/**
|
|
216
|
+
* Reconfigure the redaction engine from a policy. The engine always keeps its
|
|
217
|
+
* built-in protections; the policy can only add field-name patterns and tune
|
|
218
|
+
* value-based redaction / IP handling.
|
|
219
|
+
*/
|
|
220
|
+
private applyRedactionConfig;
|
|
221
|
+
}
|
|
222
|
+
//# sourceMappingURL=agent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"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"}
|