@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
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const MAX_BATCH_SIZE = 500;
|
|
2
|
+
export class DiscoveryBuffer {
|
|
3
|
+
client;
|
|
4
|
+
observer;
|
|
5
|
+
projectId;
|
|
6
|
+
agentId;
|
|
7
|
+
timer = null;
|
|
8
|
+
constructor(client, observer, opts) {
|
|
9
|
+
this.client = client;
|
|
10
|
+
this.observer = observer;
|
|
11
|
+
this.projectId = opts.projectId;
|
|
12
|
+
this.agentId = opts.agentId;
|
|
13
|
+
this.timer = setInterval(() => {
|
|
14
|
+
this.flush().catch(() => { });
|
|
15
|
+
}, opts.flushIntervalMs);
|
|
16
|
+
// Allow Node.js to exit even if the timer is still running
|
|
17
|
+
if (this.timer.unref)
|
|
18
|
+
this.timer.unref();
|
|
19
|
+
}
|
|
20
|
+
/** Stop the flush timer and send a final batch. */
|
|
21
|
+
async stop() {
|
|
22
|
+
if (this.timer) {
|
|
23
|
+
clearInterval(this.timer);
|
|
24
|
+
this.timer = null;
|
|
25
|
+
}
|
|
26
|
+
await this.flush();
|
|
27
|
+
}
|
|
28
|
+
async flush() {
|
|
29
|
+
const entries = this.observer.drain();
|
|
30
|
+
if (entries.length === 0)
|
|
31
|
+
return;
|
|
32
|
+
const timestamp = new Date().toISOString();
|
|
33
|
+
// Split into chunks of MAX_BATCH_SIZE
|
|
34
|
+
for (let i = 0; i < entries.length; i += MAX_BATCH_SIZE) {
|
|
35
|
+
const chunk = entries.slice(i, i + MAX_BATCH_SIZE);
|
|
36
|
+
try {
|
|
37
|
+
await this.client.sendDiscovery({
|
|
38
|
+
projectId: this.projectId,
|
|
39
|
+
agentId: this.agentId,
|
|
40
|
+
timestamp,
|
|
41
|
+
endpoints: chunk,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// Fail open
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=discovery-buffer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery-buffer.js","sourceRoot":"","sources":["../../src/api-discovery/discovery-buffer.ts"],"names":[],"mappings":"AAiBA,MAAM,cAAc,GAAG,GAAG,CAAC;AAE3B,MAAM,OAAO,eAAe;IACT,MAAM,CAAkB;IACxB,QAAQ,CAAmB;IAC3B,SAAS,CAAS;IAClB,OAAO,CAAS;IACzB,KAAK,GAA0C,IAAI,CAAC;IAE5D,YACE,MAAuB,EACvB,QAA0B,EAC1B,IAA4B;QAE5B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;QAChC,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAE5B,IAAI,CAAC,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;YAC5B,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAC/B,CAAC,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC;QAEzB,2DAA2D;QAC3D,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK;YAAE,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;IAC3C,CAAC;IAED,mDAAmD;IACnD,KAAK,CAAC,IAAI;QACR,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC1B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QACpB,CAAC;QACD,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;IAEO,KAAK,CAAC,KAAK;QACjB,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;QACtC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAEjC,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAE3C,sCAAsC;QACtC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,IAAI,cAAc,EAAE,CAAC;YACxD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,cAAc,CAAC,CAAC;YACnD,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC;oBAC9B,SAAS,EAAE,IAAI,CAAC,SAAS;oBACzB,OAAO,EAAE,IAAI,CAAC,OAAO;oBACrB,SAAS;oBACT,SAAS,EAAE,KAAK;iBACjB,CAAC,CAAC;YACL,CAAC;YAAC,MAAM,CAAC;gBACP,YAAY;YACd,CAAC;QACH,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Observes HTTP requests passively and aggregates per-endpoint statistics for
|
|
3
|
+
* runtime API discovery (Addendum A.2): endpoint inventory, auth state, data
|
|
4
|
+
* sensitivity, schema inference and traffic profile (volume, error rate,
|
|
5
|
+
* latency).
|
|
6
|
+
*
|
|
7
|
+
* All operations are synchronous and in-memory - no I/O, no throws. Designed to
|
|
8
|
+
* be called from {@link RaspAgent.inspect} (request phase) and from the
|
|
9
|
+
* integration's response hook via {@link EndpointObserver.observeOutcome}.
|
|
10
|
+
*/
|
|
11
|
+
import type { NormalizedRequest, DiscoveryEntry, RequestOutcome } from "../types.js";
|
|
12
|
+
export declare class EndpointObserver {
|
|
13
|
+
private readonly stats;
|
|
14
|
+
/** Record one request observation. Synchronous, never throws. */
|
|
15
|
+
observe(req: NormalizedRequest): void;
|
|
16
|
+
/**
|
|
17
|
+
* Record the response-phase outcome for a request. Updates the traffic
|
|
18
|
+
* profile (error rate, latency) and confirms auth middleware execution.
|
|
19
|
+
*/
|
|
20
|
+
observeOutcome(req: NormalizedRequest, outcome: RequestOutcome): void;
|
|
21
|
+
/** Drain accumulated stats and reset. */
|
|
22
|
+
drain(): DiscoveryEntry[];
|
|
23
|
+
get size(): number;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=endpoint-observer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"endpoint-observer.d.ts","sourceRoot":"","sources":["../../src/api-discovery/endpoint-observer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAc,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAwDjG,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoC;IAE1D,iEAAiE;IACjE,OAAO,CAAC,GAAG,EAAE,iBAAiB,GAAG,IAAI;IAgCrC;;;OAGG;IACH,cAAc,CAAC,GAAG,EAAE,iBAAiB,EAAE,OAAO,EAAE,cAAc,GAAG,IAAI;IAqBrE,yCAAyC;IACzC,KAAK,IAAI,cAAc,EAAE;IAqBzB,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { normalizePath } from "./route-normalizer.js";
|
|
2
|
+
import { DEFAULT_REDACTION_PATTERNS } from "../redaction/patterns.js";
|
|
3
|
+
function hasSensitiveKeys(obj) {
|
|
4
|
+
if (!obj || typeof obj !== "object")
|
|
5
|
+
return false;
|
|
6
|
+
return Object.keys(obj).some((key) => DEFAULT_REDACTION_PATTERNS.some((p) => p.matchKey.test(key)));
|
|
7
|
+
}
|
|
8
|
+
function inferAuthStatus(req) {
|
|
9
|
+
const auth = req.headers["authorization"];
|
|
10
|
+
if (auth && typeof auth === "string" && auth.length > 0) {
|
|
11
|
+
return "authenticated";
|
|
12
|
+
}
|
|
13
|
+
return "unauthenticated";
|
|
14
|
+
}
|
|
15
|
+
function inferHasSensitiveData(req) {
|
|
16
|
+
return hasSensitiveKeys(req.body) || hasSensitiveKeys(req.query);
|
|
17
|
+
}
|
|
18
|
+
function jsonType(v) {
|
|
19
|
+
if (v === null)
|
|
20
|
+
return "null";
|
|
21
|
+
if (Array.isArray(v))
|
|
22
|
+
return "array";
|
|
23
|
+
return typeof v;
|
|
24
|
+
}
|
|
25
|
+
/** Merge the top-level keys of query + body into a field -> type map. */
|
|
26
|
+
function inferSchema(req, into) {
|
|
27
|
+
const sources = [req.query, req.body];
|
|
28
|
+
for (const src of sources) {
|
|
29
|
+
if (src && typeof src === "object" && !Array.isArray(src)) {
|
|
30
|
+
for (const [k, v] of Object.entries(src)) {
|
|
31
|
+
if (!(k in into))
|
|
32
|
+
into[k] = jsonType(v);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function keyFor(req) {
|
|
38
|
+
return `${req.method.toUpperCase()}:${normalizePath(req.path)}`;
|
|
39
|
+
}
|
|
40
|
+
export class EndpointObserver {
|
|
41
|
+
stats = new Map();
|
|
42
|
+
/** Record one request observation. Synchronous, never throws. */
|
|
43
|
+
observe(req) {
|
|
44
|
+
try {
|
|
45
|
+
const key = keyFor(req);
|
|
46
|
+
const existing = this.stats.get(key);
|
|
47
|
+
if (existing) {
|
|
48
|
+
existing.count++;
|
|
49
|
+
if (existing.authStatus !== "authenticated") {
|
|
50
|
+
existing.authStatus = inferAuthStatus(req);
|
|
51
|
+
}
|
|
52
|
+
if (!existing.hasSensitiveData) {
|
|
53
|
+
existing.hasSensitiveData = inferHasSensitiveData(req);
|
|
54
|
+
}
|
|
55
|
+
inferSchema(req, existing.schemaFields);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
const schemaFields = {};
|
|
59
|
+
inferSchema(req, schemaFields);
|
|
60
|
+
this.stats.set(key, {
|
|
61
|
+
authStatus: inferAuthStatus(req),
|
|
62
|
+
hasSensitiveData: inferHasSensitiveData(req),
|
|
63
|
+
count: 1,
|
|
64
|
+
authObserved: false,
|
|
65
|
+
errorCount: 0,
|
|
66
|
+
sumDurationMs: 0,
|
|
67
|
+
timedCount: 0,
|
|
68
|
+
schemaFields,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// Fail open - never propagate to host application.
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Record the response-phase outcome for a request. Updates the traffic
|
|
78
|
+
* profile (error rate, latency) and confirms auth middleware execution.
|
|
79
|
+
*/
|
|
80
|
+
observeOutcome(req, outcome) {
|
|
81
|
+
try {
|
|
82
|
+
const key = keyFor(req);
|
|
83
|
+
const s = this.stats.get(key);
|
|
84
|
+
if (!s)
|
|
85
|
+
return;
|
|
86
|
+
if (typeof outcome.statusCode === "number" && outcome.statusCode >= 400) {
|
|
87
|
+
s.errorCount++;
|
|
88
|
+
}
|
|
89
|
+
if (typeof outcome.durationMs === "number" && outcome.durationMs >= 0) {
|
|
90
|
+
s.sumDurationMs += outcome.durationMs;
|
|
91
|
+
s.timedCount++;
|
|
92
|
+
}
|
|
93
|
+
if (outcome.authenticated) {
|
|
94
|
+
s.authObserved = true;
|
|
95
|
+
s.authStatus = "authenticated";
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// Fail open.
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/** Drain accumulated stats and reset. */
|
|
103
|
+
drain() {
|
|
104
|
+
const entries = [];
|
|
105
|
+
for (const [key, s] of this.stats) {
|
|
106
|
+
const colonIdx = key.indexOf(":");
|
|
107
|
+
entries.push({
|
|
108
|
+
method: key.slice(0, colonIdx),
|
|
109
|
+
pathPattern: key.slice(colonIdx + 1),
|
|
110
|
+
authStatus: s.authStatus,
|
|
111
|
+
hasSensitiveData: s.hasSensitiveData,
|
|
112
|
+
observationCount: s.count,
|
|
113
|
+
authObserved: s.authObserved,
|
|
114
|
+
errorCount: s.errorCount,
|
|
115
|
+
sumDurationMs: s.sumDurationMs,
|
|
116
|
+
timedCount: s.timedCount,
|
|
117
|
+
schemaFields: s.schemaFields,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
this.stats.clear();
|
|
121
|
+
return entries;
|
|
122
|
+
}
|
|
123
|
+
get size() {
|
|
124
|
+
return this.stats.size;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=endpoint-observer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"endpoint-observer.js","sourceRoot":"","sources":["../../src/api-discovery/endpoint-observer.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAC;AAatE,SAAS,gBAAgB,CAAC,GAAY;IACpC,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAClD,OAAO,MAAM,CAAC,IAAI,CAAC,GAA8B,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAC9D,0BAA0B,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAC7D,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,GAAsB;IAC7C,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;IAC1C,IAAI,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxD,OAAO,eAAe,CAAC;IACzB,CAAC;IACD,OAAO,iBAAiB,CAAC;AAC3B,CAAC;AAED,SAAS,qBAAqB,CAAC,GAAsB;IACnD,OAAO,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,gBAAgB,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;AACnE,CAAC;AAED,SAAS,QAAQ,CAAC,CAAU;IAC1B,IAAI,CAAC,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC;IAC9B,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QAAE,OAAO,OAAO,CAAC;IACrC,OAAO,OAAO,CAAC,CAAC;AAClB,CAAC;AAED,yEAAyE;AACzE,SAAS,WAAW,CAAC,GAAsB,EAAE,IAA4B;IACvE,MAAM,OAAO,GAAc,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;IACjD,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;QAC1B,IAAI,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YAC1D,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAA8B,CAAC,EAAE,CAAC;gBACpE,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;oBAAE,IAAI,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;YAC1C,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,MAAM,CAAC,GAAsB;IACpC,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,WAAW,EAAE,IAAI,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;AAClE,CAAC;AAED,MAAM,OAAO,gBAAgB;IACV,KAAK,GAAG,IAAI,GAAG,EAAyB,CAAC;IAE1D,iEAAiE;IACjE,OAAO,CAAC,GAAsB;QAC5B,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;YACxB,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACrC,IAAI,QAAQ,EAAE,CAAC;gBACb,QAAQ,CAAC,KAAK,EAAE,CAAC;gBACjB,IAAI,QAAQ,CAAC,UAAU,KAAK,eAAe,EAAE,CAAC;oBAC5C,QAAQ,CAAC,UAAU,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;gBAC7C,CAAC;gBACD,IAAI,CAAC,QAAQ,CAAC,gBAAgB,EAAE,CAAC;oBAC/B,QAAQ,CAAC,gBAAgB,GAAG,qBAAqB,CAAC,GAAG,CAAC,CAAC;gBACzD,CAAC;gBACD,WAAW,CAAC,GAAG,EAAE,QAAQ,CAAC,YAAY,CAAC,CAAC;YAC1C,CAAC;iBAAM,CAAC;gBACN,MAAM,YAAY,GAA2B,EAAE,CAAC;gBAChD,WAAW,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;gBAC/B,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE;oBAClB,UAAU,EAAE,eAAe,CAAC,GAAG,CAAC;oBAChC,gBAAgB,EAAE,qBAAqB,CAAC,GAAG,CAAC;oBAC5C,KAAK,EAAE,CAAC;oBACR,YAAY,EAAE,KAAK;oBACnB,UAAU,EAAE,CAAC;oBACb,aAAa,EAAE,CAAC;oBAChB,UAAU,EAAE,CAAC;oBACb,YAAY;iBACb,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,mDAAmD;QACrD,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,cAAc,CAAC,GAAsB,EAAE,OAAuB;QAC5D,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;YACxB,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC9B,IAAI,CAAC,CAAC;gBAAE,OAAO;YACf,IAAI,OAAO,OAAO,CAAC,UAAU,KAAK,QAAQ,IAAI,OAAO,CAAC,UAAU,IAAI,GAAG,EAAE,CAAC;gBACxE,CAAC,CAAC,UAAU,EAAE,CAAC;YACjB,CAAC;YACD,IAAI,OAAO,OAAO,CAAC,UAAU,KAAK,QAAQ,IAAI,OAAO,CAAC,UAAU,IAAI,CAAC,EAAE,CAAC;gBACtE,CAAC,CAAC,aAAa,IAAI,OAAO,CAAC,UAAU,CAAC;gBACtC,CAAC,CAAC,UAAU,EAAE,CAAC;YACjB,CAAC;YACD,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC;gBAC1B,CAAC,CAAC,YAAY,GAAG,IAAI,CAAC;gBACtB,CAAC,CAAC,UAAU,GAAG,eAAe,CAAC;YACjC,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,aAAa;QACf,CAAC;IACH,CAAC;IAED,yCAAyC;IACzC,KAAK;QACH,MAAM,OAAO,GAAqB,EAAE,CAAC;QACrC,KAAK,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAClC,MAAM,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YAClC,OAAO,CAAC,IAAI,CAAC;gBACX,MAAM,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC;gBAC9B,WAAW,EAAE,GAAG,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC;gBACpC,UAAU,EAAE,CAAC,CAAC,UAAU;gBACxB,gBAAgB,EAAE,CAAC,CAAC,gBAAgB;gBACpC,gBAAgB,EAAE,CAAC,CAAC,KAAK;gBACzB,YAAY,EAAE,CAAC,CAAC,YAAY;gBAC5B,UAAU,EAAE,CAAC,CAAC,UAAU;gBACxB,aAAa,EAAE,CAAC,CAAC,aAAa;gBAC9B,UAAU,EAAE,CAAC,CAAC,UAAU;gBACxB,YAAY,EAAE,CAAC,CAAC,YAAY;aAC7B,CAAC,CAAC;QACL,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACnB,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;IACzB,CAAC;CACF"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalises raw request paths into stable route patterns.
|
|
3
|
+
*
|
|
4
|
+
* Examples:
|
|
5
|
+
* /api/users/42 → /api/users/:id
|
|
6
|
+
* /api/users/abc-123-def → /api/users/:id (UUID-like)
|
|
7
|
+
* /api/users/me → /api/users/me (unchanged - not ID-like)
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Replace ID-like path segments with `:id`.
|
|
11
|
+
*
|
|
12
|
+
* Only the path (no query string) should be passed in.
|
|
13
|
+
*/
|
|
14
|
+
export declare function normalizePath(path: string): string;
|
|
15
|
+
//# sourceMappingURL=route-normalizer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-normalizer.d.ts","sourceRoot":"","sources":["../../src/api-discovery/route-normalizer.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAmBH;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQlD"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalises raw request paths into stable route patterns.
|
|
3
|
+
*
|
|
4
|
+
* Examples:
|
|
5
|
+
* /api/users/42 → /api/users/:id
|
|
6
|
+
* /api/users/abc-123-def → /api/users/:id (UUID-like)
|
|
7
|
+
* /api/users/me → /api/users/me (unchanged - not ID-like)
|
|
8
|
+
*/
|
|
9
|
+
/** Matches plain numeric IDs up to 12 digits (e.g. 42, 100000000000). */
|
|
10
|
+
const NUMERIC_ID_RE = /^\d{1,12}$/;
|
|
11
|
+
/**
|
|
12
|
+
* Matches UUID v4 variants (with or without hyphens, case-insensitive).
|
|
13
|
+
* Also catches CUID-style strings (cjld2cy...) and NanoID-like 20–26 char IDs.
|
|
14
|
+
*/
|
|
15
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
16
|
+
/** Matches CUID / NanoID style: starts with a letter, 20–32 alphanum chars. */
|
|
17
|
+
const CUID_RE = /^[a-z][a-z0-9]{19,31}$/i;
|
|
18
|
+
function isIdSegment(segment) {
|
|
19
|
+
return NUMERIC_ID_RE.test(segment) || UUID_RE.test(segment) || CUID_RE.test(segment);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Replace ID-like path segments with `:id`.
|
|
23
|
+
*
|
|
24
|
+
* Only the path (no query string) should be passed in.
|
|
25
|
+
*/
|
|
26
|
+
export function normalizePath(path) {
|
|
27
|
+
// Strip query string if accidentally included
|
|
28
|
+
const bare = path.split("?")[0] ?? path;
|
|
29
|
+
return bare
|
|
30
|
+
.split("/")
|
|
31
|
+
.map((segment) => (isIdSegment(segment) ? ":id" : segment))
|
|
32
|
+
.join("/");
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=route-normalizer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-normalizer.js","sourceRoot":"","sources":["../../src/api-discovery/route-normalizer.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,yEAAyE;AACzE,MAAM,aAAa,GAAG,YAAY,CAAC;AAEnC;;;GAGG;AACH,MAAM,OAAO,GACX,iEAAiE,CAAC;AAEpE,+EAA+E;AAC/E,MAAM,OAAO,GAAG,yBAAyB,CAAC;AAE1C,SAAS,WAAW,CAAC,OAAe;IAClC,OAAO,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AACvF,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,8CAA8C;IAC9C,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;IAExC,OAAO,IAAI;SACR,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;SAC1D,IAAI,CAAC,GAAG,CAAC,CAAC;AACf,CAAC"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime configuration validation for the RASP agent.
|
|
3
|
+
*
|
|
4
|
+
* Wraps user-supplied {@link RaspConfig} in a Zod schema that:
|
|
5
|
+
* - enforces required fields (`apiKey`, `projectId`, `agentId`),
|
|
6
|
+
* - applies safe defaults for everything else,
|
|
7
|
+
* - rejects degenerate values (e.g. 0 ms intervals).
|
|
8
|
+
*
|
|
9
|
+
* {@link validateConfig} is the only sanctioned way to produce a
|
|
10
|
+
* {@link ValidatedRaspConfig} - every other module relies on the post-parse
|
|
11
|
+
* defaults being present.
|
|
12
|
+
*/
|
|
13
|
+
import { z } from "zod";
|
|
14
|
+
import type { RaspConfig } from "./types.js";
|
|
15
|
+
/**
|
|
16
|
+
* Collector base URL.
|
|
17
|
+
*
|
|
18
|
+
* Per `AGENTS.md` "Security Rules", the agent must not perform arbitrary
|
|
19
|
+
* outbound network calls - only this endpoint is allowed.
|
|
20
|
+
*
|
|
21
|
+
* `RASP_COLLECTOR_URL` is intentionally only read here so that example apps
|
|
22
|
+
* and local test environments can point the agent at a local collector without
|
|
23
|
+
* modifying source code. In production the env var is not set and the
|
|
24
|
+
* hard-coded default is used.
|
|
25
|
+
*/
|
|
26
|
+
export declare const COLLECTOR_URL: string;
|
|
27
|
+
/**
|
|
28
|
+
* Default pinned policy-signing public key (the trust anchor for signed policy
|
|
29
|
+
* distribution - Addendum E.4.1).
|
|
30
|
+
*
|
|
31
|
+
* This is the DEVELOPMENT key shipped so the signed-policy flow works
|
|
32
|
+
* end-to-end out of the box. The matching private key is documented in the
|
|
33
|
+
* README ("Policy signing bootstrap") for local use ONLY.
|
|
34
|
+
*
|
|
35
|
+
* In production this constant is replaced at release time with the real public
|
|
36
|
+
* key, and/or overridden per-deployment via `RaspConfig.policyPublicKey` or the
|
|
37
|
+
* `RASP_POLICY_PUBLIC_KEY` env var. The private key never ships.
|
|
38
|
+
*/
|
|
39
|
+
export declare const DEFAULT_POLICY_PUBLIC_KEY: string;
|
|
40
|
+
/**
|
|
41
|
+
* Zod schema mirroring {@link RaspConfig}.
|
|
42
|
+
*
|
|
43
|
+
* Each default here matches the documented behaviour in `types.ts` and the
|
|
44
|
+
* lower bounds protect against pathological values (e.g. a 100 ms heartbeat
|
|
45
|
+
* that would DOS the collector).
|
|
46
|
+
*/
|
|
47
|
+
declare const RaspConfigSchema: z.ZodObject<{
|
|
48
|
+
apiKey: z.ZodString;
|
|
49
|
+
projectId: z.ZodString;
|
|
50
|
+
agentId: z.ZodString;
|
|
51
|
+
agentVersion: z.ZodOptional<z.ZodString>;
|
|
52
|
+
mode: z.ZodDefault<z.ZodEnum<{
|
|
53
|
+
monitor: "monitor";
|
|
54
|
+
block: "block";
|
|
55
|
+
}>>;
|
|
56
|
+
channel: z.ZodDefault<z.ZodEnum<{
|
|
57
|
+
stable: "stable";
|
|
58
|
+
early: "early";
|
|
59
|
+
edge: "edge";
|
|
60
|
+
}>>;
|
|
61
|
+
heartbeatIntervalMs: z.ZodDefault<z.ZodNumber>;
|
|
62
|
+
flushIntervalMs: z.ZodDefault<z.ZodNumber>;
|
|
63
|
+
bufferMaxSize: z.ZodDefault<z.ZodNumber>;
|
|
64
|
+
transportTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
65
|
+
auditLog: z.ZodDefault<z.ZodBoolean>;
|
|
66
|
+
auditLogPath: z.ZodDefault<z.ZodString>;
|
|
67
|
+
auditLogMaxBytes: z.ZodDefault<z.ZodNumber>;
|
|
68
|
+
framework: z.ZodOptional<z.ZodString>;
|
|
69
|
+
runtime: z.ZodDefault<z.ZodString>;
|
|
70
|
+
discoveryFlushIntervalMs: z.ZodDefault<z.ZodNumber>;
|
|
71
|
+
policyPublicKey: z.ZodDefault<z.ZodString>;
|
|
72
|
+
hmacSecret: z.ZodOptional<z.ZodString>;
|
|
73
|
+
instrumentDb: z.ZodDefault<z.ZodBoolean>;
|
|
74
|
+
selfProtect: z.ZodDefault<z.ZodBoolean>;
|
|
75
|
+
tls: z.ZodOptional<z.ZodObject<{
|
|
76
|
+
caCert: z.ZodOptional<z.ZodString>;
|
|
77
|
+
clientCert: z.ZodOptional<z.ZodString>;
|
|
78
|
+
clientKey: z.ZodOptional<z.ZodString>;
|
|
79
|
+
collectorFingerprints: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
80
|
+
rejectUnauthorized: z.ZodOptional<z.ZodBoolean>;
|
|
81
|
+
}, z.core.$strip>>;
|
|
82
|
+
}, z.core.$strip>;
|
|
83
|
+
/**
|
|
84
|
+
* Fully resolved configuration with all defaults applied.
|
|
85
|
+
*
|
|
86
|
+
* Internal modules (`agent.ts`, `transport/*`, `redaction/*`) accept this
|
|
87
|
+
* stricter type rather than the user-facing optional-heavy {@link RaspConfig}.
|
|
88
|
+
*/
|
|
89
|
+
export type ValidatedRaspConfig = z.infer<typeof RaspConfigSchema>;
|
|
90
|
+
/**
|
|
91
|
+
* Validate a raw user config and return a fully resolved one.
|
|
92
|
+
*
|
|
93
|
+
* @param raw - The configuration object passed to `new RaspAgent(...)`.
|
|
94
|
+
* @returns The same configuration with all defaults applied.
|
|
95
|
+
* @throws {Error} If validation fails. The error message has the form
|
|
96
|
+
* `[rasp-agent] Invalid configuration - field1: msg1; field2: msg2`.
|
|
97
|
+
*/
|
|
98
|
+
export declare function validateConfig(raw: RaspConfig): ValidatedRaspConfig;
|
|
99
|
+
export {};
|
|
100
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C;;;;;;;;;;GAUG;AACH,eAAO,MAAM,aAAa,QACsC,CAAC;AAEjE;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,yBAAyB,QAMxB,CAAC;AAEf;;;;;;GAMG;AACH,QAAA,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA8BpB,CAAC;AAEH;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAEnE;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,UAAU,GAAG,mBAAmB,CAUnE"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime configuration validation for the RASP agent.
|
|
3
|
+
*
|
|
4
|
+
* Wraps user-supplied {@link RaspConfig} in a Zod schema that:
|
|
5
|
+
* - enforces required fields (`apiKey`, `projectId`, `agentId`),
|
|
6
|
+
* - applies safe defaults for everything else,
|
|
7
|
+
* - rejects degenerate values (e.g. 0 ms intervals).
|
|
8
|
+
*
|
|
9
|
+
* {@link validateConfig} is the only sanctioned way to produce a
|
|
10
|
+
* {@link ValidatedRaspConfig} - every other module relies on the post-parse
|
|
11
|
+
* defaults being present.
|
|
12
|
+
*/
|
|
13
|
+
import { z } from "zod";
|
|
14
|
+
/**
|
|
15
|
+
* Collector base URL.
|
|
16
|
+
*
|
|
17
|
+
* Per `AGENTS.md` "Security Rules", the agent must not perform arbitrary
|
|
18
|
+
* outbound network calls - only this endpoint is allowed.
|
|
19
|
+
*
|
|
20
|
+
* `RASP_COLLECTOR_URL` is intentionally only read here so that example apps
|
|
21
|
+
* and local test environments can point the agent at a local collector without
|
|
22
|
+
* modifying source code. In production the env var is not set and the
|
|
23
|
+
* hard-coded default is used.
|
|
24
|
+
*/
|
|
25
|
+
export const COLLECTOR_URL = process.env.RASP_COLLECTOR_URL ?? "https://collector.rasp.dev";
|
|
26
|
+
/**
|
|
27
|
+
* Default pinned policy-signing public key (the trust anchor for signed policy
|
|
28
|
+
* distribution - Addendum E.4.1).
|
|
29
|
+
*
|
|
30
|
+
* This is the DEVELOPMENT key shipped so the signed-policy flow works
|
|
31
|
+
* end-to-end out of the box. The matching private key is documented in the
|
|
32
|
+
* README ("Policy signing bootstrap") for local use ONLY.
|
|
33
|
+
*
|
|
34
|
+
* In production this constant is replaced at release time with the real public
|
|
35
|
+
* key, and/or overridden per-deployment via `RaspConfig.policyPublicKey` or the
|
|
36
|
+
* `RASP_POLICY_PUBLIC_KEY` env var. The private key never ships.
|
|
37
|
+
*/
|
|
38
|
+
export const DEFAULT_POLICY_PUBLIC_KEY = process.env.RASP_POLICY_PUBLIC_KEY ??
|
|
39
|
+
[
|
|
40
|
+
"-----BEGIN PUBLIC KEY-----",
|
|
41
|
+
"MCowBQYDK2VwAyEAI/7DX+UlM7pdHvIPZXGBtr7WvYKi3ZnRY7QtOhiFufM=",
|
|
42
|
+
"-----END PUBLIC KEY-----",
|
|
43
|
+
].join("\n");
|
|
44
|
+
/**
|
|
45
|
+
* Zod schema mirroring {@link RaspConfig}.
|
|
46
|
+
*
|
|
47
|
+
* Each default here matches the documented behaviour in `types.ts` and the
|
|
48
|
+
* lower bounds protect against pathological values (e.g. a 100 ms heartbeat
|
|
49
|
+
* that would DOS the collector).
|
|
50
|
+
*/
|
|
51
|
+
const RaspConfigSchema = z.object({
|
|
52
|
+
apiKey: z.string().min(1, "apiKey is required"),
|
|
53
|
+
projectId: z.string().min(1, "projectId is required"),
|
|
54
|
+
agentId: z.string().min(1, "agentId is required"),
|
|
55
|
+
agentVersion: z.string().optional(),
|
|
56
|
+
mode: z.enum(["monitor", "block"]).default("monitor"),
|
|
57
|
+
channel: z.enum(["stable", "early", "edge"]).default("stable"),
|
|
58
|
+
heartbeatIntervalMs: z.number().int().min(5_000).default(30_000),
|
|
59
|
+
flushIntervalMs: z.number().int().min(1_000).default(5_000),
|
|
60
|
+
bufferMaxSize: z.number().int().min(1).default(50),
|
|
61
|
+
transportTimeoutMs: z.number().int().min(500).default(5_000),
|
|
62
|
+
auditLog: z.boolean().default(true),
|
|
63
|
+
auditLogPath: z.string().default("./rasp-audit.log"),
|
|
64
|
+
auditLogMaxBytes: z.number().int().min(1).default(10 * 1024 * 1024),
|
|
65
|
+
framework: z.string().optional(),
|
|
66
|
+
runtime: z.string().default("node"),
|
|
67
|
+
discoveryFlushIntervalMs: z.number().int().min(5_000).default(60_000),
|
|
68
|
+
policyPublicKey: z.string().default(DEFAULT_POLICY_PUBLIC_KEY),
|
|
69
|
+
hmacSecret: z.string().optional(),
|
|
70
|
+
instrumentDb: z.boolean().default(false),
|
|
71
|
+
selfProtect: z.boolean().default(false),
|
|
72
|
+
tls: z
|
|
73
|
+
.object({
|
|
74
|
+
caCert: z.string().optional(),
|
|
75
|
+
clientCert: z.string().optional(),
|
|
76
|
+
clientKey: z.string().optional(),
|
|
77
|
+
collectorFingerprints: z.array(z.string()).optional(),
|
|
78
|
+
rejectUnauthorized: z.boolean().optional(),
|
|
79
|
+
})
|
|
80
|
+
.optional(),
|
|
81
|
+
});
|
|
82
|
+
/**
|
|
83
|
+
* Validate a raw user config and return a fully resolved one.
|
|
84
|
+
*
|
|
85
|
+
* @param raw - The configuration object passed to `new RaspAgent(...)`.
|
|
86
|
+
* @returns The same configuration with all defaults applied.
|
|
87
|
+
* @throws {Error} If validation fails. The error message has the form
|
|
88
|
+
* `[rasp-agent] Invalid configuration - field1: msg1; field2: msg2`.
|
|
89
|
+
*/
|
|
90
|
+
export function validateConfig(raw) {
|
|
91
|
+
const result = RaspConfigSchema.safeParse(raw);
|
|
92
|
+
if (!result.success) {
|
|
93
|
+
const fields = result.error.flatten().fieldErrors;
|
|
94
|
+
const msg = Object.entries(fields)
|
|
95
|
+
.map(([k, v]) => `${k}: ${v?.join(", ")}`)
|
|
96
|
+
.join("; ");
|
|
97
|
+
throw new Error(`[rasp-agent] Invalid configuration - ${msg}`);
|
|
98
|
+
}
|
|
99
|
+
return result.data;
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BOLA-via-DB correlation (Addendum A.4 / OWASP API #1).
|
|
3
|
+
*
|
|
4
|
+
* Runs at response time. Correlates the request (authenticated user, resource
|
|
5
|
+
* id in the URL) with the DB queries that actually executed while handling it.
|
|
6
|
+
*
|
|
7
|
+
* Heuristics (each is monitor-grade - flagged, not blocked):
|
|
8
|
+
* 1. **Cross-object access**: an authenticated request whose DB queries
|
|
9
|
+
* reference id literals that match neither the URL resource id nor the
|
|
10
|
+
* user id, on an endpoint that did NOT have authorization enforced. This is
|
|
11
|
+
* the classic IDOR shape: the handler fetched an object the request was not
|
|
12
|
+
* scoped to and no authz gate ran.
|
|
13
|
+
* 2. **Fan-out enumeration**: a single request issuing queries that touch an
|
|
14
|
+
* unusually large number of distinct object ids (possible mass-extraction).
|
|
15
|
+
*/
|
|
16
|
+
import type { DetectionResult } from "../types.js";
|
|
17
|
+
import type { RequestContext } from "../runtime-context.js";
|
|
18
|
+
export declare function correlateBola(ctx: RequestContext): DetectionResult | null;
|
|
19
|
+
//# sourceMappingURL=correlate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"correlate.d.ts","sourceRoot":"","sources":["../../src/db-hooks/correlate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAK5D,wBAAgB,aAAa,CAAC,GAAG,EAAE,cAAc,GAAG,eAAe,GAAG,IAAI,CA2CzE"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** Distinct DB object ids in one request above which we flag fan-out. */
|
|
2
|
+
const FANOUT_ID_THRESHOLD = 25;
|
|
3
|
+
export function correlateBola(ctx) {
|
|
4
|
+
if (ctx.dbQueries.length === 0)
|
|
5
|
+
return null;
|
|
6
|
+
const distinctIds = new Set();
|
|
7
|
+
for (const q of ctx.dbQueries) {
|
|
8
|
+
for (const id of q.ids)
|
|
9
|
+
distinctIds.add(id);
|
|
10
|
+
}
|
|
11
|
+
// Heuristic 2: fan-out enumeration in a single request.
|
|
12
|
+
if (distinctIds.size > FANOUT_ID_THRESHOLD) {
|
|
13
|
+
return {
|
|
14
|
+
detectorName: "bola-db",
|
|
15
|
+
eventType: "bola",
|
|
16
|
+
severity: "high",
|
|
17
|
+
description: `Single request touched ${distinctIds.size} distinct object ids in the database (possible mass extraction)`,
|
|
18
|
+
location: "db",
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
// Heuristic 1: cross-object access without authorization.
|
|
22
|
+
if (ctx.userId && !ctx.authEnforced) {
|
|
23
|
+
const allowed = new Set();
|
|
24
|
+
if (ctx.userId)
|
|
25
|
+
allowed.add(ctx.userId);
|
|
26
|
+
if (ctx.pathId)
|
|
27
|
+
allowed.add(ctx.pathId);
|
|
28
|
+
for (const q of ctx.dbQueries) {
|
|
29
|
+
for (const id of q.ids) {
|
|
30
|
+
if (!allowed.has(id)) {
|
|
31
|
+
return {
|
|
32
|
+
detectorName: "bola-db",
|
|
33
|
+
eventType: "bola",
|
|
34
|
+
severity: "medium",
|
|
35
|
+
description: "Authenticated request accessed a database object id not scoped to the URL/user, with no authorization enforced",
|
|
36
|
+
matchedValue: id,
|
|
37
|
+
location: "db",
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=correlate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"correlate.js","sourceRoot":"","sources":["../../src/db-hooks/correlate.ts"],"names":[],"mappings":"AAkBA,yEAAyE;AACzE,MAAM,mBAAmB,GAAG,EAAE,CAAC;AAE/B,MAAM,UAAU,aAAa,CAAC,GAAmB;IAC/C,IAAI,GAAG,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAE5C,MAAM,WAAW,GAAG,IAAI,GAAG,EAAU,CAAC;IACtC,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC;QAC9B,KAAK,MAAM,EAAE,IAAI,CAAC,CAAC,GAAG;YAAE,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC9C,CAAC;IAED,wDAAwD;IACxD,IAAI,WAAW,CAAC,IAAI,GAAG,mBAAmB,EAAE,CAAC;QAC3C,OAAO;YACL,YAAY,EAAE,SAAS;YACvB,SAAS,EAAE,MAAM;YACjB,QAAQ,EAAE,MAAM;YAChB,WAAW,EAAE,0BAA0B,WAAW,CAAC,IAAI,iEAAiE;YACxH,QAAQ,EAAE,IAAI;SACf,CAAC;IACJ,CAAC;IAED,0DAA0D;IAC1D,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;QACpC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;QAClC,IAAI,GAAG,CAAC,MAAM;YAAE,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACxC,IAAI,GAAG,CAAC,MAAM;YAAE,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAExC,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC;YAC9B,KAAK,MAAM,EAAE,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC;gBACvB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;oBACrB,OAAO;wBACL,YAAY,EAAE,SAAS;wBACvB,SAAS,EAAE,MAAM;wBACjB,QAAQ,EAAE,QAAQ;wBAClB,WAAW,EACT,gHAAgH;wBAClH,YAAY,EAAE,EAAE;wBAChB,QAAQ,EAAE,IAAI;qBACf,CAAC;gBACJ,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verify every installed DB hook is still in place (same wrapper reference and
|
|
3
|
+
* carrying the PATCHED marker). Returns the list of tampered hooks; empty when
|
|
4
|
+
* integrity is intact.
|
|
5
|
+
*/
|
|
6
|
+
export declare function verifyHookIntegrity(): {
|
|
7
|
+
driver: string;
|
|
8
|
+
method: string;
|
|
9
|
+
}[];
|
|
10
|
+
/** Number of hooks currently registered (diagnostics / tests). */
|
|
11
|
+
export declare function installedHookCount(): number;
|
|
12
|
+
/**
|
|
13
|
+
* Instrument all detectable database drivers. Safe to call multiple times.
|
|
14
|
+
*
|
|
15
|
+
* @param requireFn - Optional resolver (used in tests). Defaults to the
|
|
16
|
+
* ambient CommonJS `require` when available.
|
|
17
|
+
*/
|
|
18
|
+
export declare function instrumentDatabaseDrivers(requireFn?: (name: string) => unknown): void;
|
|
19
|
+
/**
|
|
20
|
+
* Instrument a Prisma Client instance. Prisma cannot be patched at the module
|
|
21
|
+
* level, so the integrator passes the client (created with
|
|
22
|
+
* `new PrismaClient({ log: [{ emit: 'event', level: 'query' }] })`).
|
|
23
|
+
*/
|
|
24
|
+
export declare function instrumentPrismaClient(client: unknown): void;
|
|
25
|
+
/** Reset the one-shot guard (tests only). */
|
|
26
|
+
export declare function __resetInstrumentationForTests(): void;
|
|
27
|
+
//# sourceMappingURL=instrument.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"instrument.d.ts","sourceRoot":"","sources":["../../src/db-hooks/instrument.ts"],"names":[],"mappings":"AAgDA;;;;GAIG;AACH,wBAAgB,mBAAmB,IAAI;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAAE,CAS1E;AAED,kEAAkE;AAClE,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAqDD;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,SAAS,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAoB,GAAG,IAAI,CAiEjG;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAS5D;AAED,6CAA6C;AAC7C,wBAAgB,8BAA8B,IAAI,IAAI,CAGrD"}
|