@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,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":"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"}
@@ -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"}
@@ -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"}