@senpi/runtime 1.1.0-dev.runtime-phase-2.20260525084519 → 1.1.0-dev.runtime-phase-2.20260528085140

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.
@@ -1 +1 @@
1
- {"version":3,"file":"plugin-span-tracker.d.ts","sourceRoot":"","sources":["../../../src/tracking/spans/plugin-span-tracker.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAIhD,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,GAAG,SAAS,GACzB,WAAW,CAWb"}
1
+ {"version":3,"file":"plugin-span-tracker.d.ts","sourceRoot":"","sources":["../../../src/tracking/spans/plugin-span-tracker.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAuChD,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,GAAG,SAAS,GACzB,WAAW,CAYb"}
@@ -10,8 +10,32 @@ import { createLogger } from "../../utils/logger.js";
10
10
  import { TRACKING_API_BASE_URL } from "../constants.js";
11
11
  import { BatchingSpanTransport, NoOpSpanTransport } from "./span-transport.js";
12
12
  import { SpanTracker } from "./span-tracker.js";
13
+ import { createOpenRedactionAdapter, isPiiRedactionEnabled, setDefaultPiiRedactor, } from "./redact-pii.js";
13
14
  const logger = createLogger("[senpi-core][plugin-span-tracker]");
15
+ /**
16
+ * Initialize the process-wide default PII redactor when the feature flag is on.
17
+ * Lazy — the OpenRedaction library is only imported when the flag is set, so
18
+ * builds and gateways that don't enable redaction don't pay the import cost.
19
+ *
20
+ * Fire-and-forget. Failures are logged; spans continue flowing unredacted
21
+ * rather than blocking startup.
22
+ */
23
+ function initPiiRedactor() {
24
+ if (!isPiiRedactionEnabled()) {
25
+ logger.info("PII redaction disabled (set SENPI_PII_REDACTION_ENABLED=true to enable)");
26
+ return;
27
+ }
28
+ createOpenRedactionAdapter()
29
+ .then((redactor) => {
30
+ setDefaultPiiRedactor(redactor);
31
+ logger.info("PII redaction enabled — spans will be scrubbed for emails, international phones (US/IN/CN/KR/JP/UK/BR/+intl), vendor API keys, EVM private keys, and BIP39 mnemonics before transport");
32
+ })
33
+ .catch((err) => {
34
+ logger.warn("PII redaction init failed — spans will flow UNredacted; check openredaction install", { err: err instanceof Error ? err.message : String(err) });
35
+ });
36
+ }
14
37
  export function createPluginSpanTracker(apiKey) {
38
+ initPiiRedactor();
15
39
  const trackingBaseUrl = process.env.SENPI_TRACKING_URL ?? TRACKING_API_BASE_URL;
16
40
  const transport = apiKey
17
41
  ? new BatchingSpanTransport({ baseUrl: trackingBaseUrl, apiKey })
@@ -1 +1 @@
1
- {"version":3,"file":"plugin-span-tracker.js","sourceRoot":"","sources":["../../../src/tracking/spans/plugin-span-tracker.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC/E,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,MAAM,MAAM,GAAG,YAAY,CAAC,mCAAmC,CAAC,CAAC;AAEjE,MAAM,UAAU,uBAAuB,CACrC,MAA0B;IAE1B,MAAM,eAAe,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,qBAAqB,CAAC;IAChF,MAAM,SAAS,GAAG,MAAM;QACtB,CAAC,CAAC,IAAI,qBAAqB,CAAC,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,CAAC;QACjE,CAAC,CAAC,CAAC,GAAG,EAAE;YACJ,MAAM,CAAC,IAAI,CACT,iIAAiI,CAClI,CAAC;YACF,OAAO,IAAI,iBAAiB,EAAE,CAAC;QACjC,CAAC,CAAC,EAAE,CAAC;IACT,OAAO,IAAI,WAAW,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC;AACxC,CAAC"}
1
+ {"version":3,"file":"plugin-span-tracker.js","sourceRoot":"","sources":["../../../src/tracking/spans/plugin-span-tracker.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC/E,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EACL,0BAA0B,EAC1B,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,iBAAiB,CAAC;AAEzB,MAAM,MAAM,GAAG,YAAY,CAAC,mCAAmC,CAAC,CAAC;AAEjE;;;;;;;GAOG;AACH,SAAS,eAAe;IACtB,IAAI,CAAC,qBAAqB,EAAE,EAAE,CAAC;QAC7B,MAAM,CAAC,IAAI,CACT,yEAAyE,CAC1E,CAAC;QACF,OAAO;IACT,CAAC;IACD,0BAA0B,EAAE;SACzB,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE;QACjB,qBAAqB,CAAC,QAAQ,CAAC,CAAC;QAChC,MAAM,CAAC,IAAI,CACT,uLAAuL,CACxL,CAAC;IACJ,CAAC,CAAC;SACD,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;QACb,MAAM,CAAC,IAAI,CACT,qFAAqF,EACrF,EAAE,GAAG,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAC1D,CAAC;IACJ,CAAC,CAAC,CAAC;AACP,CAAC;AAED,MAAM,UAAU,uBAAuB,CACrC,MAA0B;IAE1B,eAAe,EAAE,CAAC;IAClB,MAAM,eAAe,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,qBAAqB,CAAC;IAChF,MAAM,SAAS,GAAG,MAAM;QACtB,CAAC,CAAC,IAAI,qBAAqB,CAAC,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,CAAC;QACjE,CAAC,CAAC,CAAC,GAAG,EAAE;YACJ,MAAM,CAAC,IAAI,CACT,iIAAiI,CAClI,CAAC;YACF,OAAO,IAAI,iBAAiB,EAAE,CAAC;QACjC,CAAC,CAAC,EAAE,CAAC;IACT,OAAO,IAAI,WAAW,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC;AACxC,CAAC"}
@@ -0,0 +1,117 @@
1
+ /**
2
+ * PII + secret redaction for span payloads.
3
+ *
4
+ * Walks the `data` field of each span and replaces matches in string values
5
+ * before the batch hits the wire. Runs at flush time (async) — the underlying
6
+ * redactor is async, and the producer is fire-and-forget, so we pay the
7
+ * latency on the network-bound path rather than on the caller's enqueue call.
8
+ *
9
+ * Scope (see createOpenRedactionAdapter): emails, phones (US, international,
10
+ * India, China, South Korea, Japan, UK mobile, Brazil), ~28 vendor API keys
11
+ * / tokens (AWS, GitHub, Stripe, OpenAI, etc.), EVM private keys, Anthropic
12
+ * `sk-ant-*` keys, generic `sk-*` keys, and BIP39 mnemonic phrases.
13
+ *
14
+ * Integration point: BatchingSpanTransport.flush() applies this to every
15
+ * span in the batch before JSON.stringify. Only `span.data` is walked;
16
+ * top-level columns (`address`, `correlationId`, etc.) are non-PII identifiers
17
+ * and stay as-is.
18
+ *
19
+ * Off by default. Enable with `SENPI_PII_REDACTION_ENABLED=true`.
20
+ */
21
+ import type { TrackingSpan } from "./types.js";
22
+ /** Minimal interface so tests can inject a synchronous stub. */
23
+ export interface PiiRedactor {
24
+ redact(text: string): Promise<string>;
25
+ }
26
+ /**
27
+ * Walks a JSON-shaped value. Strings are run through the redactor; arrays
28
+ * and plain objects are recursed; everything else (number, boolean, null,
29
+ * non-plain objects) is returned unchanged. Preserves reference identity
30
+ * when nothing changes — useful for short-circuit downstream comparisons.
31
+ */
32
+ export declare function redactStringsDeep(value: unknown, redactor: PiiRedactor): Promise<unknown>;
33
+ /**
34
+ * Redact PII inside `span.data` only. Top-level structural fields (address,
35
+ * correlationId, runId, etc.) are non-PII identifiers and skipped.
36
+ *
37
+ * Returns the same span if nothing changed (avoids object allocation when no
38
+ * PII was found).
39
+ */
40
+ export declare function redactSpanData(span: TrackingSpan, redactor: PiiRedactor): Promise<TrackingSpan>;
41
+ export declare function setDefaultPiiRedactor(r: PiiRedactor | null): void;
42
+ export declare function getDefaultPiiRedactor(): PiiRedactor | null;
43
+ /** Reads SENPI_PII_REDACTION_ENABLED. Truthy only on exact "true". */
44
+ export declare function isPiiRedactionEnabled(): boolean;
45
+ /**
46
+ * Find BIP39-mnemonic spans in `text` using a true sliding window — not
47
+ * a greedy regex with a post-hoc validator.
48
+ *
49
+ * Why this and not a regex+validator:
50
+ * - OpenRedaction calls the validator on whatever the regex captured. JS
51
+ * regex matching is left-to-right with no backtracking across `lastIndex`
52
+ * advances; once a (greedy) match is rejected, the engine moves past it
53
+ * entirely. So a 17-word lowercase run like "my secret is <12 BIP39>
54
+ * so keep safe" gets captured whole, fails validation on word count,
55
+ * and the engine doesn't retry the 12-word window starting 3 tokens in.
56
+ *
57
+ * Algorithm:
58
+ * 1. Tokenize lowercase-only words and their offsets (skips capitalized
59
+ * words and punctuation — real mnemonics from wallet exports are
60
+ * always all-lowercase space-separated).
61
+ * 2. Walk left to right. At each token position, try valid mnemonic
62
+ * counts largest-first (24, 21, 18, 15, 12) so a 24-word phrase
63
+ * inside a longer prose run isn't missed by a premature 12-match.
64
+ * 3. A window is valid only when every word is BIP39 AND the gap between
65
+ * consecutive tokens in the source is whitespace-only (rejects forms
66
+ * with commas or other punctuation between words, which real wallet
67
+ * exports don't use).
68
+ * 4. On match, record the source span and advance past it; otherwise step
69
+ * forward one token.
70
+ */
71
+ export declare function findMnemonicSpans(text: string, bip39: Set<string>): Array<{
72
+ start: number;
73
+ end: number;
74
+ }>;
75
+ /**
76
+ * Pre-redact BIP39 mnemonics in `text` before handing the result to
77
+ * OpenRedaction. Returns the original string when no mnemonics are present
78
+ * (cheap path — only the tokenizer runs and finds nothing).
79
+ */
80
+ export declare function redactMnemonicsPreRedaction(text: string): Promise<string>;
81
+ /**
82
+ * Build an OpenRedaction-backed PiiRedactor.
83
+ *
84
+ * Detects:
85
+ * - Contact PII: emails.
86
+ * - Phones (all custom — upstream phone patterns are unreliable, see
87
+ * ALLOWLISTED_PATTERN_TYPES docstring): US, international (`+` prefix),
88
+ * India, China, South Korea, Japan, UK mobile, Brazil. Each requires
89
+ * either an explicit country code OR a country-specific mobile prefix
90
+ * plus separators, so bare numeric IDs (timestamps, span IDs, UUIDs)
91
+ * survive untouched.
92
+ * - Credentials (upstream): cloud / vendor API keys + tokens (AWS, Azure,
93
+ * GCP, GitHub, Slack, Stripe, OpenAI, Twilio, …), bearer + JWT tokens,
94
+ * OAuth secrets, generic API keys, SSH/PEM private keys, URLs with
95
+ * embedded auth.
96
+ * - Crypto secrets (custom patterns): EVM private keys (0x + 64 hex),
97
+ * Anthropic `sk-ant-*` keys, generic `sk-*` keys (OpenAI/OpenRouter).
98
+ * - BIP39 mnemonic phrases (out-of-band sliding-window pre-pass — see
99
+ * `redactMnemonicsPreRedaction`): 12/15/18/21/24 BIP39 words, found and
100
+ * redacted before the OpenRedaction pass so embedded phrases like
101
+ * "my key is <12 words> keep safe" are caught.
102
+ *
103
+ * Tuning:
104
+ * - `enableFalsePositiveFilter: false` — explicit; the library defaults
105
+ * this to truthy in practice and suppresses valid international-phone
106
+ * matches in some contexts.
107
+ * - `enableContextAnalysis: false` — saves CPU; context rules add
108
+ * latency and aren't load-bearing for the patterns we keep.
109
+ * - `enableCache: true` — chat history repeats; cache skips re-scan.
110
+ * - `confidenceThreshold: 0.5` — kept conservative; custom patterns score
111
+ * 1.0 so this only affects built-ins.
112
+ *
113
+ * Lazy-imports `openredaction` and `bip39` — both deps stay cold for builds
114
+ * / tests that don't enable redaction.
115
+ */
116
+ export declare function createOpenRedactionAdapter(): Promise<PiiRedactor>;
117
+ //# sourceMappingURL=redact-pii.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact-pii.d.ts","sourceRoot":"","sources":["../../../src/tracking/spans/redact-pii.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAK/C,gEAAgE;AAChE,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACvC;AAQD;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,WAAW,GACpB,OAAO,CAAC,OAAO,CAAC,CAgClB;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,YAAY,EAClB,QAAQ,EAAE,WAAW,GACpB,OAAO,CAAC,YAAY,CAAC,CAKvB;AASD,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,WAAW,GAAG,IAAI,GAAG,IAAI,CAEjE;AAED,wBAAgB,qBAAqB,IAAI,WAAW,GAAG,IAAI,CAE1D;AAED,sEAAsE;AACtE,wBAAgB,qBAAqB,IAAI,OAAO,CAE/C;AAqOD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,GACjB,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CA0CvC;AAsBD;;;;GAIG;AACH,wBAAsB,2BAA2B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAI/E;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAsB,0BAA0B,IAAI,OAAO,CAAC,WAAW,CAAC,CAkCvE"}
@@ -0,0 +1,468 @@
1
+ /**
2
+ * PII + secret redaction for span payloads.
3
+ *
4
+ * Walks the `data` field of each span and replaces matches in string values
5
+ * before the batch hits the wire. Runs at flush time (async) — the underlying
6
+ * redactor is async, and the producer is fire-and-forget, so we pay the
7
+ * latency on the network-bound path rather than on the caller's enqueue call.
8
+ *
9
+ * Scope (see createOpenRedactionAdapter): emails, phones (US, international,
10
+ * India, China, South Korea, Japan, UK mobile, Brazil), ~28 vendor API keys
11
+ * / tokens (AWS, GitHub, Stripe, OpenAI, etc.), EVM private keys, Anthropic
12
+ * `sk-ant-*` keys, generic `sk-*` keys, and BIP39 mnemonic phrases.
13
+ *
14
+ * Integration point: BatchingSpanTransport.flush() applies this to every
15
+ * span in the batch before JSON.stringify. Only `span.data` is walked;
16
+ * top-level columns (`address`, `correlationId`, etc.) are non-PII identifiers
17
+ * and stay as-is.
18
+ *
19
+ * Off by default. Enable with `SENPI_PII_REDACTION_ENABLED=true`.
20
+ */
21
+ import { createLogger } from "../../utils/logger.js";
22
+ const logger = createLogger("[senpi-core][redact-pii]");
23
+ function isPlainObject(value) {
24
+ if (value === null || typeof value !== "object")
25
+ return false;
26
+ const proto = Object.getPrototypeOf(value);
27
+ return proto === Object.prototype || proto === null;
28
+ }
29
+ /**
30
+ * Walks a JSON-shaped value. Strings are run through the redactor; arrays
31
+ * and plain objects are recursed; everything else (number, boolean, null,
32
+ * non-plain objects) is returned unchanged. Preserves reference identity
33
+ * when nothing changes — useful for short-circuit downstream comparisons.
34
+ */
35
+ export async function redactStringsDeep(value, redactor) {
36
+ if (typeof value === "string") {
37
+ if (value.length === 0)
38
+ return value;
39
+ try {
40
+ return await redactor.redact(value);
41
+ }
42
+ catch (err) {
43
+ logger.warn("redactor.redact threw; passing through original", {
44
+ err: err instanceof Error ? err.message : String(err),
45
+ preview: value.slice(0, 64),
46
+ });
47
+ return value;
48
+ }
49
+ }
50
+ if (Array.isArray(value)) {
51
+ const next = await Promise.all(value.map((item) => redactStringsDeep(item, redactor)));
52
+ return next.some((v, i) => v !== value[i]) ? next : value;
53
+ }
54
+ if (isPlainObject(value)) {
55
+ const entries = await Promise.all(Object.entries(value).map(async ([k, v]) => [k, await redactStringsDeep(v, redactor)]));
56
+ let changed = false;
57
+ const out = {};
58
+ for (const [k, v] of entries) {
59
+ out[k] = v;
60
+ if (v !== value[k])
61
+ changed = true;
62
+ }
63
+ return changed ? out : value;
64
+ }
65
+ return value;
66
+ }
67
+ /**
68
+ * Redact PII inside `span.data` only. Top-level structural fields (address,
69
+ * correlationId, runId, etc.) are non-PII identifiers and skipped.
70
+ *
71
+ * Returns the same span if nothing changed (avoids object allocation when no
72
+ * PII was found).
73
+ */
74
+ export async function redactSpanData(span, redactor) {
75
+ if (!span.data || typeof span.data !== "object")
76
+ return span;
77
+ const next = await redactStringsDeep(span.data, redactor);
78
+ if (next === span.data)
79
+ return span;
80
+ return { ...span, data: next };
81
+ }
82
+ // ─── Default redactor lifecycle ────────────────────────────────────────────
83
+ //
84
+ // The runtime registers a default redactor at startup when the feature flag is
85
+ // on; transports look it up via getDefaultPiiRedactor(). Tests can override.
86
+ let defaultRedactor = null;
87
+ export function setDefaultPiiRedactor(r) {
88
+ defaultRedactor = r;
89
+ }
90
+ export function getDefaultPiiRedactor() {
91
+ return defaultRedactor;
92
+ }
93
+ /** Reads SENPI_PII_REDACTION_ENABLED. Truthy only on exact "true". */
94
+ export function isPiiRedactionEnabled() {
95
+ return process.env.SENPI_PII_REDACTION_ENABLED === "true";
96
+ }
97
+ /**
98
+ * Built-in OpenRedaction pattern types we allowlist for span redaction.
99
+ *
100
+ * Scoped via the `patterns` whitelist (not categories) because category
101
+ * activation sweeps in patterns that false-positive on our domain content:
102
+ *
103
+ * - `categories: ['personal']` activates NAME patterns that redact
104
+ * strategy/asset names ("Setup Bison strategy" → "[NAME_xxx] strategy",
105
+ * "long position on ETH" → "long position on [NAME_xxx]").
106
+ * - `categories: ['financial']` activates ETH_ADDR which redacts wallet
107
+ * addresses inside `data.input.strategyWalletAddress`, breaking joins.
108
+ *
109
+ * Phone patterns from upstream are NOT used — we ship our own custom
110
+ * patterns instead. Specifically:
111
+ * - `PHONE_UK` — over-permissive: regex matches any 11-12 digit run with
112
+ * no separators, so the trailing 12-digit segment of a UUID (e.g.,
113
+ * `...446655440000`) is misclassified as a UK phone.
114
+ * - `PHONE_US` (upstream) — matches bare 10-digit numbers, which collides
115
+ * with 10-digit Unix timestamps (`1716855600`).
116
+ * - `PHONE_INTERNATIONAL` (upstream) — fine in isolation, but with
117
+ * `enableContextAnalysis: false` the library still suppresses matches
118
+ * in some contexts via undocumented filters; replacing it with our own
119
+ * custom version gives us deterministic behavior.
120
+ * - `TURKEY_ID` — personal national ID, not a credential.
121
+ */
122
+ const ALLOWLISTED_PATTERN_TYPES = [
123
+ // Contact PII (phones come from custom patterns below)
124
+ "EMAIL",
125
+ // Cloud / vendor API keys + tokens
126
+ "AWS_ACCESS_KEY",
127
+ "AWS_SECRET_KEY",
128
+ "AZURE_STORAGE_KEY",
129
+ "BEARER_TOKEN",
130
+ "CARD_AUTH_CODE",
131
+ "DOCKER_AUTH",
132
+ "FIREBASE_API_KEY",
133
+ "GENERIC_API_KEY",
134
+ "GENERIC_SECRET",
135
+ "GITHUB_TOKEN",
136
+ "GOOGLE_API_KEY",
137
+ "HEROKU_API_KEY",
138
+ "JWT_TOKEN",
139
+ "KUBERNETES_SECRET",
140
+ "MAILGUN_API_KEY",
141
+ "NPM_TOKEN",
142
+ "OAUTH_CLIENT_SECRET",
143
+ "OAUTH_TOKEN",
144
+ "OPENAI_API_KEY",
145
+ "PRIVATE_KEY",
146
+ "PYPI_TOKEN",
147
+ "SENDGRID_API_KEY",
148
+ "SLACK_TOKEN",
149
+ "SSH_PRIVATE_KEY",
150
+ "STRIPE_API_KEY",
151
+ "TWILIO_API_KEY",
152
+ "URL_WITH_AUTH",
153
+ ];
154
+ /**
155
+ * Phone patterns are entirely custom because upstream is unreliable: PHONE_UK
156
+ * matches bare 11–12 digit runs (UUID trailing segments), upstream PHONE_US
157
+ * matches bare 10-digit runs (Unix-seconds timestamps), and the library
158
+ * doesn't ship India / China / Korea / Japan / Brazil patterns at all.
159
+ *
160
+ * Design rule: every pattern requires either an explicit country code OR a
161
+ * country-specific mobile prefix AND/OR separators between groups. Pure
162
+ * numeric IDs (timestamps, span IDs, counters, UUID segments) don't satisfy
163
+ * any of those constraints, so they survive untouched.
164
+ */
165
+ const PHONE_PATTERNS = [
166
+ {
167
+ type: "PHONE_INTL",
168
+ regex: /\+\d[\d\s.\-()]{8,20}\d/g,
169
+ priority: 90,
170
+ placeholder: "[PHONE_INTL_{n}]",
171
+ severity: "high",
172
+ description: "International phone with explicit + country-code prefix.",
173
+ },
174
+ {
175
+ type: "PHONE_US",
176
+ // `(?<!\d)` / `(?!\d)` instead of `\b` so `(415) 555-1234` matches at
177
+ // "space-paren" (where \b fails) and bare 10-digit timestamps don't.
178
+ regex: /(?<!\d)(?:\+1[\s.-])?(?:\(\d{3}\)[\s.-]?|\d{3}[\s.-])\d{3}[\s.-]\d{4}(?!\d)/g,
179
+ priority: 88,
180
+ placeholder: "[PHONE_US_{n}]",
181
+ severity: "high",
182
+ description: "US phone with at least one separator (rejects bare 10-digit timestamps).",
183
+ },
184
+ {
185
+ type: "PHONE_IN",
186
+ regex: /\b(?:\+?91[\s.-]?)?[6-9]\d{4}[\s.-]?\d{5}\b/g,
187
+ priority: 85,
188
+ placeholder: "[PHONE_IN_{n}]",
189
+ severity: "high",
190
+ description: "Indian mobile (10 digits starting 6-9; optional +91/91 prefix).",
191
+ },
192
+ {
193
+ type: "PHONE_CN",
194
+ regex: /\b(?:\+?86[\s.-]?)?1[3-9]\d[\s.-]?\d{4}[\s.-]?\d{4}\b/g,
195
+ priority: 85,
196
+ placeholder: "[PHONE_CN_{n}]",
197
+ severity: "high",
198
+ description: "Chinese mobile (11 digits starting 1[3-9]; optional +86/86 prefix).",
199
+ },
200
+ {
201
+ type: "PHONE_KR",
202
+ regex: /\b(?:\+?82[\s.-]?)?0?1[01679][\s.-]\d{3,4}[\s.-]\d{4}\b/g,
203
+ priority: 85,
204
+ placeholder: "[PHONE_KR_{n}]",
205
+ severity: "high",
206
+ description: "Korean mobile (010/011/016-019 + separators; optional +82 prefix).",
207
+ },
208
+ {
209
+ type: "PHONE_JP",
210
+ regex: /\b(?:\+?81[\s.-]?)?0?[789]0[\s.-]\d{4}[\s.-]\d{4}\b/g,
211
+ priority: 85,
212
+ placeholder: "[PHONE_JP_{n}]",
213
+ severity: "high",
214
+ description: "Japanese mobile (070/080/090 + separators; optional +81 prefix).",
215
+ },
216
+ {
217
+ type: "PHONE_UK_MOBILE",
218
+ regex: /\b(?:\+?44[\s.-]?|0)7\d{3}[\s.-]?\d{3}[\s.-]?\d{3}\b/g,
219
+ priority: 85,
220
+ placeholder: "[PHONE_UK_{n}]",
221
+ severity: "high",
222
+ description: "UK mobile (07xxx; optional +44 prefix).",
223
+ },
224
+ {
225
+ type: "PHONE_BR",
226
+ regex: /\b\+?55[\s.-]?\(?\d{2}\)?[\s.-]?9\d{4}[\s.-]?\d{4}\b/g,
227
+ priority: 85,
228
+ placeholder: "[PHONE_BR_{n}]",
229
+ severity: "high",
230
+ description: "Brazilian mobile (+55 required + DDD + 9-digit number).",
231
+ },
232
+ ];
233
+ /**
234
+ * Lazy-load the BIP39 English wordlist into a Set for O(1) lookup during
235
+ * mnemonic validation. Cached after first call; the `bip39` package is only
236
+ * pulled in when PII redaction is enabled, keeping cold-start cost off the
237
+ * non-redacting build.
238
+ */
239
+ let bip39WordSet = null;
240
+ async function getBip39WordSet() {
241
+ if (bip39WordSet)
242
+ return bip39WordSet;
243
+ const mod = (await import("bip39"));
244
+ const wordlists = mod.wordlists ?? mod.default?.wordlists;
245
+ if (!wordlists?.english) {
246
+ throw new Error("bip39: english wordlist not available");
247
+ }
248
+ bip39WordSet = new Set(wordlists.english);
249
+ return bip39WordSet;
250
+ }
251
+ /**
252
+ * Build the custom-pattern set we layer on top of OpenRedaction's built-ins.
253
+ * These cover credentials common in our agent traffic that the upstream
254
+ * library either misses entirely or whose built-in pattern doesn't fire
255
+ * without context:
256
+ *
257
+ * - CRYPTO_PRIVKEY_HEX: `0x` + 64 hex = EVM private key. Also matches
258
+ * SHA-256 hashes; over-redaction is acceptable since hashes inside
259
+ * `data` are rare and not load-bearing.
260
+ * - ANTHROPIC_API_KEY: `sk-ant-(api|admin)NN-...`; upstream has no
261
+ * specific Anthropic pattern.
262
+ * - SK_API_KEY: catch-all for OpenAI / OpenRouter / similar `sk-` prefixed
263
+ * keys; upstream OPENAI_API_KEY didn't fire on real `sk-proj-*` and
264
+ * `sk-svcacct-*` formats in smoke tests.
265
+ *
266
+ * NOTE: Mnemonic detection is NOT in this list. The earlier regex-with-
267
+ * validator approach was greedy and silently missed mnemonics surrounded by
268
+ * other lowercase words (e.g. "my secret is <12 BIP39 words> so keep safe"
269
+ * → regex captures the full 17-word run, validator rejects on count/words,
270
+ * library moves on without retrying a shorter window). Mnemonics are now
271
+ * handled by `redactMnemonicsPreRedaction` ahead of the OpenRedaction pass.
272
+ */
273
+ async function buildCustomPatterns() {
274
+ return [
275
+ ...PHONE_PATTERNS,
276
+ {
277
+ type: "CRYPTO_PRIVKEY_HEX",
278
+ regex: /\b0x[0-9a-fA-F]{64}\b/g,
279
+ priority: 110,
280
+ placeholder: "[CRYPTO_PRIVKEY_{n}]",
281
+ severity: "critical",
282
+ description: "EVM private key (0x + 32-byte hex). Matches SHA-256 hashes too — accepted as collateral.",
283
+ },
284
+ {
285
+ type: "ANTHROPIC_API_KEY",
286
+ regex: /\bsk-ant-(?:api|admin)\d+-[A-Za-z0-9_\-]{40,}\b/g,
287
+ priority: 110,
288
+ placeholder: "[ANTHROPIC_KEY_{n}]",
289
+ severity: "critical",
290
+ },
291
+ {
292
+ type: "SK_API_KEY",
293
+ regex: /\bsk-(?:proj|live|test|or|svcacct)?[-_]?[A-Za-z0-9_\-]{32,}\b/g,
294
+ priority: 100,
295
+ placeholder: "[SK_KEY_{n}]",
296
+ severity: "critical",
297
+ description: "Catch-all for sk-* API keys (OpenAI, OpenRouter, etc.).",
298
+ },
299
+ ];
300
+ }
301
+ /**
302
+ * Find BIP39-mnemonic spans in `text` using a true sliding window — not
303
+ * a greedy regex with a post-hoc validator.
304
+ *
305
+ * Why this and not a regex+validator:
306
+ * - OpenRedaction calls the validator on whatever the regex captured. JS
307
+ * regex matching is left-to-right with no backtracking across `lastIndex`
308
+ * advances; once a (greedy) match is rejected, the engine moves past it
309
+ * entirely. So a 17-word lowercase run like "my secret is <12 BIP39>
310
+ * so keep safe" gets captured whole, fails validation on word count,
311
+ * and the engine doesn't retry the 12-word window starting 3 tokens in.
312
+ *
313
+ * Algorithm:
314
+ * 1. Tokenize lowercase-only words and their offsets (skips capitalized
315
+ * words and punctuation — real mnemonics from wallet exports are
316
+ * always all-lowercase space-separated).
317
+ * 2. Walk left to right. At each token position, try valid mnemonic
318
+ * counts largest-first (24, 21, 18, 15, 12) so a 24-word phrase
319
+ * inside a longer prose run isn't missed by a premature 12-match.
320
+ * 3. A window is valid only when every word is BIP39 AND the gap between
321
+ * consecutive tokens in the source is whitespace-only (rejects forms
322
+ * with commas or other punctuation between words, which real wallet
323
+ * exports don't use).
324
+ * 4. On match, record the source span and advance past it; otherwise step
325
+ * forward one token.
326
+ */
327
+ export function findMnemonicSpans(text, bip39) {
328
+ const tokens = [];
329
+ const wordRe = /[a-z]+/g;
330
+ let m;
331
+ while ((m = wordRe.exec(text)) !== null) {
332
+ tokens.push({ word: m[0], start: m.index, end: m.index + m[0].length });
333
+ }
334
+ const validCounts = [24, 21, 18, 15, 12];
335
+ const spans = [];
336
+ let i = 0;
337
+ while (i < tokens.length) {
338
+ let matched = false;
339
+ for (const count of validCounts) {
340
+ if (i + count > tokens.length)
341
+ continue;
342
+ let valid = true;
343
+ for (let j = 0; j < count; j++) {
344
+ if (!bip39.has(tokens[i + j].word)) {
345
+ valid = false;
346
+ break;
347
+ }
348
+ if (j > 0) {
349
+ const gap = text.slice(tokens[i + j - 1].end, tokens[i + j].start);
350
+ if (!/^\s+$/.test(gap)) {
351
+ valid = false;
352
+ break;
353
+ }
354
+ }
355
+ }
356
+ if (valid) {
357
+ spans.push({
358
+ start: tokens[i].start,
359
+ end: tokens[i + count - 1].end,
360
+ });
361
+ i += count;
362
+ matched = true;
363
+ break;
364
+ }
365
+ }
366
+ if (!matched)
367
+ i++;
368
+ }
369
+ return spans;
370
+ }
371
+ let mnemonicCounter = 0;
372
+ function redactMnemonicSpans(text, spans) {
373
+ if (spans.length === 0)
374
+ return text;
375
+ let out = "";
376
+ let cursor = 0;
377
+ for (const span of spans) {
378
+ out += text.slice(cursor, span.start);
379
+ // Bracketed uppercase placeholder ensures the result won't re-tokenize
380
+ // back into lowercase BIP39-shaped words on a second pass.
381
+ const suffix = String(mnemonicCounter++ % 10000).padStart(4, "0");
382
+ out += `[MNEMONIC_${suffix}]`;
383
+ cursor = span.end;
384
+ }
385
+ out += text.slice(cursor);
386
+ return out;
387
+ }
388
+ /**
389
+ * Pre-redact BIP39 mnemonics in `text` before handing the result to
390
+ * OpenRedaction. Returns the original string when no mnemonics are present
391
+ * (cheap path — only the tokenizer runs and finds nothing).
392
+ */
393
+ export async function redactMnemonicsPreRedaction(text) {
394
+ const bip39 = await getBip39WordSet();
395
+ const spans = findMnemonicSpans(text, bip39);
396
+ return redactMnemonicSpans(text, spans);
397
+ }
398
+ /**
399
+ * Build an OpenRedaction-backed PiiRedactor.
400
+ *
401
+ * Detects:
402
+ * - Contact PII: emails.
403
+ * - Phones (all custom — upstream phone patterns are unreliable, see
404
+ * ALLOWLISTED_PATTERN_TYPES docstring): US, international (`+` prefix),
405
+ * India, China, South Korea, Japan, UK mobile, Brazil. Each requires
406
+ * either an explicit country code OR a country-specific mobile prefix
407
+ * plus separators, so bare numeric IDs (timestamps, span IDs, UUIDs)
408
+ * survive untouched.
409
+ * - Credentials (upstream): cloud / vendor API keys + tokens (AWS, Azure,
410
+ * GCP, GitHub, Slack, Stripe, OpenAI, Twilio, …), bearer + JWT tokens,
411
+ * OAuth secrets, generic API keys, SSH/PEM private keys, URLs with
412
+ * embedded auth.
413
+ * - Crypto secrets (custom patterns): EVM private keys (0x + 64 hex),
414
+ * Anthropic `sk-ant-*` keys, generic `sk-*` keys (OpenAI/OpenRouter).
415
+ * - BIP39 mnemonic phrases (out-of-band sliding-window pre-pass — see
416
+ * `redactMnemonicsPreRedaction`): 12/15/18/21/24 BIP39 words, found and
417
+ * redacted before the OpenRedaction pass so embedded phrases like
418
+ * "my key is <12 words> keep safe" are caught.
419
+ *
420
+ * Tuning:
421
+ * - `enableFalsePositiveFilter: false` — explicit; the library defaults
422
+ * this to truthy in practice and suppresses valid international-phone
423
+ * matches in some contexts.
424
+ * - `enableContextAnalysis: false` — saves CPU; context rules add
425
+ * latency and aren't load-bearing for the patterns we keep.
426
+ * - `enableCache: true` — chat history repeats; cache skips re-scan.
427
+ * - `confidenceThreshold: 0.5` — kept conservative; custom patterns score
428
+ * 1.0 so this only affects built-ins.
429
+ *
430
+ * Lazy-imports `openredaction` and `bip39` — both deps stay cold for builds
431
+ * / tests that don't enable redaction.
432
+ */
433
+ export async function createOpenRedactionAdapter() {
434
+ const { OpenRedaction } = await import("openredaction");
435
+ const customPatterns = await buildCustomPatterns();
436
+ // Our local custom-pattern descriptor type is structurally compatible with
437
+ // the upstream PIIPattern. Cast at the options-object boundary so callers
438
+ // don't need a build-time dependency on the openredaction type.
439
+ const options = {
440
+ patterns: [...ALLOWLISTED_PATTERN_TYPES],
441
+ customPatterns,
442
+ enableContextAnalysis: false,
443
+ enableMultiPass: false,
444
+ enableCache: true,
445
+ cacheSize: 200,
446
+ // Despite the docs saying default is `false`, leaving this unset
447
+ // causes the library to suppress otherwise-valid phone matches in some
448
+ // contexts (e.g., `+44 20 7946 0958` after "ring me on"). Force off so
449
+ // our strict custom regex is the only thing deciding what gets redacted.
450
+ enableFalsePositiveFilter: false,
451
+ confidenceThreshold: 0.5,
452
+ };
453
+ const instance = new OpenRedaction(options);
454
+ // Warm the BIP39 word set so the first user-facing call doesn't pay the
455
+ // dynamic-import latency.
456
+ await getBip39WordSet();
457
+ return {
458
+ async redact(text) {
459
+ // Mnemonic pre-pass runs FIRST — its sliding-window detector can find
460
+ // mnemonics that OpenRedaction's regex-with-validator misses when the
461
+ // phrase is embedded in surrounding lowercase prose.
462
+ const preRedacted = await redactMnemonicsPreRedaction(text);
463
+ const result = await instance.detect(preRedacted);
464
+ return result.redacted;
465
+ },
466
+ };
467
+ }
468
+ //# sourceMappingURL=redact-pii.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact-pii.js","sourceRoot":"","sources":["../../../src/tracking/spans/redact-pii.ts"],"names":[],"mappings":"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"}