@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,143 @@
1
+ /**
2
+ * Redaction engine - sanitises an event payload before it leaves the process.
3
+ *
4
+ * Layers (Addendum B.2):
5
+ * 1. Key-based redaction: object keys matching a {@link RedactionPattern} are
6
+ * replaced wholesale.
7
+ * 2. Value-based redaction: string leaves are scanned for sensitive shapes
8
+ * (credit cards, SIN, email, health IDs, IPs, SQL literals) and masked.
9
+ * 3. Mode:
10
+ * - `denylist` (default): strip known-sensitive data, pass everything else.
11
+ * - `allowlist`: redact every value except explicitly approved keys (and a
12
+ * fixed set of structural envelope keys required by the collector).
13
+ *
14
+ * The walker returns a new object - the input is never mutated. A thrown error
15
+ * is treated by the agent as a fatal redaction failure (the event is dropped).
16
+ */
17
+ import { DEFAULT_REDACTION_PATTERNS, redactValueString, } from "./patterns.js";
18
+ /**
19
+ * Structural envelope keys that must survive allowlist mode so the collector
20
+ * can still validate and route the event.
21
+ */
22
+ const STRUCTURAL_KEYS = new Set([
23
+ "projectId",
24
+ "agentId",
25
+ "agentVersion",
26
+ "runtime",
27
+ "framework",
28
+ "eventType",
29
+ "severity",
30
+ "action",
31
+ "method",
32
+ "path",
33
+ "sourceIp",
34
+ "timestamp",
35
+ "redacted",
36
+ "matchedRule",
37
+ "auditLoggedLocally",
38
+ ]);
39
+ export class RedactionEngine {
40
+ patterns;
41
+ mode;
42
+ allowKeyPatterns;
43
+ valueRedaction;
44
+ ipMode;
45
+ constructor(extraPatternsOrOptions = []) {
46
+ const opts = Array.isArray(extraPatternsOrOptions)
47
+ ? { keyPatterns: extraPatternsOrOptions }
48
+ : extraPatternsOrOptions;
49
+ this.patterns = [...DEFAULT_REDACTION_PATTERNS, ...(opts.keyPatterns ?? [])];
50
+ this.mode = opts.mode ?? "denylist";
51
+ this.allowKeyPatterns = opts.allowKeyPatterns ?? [];
52
+ this.valueRedaction = opts.valueRedaction ?? true;
53
+ this.ipMode = opts.ipMode ?? "hash";
54
+ }
55
+ /**
56
+ * Build an engine from a policy-supplied {@link RedactionConfig}. Unknown or
57
+ * absent fields fall back to safe defaults (denylist + value redaction).
58
+ */
59
+ static fromConfig(cfg) {
60
+ if (!cfg)
61
+ return new RedactionEngine();
62
+ const customKey = (cfg.customKeyPatterns ?? [])
63
+ .map((src) => safeRegex(src))
64
+ .filter((r) => r !== null)
65
+ .map((re, i) => ({ name: `custom-${i}`, matchKey: re }));
66
+ const allowKey = (cfg.allowKeyPatterns ?? [])
67
+ .map((src) => safeRegex(src))
68
+ .filter((r) => r !== null);
69
+ // metadata-only / local-only are handled at the data-residency layer; for
70
+ // redaction purposes they behave like an aggressive allowlist with no
71
+ // approved keys, masking every value.
72
+ const mode = cfg.mode === "allowlist" ? "allowlist" : "denylist";
73
+ return new RedactionEngine({
74
+ mode,
75
+ keyPatterns: customKey,
76
+ allowKeyPatterns: allowKey,
77
+ valueRedaction: cfg.valueRedaction ?? true,
78
+ ipMode: cfg.ipMode ?? "hash",
79
+ });
80
+ }
81
+ redact(value, path = "") {
82
+ const redactedFields = [];
83
+ const redacted = this.walk(value, path, redactedFields, false);
84
+ return { redacted, redactedFields };
85
+ }
86
+ walk(value, path, redactedFields, keyApproved) {
87
+ if (value === null || value === undefined)
88
+ return value;
89
+ if (Array.isArray(value)) {
90
+ return value.map((item, i) => this.walk(item, `${path}[${i}]`, redactedFields, keyApproved));
91
+ }
92
+ if (typeof value === "object") {
93
+ const result = {};
94
+ for (const [k, v] of Object.entries(value)) {
95
+ const fieldPath = path ? `${path}.${k}` : k;
96
+ // Key-based denylist match → redact the whole subtree.
97
+ if (this.shouldRedactKey(k)) {
98
+ result[k] = "[REDACTED]";
99
+ redactedFields.push(fieldPath);
100
+ continue;
101
+ }
102
+ const approved = keyApproved || this.isApprovedKey(k);
103
+ result[k] = this.walk(v, fieldPath, redactedFields, approved);
104
+ }
105
+ return result;
106
+ }
107
+ if (typeof value === "string") {
108
+ return this.redactLeaf(value, path, redactedFields, keyApproved);
109
+ }
110
+ return value;
111
+ }
112
+ redactLeaf(value, path, redactedFields, keyApproved) {
113
+ // Allowlist mode: anything not under an approved/structural key is masked.
114
+ if (this.mode === "allowlist" && !keyApproved) {
115
+ redactedFields.push(path);
116
+ return "[REDACTED]";
117
+ }
118
+ if (this.valueRedaction) {
119
+ const { value: out, redacted } = redactValueString(value, this.ipMode);
120
+ if (redacted)
121
+ redactedFields.push(path);
122
+ return out;
123
+ }
124
+ return value;
125
+ }
126
+ isApprovedKey(key) {
127
+ if (STRUCTURAL_KEYS.has(key))
128
+ return true;
129
+ return this.allowKeyPatterns.some((p) => p.test(key));
130
+ }
131
+ shouldRedactKey(key) {
132
+ return this.patterns.some((p) => p.matchKey.test(key));
133
+ }
134
+ }
135
+ function safeRegex(src) {
136
+ try {
137
+ return new RegExp(src, "i");
138
+ }
139
+ catch {
140
+ return null;
141
+ }
142
+ }
143
+ //# sourceMappingURL=engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.js","sourceRoot":"","sources":["../../src/redaction/engine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Declarative key-based redaction rule.
3
+ */
4
+ export interface RedactionPattern {
5
+ /** Human-readable name surfaced in the audit log. */
6
+ name: string;
7
+ /** Regex tested case-insensitively against the field name (object key). */
8
+ matchKey: RegExp;
9
+ /** Replacement string (default `[REDACTED]`). */
10
+ replacement?: string;
11
+ }
12
+ export declare const DEFAULT_REDACTION_PATTERNS: RedactionPattern[];
13
+ /** IP handling for value redaction. */
14
+ export type IpMode = "hash" | "mask" | "passthrough";
15
+ /**
16
+ * Scan a string for sensitive value shapes and mask them in place.
17
+ *
18
+ * @returns The possibly-transformed string and whether anything was redacted.
19
+ */
20
+ export declare function redactValueString(input: string, ipMode?: IpMode): {
21
+ value: string;
22
+ redacted: boolean;
23
+ };
24
+ //# sourceMappingURL=patterns.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patterns.d.ts","sourceRoot":"","sources":["../../src/redaction/patterns.ts"],"names":[],"mappings":"AAeA;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAC;IACb,2EAA2E;IAC3E,QAAQ,EAAE,MAAM,CAAC;IACjB,iDAAiD;IACjD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,eAAO,MAAM,0BAA0B,EAAE,gBAAgB,EAqBxD,CAAC;AAEF,uCAAuC;AACvC,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,aAAa,CAAC;AAqDrD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,EACb,MAAM,GAAE,MAAe,GACtB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAsDtC"}
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Redaction patterns and value redactors.
3
+ *
4
+ * Two complementary layers (Addendum B.2.1):
5
+ * - **Key-based** ({@link RedactionPattern}): an object key whose name matches
6
+ * is replaced wholesale. Cheap, never inspects the value.
7
+ * - **Value-based** ({@link redactValueString}): string leaves are scanned for
8
+ * sensitive data shapes (credit cards, SIN, email, health IDs, IPs, SQL
9
+ * literals) and masked in place. This catches secrets that live under benign
10
+ * key names.
11
+ *
12
+ * No raw sensitive value is ever logged or memoised by these helpers.
13
+ */
14
+ import { createHash } from "node:crypto";
15
+ export const DEFAULT_REDACTION_PATTERNS = [
16
+ { name: "authorization", matchKey: /^authorization$/i },
17
+ { name: "cookie", matchKey: /^cookie$/i },
18
+ { name: "set-cookie", matchKey: /^set-cookie$/i },
19
+ { name: "password", matchKey: /password/i },
20
+ { name: "passwd", matchKey: /passwd/i },
21
+ { name: "secret", matchKey: /secret/i },
22
+ { name: "token", matchKey: /token/i },
23
+ { name: "api.?key", matchKey: /api[_-]?key/i },
24
+ { name: "access.?key", matchKey: /access[_-]?key/i },
25
+ { name: "private.?key", matchKey: /private[_-]?key/i },
26
+ { name: "client.?secret", matchKey: /client[_-]?secret/i },
27
+ { name: "ssn", matchKey: /^ssn$/i },
28
+ { name: "sin", matchKey: /^sin$/i },
29
+ { name: "credit.?card", matchKey: /credit[_-]?card/i },
30
+ { name: "card.?number", matchKey: /card[_-]?number/i },
31
+ { name: "cvv", matchKey: /^cvv$/i },
32
+ { name: "pin", matchKey: /^pin$/i },
33
+ { name: "x-api-key", matchKey: /^x-api-key$/i },
34
+ { name: "x-auth-token", matchKey: /^x-auth-token$/i },
35
+ { name: "x-access-token", matchKey: /^x-access-token$/i },
36
+ ];
37
+ /** Luhn checksum validation (used for credit cards). */
38
+ function luhnValid(digits) {
39
+ let sum = 0;
40
+ let alt = false;
41
+ for (let i = digits.length - 1; i >= 0; i--) {
42
+ let n = digits.charCodeAt(i) - 48;
43
+ if (n < 0 || n > 9)
44
+ return false;
45
+ if (alt) {
46
+ n *= 2;
47
+ if (n > 9)
48
+ n -= 9;
49
+ }
50
+ sum += n;
51
+ alt = !alt;
52
+ }
53
+ return sum % 10 === 0;
54
+ }
55
+ function sha256Short(input) {
56
+ return createHash("sha256").update(input).digest("hex").slice(0, 16);
57
+ }
58
+ // --- Value detectors, applied in priority order. ---
59
+ const EMAIL_RE = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi;
60
+ // Candidate card: 13-19 digits, optionally separated by spaces/dashes.
61
+ const CARD_RE = /\b(?:\d[ -]?){13,19}\b/g;
62
+ // Canadian SIN: NNN-NNN-NNN or 9 contiguous digits.
63
+ const SIN_RE = /\b\d{3}[-\s]?\d{3}[-\s]?\d{3}\b/g;
64
+ // RAMQ (Quebec): 4 letters + 8 digits. OHIP (Ontario): 10 digits + optional 2-letter version.
65
+ const RAMQ_RE = /\b[A-Z]{4}\d{8}\b/gi;
66
+ const OHIP_RE = /\b\d{4}[-\s]?\d{3}[-\s]?\d{3}[-\s]?[A-Z]{2}\b/gi;
67
+ const IPV4_RE = /\b(?:(?:25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(?:25[0-5]|2[0-4]\d|1?\d?\d)\b/g;
68
+ const SQL_HINT_RE = /\b(select|insert|update|delete|where|values|set)\b/i;
69
+ function maskIp(ip, mode) {
70
+ if (mode === "passthrough")
71
+ return ip;
72
+ if (mode === "hash")
73
+ return `[IP:${sha256Short(ip)}]`;
74
+ // mask last octet
75
+ const parts = ip.split(".");
76
+ if (parts.length === 4)
77
+ return `${parts[0]}.${parts[1]}.${parts[2]}.x`;
78
+ return "[IP REDACTED]";
79
+ }
80
+ /** Replace string literals and numeric literals in a SQL-looking string. */
81
+ function scrubSql(input) {
82
+ return input
83
+ .replace(/'[^']*'/g, "[STRING]")
84
+ .replace(/"[^"]*"/g, "[STRING]")
85
+ .replace(/(=\s*)(-?\d+(\.\d+)?)/g, "$1[INT]");
86
+ }
87
+ /**
88
+ * Scan a string for sensitive value shapes and mask them in place.
89
+ *
90
+ * @returns The possibly-transformed string and whether anything was redacted.
91
+ */
92
+ export function redactValueString(input, ipMode = "hash") {
93
+ if (!input || input.length > 20_000)
94
+ return { value: input, redacted: false };
95
+ let out = input;
96
+ let redacted = false;
97
+ // SQL scrubbing first so literals (incl. emails/cards inside quotes) collapse.
98
+ if (SQL_HINT_RE.test(out)) {
99
+ const scrubbed = scrubSql(out);
100
+ if (scrubbed !== out) {
101
+ out = scrubbed;
102
+ redacted = true;
103
+ }
104
+ }
105
+ // Email → stable hash (preserves correlation without exposing identity).
106
+ out = out.replace(EMAIL_RE, (m) => {
107
+ redacted = true;
108
+ return `[EMAIL:${sha256Short(m.toLowerCase())}]`;
109
+ });
110
+ // Credit cards (Luhn-validated).
111
+ out = out.replace(CARD_RE, (m) => {
112
+ const digits = m.replace(/[ -]/g, "");
113
+ if (digits.length >= 13 && digits.length <= 19 && luhnValid(digits)) {
114
+ redacted = true;
115
+ return `****-****-****-${digits.slice(-4)}`;
116
+ }
117
+ return m;
118
+ });
119
+ // Health IDs (RAMQ / OHIP) before SIN so digit shapes don't collide.
120
+ out = out.replace(RAMQ_RE, () => {
121
+ redacted = true;
122
+ return "[HEALTH_ID REDACTED]";
123
+ });
124
+ out = out.replace(OHIP_RE, () => {
125
+ redacted = true;
126
+ return "[HEALTH_ID REDACTED]";
127
+ });
128
+ // Canadian SIN.
129
+ out = out.replace(SIN_RE, () => {
130
+ redacted = true;
131
+ return "[SIN REDACTED]";
132
+ });
133
+ // IP addresses.
134
+ out = out.replace(IPV4_RE, (m) => {
135
+ if (ipMode === "passthrough")
136
+ return m;
137
+ redacted = true;
138
+ return maskIp(m, ipMode);
139
+ });
140
+ return { value: out, redacted };
141
+ }
142
+ //# sourceMappingURL=patterns.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patterns.js","sourceRoot":"","sources":["../../src/redaction/patterns.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ export interface DbQueryRecord {
2
+ /** Driver that issued the query (e.g. `pg`, `mysql2`, `mongoose`). */
3
+ driver: string;
4
+ /** Truncated, non-parameterised query text or operation descriptor. */
5
+ op: string;
6
+ /** Id-like literals (numeric / UUID) extracted from the query text. */
7
+ ids: string[];
8
+ }
9
+ export interface RequestContext {
10
+ method: string;
11
+ path: string;
12
+ /** Resource id taken from the URL path, if any. */
13
+ pathId: string | null;
14
+ /** Authenticated user id (JWT sub or req.user.id), if known. */
15
+ userId: string | null;
16
+ sourceIp?: string;
17
+ /** Whether an authorization middleware was confirmed for this request. */
18
+ authEnforced: boolean;
19
+ /** DB queries observed during this request. */
20
+ dbQueries: DbQueryRecord[];
21
+ }
22
+ /**
23
+ * Bind a context to the current async execution for the remainder of the
24
+ * request. Uses `enterWith` so it survives across framework hooks and awaits
25
+ * without needing to wrap the downstream handler.
26
+ */
27
+ export declare function enterContext(ctx: RequestContext): void;
28
+ export declare function getContext(): RequestContext | undefined;
29
+ /** Record a DB query against the active request context (no-op if none). */
30
+ export declare function recordDbQuery(record: DbQueryRecord): void;
31
+ /** Extract id-like literals from a query string (bounded). */
32
+ export declare function extractIdLiterals(sql: string): string[];
33
+ //# sourceMappingURL=runtime-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-context.d.ts","sourceRoot":"","sources":["../src/runtime-context.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,aAAa;IAC5B,sEAAsE;IACtE,MAAM,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,EAAE,EAAE,MAAM,CAAC;IACX,uEAAuE;IACvE,GAAG,EAAE,MAAM,EAAE,CAAC;CACf;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,mDAAmD;IACnD,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,gEAAgE;IAChE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,YAAY,EAAE,OAAO,CAAC;IACtB,+CAA+C;IAC/C,SAAS,EAAE,aAAa,EAAE,CAAC;CAC5B;AAID;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,cAAc,GAAG,IAAI,CAEtD;AAED,wBAAgB,UAAU,IAAI,cAAc,GAAG,SAAS,CAEvD;AAED,4EAA4E;AAC5E,wBAAgB,aAAa,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAKzD;AAKD,8DAA8D;AAC9D,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAQvD"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Per-request runtime context, propagated via {@link AsyncLocalStorage}.
3
+ *
4
+ * The framework integration enters a context at the start of each request; the
5
+ * database hooks ({@link instrumentDatabaseDrivers}) then record every query
6
+ * issued while handling that request into the same context. At response time
7
+ * the agent correlates the request (path/user) with the observed DB queries to
8
+ * detect Broken Object Level Authorization (BOLA / IDOR).
9
+ *
10
+ * All accessors are fail-safe: if no context is active they no-op, so DB hooks
11
+ * never throw in code paths unrelated to an HTTP request.
12
+ */
13
+ import { AsyncLocalStorage } from "node:async_hooks";
14
+ const storage = new AsyncLocalStorage();
15
+ /**
16
+ * Bind a context to the current async execution for the remainder of the
17
+ * request. Uses `enterWith` so it survives across framework hooks and awaits
18
+ * without needing to wrap the downstream handler.
19
+ */
20
+ export function enterContext(ctx) {
21
+ storage.enterWith(ctx);
22
+ }
23
+ export function getContext() {
24
+ return storage.getStore();
25
+ }
26
+ /** Record a DB query against the active request context (no-op if none). */
27
+ export function recordDbQuery(record) {
28
+ const ctx = storage.getStore();
29
+ if (!ctx)
30
+ return;
31
+ // Bound memory: keep at most 200 queries per request.
32
+ if (ctx.dbQueries.length < 200)
33
+ ctx.dbQueries.push(record);
34
+ }
35
+ const ID_LITERAL_RE = /\b(\d{1,12}|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\b/gi;
36
+ /** Extract id-like literals from a query string (bounded). */
37
+ export function extractIdLiterals(sql) {
38
+ const out = [];
39
+ let m;
40
+ ID_LITERAL_RE.lastIndex = 0;
41
+ while ((m = ID_LITERAL_RE.exec(sql)) !== null && out.length < 20) {
42
+ out.push(m[1]);
43
+ }
44
+ return out;
45
+ }
46
+ //# sourceMappingURL=runtime-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-context.js","sourceRoot":"","sources":["../src/runtime-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAyBrD,MAAM,OAAO,GAAG,IAAI,iBAAiB,EAAkB,CAAC;AAExD;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,GAAmB;IAC9C,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;AACzB,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,OAAO,OAAO,CAAC,QAAQ,EAAE,CAAC;AAC5B,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,aAAa,CAAC,MAAqB;IACjD,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IAC/B,IAAI,CAAC,GAAG;QAAE,OAAO;IACjB,sDAAsD;IACtD,IAAI,GAAG,CAAC,SAAS,CAAC,MAAM,GAAG,GAAG;QAAE,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAC7D,CAAC;AAED,MAAM,aAAa,GACjB,+EAA+E,CAAC;AAElF,8DAA8D;AAC9D,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,IAAI,CAAyB,CAAC;IAC9B,aAAa,CAAC,SAAS,GAAG,CAAC,CAAC;IAC5B,OAAO,CAAC,CAAC,GAAG,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,IAAI,GAAG,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;QACjE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAE,CAAC,CAAC;IAClB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Encrypts string secrets in memory under a per-process key. The key lives only
3
+ * in this closure and is never persisted, so the stored ciphertext is useless
4
+ * outside the running process.
5
+ */
6
+ export declare class SecureStore {
7
+ private readonly key;
8
+ private readonly entries;
9
+ set(name: string, value: string): void;
10
+ get(name: string): string | null;
11
+ has(name: string): boolean;
12
+ clear(): void;
13
+ }
14
+ /**
15
+ * Detect whether a V8 inspector/debugger is attached or was requested via exec
16
+ * args (`--inspect`, `--inspect-brk`, `--debug`). Returns the reason, or null.
17
+ */
18
+ export declare function detectDebugger(): string | null;
19
+ export interface SelfProtectionOptions {
20
+ /** Verify DB hook integrity (only meaningful when DB instrumentation is on). */
21
+ checkHooks?: boolean;
22
+ /** Watch for an attached debugger. */
23
+ antiDebug?: boolean;
24
+ /** Poll interval in ms. Default 30s. */
25
+ intervalMs?: number;
26
+ /** Sink for warnings. Defaults to `console.warn`. */
27
+ onWarning?: (message: string, detail: unknown) => void;
28
+ }
29
+ /**
30
+ * Start periodic self-protection checks. Returns a stop function. The timer is
31
+ * `unref`'d so it never keeps the host process alive.
32
+ */
33
+ export declare function startSelfProtection(options?: SelfProtectionOptions): () => void;
34
+ //# sourceMappingURL=self-protect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"self-protect.d.ts","sourceRoot":"","sources":["../src/self-protect.ts"],"names":[],"mappings":"AAwBA;;;;GAIG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAmB;IACvC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgE;IAExF,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAQtC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAYhC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI1B,KAAK,IAAI,IAAI;CAGd;AAED;;;GAGG;AACH,wBAAgB,cAAc,IAAI,MAAM,GAAG,IAAI,CAiB9C;AAED,MAAM,WAAW,qBAAqB;IACpC,gFAAgF;IAChF,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,sCAAsC;IACtC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,wCAAwC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qDAAqD;IACrD,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;CACxD;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,qBAA0B,GAAG,MAAM,IAAI,CA8CnF"}
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Agent self-protection (Addendum E.7).
3
+ *
4
+ * Three best-effort, fail-open defences that raise the bar for tampering with
5
+ * the agent at runtime:
6
+ *
7
+ * 1. {@link SecureStore} - keeps secrets (API key, HMAC secret) encrypted in
8
+ * memory with a per-process random key, so a heap dump or accidental log of
9
+ * the config object does not leak them in plaintext.
10
+ * 2. Hook-integrity monitoring - periodically verifies the DB instrumentation
11
+ * hooks are still installed and unmodified (detects an attacker un-hooking
12
+ * the agent to evade BOLA-via-DB detection).
13
+ * 3. Basic anti-debug - detects an attached V8 inspector / `--inspect` flag,
14
+ * which is a common foothold for tampering with a running process.
15
+ *
16
+ * None of these are a substitute for OS-level hardening; they are defence in
17
+ * depth and never crash the host application.
18
+ */
19
+ import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
20
+ import { createRequire } from "node:module";
21
+ import { verifyHookIntegrity } from "./db-hooks/instrument.js";
22
+ const nodeRequire = createRequire(import.meta.url);
23
+ /**
24
+ * Encrypts string secrets in memory under a per-process key. The key lives only
25
+ * in this closure and is never persisted, so the stored ciphertext is useless
26
+ * outside the running process.
27
+ */
28
+ export class SecureStore {
29
+ key = randomBytes(32);
30
+ entries = new Map();
31
+ set(name, value) {
32
+ const iv = randomBytes(12);
33
+ const cipher = createCipheriv("aes-256-gcm", this.key, iv);
34
+ const data = Buffer.concat([cipher.update(value, "utf8"), cipher.final()]);
35
+ const tag = cipher.getAuthTag();
36
+ this.entries.set(name, { iv, tag, data });
37
+ }
38
+ get(name) {
39
+ const e = this.entries.get(name);
40
+ if (!e)
41
+ return null;
42
+ try {
43
+ const decipher = createDecipheriv("aes-256-gcm", this.key, e.iv);
44
+ decipher.setAuthTag(e.tag);
45
+ return Buffer.concat([decipher.update(e.data), decipher.final()]).toString("utf8");
46
+ }
47
+ catch {
48
+ return null;
49
+ }
50
+ }
51
+ has(name) {
52
+ return this.entries.has(name);
53
+ }
54
+ clear() {
55
+ this.entries.clear();
56
+ }
57
+ }
58
+ /**
59
+ * Detect whether a V8 inspector/debugger is attached or was requested via exec
60
+ * args (`--inspect`, `--inspect-brk`, `--debug`). Returns the reason, or null.
61
+ */
62
+ export function detectDebugger() {
63
+ try {
64
+ const inspectArg = process.execArgv.find((a) => /^--(inspect|debug)/.test(a));
65
+ if (inspectArg)
66
+ return `exec-arg:${inspectArg}`;
67
+ }
68
+ catch {
69
+ /* ignore */
70
+ }
71
+ try {
72
+ // Lazy require so environments without the module are unaffected.
73
+ const inspector = nodeRequire("node:inspector");
74
+ if (typeof inspector.url === "function" && inspector.url()) {
75
+ return "inspector-attached";
76
+ }
77
+ }
78
+ catch {
79
+ /* ignore */
80
+ }
81
+ return null;
82
+ }
83
+ /**
84
+ * Start periodic self-protection checks. Returns a stop function. The timer is
85
+ * `unref`'d so it never keeps the host process alive.
86
+ */
87
+ export function startSelfProtection(options = {}) {
88
+ const interval = options.intervalMs ?? 30_000;
89
+ const warn = options.onWarning ??
90
+ ((message, detail) => {
91
+ try {
92
+ console.warn(`[rasp:self-protect] ${message}`, detail);
93
+ }
94
+ catch {
95
+ /* ignore */
96
+ }
97
+ });
98
+ let debuggerReported = false;
99
+ const tick = () => {
100
+ if (options.checkHooks) {
101
+ try {
102
+ const tampered = verifyHookIntegrity();
103
+ if (tampered.length > 0) {
104
+ warn("DB hook integrity violation detected", tampered);
105
+ }
106
+ }
107
+ catch {
108
+ /* fail open */
109
+ }
110
+ }
111
+ if (options.antiDebug) {
112
+ try {
113
+ const reason = detectDebugger();
114
+ if (reason && !debuggerReported) {
115
+ debuggerReported = true;
116
+ warn("debugger/inspector detected", { reason });
117
+ }
118
+ else if (!reason) {
119
+ debuggerReported = false;
120
+ }
121
+ }
122
+ catch {
123
+ /* fail open */
124
+ }
125
+ }
126
+ };
127
+ const timer = setInterval(tick, interval);
128
+ if (typeof timer.unref === "function")
129
+ timer.unref();
130
+ // Run one immediate check so violations present at boot are surfaced fast.
131
+ tick();
132
+ return () => clearInterval(timer);
133
+ }
134
+ //# sourceMappingURL=self-protect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"self-protect.js","sourceRoot":"","sources":["../src/self-protect.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * In-memory event buffer with periodic and size-triggered flushing.
3
+ *
4
+ * Acts as a small queue between {@link RaspAgent.inspect} (synchronous, in
5
+ * the request hot path) and the network-bound {@link TransportClient}.
6
+ *
7
+ * Flush triggers:
8
+ * - **Periodic**: `setInterval(flush, flushIntervalMs)`.
9
+ * - **Size-based**: as soon as the queue reaches `maxSize`, `enqueue`
10
+ * triggers an opportunistic flush.
11
+ * - **Final**: {@link stop} cancels the timer and drains the queue once.
12
+ *
13
+ * Fail-open guarantees:
14
+ * - Individual `sendEvent` failures are swallowed (the event is dropped).
15
+ * - The flush timer is `unref`'d so it never keeps the Node process alive.
16
+ */
17
+ import type { EventPayload } from "../types.js";
18
+ import type { TransportClient } from "./client.js";
19
+ export interface BufferConfig {
20
+ /** Periodic flush interval in ms. */
21
+ flushIntervalMs: number;
22
+ /** Queue length above which `enqueue` triggers an opportunistic flush. */
23
+ maxSize: number;
24
+ }
25
+ export declare class EventBuffer {
26
+ private readonly queue;
27
+ private readonly client;
28
+ private readonly maxSize;
29
+ private timer;
30
+ constructor(client: TransportClient, cfg: BufferConfig);
31
+ /**
32
+ * Append an event. Triggers an immediate flush once the queue reaches
33
+ * `maxSize`.
34
+ *
35
+ * The event is expected to have already passed through the redaction
36
+ * engine - the buffer does not sanitise.
37
+ */
38
+ enqueue(event: EventPayload): void;
39
+ /**
40
+ * Atomically drain the queue and fire all sends in parallel.
41
+ *
42
+ * Uses `Promise.allSettled` so a single failed send cannot block the
43
+ * others. Per-event errors are caught and ignored (the event is lost).
44
+ */
45
+ flush(): Promise<void>;
46
+ /**
47
+ * Cancel the timer and perform a final drain. Awaited during agent
48
+ * shutdown.
49
+ */
50
+ stop(): Promise<void>;
51
+ }
52
+ //# sourceMappingURL=buffer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buffer.d.ts","sourceRoot":"","sources":["../../src/transport/buffer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,MAAM,WAAW,YAAY;IAC3B,qCAAqC;IACrC,eAAe,EAAE,MAAM,CAAC;IACxB,0EAA0E;IAC1E,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAsB;IAC5C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,KAAK,CAA+C;gBAEhD,MAAM,EAAE,eAAe,EAAE,GAAG,EAAE,YAAY;IAetD;;;;;;OAMG;IACH,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI;IAQlC;;;;;OAKG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAc5B;;;OAGG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAO5B"}