@graphorin/observability 0.5.0

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 (138) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/LICENSE +21 -0
  3. package/README.md +115 -0
  4. package/dist/cost/cost-tracker.d.ts +28 -0
  5. package/dist/cost/cost-tracker.d.ts.map +1 -0
  6. package/dist/cost/cost-tracker.js +169 -0
  7. package/dist/cost/cost-tracker.js.map +1 -0
  8. package/dist/cost/index.d.ts +3 -0
  9. package/dist/cost/index.js +3 -0
  10. package/dist/cost/types.d.ts +104 -0
  11. package/dist/cost/types.d.ts.map +1 -0
  12. package/dist/eval/index.d.ts +3 -0
  13. package/dist/eval/index.js +3 -0
  14. package/dist/eval/runner.d.ts +11 -0
  15. package/dist/eval/runner.d.ts.map +1 -0
  16. package/dist/eval/runner.js +113 -0
  17. package/dist/eval/runner.js.map +1 -0
  18. package/dist/eval/types.d.ts +108 -0
  19. package/dist/eval/types.d.ts.map +1 -0
  20. package/dist/exporters/console.d.ts +27 -0
  21. package/dist/exporters/console.d.ts.map +1 -0
  22. package/dist/exporters/console.js +47 -0
  23. package/dist/exporters/console.js.map +1 -0
  24. package/dist/exporters/index.d.ts +6 -0
  25. package/dist/exporters/index.js +7 -0
  26. package/dist/exporters/jsonl.d.ts +57 -0
  27. package/dist/exporters/jsonl.d.ts.map +1 -0
  28. package/dist/exporters/jsonl.js +95 -0
  29. package/dist/exporters/jsonl.js.map +1 -0
  30. package/dist/exporters/otlp-http.d.ts +38 -0
  31. package/dist/exporters/otlp-http.d.ts.map +1 -0
  32. package/dist/exporters/otlp-http.js +115 -0
  33. package/dist/exporters/otlp-http.js.map +1 -0
  34. package/dist/exporters/types.d.ts +72 -0
  35. package/dist/exporters/types.d.ts.map +1 -0
  36. package/dist/exporters/types.js +13 -0
  37. package/dist/exporters/types.js.map +1 -0
  38. package/dist/exporters/with-validation.d.ts +45 -0
  39. package/dist/exporters/with-validation.d.ts.map +1 -0
  40. package/dist/exporters/with-validation.js +99 -0
  41. package/dist/exporters/with-validation.js.map +1 -0
  42. package/dist/gen-ai/emit.d.ts +27 -0
  43. package/dist/gen-ai/emit.d.ts.map +1 -0
  44. package/dist/gen-ai/emit.js +88 -0
  45. package/dist/gen-ai/emit.js.map +1 -0
  46. package/dist/gen-ai/index.d.ts +5 -0
  47. package/dist/gen-ai/index.js +5 -0
  48. package/dist/gen-ai/operation-mapping.d.ts +24 -0
  49. package/dist/gen-ai/operation-mapping.d.ts.map +1 -0
  50. package/dist/gen-ai/operation-mapping.js +67 -0
  51. package/dist/gen-ai/operation-mapping.js.map +1 -0
  52. package/dist/gen-ai/system-derivation.d.ts +41 -0
  53. package/dist/gen-ai/system-derivation.d.ts.map +1 -0
  54. package/dist/gen-ai/system-derivation.js +67 -0
  55. package/dist/gen-ai/system-derivation.js.map +1 -0
  56. package/dist/gen-ai/types.d.ts +87 -0
  57. package/dist/gen-ai/types.d.ts.map +1 -0
  58. package/dist/index.d.ts +58 -0
  59. package/dist/index.d.ts.map +1 -0
  60. package/dist/index.js +60 -0
  61. package/dist/index.js.map +1 -0
  62. package/dist/logger/index.d.ts +2 -0
  63. package/dist/logger/index.js +3 -0
  64. package/dist/logger/logger.d.ts +53 -0
  65. package/dist/logger/logger.d.ts.map +1 -0
  66. package/dist/logger/logger.js +145 -0
  67. package/dist/logger/logger.js.map +1 -0
  68. package/dist/openinference/index.d.ts +45 -0
  69. package/dist/openinference/index.d.ts.map +1 -0
  70. package/dist/openinference/index.js +92 -0
  71. package/dist/openinference/index.js.map +1 -0
  72. package/dist/redaction/config.d.ts +39 -0
  73. package/dist/redaction/config.d.ts.map +1 -0
  74. package/dist/redaction/config.js +15 -0
  75. package/dist/redaction/config.js.map +1 -0
  76. package/dist/redaction/errors.d.ts +36 -0
  77. package/dist/redaction/errors.d.ts.map +1 -0
  78. package/dist/redaction/errors.js +42 -0
  79. package/dist/redaction/errors.js.map +1 -0
  80. package/dist/redaction/imperative-patterns.d.ts +108 -0
  81. package/dist/redaction/imperative-patterns.d.ts.map +1 -0
  82. package/dist/redaction/imperative-patterns.js +187 -0
  83. package/dist/redaction/imperative-patterns.js.map +1 -0
  84. package/dist/redaction/index.d.ts +7 -0
  85. package/dist/redaction/index.js +7 -0
  86. package/dist/redaction/patterns.d.ts +85 -0
  87. package/dist/redaction/patterns.d.ts.map +1 -0
  88. package/dist/redaction/patterns.js +172 -0
  89. package/dist/redaction/patterns.js.map +1 -0
  90. package/dist/redaction/types.d.ts +103 -0
  91. package/dist/redaction/types.d.ts.map +1 -0
  92. package/dist/redaction/validator.d.ts +26 -0
  93. package/dist/redaction/validator.d.ts.map +1 -0
  94. package/dist/redaction/validator.js +195 -0
  95. package/dist/redaction/validator.js.map +1 -0
  96. package/dist/replay/config.d.ts +56 -0
  97. package/dist/replay/config.d.ts.map +1 -0
  98. package/dist/replay/config.js +18 -0
  99. package/dist/replay/config.js.map +1 -0
  100. package/dist/replay/index.d.ts +5 -0
  101. package/dist/replay/index.js +5 -0
  102. package/dist/replay/log.d.ts +39 -0
  103. package/dist/replay/log.d.ts.map +1 -0
  104. package/dist/replay/log.js +59 -0
  105. package/dist/replay/log.js.map +1 -0
  106. package/dist/replay/replay.d.ts +25 -0
  107. package/dist/replay/replay.d.ts.map +1 -0
  108. package/dist/replay/replay.js +173 -0
  109. package/dist/replay/replay.js.map +1 -0
  110. package/dist/replay/types.d.ts +110 -0
  111. package/dist/replay/types.d.ts.map +1 -0
  112. package/dist/telemetry/index.d.ts +63 -0
  113. package/dist/telemetry/index.d.ts.map +1 -0
  114. package/dist/telemetry/index.js +49 -0
  115. package/dist/telemetry/index.js.map +1 -0
  116. package/dist/tracer/ids.d.ts +23 -0
  117. package/dist/tracer/ids.d.ts.map +1 -0
  118. package/dist/tracer/ids.js +30 -0
  119. package/dist/tracer/ids.js.map +1 -0
  120. package/dist/tracer/index.d.ts +6 -0
  121. package/dist/tracer/index.js +6 -0
  122. package/dist/tracer/sampling.d.ts +77 -0
  123. package/dist/tracer/sampling.d.ts.map +1 -0
  124. package/dist/tracer/sampling.js +67 -0
  125. package/dist/tracer/sampling.js.map +1 -0
  126. package/dist/tracer/span-names.d.ts +11 -0
  127. package/dist/tracer/span-names.d.ts.map +1 -0
  128. package/dist/tracer/span-names.js +11 -0
  129. package/dist/tracer/span-names.js.map +1 -0
  130. package/dist/tracer/span.d.ts +27 -0
  131. package/dist/tracer/span.d.ts.map +1 -0
  132. package/dist/tracer/span.js +80 -0
  133. package/dist/tracer/span.js.map +1 -0
  134. package/dist/tracer/tracer.d.ts +101 -0
  135. package/dist/tracer/tracer.d.ts.map +1 -0
  136. package/dist/tracer/tracer.js +150 -0
  137. package/dist/tracer/tracer.js.map +1 -0
  138. package/package.json +138 -0
@@ -0,0 +1,42 @@
1
+ //#region src/redaction/errors.ts
2
+ /**
3
+ * Thrown by the validator when `failOnUnredactedSensitive: true` and
4
+ * a value would otherwise be dropped or masked. The error never
5
+ * carries the secret value itself; only the sanitized
6
+ * {@link RedactionViolation} metadata.
7
+ *
8
+ * @stable
9
+ */
10
+ var RedactionValidationError = class extends Error {
11
+ /** @stable */
12
+ kind = "redaction-validation-failed";
13
+ /** @stable */
14
+ violation;
15
+ constructor(message, violation) {
16
+ super(message);
17
+ this.name = "RedactionValidationError";
18
+ this.violation = violation;
19
+ }
20
+ };
21
+ /**
22
+ * Thrown at startup when an exporter is registered without going
23
+ * through `withValidation(...)`. Enforces ADR-035: every exporter
24
+ * must validate before forwarding.
25
+ *
26
+ * @stable
27
+ */
28
+ var UnvalidatedExporterError = class extends Error {
29
+ /** @stable */
30
+ kind = "unvalidated-exporter";
31
+ /** Exporter identifier (typically `exporter.constructor.name`). */
32
+ exporterId;
33
+ constructor(exporterId) {
34
+ super(`Exporter "${exporterId}" was registered without withValidation(). All exporters must be wrapped — see RedactionValidator policy. Wrap with \`withValidation(exporter, opts)\` or set \`validation: 'off'\` (NOT recommended) on the tracer to opt out explicitly (logs a startup WARN).`);
35
+ this.name = "UnvalidatedExporterError";
36
+ this.exporterId = exporterId;
37
+ }
38
+ };
39
+
40
+ //#endregion
41
+ export { RedactionValidationError, UnvalidatedExporterError };
42
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","names":[],"sources":["../../src/redaction/errors.ts"],"sourcesContent":["/**\n * Errors thrown by the redaction layer.\n *\n * @packageDocumentation\n */\n\nimport type { RedactionViolation } from './types.js';\n\n/**\n * Thrown by the validator when `failOnUnredactedSensitive: true` and\n * a value would otherwise be dropped or masked. The error never\n * carries the secret value itself; only the sanitized\n * {@link RedactionViolation} metadata.\n *\n * @stable\n */\nexport class RedactionValidationError extends Error {\n /** @stable */\n readonly kind = 'redaction-validation-failed' as const;\n /** @stable */\n readonly violation: RedactionViolation;\n\n constructor(message: string, violation: RedactionViolation) {\n super(message);\n this.name = 'RedactionValidationError';\n this.violation = violation;\n }\n}\n\n/**\n * Thrown at startup when an exporter is registered without going\n * through `withValidation(...)`. Enforces ADR-035: every exporter\n * must validate before forwarding.\n *\n * @stable\n */\nexport class UnvalidatedExporterError extends Error {\n /** @stable */\n readonly kind = 'unvalidated-exporter' as const;\n /** Exporter identifier (typically `exporter.constructor.name`). */\n readonly exporterId: string;\n\n constructor(exporterId: string) {\n super(\n `Exporter \"${exporterId}\" was registered without withValidation(). All ` +\n 'exporters must be wrapped — see RedactionValidator policy. Wrap with ' +\n \"`withValidation(exporter, opts)` or set `validation: 'off'` (NOT recommended) \" +\n 'on the tracer to opt out explicitly (logs a startup WARN).',\n );\n this.name = 'UnvalidatedExporterError';\n this.exporterId = exporterId;\n }\n}\n"],"mappings":";;;;;;;;;AAgBA,IAAa,2BAAb,cAA8C,MAAM;;CAElD,AAAS,OAAO;;CAEhB,AAAS;CAET,YAAY,SAAiB,WAA+B;AAC1D,QAAM,QAAQ;AACd,OAAK,OAAO;AACZ,OAAK,YAAY;;;;;;;;;;AAWrB,IAAa,2BAAb,cAA8C,MAAM;;CAElD,AAAS,OAAO;;CAEhB,AAAS;CAET,YAAY,YAAoB;AAC9B,QACE,aAAa,WAAW,kQAIzB;AACD,OAAK,OAAO;AACZ,OAAK,aAAa"}
@@ -0,0 +1,108 @@
1
+ //#region src/redaction/imperative-patterns.d.ts
2
+ /**
3
+ * Imperative-pattern catalogue for inbound prompt-injection defence.
4
+ *
5
+ * Sibling to {@link BUILT_IN_PATTERNS} (PII / secrets) — the two
6
+ * catalogues are disjoint by construction. The imperative catalogue
7
+ * is consumed by the inbound sanitization layer in `@graphorin/tools`
8
+ * to scan tool / MCP results before they reach the agent's message
9
+ * store; the PII / secrets catalogue is consumed by the outbound
10
+ * exporter validators to scan span attributes before they reach an
11
+ * exporter.
12
+ *
13
+ * The patterns target the canonical English "ignore previous
14
+ * instructions" / "system override" injection family — the concrete
15
+ * surface that an untrusted-skill or MCP server result might use to
16
+ * smuggle imperative content into the next provider call. The
17
+ * catalogue is intentionally conservative: every entry has a fixed-
18
+ * substring prefilter so the per-byte scan budget stays sub-millisecond
19
+ * on typical 16 KB tool results.
20
+ *
21
+ * @packageDocumentation
22
+ */
23
+ /**
24
+ * Stable name of an imperative pattern. The catalogue is curated;
25
+ * user-supplied patterns can use any identifier they want and will be
26
+ * passed through the sanitization layer alongside the built-ins.
27
+ *
28
+ * @stable
29
+ */
30
+ type ImperativePatternName = 'ignore-previous-instructions' | 'forget-instructions' | 'override-instructions' | 'system-prompt-leak' | 'role-reassignment' | 'developer-mode' | 'jailbreak-marker' | 'tool-call-injection' | 'role-tag-injection';
31
+ /**
32
+ * One entry in the imperative-pattern catalogue. The shape mirrors
33
+ * {@link BUILT_IN_PATTERNS} so consumers can share scan / replace
34
+ * machinery, but the fields are typed as imperative-only so the two
35
+ * catalogues do not accidentally merge.
36
+ *
37
+ * @stable
38
+ */
39
+ interface ImperativePattern {
40
+ readonly name: ImperativePatternName | (string & {});
41
+ readonly description: string;
42
+ /**
43
+ * Cheap substring prefilter applied before the regex; if the body
44
+ * does not contain any of the prefilter substrings the regex is
45
+ * skipped entirely. The prefilter is case-insensitive.
46
+ */
47
+ readonly prefilter: ReadonlyArray<string>;
48
+ /**
49
+ * Full regex applied when the prefilter matches. Always carries the
50
+ * `g` and `i` flags; the catalogue construction validates this.
51
+ */
52
+ readonly regex: RegExp;
53
+ /** Replacement string applied by the `'detect-and-strip*'` policies. */
54
+ readonly mask: string;
55
+ }
56
+ /**
57
+ * The default-on imperative-pattern catalogue. Stable across patches;
58
+ * additions during the pre-1.0 window are minor-bumps because new
59
+ * patterns may produce additional `tool.inbound.sanitization.hit{...}`
60
+ * counter increments on existing deployments.
61
+ *
62
+ * @stable
63
+ */
64
+ declare const BUILT_IN_IMPERATIVE_PATTERNS: readonly ImperativePattern[];
65
+ /**
66
+ * Combined Aho-Corasick-style prefilter set across every pattern.
67
+ * Lower-cased substrings; consumers test the body once with the
68
+ * combined filter before iterating regexes.
69
+ *
70
+ * @stable
71
+ */
72
+ declare const IMPERATIVE_PREFILTER_SUBSTRINGS: ReadonlyArray<string>;
73
+ /**
74
+ * Compiled scan helper. Returns the list of pattern names that fired
75
+ * AND the number of bytes the strip would remove if applied. Bounded
76
+ * by the budget hint — when exceeded, returns `null` to let the caller
77
+ * apply the best-effort `'detect-failed'` annotation.
78
+ *
79
+ * @stable
80
+ */
81
+ interface ScanResult {
82
+ readonly hits: ReadonlyArray<{
83
+ readonly pattern: string;
84
+ readonly matchCount: number;
85
+ }>;
86
+ readonly bytesMatched: number;
87
+ readonly scanDurationUs: number;
88
+ }
89
+ /**
90
+ * Run the imperative-pattern scan against `body`. Patterns are
91
+ * iterated in catalogue order; the prefilter shortcut returns early
92
+ * for bodies that do not contain any imperative-family substring.
93
+ *
94
+ * @stable
95
+ */
96
+ declare function scanImperativePatterns(body: string, patterns?: ReadonlyArray<ImperativePattern>, budgetMs?: number): ScanResult | null;
97
+ /**
98
+ * Apply `pattern.mask` to every match of every pattern in `body`. Used
99
+ * by the `'detect-and-strip*'` policies. The mask is calibrated to NOT
100
+ * match any imperative pattern itself, so post-strip bodies do not
101
+ * trigger another scan hit on round trips.
102
+ *
103
+ * @stable
104
+ */
105
+ declare function stripImperativePatterns(body: string, patterns?: ReadonlyArray<ImperativePattern>): string;
106
+ //#endregion
107
+ export { BUILT_IN_IMPERATIVE_PATTERNS, IMPERATIVE_PREFILTER_SUBSTRINGS, ImperativePattern, ImperativePatternName, ScanResult, scanImperativePatterns, stripImperativePatterns };
108
+ //# sourceMappingURL=imperative-patterns.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"imperative-patterns.d.ts","names":[],"sources":["../../src/redaction/imperative-patterns.ts"],"sourcesContent":[],"mappings":";;AA6BA;AAmBA;;;;;AAuGA;AASA;AAYA;AAaA;;;;;AA4DA;;;;;;;;;;;;;KAxNY,qBAAA;;;;;;;;;UAmBK,iBAAA;iBACA;;;;;;;sBAOK;;;;;kBAKJ;;;;;;;;;;;;cA0FL,uCAAuC;;;;;;;;cASvC,iCAAiC;;;;;;;;;UAY7B,UAAA;iBACA;;;;;;;;;;;;;;iBAYD,sBAAA,0BAEJ,cAAc,wCAEvB;;;;;;;;;iBAwDa,uBAAA,0BAEJ,cAAc"}
@@ -0,0 +1,187 @@
1
+ //#region src/redaction/imperative-patterns.ts
2
+ const PATTERNS = [
3
+ {
4
+ name: "ignore-previous-instructions",
5
+ description: "The canonical \"ignore previous instructions\" injection — the most common form of prompt injection seen across MCP results.",
6
+ prefilter: [
7
+ "ignore",
8
+ "disregard",
9
+ "forget"
10
+ ],
11
+ regex: /\b(?:ignore|disregard|forget)\s+(?:all\s+)?(?:the\s+)?(?:previous|prior|above|earlier|preceding|original)\s+(?:instructions?|prompts?|messages?|directives?|rules?)\b/gi,
12
+ mask: "[REDACTED:imperative-pattern]"
13
+ },
14
+ {
15
+ name: "forget-instructions",
16
+ description: "Imperative \"forget what I told you\" / memory-wipe family.",
17
+ prefilter: [
18
+ "forget",
19
+ "erase",
20
+ "clear"
21
+ ],
22
+ regex: /\b(?:forget|erase|clear|wipe)\s+(?:everything|all)\s+(?:above|before|previous|i\s+(?:told|said))\b/gi,
23
+ mask: "[REDACTED:imperative-pattern]"
24
+ },
25
+ {
26
+ name: "override-instructions",
27
+ description: "\"Override the system prompt\" / \"new instructions\" injection family.",
28
+ prefilter: [
29
+ "override",
30
+ "new instructions",
31
+ "updated instructions",
32
+ "replace"
33
+ ],
34
+ regex: /\b(?:override|replace)\s+(?:the\s+)?(?:system\s+)?(?:prompt|instructions?|directives?)\b|\bnew\s+(?:instructions?|prompts?|directives?)\s*(?::|follow|are)\b|\bupdated\s+(?:instructions?|prompts?|directives?)\b/gi,
35
+ mask: "[REDACTED:imperative-pattern]"
36
+ },
37
+ {
38
+ name: "system-prompt-leak",
39
+ description: "Requests asking the model to reveal its system prompt / hidden instructions / configuration.",
40
+ prefilter: [
41
+ "system prompt",
42
+ "system message",
43
+ "reveal",
44
+ "print",
45
+ "show"
46
+ ],
47
+ regex: /\b(?:reveal|print|show|output|repeat|expose|disclose)\s+(?:your|the)\s+(?:system\s+(?:prompt|message|instructions?)|hidden\s+instructions?|initial\s+(?:prompt|instructions?)|configuration)\b/gi,
48
+ mask: "[REDACTED:imperative-pattern]"
49
+ },
50
+ {
51
+ name: "role-reassignment",
52
+ description: "\"You are now ...\" / role-reassignment imperative family.",
53
+ prefilter: [
54
+ "you are now",
55
+ "you are no longer",
56
+ "pretend",
57
+ "act as"
58
+ ],
59
+ regex: /\byou\s+are\s+(?:now|no\s+longer)\s+(?:a\b|an\b|the\b)|\bpretend\s+(?:to\s+be|you\s+are)\b|\bact\s+as\s+(?:a|an|the|if\s+you)\b/gi,
60
+ mask: "[REDACTED:imperative-pattern]"
61
+ },
62
+ {
63
+ name: "developer-mode",
64
+ description: "Requests to enter \"developer mode\" / \"DAN\" / unrestricted modes — a long-running jailbreak family.",
65
+ prefilter: [
66
+ "developer mode",
67
+ "admin mode",
68
+ "unrestricted"
69
+ ],
70
+ regex: /\b(?:enter|enable|activate)\s+(?:developer|admin|debug|unrestricted|god|root)\s+mode\b/gi,
71
+ mask: "[REDACTED:imperative-pattern]"
72
+ },
73
+ {
74
+ name: "jailbreak-marker",
75
+ description: "Direct \"jailbreak\" markers in tool result content.",
76
+ prefilter: ["jailbreak", "jailbroken"],
77
+ regex: /\bjailbroken?\s+(?:mode|response|model|assistant)\b/gi,
78
+ mask: "[REDACTED:imperative-pattern]"
79
+ },
80
+ {
81
+ name: "tool-call-injection",
82
+ description: "Tool-call markers that try to coerce the model into invoking a tool from the result content.",
83
+ prefilter: [
84
+ "<tool_use",
85
+ "<function_call",
86
+ "<invoke"
87
+ ],
88
+ regex: /<(?:tool_use|function_call|invoke|tool_call)\b[^>]*>/gi,
89
+ mask: "[REDACTED:imperative-pattern]"
90
+ },
91
+ {
92
+ name: "role-tag-injection",
93
+ description: "Chat-role tags injected into tool results to spoof a system / assistant message in the conversation.",
94
+ prefilter: [
95
+ "<|im_start|",
96
+ "<|system|",
97
+ "<|assistant|",
98
+ "<|user|"
99
+ ],
100
+ regex: /<\|(?:im_start|system|assistant|user|im_end)\|>/gi,
101
+ mask: "[REDACTED:imperative-pattern]"
102
+ }
103
+ ];
104
+ /**
105
+ * The default-on imperative-pattern catalogue. Stable across patches;
106
+ * additions during the pre-1.0 window are minor-bumps because new
107
+ * patterns may produce additional `tool.inbound.sanitization.hit{...}`
108
+ * counter increments on existing deployments.
109
+ *
110
+ * @stable
111
+ */
112
+ const BUILT_IN_IMPERATIVE_PATTERNS = PATTERNS;
113
+ /**
114
+ * Combined Aho-Corasick-style prefilter set across every pattern.
115
+ * Lower-cased substrings; consumers test the body once with the
116
+ * combined filter before iterating regexes.
117
+ *
118
+ * @stable
119
+ */
120
+ const IMPERATIVE_PREFILTER_SUBSTRINGS = Object.freeze([...new Set(BUILT_IN_IMPERATIVE_PATTERNS.flatMap((p) => p.prefilter.map((s) => s.toLowerCase())))]);
121
+ /**
122
+ * Run the imperative-pattern scan against `body`. Patterns are
123
+ * iterated in catalogue order; the prefilter shortcut returns early
124
+ * for bodies that do not contain any imperative-family substring.
125
+ *
126
+ * @stable
127
+ */
128
+ function scanImperativePatterns(body, patterns = BUILT_IN_IMPERATIVE_PATTERNS, budgetMs = 5) {
129
+ const start = performance.now();
130
+ if (body.length === 0) return {
131
+ hits: [],
132
+ bytesMatched: 0,
133
+ scanDurationUs: 0
134
+ };
135
+ const lower = body.toLowerCase();
136
+ let prefilterHit = false;
137
+ for (const sub of IMPERATIVE_PREFILTER_SUBSTRINGS) if (lower.includes(sub)) {
138
+ prefilterHit = true;
139
+ break;
140
+ }
141
+ if (!prefilterHit) return {
142
+ hits: [],
143
+ bytesMatched: 0,
144
+ scanDurationUs: Math.round((performance.now() - start) * 1e3)
145
+ };
146
+ const hits = [];
147
+ let bytesMatched = 0;
148
+ for (const pattern of patterns) {
149
+ if (performance.now() - start > budgetMs) return null;
150
+ if (!pattern.prefilter.some((sub) => lower.includes(sub.toLowerCase()))) continue;
151
+ let matchCount = 0;
152
+ pattern.regex.lastIndex = 0;
153
+ let match = pattern.regex.exec(body);
154
+ while (match !== null) {
155
+ matchCount++;
156
+ bytesMatched += match[0].length;
157
+ if (pattern.regex.lastIndex === match.index) pattern.regex.lastIndex = match.index + 1;
158
+ match = pattern.regex.exec(body);
159
+ }
160
+ if (matchCount > 0) hits.push({
161
+ pattern: pattern.name,
162
+ matchCount
163
+ });
164
+ }
165
+ return {
166
+ hits,
167
+ bytesMatched,
168
+ scanDurationUs: Math.round((performance.now() - start) * 1e3)
169
+ };
170
+ }
171
+ /**
172
+ * Apply `pattern.mask` to every match of every pattern in `body`. Used
173
+ * by the `'detect-and-strip*'` policies. The mask is calibrated to NOT
174
+ * match any imperative pattern itself, so post-strip bodies do not
175
+ * trigger another scan hit on round trips.
176
+ *
177
+ * @stable
178
+ */
179
+ function stripImperativePatterns(body, patterns = BUILT_IN_IMPERATIVE_PATTERNS) {
180
+ let out = body;
181
+ for (const pattern of patterns) out = out.replace(pattern.regex, pattern.mask);
182
+ return out;
183
+ }
184
+
185
+ //#endregion
186
+ export { BUILT_IN_IMPERATIVE_PATTERNS, IMPERATIVE_PREFILTER_SUBSTRINGS, scanImperativePatterns, stripImperativePatterns };
187
+ //# sourceMappingURL=imperative-patterns.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"imperative-patterns.js","names":["PATTERNS: readonly ImperativePattern[]","BUILT_IN_IMPERATIVE_PATTERNS: readonly ImperativePattern[]","IMPERATIVE_PREFILTER_SUBSTRINGS: ReadonlyArray<string>","hits: { pattern: string; matchCount: number }[]","match: RegExpExecArray | null"],"sources":["../../src/redaction/imperative-patterns.ts"],"sourcesContent":["/**\n * Imperative-pattern catalogue for inbound prompt-injection defence.\n *\n * Sibling to {@link BUILT_IN_PATTERNS} (PII / secrets) — the two\n * catalogues are disjoint by construction. The imperative catalogue\n * is consumed by the inbound sanitization layer in `@graphorin/tools`\n * to scan tool / MCP results before they reach the agent's message\n * store; the PII / secrets catalogue is consumed by the outbound\n * exporter validators to scan span attributes before they reach an\n * exporter.\n *\n * The patterns target the canonical English \"ignore previous\n * instructions\" / \"system override\" injection family — the concrete\n * surface that an untrusted-skill or MCP server result might use to\n * smuggle imperative content into the next provider call. The\n * catalogue is intentionally conservative: every entry has a fixed-\n * substring prefilter so the per-byte scan budget stays sub-millisecond\n * on typical 16 KB tool results.\n *\n * @packageDocumentation\n */\n\n/**\n * Stable name of an imperative pattern. The catalogue is curated;\n * user-supplied patterns can use any identifier they want and will be\n * passed through the sanitization layer alongside the built-ins.\n *\n * @stable\n */\nexport type ImperativePatternName =\n | 'ignore-previous-instructions'\n | 'forget-instructions'\n | 'override-instructions'\n | 'system-prompt-leak'\n | 'role-reassignment'\n | 'developer-mode'\n | 'jailbreak-marker'\n | 'tool-call-injection'\n | 'role-tag-injection';\n\n/**\n * One entry in the imperative-pattern catalogue. The shape mirrors\n * {@link BUILT_IN_PATTERNS} so consumers can share scan / replace\n * machinery, but the fields are typed as imperative-only so the two\n * catalogues do not accidentally merge.\n *\n * @stable\n */\nexport interface ImperativePattern {\n readonly name: ImperativePatternName | (string & {});\n readonly description: string;\n /**\n * Cheap substring prefilter applied before the regex; if the body\n * does not contain any of the prefilter substrings the regex is\n * skipped entirely. The prefilter is case-insensitive.\n */\n readonly prefilter: ReadonlyArray<string>;\n /**\n * Full regex applied when the prefilter matches. Always carries the\n * `g` and `i` flags; the catalogue construction validates this.\n */\n readonly regex: RegExp;\n /** Replacement string applied by the `'detect-and-strip*'` policies. */\n readonly mask: string;\n}\n\nconst PATTERNS: readonly ImperativePattern[] = [\n {\n name: 'ignore-previous-instructions',\n description:\n 'The canonical \"ignore previous instructions\" injection — the most common form of prompt injection seen across MCP results.',\n prefilter: ['ignore', 'disregard', 'forget'],\n regex:\n /\\b(?:ignore|disregard|forget)\\s+(?:all\\s+)?(?:the\\s+)?(?:previous|prior|above|earlier|preceding|original)\\s+(?:instructions?|prompts?|messages?|directives?|rules?)\\b/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n {\n name: 'forget-instructions',\n description: 'Imperative \"forget what I told you\" / memory-wipe family.',\n prefilter: ['forget', 'erase', 'clear'],\n regex:\n /\\b(?:forget|erase|clear|wipe)\\s+(?:everything|all)\\s+(?:above|before|previous|i\\s+(?:told|said))\\b/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n {\n name: 'override-instructions',\n description: '\"Override the system prompt\" / \"new instructions\" injection family.',\n prefilter: ['override', 'new instructions', 'updated instructions', 'replace'],\n regex:\n /\\b(?:override|replace)\\s+(?:the\\s+)?(?:system\\s+)?(?:prompt|instructions?|directives?)\\b|\\bnew\\s+(?:instructions?|prompts?|directives?)\\s*(?::|follow|are)\\b|\\bupdated\\s+(?:instructions?|prompts?|directives?)\\b/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n {\n name: 'system-prompt-leak',\n description:\n 'Requests asking the model to reveal its system prompt / hidden instructions / configuration.',\n prefilter: ['system prompt', 'system message', 'reveal', 'print', 'show'],\n regex:\n /\\b(?:reveal|print|show|output|repeat|expose|disclose)\\s+(?:your|the)\\s+(?:system\\s+(?:prompt|message|instructions?)|hidden\\s+instructions?|initial\\s+(?:prompt|instructions?)|configuration)\\b/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n {\n name: 'role-reassignment',\n description: '\"You are now ...\" / role-reassignment imperative family.',\n prefilter: ['you are now', 'you are no longer', 'pretend', 'act as'],\n regex:\n /\\byou\\s+are\\s+(?:now|no\\s+longer)\\s+(?:a\\b|an\\b|the\\b)|\\bpretend\\s+(?:to\\s+be|you\\s+are)\\b|\\bact\\s+as\\s+(?:a|an|the|if\\s+you)\\b/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n {\n name: 'developer-mode',\n description:\n 'Requests to enter \"developer mode\" / \"DAN\" / unrestricted modes — a long-running jailbreak family.',\n prefilter: ['developer mode', 'admin mode', 'unrestricted'],\n regex:\n /\\b(?:enter|enable|activate)\\s+(?:developer|admin|debug|unrestricted|god|root)\\s+mode\\b/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n {\n name: 'jailbreak-marker',\n description: 'Direct \"jailbreak\" markers in tool result content.',\n prefilter: ['jailbreak', 'jailbroken'],\n regex: /\\bjailbroken?\\s+(?:mode|response|model|assistant)\\b/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n {\n name: 'tool-call-injection',\n description:\n 'Tool-call markers that try to coerce the model into invoking a tool from the result content.',\n prefilter: ['<tool_use', '<function_call', '<invoke'],\n regex: /<(?:tool_use|function_call|invoke|tool_call)\\b[^>]*>/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n {\n name: 'role-tag-injection',\n description:\n 'Chat-role tags injected into tool results to spoof a system / assistant message in the conversation.',\n prefilter: ['<|im_start|', '<|system|', '<|assistant|', '<|user|'],\n regex: /<\\|(?:im_start|system|assistant|user|im_end)\\|>/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n];\n\n/**\n * The default-on imperative-pattern catalogue. Stable across patches;\n * additions during the pre-1.0 window are minor-bumps because new\n * patterns may produce additional `tool.inbound.sanitization.hit{...}`\n * counter increments on existing deployments.\n *\n * @stable\n */\nexport const BUILT_IN_IMPERATIVE_PATTERNS: readonly ImperativePattern[] = PATTERNS;\n\n/**\n * Combined Aho-Corasick-style prefilter set across every pattern.\n * Lower-cased substrings; consumers test the body once with the\n * combined filter before iterating regexes.\n *\n * @stable\n */\nexport const IMPERATIVE_PREFILTER_SUBSTRINGS: ReadonlyArray<string> = Object.freeze([\n ...new Set(BUILT_IN_IMPERATIVE_PATTERNS.flatMap((p) => p.prefilter.map((s) => s.toLowerCase()))),\n]);\n\n/**\n * Compiled scan helper. Returns the list of pattern names that fired\n * AND the number of bytes the strip would remove if applied. Bounded\n * by the budget hint — when exceeded, returns `null` to let the caller\n * apply the best-effort `'detect-failed'` annotation.\n *\n * @stable\n */\nexport interface ScanResult {\n readonly hits: ReadonlyArray<{ readonly pattern: string; readonly matchCount: number }>;\n readonly bytesMatched: number;\n readonly scanDurationUs: number;\n}\n\n/**\n * Run the imperative-pattern scan against `body`. Patterns are\n * iterated in catalogue order; the prefilter shortcut returns early\n * for bodies that do not contain any imperative-family substring.\n *\n * @stable\n */\nexport function scanImperativePatterns(\n body: string,\n patterns: ReadonlyArray<ImperativePattern> = BUILT_IN_IMPERATIVE_PATTERNS,\n budgetMs = 5,\n): ScanResult | null {\n const start = performance.now();\n if (body.length === 0) {\n return { hits: [], bytesMatched: 0, scanDurationUs: 0 };\n }\n const lower = body.toLowerCase();\n let prefilterHit = false;\n for (const sub of IMPERATIVE_PREFILTER_SUBSTRINGS) {\n if (lower.includes(sub)) {\n prefilterHit = true;\n break;\n }\n }\n if (!prefilterHit) {\n return {\n hits: [],\n bytesMatched: 0,\n scanDurationUs: Math.round((performance.now() - start) * 1000),\n };\n }\n const hits: { pattern: string; matchCount: number }[] = [];\n let bytesMatched = 0;\n for (const pattern of patterns) {\n if (performance.now() - start > budgetMs) return null;\n const localPrefilterMatch = pattern.prefilter.some((sub) => lower.includes(sub.toLowerCase()));\n if (!localPrefilterMatch) continue;\n let matchCount = 0;\n pattern.regex.lastIndex = 0;\n let match: RegExpExecArray | null = pattern.regex.exec(body);\n while (match !== null) {\n matchCount++;\n bytesMatched += match[0].length;\n if (pattern.regex.lastIndex === match.index) {\n pattern.regex.lastIndex = match.index + 1;\n }\n match = pattern.regex.exec(body);\n }\n if (matchCount > 0) {\n hits.push({ pattern: pattern.name as string, matchCount });\n }\n }\n return {\n hits,\n bytesMatched,\n scanDurationUs: Math.round((performance.now() - start) * 1000),\n };\n}\n\n/**\n * Apply `pattern.mask` to every match of every pattern in `body`. Used\n * by the `'detect-and-strip*'` policies. The mask is calibrated to NOT\n * match any imperative pattern itself, so post-strip bodies do not\n * trigger another scan hit on round trips.\n *\n * @stable\n */\nexport function stripImperativePatterns(\n body: string,\n patterns: ReadonlyArray<ImperativePattern> = BUILT_IN_IMPERATIVE_PATTERNS,\n): string {\n let out = body;\n for (const pattern of patterns) {\n out = out.replace(pattern.regex, pattern.mask);\n }\n return out;\n}\n"],"mappings":";AAkEA,MAAMA,WAAyC;CAC7C;EACE,MAAM;EACN,aACE;EACF,WAAW;GAAC;GAAU;GAAa;GAAS;EAC5C,OACE;EACF,MAAM;EACP;CACD;EACE,MAAM;EACN,aAAa;EACb,WAAW;GAAC;GAAU;GAAS;GAAQ;EACvC,OACE;EACF,MAAM;EACP;CACD;EACE,MAAM;EACN,aAAa;EACb,WAAW;GAAC;GAAY;GAAoB;GAAwB;GAAU;EAC9E,OACE;EACF,MAAM;EACP;CACD;EACE,MAAM;EACN,aACE;EACF,WAAW;GAAC;GAAiB;GAAkB;GAAU;GAAS;GAAO;EACzE,OACE;EACF,MAAM;EACP;CACD;EACE,MAAM;EACN,aAAa;EACb,WAAW;GAAC;GAAe;GAAqB;GAAW;GAAS;EACpE,OACE;EACF,MAAM;EACP;CACD;EACE,MAAM;EACN,aACE;EACF,WAAW;GAAC;GAAkB;GAAc;GAAe;EAC3D,OACE;EACF,MAAM;EACP;CACD;EACE,MAAM;EACN,aAAa;EACb,WAAW,CAAC,aAAa,aAAa;EACtC,OAAO;EACP,MAAM;EACP;CACD;EACE,MAAM;EACN,aACE;EACF,WAAW;GAAC;GAAa;GAAkB;GAAU;EACrD,OAAO;EACP,MAAM;EACP;CACD;EACE,MAAM;EACN,aACE;EACF,WAAW;GAAC;GAAe;GAAa;GAAgB;GAAU;EAClE,OAAO;EACP,MAAM;EACP;CACF;;;;;;;;;AAUD,MAAaC,+BAA6D;;;;;;;;AAS1E,MAAaC,kCAAyD,OAAO,OAAO,CAClF,GAAG,IAAI,IAAI,6BAA6B,SAAS,MAAM,EAAE,UAAU,KAAK,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC,CACjG,CAAC;;;;;;;;AAuBF,SAAgB,uBACd,MACA,WAA6C,8BAC7C,WAAW,GACQ;CACnB,MAAM,QAAQ,YAAY,KAAK;AAC/B,KAAI,KAAK,WAAW,EAClB,QAAO;EAAE,MAAM,EAAE;EAAE,cAAc;EAAG,gBAAgB;EAAG;CAEzD,MAAM,QAAQ,KAAK,aAAa;CAChC,IAAI,eAAe;AACnB,MAAK,MAAM,OAAO,gCAChB,KAAI,MAAM,SAAS,IAAI,EAAE;AACvB,iBAAe;AACf;;AAGJ,KAAI,CAAC,aACH,QAAO;EACL,MAAM,EAAE;EACR,cAAc;EACd,gBAAgB,KAAK,OAAO,YAAY,KAAK,GAAG,SAAS,IAAK;EAC/D;CAEH,MAAMC,OAAkD,EAAE;CAC1D,IAAI,eAAe;AACnB,MAAK,MAAM,WAAW,UAAU;AAC9B,MAAI,YAAY,KAAK,GAAG,QAAQ,SAAU,QAAO;AAEjD,MAAI,CADwB,QAAQ,UAAU,MAAM,QAAQ,MAAM,SAAS,IAAI,aAAa,CAAC,CAAC,CACpE;EAC1B,IAAI,aAAa;AACjB,UAAQ,MAAM,YAAY;EAC1B,IAAIC,QAAgC,QAAQ,MAAM,KAAK,KAAK;AAC5D,SAAO,UAAU,MAAM;AACrB;AACA,mBAAgB,MAAM,GAAG;AACzB,OAAI,QAAQ,MAAM,cAAc,MAAM,MACpC,SAAQ,MAAM,YAAY,MAAM,QAAQ;AAE1C,WAAQ,QAAQ,MAAM,KAAK,KAAK;;AAElC,MAAI,aAAa,EACf,MAAK,KAAK;GAAE,SAAS,QAAQ;GAAgB;GAAY,CAAC;;AAG9D,QAAO;EACL;EACA;EACA,gBAAgB,KAAK,OAAO,YAAY,KAAK,GAAG,SAAS,IAAK;EAC/D;;;;;;;;;;AAWH,SAAgB,wBACd,MACA,WAA6C,8BACrC;CACR,IAAI,MAAM;AACV,MAAK,MAAM,WAAW,SACpB,OAAM,IAAI,QAAQ,QAAQ,OAAO,QAAQ,KAAK;AAEhD,QAAO"}
@@ -0,0 +1,7 @@
1
+ import { ALL_BUILT_IN_PATTERNS, BUILT_IN_PATTERNS, BuiltInPatternName, OPT_IN_PATTERNS, PatternCategory, RedactionPattern } from "./patterns.js";
2
+ import { RedactionCounters, RedactionInput, RedactionOutput, RedactionValidator, RedactionValidatorInstance, RedactionValidatorOptions, RedactionViolation, RedactionViolationCallback } from "./types.js";
3
+ import { DEFAULT_VALIDATION_CONFIG, ValidationConfig } from "./config.js";
4
+ import { RedactionValidationError, UnvalidatedExporterError } from "./errors.js";
5
+ import { BUILT_IN_IMPERATIVE_PATTERNS, IMPERATIVE_PREFILTER_SUBSTRINGS, ImperativePattern, ImperativePatternName, ScanResult, scanImperativePatterns, stripImperativePatterns } from "./imperative-patterns.js";
6
+ import { compareSensitivityTiers, createRedactionValidator } from "./validator.js";
7
+ export { ALL_BUILT_IN_PATTERNS, BUILT_IN_IMPERATIVE_PATTERNS, BUILT_IN_PATTERNS, type BuiltInPatternName, DEFAULT_VALIDATION_CONFIG, IMPERATIVE_PREFILTER_SUBSTRINGS, type ImperativePattern, type ImperativePatternName, type ScanResult as ImperativeScanResult, OPT_IN_PATTERNS, type PatternCategory, type RedactionCounters, type RedactionInput, type RedactionOutput, type RedactionPattern, RedactionValidationError, type RedactionValidator, type RedactionValidatorInstance, type RedactionValidatorOptions, type RedactionViolation, type RedactionViolationCallback, UnvalidatedExporterError, type ValidationConfig, compareSensitivityTiers, createRedactionValidator, scanImperativePatterns, stripImperativePatterns };
@@ -0,0 +1,7 @@
1
+ import { RedactionValidationError, UnvalidatedExporterError } from "./errors.js";
2
+ import { ALL_BUILT_IN_PATTERNS, BUILT_IN_PATTERNS, OPT_IN_PATTERNS } from "./patterns.js";
3
+ import { compareSensitivityTiers, createRedactionValidator } from "./validator.js";
4
+ import { DEFAULT_VALIDATION_CONFIG } from "./config.js";
5
+ import { BUILT_IN_IMPERATIVE_PATTERNS, IMPERATIVE_PREFILTER_SUBSTRINGS, scanImperativePatterns, stripImperativePatterns } from "./imperative-patterns.js";
6
+
7
+ export { ALL_BUILT_IN_PATTERNS, BUILT_IN_IMPERATIVE_PATTERNS, BUILT_IN_PATTERNS, DEFAULT_VALIDATION_CONFIG, IMPERATIVE_PREFILTER_SUBSTRINGS, OPT_IN_PATTERNS, RedactionValidationError, UnvalidatedExporterError, compareSensitivityTiers, createRedactionValidator, scanImperativePatterns, stripImperativePatterns };
@@ -0,0 +1,85 @@
1
+ //#region src/redaction/patterns.d.ts
2
+ /**
3
+ * Built-in PII / secret detection patterns. The catalogue is intentionally
4
+ * conservative — every pattern has both positive and negative test
5
+ * fixtures and is documented so operators understand exactly what is
6
+ * matched.
7
+ *
8
+ * The catalogue is split into two groups:
9
+ *
10
+ * - **secret** — credentials, API tokens, JWTs, private keys. Matches
11
+ * are always dropped + counted, regardless of the configured tier
12
+ * floor.
13
+ * - **pii** — email / phone / IBAN / credit card / SSN / IP address.
14
+ * Subject to the configured tier floor + per-pattern enable / disable
15
+ * knobs.
16
+ *
17
+ * @packageDocumentation
18
+ */
19
+ /**
20
+ * Stable pattern identifier. The catalogue is curated; user-supplied
21
+ * patterns can use any identifier they want and will be passed through
22
+ * the validator in addition to the built-ins.
23
+ *
24
+ * @stable
25
+ */
26
+ type BuiltInPatternName = 'graphorin-token' | 'openai-key' | 'anthropic-key' | 'aws-access-key' | 'gcp-service-account' | 'github-pat' | 'jwt' | 'bearer-header' | 'basic-auth' | 'private-key-pem' | 'email' | 'creditcard' | 'us-ssn' | 'phone-e164' | 'iban' | 'ipv4' | 'ipv6';
27
+ /**
28
+ * Pattern category — `secret` matches always force a drop; `pii`
29
+ * matches respect the configured `enabledPatterns` allow-list.
30
+ *
31
+ * @stable
32
+ */
33
+ type PatternCategory = 'secret' | 'pii';
34
+ /**
35
+ * One entry in the redaction catalogue.
36
+ *
37
+ * @stable
38
+ */
39
+ interface RedactionPattern {
40
+ readonly name: string;
41
+ readonly category: PatternCategory;
42
+ readonly description: string;
43
+ readonly regex: RegExp;
44
+ /** Replacement string used when `mode === 'mask'`. */
45
+ readonly mask?: string;
46
+ /**
47
+ * Optional per-match predicate (RP-21). When present, a regex hit is only
48
+ * treated as a real match — and masked — when this returns `true` for the
49
+ * matched substring. Used by the `creditcard` pattern to require a valid
50
+ * Luhn checksum so look-alike digit runs (epoch-ms timestamps, order ids)
51
+ * are not corrupted.
52
+ */
53
+ readonly verify?: (match: string) => boolean;
54
+ /**
55
+ * Optional opt-in flag. When `true` the pattern is **not** active by
56
+ * default; operators must add it to `enabledPatterns` explicitly. Used
57
+ * by the IPv4 / IPv6 patterns because raw IPs frequently appear in
58
+ * non-PII log lines (host headers, debug traces, …).
59
+ */
60
+ readonly optIn?: boolean;
61
+ }
62
+ /**
63
+ * The 14 default-on built-in patterns (the IPv4 and IPv6 detectors are
64
+ * opt-in and live in {@link OPT_IN_PATTERNS}).
65
+ *
66
+ * @stable
67
+ */
68
+ declare const BUILT_IN_PATTERNS: readonly RedactionPattern[];
69
+ /**
70
+ * Patterns that are recognised by the validator but are NOT enabled by
71
+ * default. Use them via `patterns: [...BUILT_IN_PATTERNS, ...OPT_IN_PATTERNS]`.
72
+ *
73
+ * @stable
74
+ */
75
+ declare const OPT_IN_PATTERNS: readonly RedactionPattern[];
76
+ /**
77
+ * Full registry — for tooling that wants to introspect every pattern
78
+ * the framework knows about (e.g. CLI `graphorin redaction list`).
79
+ *
80
+ * @stable
81
+ */
82
+ declare const ALL_BUILT_IN_PATTERNS: readonly RedactionPattern[];
83
+ //#endregion
84
+ export { ALL_BUILT_IN_PATTERNS, BUILT_IN_PATTERNS, BuiltInPatternName, OPT_IN_PATTERNS, PatternCategory, RedactionPattern };
85
+ //# sourceMappingURL=patterns.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patterns.d.ts","names":[],"sources":["../../src/redaction/patterns.ts"],"sourcesContent":[],"mappings":";;AAyBA;AAyBA;AAOA;AA+JA;AAUA;AAUA;;;;;;;;;;;;;;;;;;KAnNY,kBAAA;;;;;;;KAyBA,eAAA;;;;;;UAOK,gBAAA;;qBAEI;;kBAEH;;;;;;;;;;;;;;;;;;;;;;;;;cA2JL,4BAA4B;;;;;;;cAU5B,0BAA0B;;;;;;;cAU1B,gCAAgC"}
@@ -0,0 +1,172 @@
1
+ //#region src/redaction/patterns.ts
2
+ const PATTERNS = [
3
+ {
4
+ name: "graphorin-token",
5
+ category: "secret",
6
+ description: "Graphorin server token (`kru_<env>_v1_<entropy>_<crc32>`).",
7
+ regex: /kru_(?:dev|test|prod)_v1_[A-Za-z0-9]{20,80}_[A-Za-z0-9]{6}/g,
8
+ mask: "[REDACTED graphorin-token]"
9
+ },
10
+ {
11
+ name: "openai-key",
12
+ category: "secret",
13
+ description: "OpenAI API key (`sk-...`).",
14
+ regex: /\bsk-[A-Za-z0-9_-]{20,}\b/g,
15
+ mask: "[REDACTED openai-key]"
16
+ },
17
+ {
18
+ name: "anthropic-key",
19
+ category: "secret",
20
+ description: "Anthropic API key (`sk-ant-...`).",
21
+ regex: /\bsk-ant-[A-Za-z0-9_-]{20,}\b/g,
22
+ mask: "[REDACTED anthropic-key]"
23
+ },
24
+ {
25
+ name: "aws-access-key",
26
+ category: "secret",
27
+ description: "AWS access-key id (`AKIA...`).",
28
+ regex: /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g,
29
+ mask: "[REDACTED aws-access-key]"
30
+ },
31
+ {
32
+ name: "gcp-service-account",
33
+ category: "secret",
34
+ description: "GCP service-account email (`...@...iam.gserviceaccount.com`). Opt-in; the `email` pattern already redacts the address shape; this entry exists for operators wanting the explicit `gcp-service-account` counter label.",
35
+ regex: /[A-Za-z0-9._-]+@[A-Za-z0-9-]+\.iam\.gserviceaccount\.com\b/g,
36
+ mask: "[REDACTED gcp-service-account]",
37
+ optIn: true
38
+ },
39
+ {
40
+ name: "github-pat",
41
+ category: "secret",
42
+ description: "GitHub personal-access token / OAuth token / app token.",
43
+ regex: /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{36,255}\b/g,
44
+ mask: "[REDACTED github-pat]"
45
+ },
46
+ {
47
+ name: "jwt",
48
+ category: "secret",
49
+ description: "JSON Web Token (`eyJ...`).",
50
+ regex: /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g,
51
+ mask: "[REDACTED jwt]"
52
+ },
53
+ {
54
+ name: "bearer-header",
55
+ category: "secret",
56
+ description: "`Authorization: Bearer ...` header values.",
57
+ regex: /\bBearer\s+[A-Za-z0-9._-]{16,}\b/gi,
58
+ mask: "Bearer [REDACTED]"
59
+ },
60
+ {
61
+ name: "basic-auth",
62
+ category: "secret",
63
+ description: "`Authorization: Basic ...` header values.",
64
+ regex: /\bBasic\s+[A-Za-z0-9+/=]{16,}\b/gi,
65
+ mask: "Basic [REDACTED]"
66
+ },
67
+ {
68
+ name: "private-key-pem",
69
+ category: "secret",
70
+ description: "PEM private-key block (`-----BEGIN ... PRIVATE KEY-----`).",
71
+ regex: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]+?-----END [A-Z ]*PRIVATE KEY-----/g,
72
+ mask: "[REDACTED private-key-pem]"
73
+ },
74
+ {
75
+ name: "email",
76
+ category: "pii",
77
+ description: "RFC-5322-ish email address.",
78
+ regex: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g,
79
+ mask: "[REDACTED email]"
80
+ },
81
+ {
82
+ name: "creditcard",
83
+ category: "pii",
84
+ description: "Credit card number (13–19 digits, optional spaces / dashes; Luhn-checked).",
85
+ regex: /\b(?:\d[\s-]*?){13,19}\b/g,
86
+ mask: "[REDACTED creditcard]",
87
+ verify: isLuhnValid
88
+ },
89
+ {
90
+ name: "us-ssn",
91
+ category: "pii",
92
+ description: "US Social Security Number (`123-45-6789`).",
93
+ regex: /\b\d{3}-\d{2}-\d{4}\b/g,
94
+ mask: "[REDACTED us-ssn]"
95
+ },
96
+ {
97
+ name: "phone-e164",
98
+ category: "pii",
99
+ description: "E.164 phone number (`+11234567890`).",
100
+ regex: /\+\d{7,15}\b/g,
101
+ mask: "[REDACTED phone-e164]"
102
+ },
103
+ {
104
+ name: "iban",
105
+ category: "pii",
106
+ description: "IBAN (2-letter country + 2 check digits + up to 30 alphanumerics).",
107
+ regex: /\b[A-Z]{2}\d{2}[A-Z0-9]{11,30}\b/g,
108
+ mask: "[REDACTED iban]"
109
+ },
110
+ {
111
+ name: "ipv4",
112
+ category: "pii",
113
+ description: "IPv4 dotted-quad address. Opt-in (often appears legitimately in logs).",
114
+ regex: /\b(?:\d{1,3}\.){3}\d{1,3}\b/g,
115
+ mask: "[REDACTED ipv4]",
116
+ optIn: true
117
+ },
118
+ {
119
+ name: "ipv6",
120
+ category: "pii",
121
+ description: "IPv6 address (full / compressed). Opt-in.",
122
+ regex: /\b(?:[0-9a-fA-F]{1,4}:){2,7}[0-9a-fA-F]{1,4}\b/g,
123
+ mask: "[REDACTED ipv6]",
124
+ optIn: true
125
+ }
126
+ ];
127
+ /**
128
+ * The 14 default-on built-in patterns (the IPv4 and IPv6 detectors are
129
+ * opt-in and live in {@link OPT_IN_PATTERNS}).
130
+ *
131
+ * @stable
132
+ */
133
+ const BUILT_IN_PATTERNS = PATTERNS.filter((p) => p.optIn !== true);
134
+ /**
135
+ * Patterns that are recognised by the validator but are NOT enabled by
136
+ * default. Use them via `patterns: [...BUILT_IN_PATTERNS, ...OPT_IN_PATTERNS]`.
137
+ *
138
+ * @stable
139
+ */
140
+ const OPT_IN_PATTERNS = PATTERNS.filter((p) => p.optIn === true);
141
+ /**
142
+ * Full registry — for tooling that wants to introspect every pattern
143
+ * the framework knows about (e.g. CLI `graphorin redaction list`).
144
+ *
145
+ * @stable
146
+ */
147
+ const ALL_BUILT_IN_PATTERNS = PATTERNS;
148
+ /**
149
+ * Luhn (mod-10) checksum validator used by the `creditcard` pattern (RP-21).
150
+ * Strips spaces / dashes, bounds the length to 13–19 digits, and verifies the
151
+ * checksum so a digit run that merely *looks* like a PAN is not redacted.
152
+ */
153
+ function isLuhnValid(value) {
154
+ const digits = value.replace(/\D/g, "");
155
+ if (digits.length < 13 || digits.length > 19) return false;
156
+ let sum = 0;
157
+ let double = false;
158
+ for (let i = digits.length - 1; i >= 0; i -= 1) {
159
+ let d = digits.charCodeAt(i) - 48;
160
+ if (double) {
161
+ d *= 2;
162
+ if (d > 9) d -= 9;
163
+ }
164
+ sum += d;
165
+ double = !double;
166
+ }
167
+ return sum % 10 === 0;
168
+ }
169
+
170
+ //#endregion
171
+ export { ALL_BUILT_IN_PATTERNS, BUILT_IN_PATTERNS, OPT_IN_PATTERNS };
172
+ //# sourceMappingURL=patterns.js.map