@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.
Files changed (154) hide show
  1. package/README.md +421 -0
  2. package/dist/agent.d.ts +222 -0
  3. package/dist/agent.d.ts.map +1 -0
  4. package/dist/agent.js +591 -0
  5. package/dist/agent.js.map +1 -0
  6. package/dist/api-discovery/discovery-buffer.d.ts +27 -0
  7. package/dist/api-discovery/discovery-buffer.d.ts.map +1 -0
  8. package/dist/api-discovery/discovery-buffer.js +50 -0
  9. package/dist/api-discovery/discovery-buffer.js.map +1 -0
  10. package/dist/api-discovery/endpoint-observer.d.ts +25 -0
  11. package/dist/api-discovery/endpoint-observer.d.ts.map +1 -0
  12. package/dist/api-discovery/endpoint-observer.js +127 -0
  13. package/dist/api-discovery/endpoint-observer.js.map +1 -0
  14. package/dist/api-discovery/route-normalizer.d.ts +15 -0
  15. package/dist/api-discovery/route-normalizer.d.ts.map +1 -0
  16. package/dist/api-discovery/route-normalizer.js +34 -0
  17. package/dist/api-discovery/route-normalizer.js.map +1 -0
  18. package/dist/config.d.ts +100 -0
  19. package/dist/config.d.ts.map +1 -0
  20. package/dist/config.js +101 -0
  21. package/dist/config.js.map +1 -0
  22. package/dist/db-hooks/correlate.d.ts +19 -0
  23. package/dist/db-hooks/correlate.d.ts.map +1 -0
  24. package/dist/db-hooks/correlate.js +45 -0
  25. package/dist/db-hooks/correlate.js.map +1 -0
  26. package/dist/db-hooks/instrument.d.ts +27 -0
  27. package/dist/db-hooks/instrument.d.ts.map +1 -0
  28. package/dist/db-hooks/instrument.js +194 -0
  29. package/dist/db-hooks/instrument.js.map +1 -0
  30. package/dist/detectors/base.d.ts +61 -0
  31. package/dist/detectors/base.d.ts.map +1 -0
  32. package/dist/detectors/base.js +57 -0
  33. package/dist/detectors/base.js.map +1 -0
  34. package/dist/detectors/bola.d.ts +60 -0
  35. package/dist/detectors/bola.d.ts.map +1 -0
  36. package/dist/detectors/bola.js +108 -0
  37. package/dist/detectors/bola.js.map +1 -0
  38. package/dist/detectors/command-injection.d.ts +22 -0
  39. package/dist/detectors/command-injection.d.ts.map +1 -0
  40. package/dist/detectors/command-injection.js +41 -0
  41. package/dist/detectors/command-injection.js.map +1 -0
  42. package/dist/detectors/custom-rule.d.ts +24 -0
  43. package/dist/detectors/custom-rule.d.ts.map +1 -0
  44. package/dist/detectors/custom-rule.js +65 -0
  45. package/dist/detectors/custom-rule.js.map +1 -0
  46. package/dist/detectors/index.d.ts +17 -0
  47. package/dist/detectors/index.d.ts.map +1 -0
  48. package/dist/detectors/index.js +31 -0
  49. package/dist/detectors/index.js.map +1 -0
  50. package/dist/detectors/nosql-injection.d.ts +23 -0
  51. package/dist/detectors/nosql-injection.d.ts.map +1 -0
  52. package/dist/detectors/nosql-injection.js +54 -0
  53. package/dist/detectors/nosql-injection.js.map +1 -0
  54. package/dist/detectors/path-traversal.d.ts +21 -0
  55. package/dist/detectors/path-traversal.d.ts.map +1 -0
  56. package/dist/detectors/path-traversal.js +54 -0
  57. package/dist/detectors/path-traversal.js.map +1 -0
  58. package/dist/detectors/prototype-pollution.d.ts +23 -0
  59. package/dist/detectors/prototype-pollution.d.ts.map +1 -0
  60. package/dist/detectors/prototype-pollution.js +50 -0
  61. package/dist/detectors/prototype-pollution.js.map +1 -0
  62. package/dist/detectors/sql-injection.d.ts +22 -0
  63. package/dist/detectors/sql-injection.d.ts.map +1 -0
  64. package/dist/detectors/sql-injection.js +42 -0
  65. package/dist/detectors/sql-injection.js.map +1 -0
  66. package/dist/detectors/ssrf.d.ts +26 -0
  67. package/dist/detectors/ssrf.d.ts.map +1 -0
  68. package/dist/detectors/ssrf.js +37 -0
  69. package/dist/detectors/ssrf.js.map +1 -0
  70. package/dist/detectors/suspicious-headers.d.ts +25 -0
  71. package/dist/detectors/suspicious-headers.d.ts.map +1 -0
  72. package/dist/detectors/suspicious-headers.js +87 -0
  73. package/dist/detectors/suspicious-headers.js.map +1 -0
  74. package/dist/detectors/template-injection.d.ts +27 -0
  75. package/dist/detectors/template-injection.d.ts.map +1 -0
  76. package/dist/detectors/template-injection.js +35 -0
  77. package/dist/detectors/template-injection.js.map +1 -0
  78. package/dist/detectors/xss.d.ts +22 -0
  79. package/dist/detectors/xss.d.ts.map +1 -0
  80. package/dist/detectors/xss.js +38 -0
  81. package/dist/detectors/xss.js.map +1 -0
  82. package/dist/index.d.ts +28 -0
  83. package/dist/index.d.ts.map +1 -0
  84. package/dist/index.js +24 -0
  85. package/dist/index.js.map +1 -0
  86. package/dist/integrations/express.d.ts +39 -0
  87. package/dist/integrations/express.d.ts.map +1 -0
  88. package/dist/integrations/express.js +62 -0
  89. package/dist/integrations/express.js.map +1 -0
  90. package/dist/integrations/fastify.d.ts +33 -0
  91. package/dist/integrations/fastify.d.ts.map +1 -0
  92. package/dist/integrations/fastify.js +63 -0
  93. package/dist/integrations/fastify.js.map +1 -0
  94. package/dist/integrations/nestjs.d.ts +40 -0
  95. package/dist/integrations/nestjs.d.ts.map +1 -0
  96. package/dist/integrations/nestjs.js +58 -0
  97. package/dist/integrations/nestjs.js.map +1 -0
  98. package/dist/policy/canonical.d.ts +23 -0
  99. package/dist/policy/canonical.d.ts.map +1 -0
  100. package/dist/policy/canonical.js +40 -0
  101. package/dist/policy/canonical.js.map +1 -0
  102. package/dist/policy/policy-manager.d.ts +43 -0
  103. package/dist/policy/policy-manager.d.ts.map +1 -0
  104. package/dist/policy/policy-manager.js +89 -0
  105. package/dist/policy/policy-manager.js.map +1 -0
  106. package/dist/policy/types.d.ts +70 -0
  107. package/dist/policy/types.d.ts.map +1 -0
  108. package/dist/policy/types.js +2 -0
  109. package/dist/policy/types.js.map +1 -0
  110. package/dist/policy/verify.d.ts +11 -0
  111. package/dist/policy/verify.d.ts.map +1 -0
  112. package/dist/policy/verify.js +61 -0
  113. package/dist/policy/verify.js.map +1 -0
  114. package/dist/redaction/audit-log.d.ts +40 -0
  115. package/dist/redaction/audit-log.d.ts.map +1 -0
  116. package/dist/redaction/audit-log.js +110 -0
  117. package/dist/redaction/audit-log.js.map +1 -0
  118. package/dist/redaction/engine.d.ts +50 -0
  119. package/dist/redaction/engine.d.ts.map +1 -0
  120. package/dist/redaction/engine.js +143 -0
  121. package/dist/redaction/engine.js.map +1 -0
  122. package/dist/redaction/patterns.d.ts +24 -0
  123. package/dist/redaction/patterns.d.ts.map +1 -0
  124. package/dist/redaction/patterns.js +142 -0
  125. package/dist/redaction/patterns.js.map +1 -0
  126. package/dist/runtime-context.d.ts +33 -0
  127. package/dist/runtime-context.d.ts.map +1 -0
  128. package/dist/runtime-context.js +46 -0
  129. package/dist/runtime-context.js.map +1 -0
  130. package/dist/self-protect.d.ts +34 -0
  131. package/dist/self-protect.d.ts.map +1 -0
  132. package/dist/self-protect.js +134 -0
  133. package/dist/self-protect.js.map +1 -0
  134. package/dist/transport/buffer.d.ts +52 -0
  135. package/dist/transport/buffer.d.ts.map +1 -0
  136. package/dist/transport/buffer.js +57 -0
  137. package/dist/transport/buffer.js.map +1 -0
  138. package/dist/transport/client.d.ts +77 -0
  139. package/dist/transport/client.d.ts.map +1 -0
  140. package/dist/transport/client.js +178 -0
  141. package/dist/transport/client.js.map +1 -0
  142. package/dist/transport/heartbeat.d.ts +86 -0
  143. package/dist/transport/heartbeat.d.ts.map +1 -0
  144. package/dist/transport/heartbeat.js +110 -0
  145. package/dist/transport/heartbeat.js.map +1 -0
  146. package/dist/transport/secure-request.d.ts +30 -0
  147. package/dist/transport/secure-request.d.ts.map +1 -0
  148. package/dist/transport/secure-request.js +95 -0
  149. package/dist/transport/secure-request.js.map +1 -0
  150. package/dist/types.d.ts +311 -0
  151. package/dist/types.d.ts.map +1 -0
  152. package/dist/types.js +12 -0
  153. package/dist/types.js.map +1 -0
  154. package/package.json +60 -0
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Database driver instrumentation.
3
+ *
4
+ * Monkey-patches the query entry point of common Node.js database drivers so
5
+ * that every query issued while handling an HTTP request is recorded into the
6
+ * active {@link RequestContext}. This is the data source for BOLA-via-DB
7
+ * correlation (Addendum A.4 / OWASP API #1).
8
+ *
9
+ * Design constraints:
10
+ * - **Never break the host app.** Every patch is wrapped in try/catch; if a
11
+ * driver is absent or its shape is unexpected, instrumentation is skipped.
12
+ * - **Never change query behaviour.** Patches record metadata then delegate to
13
+ * the original method with the original arguments and `this`.
14
+ * - **Idempotent.** Re-patching is guarded by a marker symbol.
15
+ *
16
+ * Supported drivers (best-effort): pg, mysql2, mongoose, sequelize, knex.
17
+ * Prisma is instrumented separately via {@link instrumentPrismaClient} because
18
+ * it requires the client instance.
19
+ */
20
+ import { createRequire } from "node:module";
21
+ import { recordDbQuery, extractIdLiterals } from "../runtime-context.js";
22
+ const PATCHED = Symbol.for("rasp.dbHook.patched");
23
+ const nodeRequire = createRequire(import.meta.url);
24
+ function isPatched(fn) {
25
+ return Boolean(fn[PATCHED]);
26
+ }
27
+ function markPatched(fn) {
28
+ fn[PATCHED] = true;
29
+ }
30
+ const hookRegistry = [];
31
+ /**
32
+ * Verify every installed DB hook is still in place (same wrapper reference and
33
+ * carrying the PATCHED marker). Returns the list of tampered hooks; empty when
34
+ * integrity is intact.
35
+ */
36
+ export function verifyHookIntegrity() {
37
+ const tampered = [];
38
+ for (const h of hookRegistry) {
39
+ const current = h.proto[h.method];
40
+ if (current !== h.wrapped || !isPatched(current)) {
41
+ tampered.push({ driver: h.driver, method: h.method });
42
+ }
43
+ }
44
+ return tampered;
45
+ }
46
+ /** Number of hooks currently registered (diagnostics / tests). */
47
+ export function installedHookCount() {
48
+ return hookRegistry.length;
49
+ }
50
+ function record(driver, op) {
51
+ try {
52
+ const text = typeof op === "string" ? op : safeStringify(op);
53
+ if (!text)
54
+ return;
55
+ recordDbQuery({ driver, op: text.slice(0, 500), ids: extractIdLiterals(text) });
56
+ }
57
+ catch {
58
+ // Never throw from the hot path.
59
+ }
60
+ }
61
+ function safeStringify(op) {
62
+ if (op && typeof op === "object") {
63
+ const o = op;
64
+ if (typeof o.text === "string")
65
+ return o.text;
66
+ if (typeof o.sql === "string")
67
+ return o.sql;
68
+ try {
69
+ return JSON.stringify(op);
70
+ }
71
+ catch {
72
+ return "";
73
+ }
74
+ }
75
+ return String(op ?? "");
76
+ }
77
+ function tryRequire(name) {
78
+ try {
79
+ return nodeRequire(name);
80
+ }
81
+ catch {
82
+ return null;
83
+ }
84
+ }
85
+ function patchMethod(proto, method, driver) {
86
+ if (!proto || typeof proto[method] !== "function")
87
+ return;
88
+ const fn = proto[method];
89
+ if (isPatched(fn))
90
+ return;
91
+ const wrapped = function (...args) {
92
+ record(driver, args[0]);
93
+ return fn.apply(this, args);
94
+ };
95
+ markPatched(wrapped);
96
+ proto[method] = wrapped;
97
+ hookRegistry.push({ driver, method, proto, wrapped });
98
+ }
99
+ let done = false;
100
+ /**
101
+ * Instrument all detectable database drivers. Safe to call multiple times.
102
+ *
103
+ * @param requireFn - Optional resolver (used in tests). Defaults to the
104
+ * ambient CommonJS `require` when available.
105
+ */
106
+ export function instrumentDatabaseDrivers(requireFn = tryRequire) {
107
+ if (done)
108
+ return;
109
+ done = true;
110
+ // pg - Client.prototype.query and Pool.prototype.query.
111
+ try {
112
+ const pg = requireFn("pg");
113
+ if (pg) {
114
+ patchMethod(pg.Client?.prototype, "query", "pg");
115
+ patchMethod(pg.Pool?.prototype, "query", "pg");
116
+ }
117
+ }
118
+ catch {
119
+ /* skip */
120
+ }
121
+ // mysql2 - Connection.prototype.query / execute.
122
+ try {
123
+ const conn = requireFn("mysql2/lib/connection.js");
124
+ if (conn) {
125
+ patchMethod(conn.prototype, "query", "mysql2");
126
+ patchMethod(conn.prototype, "execute", "mysql2");
127
+ }
128
+ }
129
+ catch {
130
+ /* skip */
131
+ }
132
+ // sequelize - Sequelize.prototype.query.
133
+ try {
134
+ const seq = requireFn("sequelize");
135
+ if (seq?.Sequelize) {
136
+ patchMethod(seq.Sequelize.prototype, "query", "sequelize");
137
+ }
138
+ }
139
+ catch {
140
+ /* skip */
141
+ }
142
+ // mongoose - Query.prototype.exec (covers find/findOne/update/etc).
143
+ try {
144
+ const mongoose = requireFn("mongoose");
145
+ if (mongoose?.Query) {
146
+ const proto = mongoose.Query.prototype;
147
+ if (typeof proto.exec === "function" && !isPatched(proto.exec)) {
148
+ const orig = proto.exec;
149
+ const wrapped = function (...args) {
150
+ record("mongoose", { op: this.op, model: this.model?.modelName, filter: this._conditions });
151
+ return orig.apply(this, args);
152
+ };
153
+ markPatched(wrapped);
154
+ proto.exec = wrapped;
155
+ hookRegistry.push({ driver: "mongoose", method: "exec", proto, wrapped });
156
+ }
157
+ }
158
+ }
159
+ catch {
160
+ /* skip */
161
+ }
162
+ // knex - client.prototype.query (knex/lib/client).
163
+ try {
164
+ const Client = requireFn("knex/lib/client.js");
165
+ if (Client) {
166
+ patchMethod(Client.prototype, "query", "knex");
167
+ }
168
+ }
169
+ catch {
170
+ /* skip */
171
+ }
172
+ }
173
+ /**
174
+ * Instrument a Prisma Client instance. Prisma cannot be patched at the module
175
+ * level, so the integrator passes the client (created with
176
+ * `new PrismaClient({ log: [{ emit: 'event', level: 'query' }] })`).
177
+ */
178
+ export function instrumentPrismaClient(client) {
179
+ try {
180
+ const c = client;
181
+ if (typeof c.$on === "function") {
182
+ c.$on("query", (e) => record("prisma", e.query ?? ""));
183
+ }
184
+ }
185
+ catch {
186
+ /* skip */
187
+ }
188
+ }
189
+ /** Reset the one-shot guard (tests only). */
190
+ export function __resetInstrumentationForTests() {
191
+ done = false;
192
+ hookRegistry.length = 0;
193
+ }
194
+ //# sourceMappingURL=instrument.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instrument.js","sourceRoot":"","sources":["../../src/db-hooks/instrument.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Shared contract and helpers for all detectors.
3
+ *
4
+ * A detector inspects a {@link NormalizedRequest} and reports a single
5
+ * {@link DetectionResult} when a signature matches. Detectors are intentionally
6
+ * stateless or near-stateless (e.g. {@link BolaDetector} keeps a per-IP window)
7
+ * so they can be reordered, disabled, or stress-tested in isolation.
8
+ *
9
+ * Hard contract:
10
+ * - `detect` must be cheap (it runs in the request hot path).
11
+ * - `detect` must **never** throw. The agent catches anyway as a safety net,
12
+ * but a thrown error short-circuits the detector for the request and is a
13
+ * silent loss of coverage.
14
+ */
15
+ import type { DetectionResult, NormalizedRequest } from "../types.js";
16
+ /**
17
+ * Implementation contract for a single detector.
18
+ */
19
+ export interface Detector {
20
+ /** Unique identifier, surfaced in `DetectionResult.detectorName`. */
21
+ readonly name: string;
22
+ /**
23
+ * Inspect a request.
24
+ *
25
+ * @returns A {@link DetectionResult} on the first match, or `null` if the
26
+ * request looks clean to this detector.
27
+ */
28
+ detect(req: NormalizedRequest): DetectionResult | null;
29
+ }
30
+ /**
31
+ * Recursively flatten an arbitrary value into the list of string-castable
32
+ * leaves it contains.
33
+ *
34
+ * Used by signature-based detectors to obtain a flat list of values to run
35
+ * regexes against, regardless of whether the input is a string, an array, or
36
+ * a deeply nested object.
37
+ *
38
+ * @param value - The value to flatten. Objects and arrays are traversed.
39
+ * @param depth - Internal recursion guard. Defaults to 0.
40
+ * @returns The list of leaf string values. Numbers and booleans are coerced;
41
+ * `null`, `undefined`, functions and other non-stringifiable values are
42
+ * skipped.
43
+ *
44
+ * @remarks Recursion depth is capped at 6 to bound CPU/memory in case the
45
+ * request contains an attacker-controlled deeply nested object.
46
+ */
47
+ export declare function flattenValues(value: unknown, depth?: number): string[];
48
+ /**
49
+ * Like {@link flattenValues}, but also exposes the dotted path leading to
50
+ * each string leaf.
51
+ *
52
+ * Used when a detector needs to match against **keys** as well as values
53
+ * (NoSQL `$gt`, prototype-pollution `__proto__`, …).
54
+ *
55
+ * @returns A list of `[dottedPath, leafStringValue]` tuples. Non-string
56
+ * leaves are dropped.
57
+ *
58
+ * @remarks Depth is capped at 6, same rationale as {@link flattenValues}.
59
+ */
60
+ export declare function flattenEntries(value: unknown, depth?: number, prefix?: string): Array<[key: string, value: string]>;
61
+ //# sourceMappingURL=base.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../src/detectors/base.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEtE;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,qEAAqE;IACrE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,MAAM,CAAC,GAAG,EAAE,iBAAiB,GAAG,eAAe,GAAG,IAAI,CAAC;CACxD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,SAAI,GAAG,MAAM,EAAE,CAWjE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,OAAO,EACd,KAAK,SAAI,EACT,MAAM,SAAK,GACV,KAAK,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAWrC"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Recursively flatten an arbitrary value into the list of string-castable
3
+ * leaves it contains.
4
+ *
5
+ * Used by signature-based detectors to obtain a flat list of values to run
6
+ * regexes against, regardless of whether the input is a string, an array, or
7
+ * a deeply nested object.
8
+ *
9
+ * @param value - The value to flatten. Objects and arrays are traversed.
10
+ * @param depth - Internal recursion guard. Defaults to 0.
11
+ * @returns The list of leaf string values. Numbers and booleans are coerced;
12
+ * `null`, `undefined`, functions and other non-stringifiable values are
13
+ * skipped.
14
+ *
15
+ * @remarks Recursion depth is capped at 6 to bound CPU/memory in case the
16
+ * request contains an attacker-controlled deeply nested object.
17
+ */
18
+ export function flattenValues(value, depth = 0) {
19
+ if (depth > 6)
20
+ return [];
21
+ if (typeof value === "string")
22
+ return [value];
23
+ if (typeof value === "number" || typeof value === "boolean")
24
+ return [String(value)];
25
+ if (Array.isArray(value))
26
+ return value.flatMap((v) => flattenValues(v, depth + 1));
27
+ if (value !== null && typeof value === "object") {
28
+ return Object.values(value).flatMap((v) => flattenValues(v, depth + 1));
29
+ }
30
+ return [];
31
+ }
32
+ /**
33
+ * Like {@link flattenValues}, but also exposes the dotted path leading to
34
+ * each string leaf.
35
+ *
36
+ * Used when a detector needs to match against **keys** as well as values
37
+ * (NoSQL `$gt`, prototype-pollution `__proto__`, …).
38
+ *
39
+ * @returns A list of `[dottedPath, leafStringValue]` tuples. Non-string
40
+ * leaves are dropped.
41
+ *
42
+ * @remarks Depth is capped at 6, same rationale as {@link flattenValues}.
43
+ */
44
+ export function flattenEntries(value, depth = 0, prefix = "") {
45
+ if (depth > 6)
46
+ return [];
47
+ if (value !== null && typeof value === "object" && !Array.isArray(value)) {
48
+ return Object.entries(value).flatMap(([k, v]) => {
49
+ const fullKey = prefix ? `${prefix}.${k}` : k;
50
+ const nested = flattenEntries(v, depth + 1, fullKey);
51
+ const self = typeof v === "string" ? [[fullKey, v]] : [];
52
+ return [...self, ...nested];
53
+ });
54
+ }
55
+ return [];
56
+ }
57
+ //# sourceMappingURL=base.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base.js","sourceRoot":"","sources":["../../src/detectors/base.ts"],"names":[],"mappings":"AA+BA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,aAAa,CAAC,KAAc,EAAE,KAAK,GAAG,CAAC;IACrD,IAAI,KAAK,GAAG,CAAC;QAAE,OAAO,EAAE,CAAC;IACzB,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IAC9C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,SAAS;QAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IACpF,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC;IACnF,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAChD,OAAO,MAAM,CAAC,MAAM,CAAC,KAAgC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CACnE,aAAa,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,CAC5B,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,cAAc,CAC5B,KAAc,EACd,KAAK,GAAG,CAAC,EACT,MAAM,GAAG,EAAE;IAEX,IAAI,KAAK,GAAG,CAAC;QAAE,OAAO,EAAE,CAAC;IACzB,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzE,OAAO,MAAM,CAAC,OAAO,CAAC,KAAgC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE;YACzE,MAAM,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;YAC9C,MAAM,MAAM,GAAG,cAAc,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC;YACrD,MAAM,IAAI,GAA4B,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAClF,OAAO,CAAC,GAAG,IAAI,EAAE,GAAG,MAAM,CAAC,CAAC;QAC9B,CAAC,CAAC,CAAC;IACL,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Broken Object Level Authorization (BOLA / IDOR) detector.
3
+ *
4
+ * OWASP API Security Top-10 #1. Two complementary heuristics:
5
+ *
6
+ * 1. **JWT-sub mismatch** - if the `Authorization: Bearer …` header carries
7
+ * a JWT whose `sub` claim does not match the resource ID present in the
8
+ * URL path, the request is flagged. This catches user A trying to read
9
+ * user B's resource by directly tampering with the URL.
10
+ *
11
+ * 2. **High-velocity ID enumeration** - per source IP, the detector tracks
12
+ * distinct resource IDs seen in the path over a rolling
13
+ * {@link WINDOW_MS} window. More than {@link DISTINCT_ID_THRESHOLD}
14
+ * distinct IDs in that window triggers an alert: behaviour consistent
15
+ * with a scripted scrape.
16
+ *
17
+ * Both heuristics rely on the path containing a recognisable object ID -
18
+ * the detector looks for short numeric segments or RFC 4122 UUIDs.
19
+ *
20
+ * Severity: `high`.
21
+ *
22
+ * State note: per-IP windows are stored in memory; {@link prune} drops
23
+ * entries older than `2 * WINDOW_MS` and should be called periodically by
24
+ * the host process to bound memory.
25
+ */
26
+ import type { Detector } from "./base.js";
27
+ import type { DetectionResult, NormalizedRequest } from "../types.js";
28
+ export declare class BolaDetector implements Detector {
29
+ readonly name = "bola";
30
+ private readonly ipState;
31
+ detect(req: NormalizedRequest): DetectionResult | null;
32
+ /**
33
+ * Record an `(ip, id)` observation and flag the IP when too many distinct
34
+ * IDs have been seen within {@link WINDOW_MS}.
35
+ */
36
+ private trackIpId;
37
+ /**
38
+ * Drop per-IP state older than `2 * WINDOW_MS`.
39
+ *
40
+ * Safe to call at any time; the host process should invoke this on a
41
+ * timer (e.g. once a minute) to prevent unbounded memory growth on
42
+ * long-lived processes.
43
+ */
44
+ prune(): void;
45
+ }
46
+ /**
47
+ * Return the first path segment that looks like a resource ID, or `null` if
48
+ * no segment matches.
49
+ */
50
+ export declare function extractPathId(path: string): string | null;
51
+ /**
52
+ * Decode the `sub` claim of a `Bearer <jwt>` header **without verifying the
53
+ * signature**.
54
+ *
55
+ * This is acceptable here because the value is only used as a heuristic
56
+ * cross-check (BOLA), never for authentication decisions. Returns `null`
57
+ * for any malformed input.
58
+ */
59
+ export declare function extractJwtSub(authHeader: string | string[] | undefined): string | null;
60
+ //# sourceMappingURL=bola.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bola.d.ts","sourceRoot":"","sources":["../../src/detectors/bola.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAiBtE,qBAAa,YAAa,YAAW,QAAQ;IAC3C,QAAQ,CAAC,IAAI,UAAU;IAEvB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA8B;IAEtD,MAAM,CAAC,GAAG,EAAE,iBAAiB,GAAG,eAAe,GAAG,IAAI;IAuBtD;;;OAGG;IACH,OAAO,CAAC,SAAS;IAwBjB;;;;;;OAMG;IACH,KAAK,IAAI,IAAI;CAQd;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQzD;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAYtF"}
@@ -0,0 +1,108 @@
1
+ /** Rolling window length used by the enumeration heuristic, in ms. */
2
+ const WINDOW_MS = 60_000;
3
+ /** Distinct-ID count above which an IP is treated as enumerating. */
4
+ const DISTINCT_ID_THRESHOLD = 20;
5
+ /** Short numeric IDs (typical `GET /users/42` style). */
6
+ const NUMERIC_ID_RE = /^\d{1,12}$/;
7
+ /** Canonical RFC 4122 UUID, case-insensitive. */
8
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
9
+ export class BolaDetector {
10
+ name = "bola";
11
+ ipState = new Map();
12
+ detect(req) {
13
+ const pathId = extractPathId(req.path);
14
+ const jwtSub = extractJwtSub(req.headers["authorization"]);
15
+ if (jwtSub && pathId && pathId !== jwtSub) {
16
+ return {
17
+ detectorName: this.name,
18
+ eventType: "bola",
19
+ severity: "high",
20
+ description: "Path resource ID does not match authenticated user (JWT sub)",
21
+ matchedValue: pathId,
22
+ location: "path",
23
+ };
24
+ }
25
+ if (pathId && req.sourceIp) {
26
+ const result = this.trackIpId(req.sourceIp, pathId);
27
+ if (result)
28
+ return result;
29
+ }
30
+ return null;
31
+ }
32
+ /**
33
+ * Record an `(ip, id)` observation and flag the IP when too many distinct
34
+ * IDs have been seen within {@link WINDOW_MS}.
35
+ */
36
+ trackIpId(sourceIp, id) {
37
+ const now = Date.now();
38
+ let state = this.ipState.get(sourceIp);
39
+ if (!state || now - state.windowStart > WINDOW_MS) {
40
+ state = { ids: new Set(), windowStart: now };
41
+ this.ipState.set(sourceIp, state);
42
+ }
43
+ state.ids.add(id);
44
+ if (state.ids.size > DISTINCT_ID_THRESHOLD) {
45
+ return {
46
+ detectorName: this.name,
47
+ eventType: "bola",
48
+ severity: "high",
49
+ description: `BOLA enumeration: ${state.ids.size} distinct resource IDs accessed from ${sourceIp} within 60s`,
50
+ location: "path",
51
+ };
52
+ }
53
+ return null;
54
+ }
55
+ /**
56
+ * Drop per-IP state older than `2 * WINDOW_MS`.
57
+ *
58
+ * Safe to call at any time; the host process should invoke this on a
59
+ * timer (e.g. once a minute) to prevent unbounded memory growth on
60
+ * long-lived processes.
61
+ */
62
+ prune() {
63
+ const cutoff = Date.now() - WINDOW_MS * 2;
64
+ for (const [ip, state] of this.ipState) {
65
+ if (state.windowStart < cutoff) {
66
+ this.ipState.delete(ip);
67
+ }
68
+ }
69
+ }
70
+ }
71
+ /**
72
+ * Return the first path segment that looks like a resource ID, or `null` if
73
+ * no segment matches.
74
+ */
75
+ export function extractPathId(path) {
76
+ const segments = path.split("/").filter(Boolean);
77
+ for (const seg of segments) {
78
+ if (NUMERIC_ID_RE.test(seg) || UUID_RE.test(seg)) {
79
+ return seg;
80
+ }
81
+ }
82
+ return null;
83
+ }
84
+ /**
85
+ * Decode the `sub` claim of a `Bearer <jwt>` header **without verifying the
86
+ * signature**.
87
+ *
88
+ * This is acceptable here because the value is only used as a heuristic
89
+ * cross-check (BOLA), never for authentication decisions. Returns `null`
90
+ * for any malformed input.
91
+ */
92
+ export function extractJwtSub(authHeader) {
93
+ const header = Array.isArray(authHeader) ? authHeader[0] : authHeader;
94
+ if (!header?.startsWith("Bearer "))
95
+ return null;
96
+ const token = header.slice(7);
97
+ const parts = token.split(".");
98
+ if (parts.length !== 3)
99
+ return null;
100
+ try {
101
+ const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
102
+ return typeof payload["sub"] === "string" ? payload["sub"] : null;
103
+ }
104
+ catch {
105
+ return null;
106
+ }
107
+ }
108
+ //# sourceMappingURL=bola.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bola.js","sourceRoot":"","sources":["../../src/detectors/bola.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * OS command injection signature detector.
3
+ *
4
+ * Scans `query`, `body` and `path` for shell metacharacters chained with
5
+ * common reconnaissance binaries (`ls`, `cat`, `id`, `whoami`, `curl`,
6
+ * `wget`, `nc`, …), `$(…)` / backtick command substitution, pipes into
7
+ * shells, redirection into `/dev/tcp`, references to `/etc/passwd` and
8
+ * `/proc/self`, and the language-level eval gadgets (`exec()`, `eval()`,
9
+ * `system()`, `popen()`).
10
+ *
11
+ * Severity: `critical` - successful OS command injection is full RCE.
12
+ *
13
+ * Known limits: pattern-based; obfuscation via env-var assembly (`a=ls;$a`)
14
+ * or base64 piped into `sh` may evade signatures.
15
+ */
16
+ import type { Detector } from "./base.js";
17
+ import type { DetectionResult, NormalizedRequest } from "../types.js";
18
+ export declare class CommandInjectionDetector implements Detector {
19
+ readonly name = "command-injection";
20
+ detect(req: NormalizedRequest): DetectionResult | null;
21
+ }
22
+ //# sourceMappingURL=command-injection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-injection.d.ts","sourceRoot":"","sources":["../../src/detectors/command-injection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAE1C,OAAO,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAiBtE,qBAAa,wBAAyB,YAAW,QAAQ;IACvD,QAAQ,CAAC,IAAI,uBAAuB;IAEpC,MAAM,CAAC,GAAG,EAAE,iBAAiB,GAAG,eAAe,GAAG,IAAI;CAuBvD"}
@@ -0,0 +1,41 @@
1
+ import { flattenValues } from "./base.js";
2
+ const CMD_PATTERNS = [
3
+ /[;&|`]\s*(ls|cat|id|whoami|pwd|uname|curl|wget|bash|sh|python|perl|ruby|nc|netcat)\b/i,
4
+ /\$\(.*\)/,
5
+ /`[^`]+`/,
6
+ /\|\s*(bash|sh|cmd|powershell)/i,
7
+ />\s*\/dev\/(null|tcp|udp)/i,
8
+ /\d+\.\d+\.\d+\.\d+\s*[\d]+/,
9
+ /\/etc\/(passwd|shadow|hosts|crontab)/i,
10
+ /\/proc\/self/i,
11
+ /\bexec\s*\(/i,
12
+ /\beval\s*\(/i,
13
+ /\bsystem\s*\(/i,
14
+ /\bpopen\s*\(/i,
15
+ ];
16
+ export class CommandInjectionDetector {
17
+ name = "command-injection";
18
+ detect(req) {
19
+ const values = [
20
+ ...flattenValues(req.query),
21
+ ...flattenValues(req.body),
22
+ req.path,
23
+ ];
24
+ for (const val of values) {
25
+ for (const pattern of CMD_PATTERNS) {
26
+ if (pattern.test(val)) {
27
+ return {
28
+ detectorName: this.name,
29
+ eventType: "command_injection",
30
+ severity: "critical",
31
+ description: "Command injection pattern detected",
32
+ matchedValue: val.slice(0, 200),
33
+ location: "query/body/path",
34
+ };
35
+ }
36
+ }
37
+ }
38
+ return null;
39
+ }
40
+ }
41
+ //# sourceMappingURL=command-injection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-injection.js","sourceRoot":"","sources":["../../src/detectors/command-injection.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAG1C,MAAM,YAAY,GAAG;IACnB,uFAAuF;IACvF,UAAU;IACV,SAAS;IACT,gCAAgC;IAChC,4BAA4B;IAC5B,4BAA4B;IAC5B,uCAAuC;IACvC,eAAe;IACf,cAAc;IACd,cAAc;IACd,gBAAgB;IAChB,eAAe;CAChB,CAAC;AAEF,MAAM,OAAO,wBAAwB;IAC1B,IAAI,GAAG,mBAAmB,CAAC;IAEpC,MAAM,CAAC,GAAsB;QAC3B,MAAM,MAAM,GAAG;YACb,GAAG,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC;YAC3B,GAAG,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC;YAC1B,GAAG,CAAC,IAAI;SACT,CAAC;QAEF,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;YACzB,KAAK,MAAM,OAAO,IAAI,YAAY,EAAE,CAAC;gBACnC,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;oBACtB,OAAO;wBACL,YAAY,EAAE,IAAI,CAAC,IAAI;wBACvB,SAAS,EAAE,mBAAmB;wBAC9B,QAAQ,EAAE,UAAU;wBACpB,WAAW,EAAE,oCAAoC;wBACjD,YAAY,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;wBAC/B,QAAQ,EAAE,iBAAiB;qBAC5B,CAAC;gBACJ,CAAC;YACH,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;CACF"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Customer-defined rule detector.
3
+ *
4
+ * Evaluates {@link CustomRuleSpec}s pushed by the control plane via a signed
5
+ * policy. This is the codeable form of the Addendum requirement that customers
6
+ * can author detection rules that run inside the RASP agent. Rules are compiled
7
+ * from regex source strings and matched against the chosen request target.
8
+ *
9
+ * Safety: rule compilation and matching are wrapped so a malformed pattern can
10
+ * never crash the request hot path - a bad rule is simply skipped.
11
+ */
12
+ import type { Detector } from "./base.js";
13
+ import type { DetectionResult, NormalizedRequest } from "../types.js";
14
+ import type { CustomRuleSpec } from "../policy/types.js";
15
+ export declare class CustomRuleDetector implements Detector {
16
+ readonly name = "custom-rule";
17
+ private readonly rules;
18
+ constructor(specs: CustomRuleSpec[]);
19
+ /** Number of active compiled rules (useful for diagnostics/tests). */
20
+ get size(): number;
21
+ private valuesFor;
22
+ detect(req: NormalizedRequest): DetectionResult | null;
23
+ }
24
+ //# sourceMappingURL=custom-rule.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"custom-rule.d.ts","sourceRoot":"","sources":["../../src/detectors/custom-rule.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAE1C,OAAO,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACtE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAOzD,qBAAa,kBAAmB,YAAW,QAAQ;IACjD,QAAQ,CAAC,IAAI,iBAAiB;IAC9B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiB;gBAE3B,KAAK,EAAE,cAAc,EAAE;IAanC,sEAAsE;IACtE,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,OAAO,CAAC,SAAS;IAqBjB,MAAM,CAAC,GAAG,EAAE,iBAAiB,GAAG,eAAe,GAAG,IAAI;CAmBvD"}