@tangle-network/agent-app 0.45.59 → 0.45.61
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.
- package/dist/chat-routes/attachment-store.d.ts +7 -4
- package/dist/chat-routes/index.js +61 -34
- package/dist/chat-routes/index.js.map +1 -1
- package/dist/chat-routes/promote-file-part.d.ts +2 -0
- package/dist/{chunk-CDC5HFKL.js → chunk-MH74DY2I.js} +13 -3
- package/dist/chunk-MH74DY2I.js.map +1 -0
- package/dist/redact/index.js +1 -1
- package/package.json +18 -18
- package/dist/chunk-CDC5HFKL.js.map +0 -1
|
@@ -96,4 +96,6 @@ export interface AtomicPromoteAgentFilePartOptions extends PromoteFilePartCommon
|
|
|
96
96
|
export declare function promoteAgentFilePart(options: PromoteAgentFilePartOptions): Promise<PromoteFilePartResult>;
|
|
97
97
|
/** Promote a part using an ownership-safe writer and cleanup adapter. */
|
|
98
98
|
export declare function promoteAgentFilePart(options: AtomicPromoteAgentFilePartOptions): Promise<PromoteFilePartResult>;
|
|
99
|
+
/** Promote a part when the caller selects the writer contract at runtime. */
|
|
100
|
+
export declare function promoteAgentFilePart(options: PromoteAgentFilePartOptions | AtomicPromoteAgentFilePartOptions): Promise<PromoteFilePartResult>;
|
|
99
101
|
export {};
|
|
@@ -17,7 +17,8 @@ var SENSITIVE_KEYS = /* @__PURE__ */ new Set([
|
|
|
17
17
|
function redactString(value, patterns) {
|
|
18
18
|
for (const { kind, pattern, validate } of patterns) {
|
|
19
19
|
if (!validate) {
|
|
20
|
-
|
|
20
|
+
const testPattern = /[gy]/.test(pattern.flags) ? new RegExp(pattern.source, pattern.flags.replace(/[gy]/g, "")) : pattern;
|
|
21
|
+
if (testPattern.test(value)) return `[REDACTED:${kind}]`;
|
|
21
22
|
continue;
|
|
22
23
|
}
|
|
23
24
|
const g = new RegExp(pattern.source, pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`);
|
|
@@ -40,13 +41,22 @@ function maskSpans(text, patterns = DEFAULT_REDACTION_PATTERNS) {
|
|
|
40
41
|
if (pos < text.length) out += text.slice(pos);
|
|
41
42
|
return out;
|
|
42
43
|
}
|
|
44
|
+
var ERROR_SECRET_KEY = String.raw`(?:access[_-]?key[_-]?id|secret[_-]?access[_-]?key|session[_-]?token|security[_-]?token|x-amz-(?:credential|signature|security-token)|aws[_-]?access[_-]?key[_-]?id|aws[_-]?secret[_-]?access[_-]?key|aws[_-]?session[_-]?token|api[_-]?key|client[_-]?secret|credential|token|secret|password|signature|sig|authorization)`;
|
|
43
45
|
var ERROR_SECRET_PATTERNS = [
|
|
44
46
|
...DEFAULT_REDACTION_PATTERNS,
|
|
47
|
+
{ kind: "email", pattern: /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/i },
|
|
45
48
|
{ kind: "bearer", pattern: /Bearer\s+[^\s]+/i },
|
|
46
49
|
{ kind: "credential", pattern: /\b(?:sk|pk|tc|ghp|xoxb)[_-][A-Za-z0-9_-]{8,}\b/i },
|
|
47
50
|
{
|
|
48
51
|
kind: "credential",
|
|
49
|
-
pattern:
|
|
52
|
+
pattern: /["']?\b(?:cookie|set-cookie)\b["']?\s*[:=]\s*(?:"[^"]*"|'[^']*'|[^\r\n}]*)/i
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
kind: "credential",
|
|
56
|
+
pattern: new RegExp(
|
|
57
|
+
String.raw`["']?\b${ERROR_SECRET_KEY}\b["']?\s*[:=]\s*(?:"[^"]*"|'[^']*'|(?:Bearer|Basic)\s+[^\s,;&}"']+|[^\r\n,;&}]+?)(?=\s+(?:["']?\b[A-Za-z][A-Za-z0-9_-]*["']?\s*[:=]|(?:Bearer|Basic)\b)|\s*,\s*["']?\b[A-Za-z][A-Za-z0-9_-]*["']?\s*[:=]|\s*[,;&}]|\s*[\r\n]|$)`,
|
|
58
|
+
"i"
|
|
59
|
+
)
|
|
50
60
|
}
|
|
51
61
|
];
|
|
52
62
|
function safeString(value) {
|
|
@@ -162,4 +172,4 @@ export {
|
|
|
162
172
|
buildRedactedDocument,
|
|
163
173
|
revealSpan
|
|
164
174
|
};
|
|
165
|
-
//# sourceMappingURL=chunk-
|
|
175
|
+
//# sourceMappingURL=chunk-MH74DY2I.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/redact/index.ts"],"sourcesContent":["/**\n * PII redaction — two complementary modes.\n *\n * 1. ONE-WAY scrub (`redactForIngestion`): for production trace payloads. Tool\n * args + results (and once, the LLM span's prompt) cross the wire into the\n * ingestion store, which also feeds the analyst-loop's LLM prompts, so\n * personal identifiers MUST be stripped before they leave the request path.\n * Destructive — the original is gone, replaced by a sentinel.\n *\n * 2. REVERSIBLE redaction (`buildRedactedDocument` / `revealSpan`): for the UI.\n * A document is split into text + redacted segments; each redacted original\n * is kept ENCRYPTED (via a caller-supplied `encrypt` seam → `agent-app/crypto`)\n * so a viewer can reveal a single span on demand, gated by an authorization\n * callback and an audit hook. The mask is presentation; the original is\n * recoverable by an authorized reveal, not lost.\n *\n * Discipline: cheap deterministic string patterns + well-known sensitive object\n * keys (value replaced, key kept, so the shape stays debuggable); recurse arrays\n * + plain objects only; NEVER throw on the one-way path.\n */\n\n/** A named PII pattern. `pattern` is matched case-insensitively at the string\n * level; keep it non-global (global instances are derived where needed). */\nexport interface RedactionPattern {\n kind: string\n pattern: RegExp\n /** Optional predicate over each match — the pattern fires only when it returns\n * true. For matches a regex alone can't decide (e.g. a Luhn check on a\n * card-number candidate). When set, the value is scanned globally and the\n * first match that passes wins; when absent, a plain `pattern.test` decides. */\n validate?: (match: string) => boolean\n}\n\n/** The default deterministic patterns. Extend via the `extraPatterns` /\n * `patterns` options rather than forking this module (the seam that lets a\n * product add e.g. a credit-card matcher without a local copy). */\nexport const DEFAULT_REDACTION_PATTERNS: readonly RedactionPattern[] = [\n { kind: 'ssn', pattern: /\\d{3}-\\d{2}-\\d{4}/ },\n { kind: 'ein', pattern: /\\d{2}-\\d{7}/ },\n]\n\nconst SENSITIVE_KEYS = new Set([\n 'ssn',\n 'ein',\n 'password',\n 'apikey',\n 'token',\n 'secret',\n 'authorization',\n 'email',\n 'phone',\n])\n\n/** Define options to customize sensitive data redaction patterns and key names for ingestion */\nexport interface RedactForIngestionOptions {\n /** Extra patterns appended to {@link DEFAULT_REDACTION_PATTERNS} for the\n * string-level scrub (e.g. credit-card). Additive — defaults still apply. */\n extraPatterns?: readonly RedactionPattern[]\n /** Extra sensitive object-key names (case-insensitive) added to the built-in\n * set, e.g. the snake_case `api_key` an intake form uses. Additive. */\n extraSensitiveKeys?: readonly string[]\n /**\n * How a matched string is rewritten:\n * - `'collapse'` (default) — the whole string becomes `[REDACTED:<kind>]` on\n * the first matching pattern. Safest for telemetry: nothing of the original\n * survives.\n * - `'mask-spans'` — only the matched substrings are replaced (each with\n * `[REDACTED:<kind>]`), preserving surrounding text. Use when a downstream\n * reader needs the non-PII context (e.g. an analyst loop reading prose).\n */\n stringMode?: 'collapse' | 'mask-spans'\n}\n\nfunction redactString(value: string, patterns: readonly RedactionPattern[]): string {\n for (const { kind, pattern, validate } of patterns) {\n if (!validate) {\n const testPattern = /[gy]/.test(pattern.flags)\n ? new RegExp(pattern.source, pattern.flags.replace(/[gy]/g, ''))\n : pattern\n if (testPattern.test(value)) return `[REDACTED:${kind}]`\n continue\n }\n const g = new RegExp(pattern.source, pattern.flags.includes('g') ? pattern.flags : `${pattern.flags}g`)\n for (const m of value.matchAll(g)) {\n if (m[0].length > 0 && validate(m[0])) return `[REDACTED:${kind}]`\n }\n }\n return value\n}\n\n/**\n * Replace only the PII substrings in `text`, preserving everything around them\n * (the `mask-spans` string mode). Built on {@link detectSpans} so matching,\n * non-overlap, and `validate` predicates behave identically to the reversible\n * path. Each span becomes `[REDACTED:<kind>]`.\n */\nexport function maskSpans(\n text: string,\n patterns: readonly RedactionPattern[] = DEFAULT_REDACTION_PATTERNS,\n): string {\n const spans = detectSpans(text, patterns)\n if (spans.length === 0) return text\n let out = ''\n let pos = 0\n for (const s of spans) {\n if (s.start > pos) out += text.slice(pos, s.start)\n out += `[REDACTED:${s.kind}]`\n pos = s.end\n }\n if (pos < text.length) out += text.slice(pos)\n return out\n}\n\nconst ERROR_SECRET_KEY = String.raw`(?:access[_-]?key[_-]?id|secret[_-]?access[_-]?key|session[_-]?token|security[_-]?token|x-amz-(?:credential|signature|security-token)|aws[_-]?access[_-]?key[_-]?id|aws[_-]?secret[_-]?access[_-]?key|aws[_-]?session[_-]?token|api[_-]?key|client[_-]?secret|credential|token|secret|password|signature|sig|authorization)`\n\nconst ERROR_SECRET_PATTERNS: readonly RedactionPattern[] = [\n ...DEFAULT_REDACTION_PATTERNS,\n { kind: 'email', pattern: /\\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}\\b/i },\n { kind: 'bearer', pattern: /Bearer\\s+[^\\s]+/i },\n { kind: 'credential', pattern: /\\b(?:sk|pk|tc|ghp|xoxb)[_-][A-Za-z0-9_-]{8,}\\b/i },\n {\n kind: 'credential',\n pattern: /[\"']?\\b(?:cookie|set-cookie)\\b[\"']?\\s*[:=]\\s*(?:\"[^\"]*\"|'[^']*'|[^\\r\\n}]*)/i,\n },\n {\n kind: 'credential',\n pattern: new RegExp(\n String.raw`[\"']?\\b${ERROR_SECRET_KEY}\\b[\"']?\\s*[:=]\\s*(?:\"[^\"]*\"|'[^']*'|(?:Bearer|Basic)\\s+[^\\s,;&}\"']+|[^\\r\\n,;&}]+?)(?=\\s+(?:[\"']?\\b[A-Za-z][A-Za-z0-9_-]*[\"']?\\s*[:=]|(?:Bearer|Basic)\\b)|\\s*,\\s*[\"']?\\b[A-Za-z][A-Za-z0-9_-]*[\"']?\\s*[:=]|\\s*[,;&}]|\\s*[\\r\\n]|$)`,\n 'i',\n ),\n },\n]\n\n/** Sanitize an untrusted backend error for server logs. This is deliberately\n * separate from public response text: callers should return an opaque message\n * and log this bounded, redacted value for operators. */\nfunction safeString(value: unknown): string | undefined {\n try {\n return typeof value === 'string' ? value : String(value)\n } catch {\n return undefined\n }\n}\n\nfunction safeErrorText(input: unknown): string {\n if (input !== null && (typeof input === 'object' || typeof input === 'function')) {\n try {\n const message = Reflect.get(input, 'message')\n const messageText = safeString(message)\n if (messageText !== undefined) return messageText\n } catch {\n // Hostile getters and proxies are still untrusted error values.\n }\n }\n return safeString(input) ?? ''\n}\n\nexport function redactErrorMessage(input: unknown, fallback = 'unknown error'): string {\n const fallbackText = safeString(fallback)?.trim() || 'unknown error'\n const raw = safeErrorText(input)\n const message = raw.trim() || fallbackText\n try {\n const redacted = maskSpans(message, ERROR_SECRET_PATTERNS).trim()\n return redacted.length > 240 ? `${redacted.slice(0, 240)}…` : redacted || fallbackText\n } catch {\n return fallbackText\n }\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (value === null || typeof value !== 'object') return false\n const proto = Object.getPrototypeOf(value)\n return proto === Object.prototype || proto === null\n}\n\n/**\n * One-way PII scrub for telemetry/ingestion. Backward-compatible: called with no\n * options it behaves exactly as before (SSN/EIN strings + sensitive object keys\n * → sentinels). `extraPatterns` lets a product add matchers (e.g. credit-card)\n * without forking this module.\n */\nexport function redactForIngestion(value: unknown, options: RedactForIngestionOptions = {}): unknown {\n const patterns = options.extraPatterns\n ? [...DEFAULT_REDACTION_PATTERNS, ...options.extraPatterns]\n : DEFAULT_REDACTION_PATTERNS\n const sensitiveKeys = options.extraSensitiveKeys\n ? new Set([...SENSITIVE_KEYS, ...options.extraSensitiveKeys.map((k) => k.toLowerCase())])\n : SENSITIVE_KEYS\n const maskString =\n options.stringMode === 'mask-spans'\n ? (s: string) => maskSpans(s, patterns)\n : (s: string) => redactString(s, patterns)\n // Cycle guard: a payload with a circular reference would otherwise recurse\n // forever. On re-encountering an object/array, return it untouched to break\n // the cycle (the same value was already redacted on its first visit).\n const seen = new WeakSet<object>()\n const walk = (v: unknown): unknown => {\n if (typeof v === 'string') return maskString(v)\n if (Array.isArray(v)) {\n if (seen.has(v)) return v\n seen.add(v)\n return v.map(walk)\n }\n if (isPlainObject(v)) {\n if (seen.has(v)) return v\n seen.add(v)\n const out: Record<string, unknown> = {}\n for (const [k, val] of Object.entries(v)) {\n out[k] = sensitiveKeys.has(k.toLowerCase()) ? '[REDACTED:field]' : walk(val)\n }\n return out\n }\n return v\n }\n return walk(value)\n}\n\n// ── Reversible document redaction (the UI path) ─────────────────────────────\n\n/** A detected PII span in a source string. */\nexport interface RedactionSpan {\n /** Stable within a document (index-derived) — used for reveal + audit. */\n id: string\n kind: string\n start: number\n end: number\n text: string\n}\n\n/**\n * Find non-overlapping PII spans in `text`. Matches every pattern, sorts by\n * position, and drops overlaps (first match wins). Deterministic — no ids that\n * vary per call.\n */\nexport function detectSpans(\n text: string,\n patterns: readonly RedactionPattern[] = DEFAULT_REDACTION_PATTERNS,\n): RedactionSpan[] {\n const raw: Array<{ kind: string; start: number; end: number; text: string }> = []\n for (const { kind, pattern, validate } of patterns) {\n const g = new RegExp(pattern.source, pattern.flags.includes('g') ? pattern.flags : `${pattern.flags}g`)\n for (const m of text.matchAll(g)) {\n if (m.index === undefined || m[0].length === 0) continue\n if (validate && !validate(m[0])) continue\n raw.push({ kind, start: m.index, end: m.index + m[0].length, text: m[0] })\n }\n }\n raw.sort((a, b) => a.start - b.start || b.end - a.end)\n const spans: RedactionSpan[] = []\n let cursor = -1\n let i = 0\n for (const s of raw) {\n if (s.start < cursor) continue // overlaps an earlier (higher-priority) span\n spans.push({ id: `span-${i++}`, ...s })\n cursor = s.end\n }\n return spans\n}\n\n/** A redacted document segment: literal text, or a masked span with the\n * original kept ENCRYPTED for an authorized reveal. */\nexport type RedactedDocSegment =\n | { type: 'text'; text: string }\n | { type: 'redacted'; id: string; kind: string; cipher: string }\n\n/** Define a document composed of multiple redacted content segments */\nexport interface RedactedDocument {\n segments: RedactedDocSegment[]\n}\n\n/** Define options to encrypt text and specify patterns for redacting sensitive document content */\nexport interface BuildRedactedDocumentOptions {\n /** Encrypt one original span value. Wire it to `agent-app/crypto`\n * (`encryptWithKey` / `createFieldCrypto`). The cipher is what's stored. */\n encrypt: (plaintext: string) => string | Promise<string>\n /** Patterns to detect (default: {@link DEFAULT_REDACTION_PATTERNS}). */\n patterns?: readonly RedactionPattern[]\n}\n\n/**\n * Split `text` into text + redacted segments, encrypting each redacted span's\n * original. The result carries NO plaintext PII — only the masked structure and\n * ciphertext — so it is safe to ship to a client; reveal happens server-side via\n * {@link revealSpan}.\n */\nexport async function buildRedactedDocument(\n text: string,\n options: BuildRedactedDocumentOptions,\n): Promise<RedactedDocument> {\n const spans = detectSpans(text, options.patterns)\n const segments: RedactedDocSegment[] = []\n let pos = 0\n for (const span of spans) {\n if (span.start > pos) segments.push({ type: 'text', text: text.slice(pos, span.start) })\n segments.push({ type: 'redacted', id: span.id, kind: span.kind, cipher: await options.encrypt(span.text) })\n pos = span.end\n }\n if (pos < text.length) segments.push({ type: 'text', text: text.slice(pos) })\n return { segments }\n}\n\n/** Define options to decrypt, authorize, and audit the reveal of a span segment */\nexport interface RevealSpanOptions {\n /** Decrypt a span cipher. Wire to `agent-app/crypto` (`decryptWithKey`). */\n decrypt: (cipher: string) => string | Promise<string>\n /** Authorization gate — return false to deny the reveal (fail-closed). */\n canReveal: (segment: { id: string; kind: string }) => boolean | Promise<boolean>\n /** Audit hook — invoked only on a granted reveal (the caller records who/when). */\n onReveal?: (segment: { id: string; kind: string }) => void | Promise<void>\n}\n\n/** Describe the outcome of a reveal operation including success status, value, and failure reason */\nexport interface RevealResult {\n ok: boolean\n value?: string\n /** `not_found` | `forbidden` when `ok` is false. */\n reason?: string\n}\n\n/**\n * Reveal one redacted span's original, gated + audited. Fail-closed: an unknown\n * id or a denied `canReveal` returns `{ ok: false }` and never decrypts; a\n * granted reveal decrypts, fires `onReveal` for the audit trail, and returns the\n * value.\n */\nexport async function revealSpan(\n doc: RedactedDocument,\n spanId: string,\n options: RevealSpanOptions,\n): Promise<RevealResult> {\n const seg = doc.segments.find((s): s is Extract<RedactedDocSegment, { type: 'redacted' }> =>\n s.type === 'redacted' && s.id === spanId,\n )\n if (!seg) return { ok: false, reason: 'not_found' }\n const allowed = await options.canReveal({ id: seg.id, kind: seg.kind })\n if (!allowed) return { ok: false, reason: 'forbidden' }\n const value = await options.decrypt(seg.cipher)\n if (options.onReveal) await options.onReveal({ id: seg.id, kind: seg.kind })\n return { ok: true, value }\n}\n"],"mappings":"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","names":[]}
|
package/dist/redact/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/agent-app",
|
|
3
|
-
"version": "0.45.
|
|
3
|
+
"version": "0.45.61",
|
|
4
4
|
"packageManager": "pnpm@11.17.0",
|
|
5
5
|
"description": "Build agent applications with typed chat, tools, sandboxes, integrations, billing, and evaluation.",
|
|
6
6
|
"keywords": [
|
|
@@ -519,15 +519,15 @@
|
|
|
519
519
|
"@storybook/react": "^10.5.5",
|
|
520
520
|
"@storybook/react-vite": "^10.5.5",
|
|
521
521
|
"@tangle-network/agent-docs": "0.2.1",
|
|
522
|
-
"@tangle-network/agent-eval": "0.145.
|
|
523
|
-
"@tangle-network/agent-integrations": "0.53.
|
|
524
|
-
"@tangle-network/agent-interface": "0.
|
|
525
|
-
"@tangle-network/agent-knowledge": "
|
|
526
|
-
"@tangle-network/agent-profile-materialize": "0.
|
|
527
|
-
"@tangle-network/agent-runtime": "0.
|
|
528
|
-
"@tangle-network/brand": "1.
|
|
529
|
-
"@tangle-network/sandbox": "0.
|
|
530
|
-
"@tangle-network/sandbox-ui": "0.
|
|
522
|
+
"@tangle-network/agent-eval": "0.145.15",
|
|
523
|
+
"@tangle-network/agent-integrations": "0.53.54",
|
|
524
|
+
"@tangle-network/agent-interface": "0.53.0",
|
|
525
|
+
"@tangle-network/agent-knowledge": "8.0.1",
|
|
526
|
+
"@tangle-network/agent-profile-materialize": "0.15.1",
|
|
527
|
+
"@tangle-network/agent-runtime": "0.135.3",
|
|
528
|
+
"@tangle-network/brand": "1.5.0",
|
|
529
|
+
"@tangle-network/sandbox": "0.27.0",
|
|
530
|
+
"@tangle-network/sandbox-ui": "0.103.1",
|
|
531
531
|
"@tangle-network/ui": "^11.5.0",
|
|
532
532
|
"@testing-library/dom": "^10.4.1",
|
|
533
533
|
"@testing-library/react": "^16.3.2",
|
|
@@ -571,15 +571,15 @@
|
|
|
571
571
|
"@firecrawl/pdf-inspector-wasm": ">=0.1.3",
|
|
572
572
|
"@huggingface/transformers": ">=3",
|
|
573
573
|
"@radix-ui/react-dialog": ">=1.1",
|
|
574
|
-
"@tangle-network/agent-eval": ">=0.145.
|
|
574
|
+
"@tangle-network/agent-eval": ">=0.145.15",
|
|
575
575
|
"@tangle-network/agent-integrations": ">=0.52.0",
|
|
576
|
-
"@tangle-network/agent-interface": ">=0.
|
|
577
|
-
"@tangle-network/agent-knowledge": ">=
|
|
578
|
-
"@tangle-network/agent-profile-materialize": ">=0.
|
|
579
|
-
"@tangle-network/agent-runtime": ">=0.
|
|
580
|
-
"@tangle-network/brand": ">=1.
|
|
581
|
-
"@tangle-network/sandbox": ">=0.
|
|
582
|
-
"@tangle-network/sandbox-ui": ">=0.
|
|
576
|
+
"@tangle-network/agent-interface": ">=0.53.0 <0.54.0",
|
|
577
|
+
"@tangle-network/agent-knowledge": ">=8.0.1",
|
|
578
|
+
"@tangle-network/agent-profile-materialize": ">=0.15.1",
|
|
579
|
+
"@tangle-network/agent-runtime": ">=0.135.3",
|
|
580
|
+
"@tangle-network/brand": ">=1.5.0",
|
|
581
|
+
"@tangle-network/sandbox": ">=0.27.0",
|
|
582
|
+
"@tangle-network/sandbox-ui": ">=0.103.1",
|
|
583
583
|
"@tangle-network/ui": ">=11.5.0",
|
|
584
584
|
"@xyflow/react": ">=12.0.0",
|
|
585
585
|
"better-auth": ">=1.6.16",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/redact/index.ts"],"sourcesContent":["/**\n * PII redaction — two complementary modes.\n *\n * 1. ONE-WAY scrub (`redactForIngestion`): for production trace payloads. Tool\n * args + results (and once, the LLM span's prompt) cross the wire into the\n * ingestion store, which also feeds the analyst-loop's LLM prompts, so\n * personal identifiers MUST be stripped before they leave the request path.\n * Destructive — the original is gone, replaced by a sentinel.\n *\n * 2. REVERSIBLE redaction (`buildRedactedDocument` / `revealSpan`): for the UI.\n * A document is split into text + redacted segments; each redacted original\n * is kept ENCRYPTED (via a caller-supplied `encrypt` seam → `agent-app/crypto`)\n * so a viewer can reveal a single span on demand, gated by an authorization\n * callback and an audit hook. The mask is presentation; the original is\n * recoverable by an authorized reveal, not lost.\n *\n * Discipline: cheap deterministic string patterns + well-known sensitive object\n * keys (value replaced, key kept, so the shape stays debuggable); recurse arrays\n * + plain objects only; NEVER throw on the one-way path.\n */\n\n/** A named PII pattern. `pattern` is matched case-insensitively at the string\n * level; keep it non-global (global instances are derived where needed). */\nexport interface RedactionPattern {\n kind: string\n pattern: RegExp\n /** Optional predicate over each match — the pattern fires only when it returns\n * true. For matches a regex alone can't decide (e.g. a Luhn check on a\n * card-number candidate). When set, the value is scanned globally and the\n * first match that passes wins; when absent, a plain `pattern.test` decides. */\n validate?: (match: string) => boolean\n}\n\n/** The default deterministic patterns. Extend via the `extraPatterns` /\n * `patterns` options rather than forking this module (the seam that lets a\n * product add e.g. a credit-card matcher without a local copy). */\nexport const DEFAULT_REDACTION_PATTERNS: readonly RedactionPattern[] = [\n { kind: 'ssn', pattern: /\\d{3}-\\d{2}-\\d{4}/ },\n { kind: 'ein', pattern: /\\d{2}-\\d{7}/ },\n]\n\nconst SENSITIVE_KEYS = new Set([\n 'ssn',\n 'ein',\n 'password',\n 'apikey',\n 'token',\n 'secret',\n 'authorization',\n 'email',\n 'phone',\n])\n\n/** Define options to customize sensitive data redaction patterns and key names for ingestion */\nexport interface RedactForIngestionOptions {\n /** Extra patterns appended to {@link DEFAULT_REDACTION_PATTERNS} for the\n * string-level scrub (e.g. credit-card). Additive — defaults still apply. */\n extraPatterns?: readonly RedactionPattern[]\n /** Extra sensitive object-key names (case-insensitive) added to the built-in\n * set, e.g. the snake_case `api_key` an intake form uses. Additive. */\n extraSensitiveKeys?: readonly string[]\n /**\n * How a matched string is rewritten:\n * - `'collapse'` (default) — the whole string becomes `[REDACTED:<kind>]` on\n * the first matching pattern. Safest for telemetry: nothing of the original\n * survives.\n * - `'mask-spans'` — only the matched substrings are replaced (each with\n * `[REDACTED:<kind>]`), preserving surrounding text. Use when a downstream\n * reader needs the non-PII context (e.g. an analyst loop reading prose).\n */\n stringMode?: 'collapse' | 'mask-spans'\n}\n\nfunction redactString(value: string, patterns: readonly RedactionPattern[]): string {\n for (const { kind, pattern, validate } of patterns) {\n if (!validate) {\n if (pattern.test(value)) return `[REDACTED:${kind}]`\n continue\n }\n const g = new RegExp(pattern.source, pattern.flags.includes('g') ? pattern.flags : `${pattern.flags}g`)\n for (const m of value.matchAll(g)) {\n if (m[0].length > 0 && validate(m[0])) return `[REDACTED:${kind}]`\n }\n }\n return value\n}\n\n/**\n * Replace only the PII substrings in `text`, preserving everything around them\n * (the `mask-spans` string mode). Built on {@link detectSpans} so matching,\n * non-overlap, and `validate` predicates behave identically to the reversible\n * path. Each span becomes `[REDACTED:<kind>]`.\n */\nexport function maskSpans(\n text: string,\n patterns: readonly RedactionPattern[] = DEFAULT_REDACTION_PATTERNS,\n): string {\n const spans = detectSpans(text, patterns)\n if (spans.length === 0) return text\n let out = ''\n let pos = 0\n for (const s of spans) {\n if (s.start > pos) out += text.slice(pos, s.start)\n out += `[REDACTED:${s.kind}]`\n pos = s.end\n }\n if (pos < text.length) out += text.slice(pos)\n return out\n}\n\nconst ERROR_SECRET_PATTERNS: readonly RedactionPattern[] = [\n ...DEFAULT_REDACTION_PATTERNS,\n { kind: 'bearer', pattern: /Bearer\\s+[^\\s]+/i },\n { kind: 'credential', pattern: /\\b(?:sk|pk|tc|ghp|xoxb)[_-][A-Za-z0-9_-]{8,}\\b/i },\n {\n kind: 'credential',\n pattern: /\\b(?:access[_-]?key[_-]?id|secret[_-]?access[_-]?key|session[_-]?token|security[_-]?token|x-amz-(?:credential|signature|security-token)|aws[_-]?access[_-]?key[_-]?id|aws[_-]?secret[_-]?access[_-]?key|aws[_-]?session[_-]?token|api[_-]?key|client[_-]?secret|credential|token|secret|password|signature|sig|authorization)\\s*[:=]\\s*[^\\s,;&]+/i,\n },\n]\n\n/** Sanitize an untrusted backend error for server logs. This is deliberately\n * separate from public response text: callers should return an opaque message\n * and log this bounded, redacted value for operators. */\nfunction safeString(value: unknown): string | undefined {\n try {\n return typeof value === 'string' ? value : String(value)\n } catch {\n return undefined\n }\n}\n\nfunction safeErrorText(input: unknown): string {\n if (input !== null && (typeof input === 'object' || typeof input === 'function')) {\n try {\n const message = Reflect.get(input, 'message')\n const messageText = safeString(message)\n if (messageText !== undefined) return messageText\n } catch {\n // Hostile getters and proxies are still untrusted error values.\n }\n }\n return safeString(input) ?? ''\n}\n\nexport function redactErrorMessage(input: unknown, fallback = 'unknown error'): string {\n const fallbackText = safeString(fallback)?.trim() || 'unknown error'\n const raw = safeErrorText(input)\n const message = raw.trim() || fallbackText\n try {\n const redacted = maskSpans(message, ERROR_SECRET_PATTERNS).trim()\n return redacted.length > 240 ? `${redacted.slice(0, 240)}…` : redacted || fallbackText\n } catch {\n return fallbackText\n }\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (value === null || typeof value !== 'object') return false\n const proto = Object.getPrototypeOf(value)\n return proto === Object.prototype || proto === null\n}\n\n/**\n * One-way PII scrub for telemetry/ingestion. Backward-compatible: called with no\n * options it behaves exactly as before (SSN/EIN strings + sensitive object keys\n * → sentinels). `extraPatterns` lets a product add matchers (e.g. credit-card)\n * without forking this module.\n */\nexport function redactForIngestion(value: unknown, options: RedactForIngestionOptions = {}): unknown {\n const patterns = options.extraPatterns\n ? [...DEFAULT_REDACTION_PATTERNS, ...options.extraPatterns]\n : DEFAULT_REDACTION_PATTERNS\n const sensitiveKeys = options.extraSensitiveKeys\n ? new Set([...SENSITIVE_KEYS, ...options.extraSensitiveKeys.map((k) => k.toLowerCase())])\n : SENSITIVE_KEYS\n const maskString =\n options.stringMode === 'mask-spans'\n ? (s: string) => maskSpans(s, patterns)\n : (s: string) => redactString(s, patterns)\n // Cycle guard: a payload with a circular reference would otherwise recurse\n // forever. On re-encountering an object/array, return it untouched to break\n // the cycle (the same value was already redacted on its first visit).\n const seen = new WeakSet<object>()\n const walk = (v: unknown): unknown => {\n if (typeof v === 'string') return maskString(v)\n if (Array.isArray(v)) {\n if (seen.has(v)) return v\n seen.add(v)\n return v.map(walk)\n }\n if (isPlainObject(v)) {\n if (seen.has(v)) return v\n seen.add(v)\n const out: Record<string, unknown> = {}\n for (const [k, val] of Object.entries(v)) {\n out[k] = sensitiveKeys.has(k.toLowerCase()) ? '[REDACTED:field]' : walk(val)\n }\n return out\n }\n return v\n }\n return walk(value)\n}\n\n// ── Reversible document redaction (the UI path) ─────────────────────────────\n\n/** A detected PII span in a source string. */\nexport interface RedactionSpan {\n /** Stable within a document (index-derived) — used for reveal + audit. */\n id: string\n kind: string\n start: number\n end: number\n text: string\n}\n\n/**\n * Find non-overlapping PII spans in `text`. Matches every pattern, sorts by\n * position, and drops overlaps (first match wins). Deterministic — no ids that\n * vary per call.\n */\nexport function detectSpans(\n text: string,\n patterns: readonly RedactionPattern[] = DEFAULT_REDACTION_PATTERNS,\n): RedactionSpan[] {\n const raw: Array<{ kind: string; start: number; end: number; text: string }> = []\n for (const { kind, pattern, validate } of patterns) {\n const g = new RegExp(pattern.source, pattern.flags.includes('g') ? pattern.flags : `${pattern.flags}g`)\n for (const m of text.matchAll(g)) {\n if (m.index === undefined || m[0].length === 0) continue\n if (validate && !validate(m[0])) continue\n raw.push({ kind, start: m.index, end: m.index + m[0].length, text: m[0] })\n }\n }\n raw.sort((a, b) => a.start - b.start || b.end - a.end)\n const spans: RedactionSpan[] = []\n let cursor = -1\n let i = 0\n for (const s of raw) {\n if (s.start < cursor) continue // overlaps an earlier (higher-priority) span\n spans.push({ id: `span-${i++}`, ...s })\n cursor = s.end\n }\n return spans\n}\n\n/** A redacted document segment: literal text, or a masked span with the\n * original kept ENCRYPTED for an authorized reveal. */\nexport type RedactedDocSegment =\n | { type: 'text'; text: string }\n | { type: 'redacted'; id: string; kind: string; cipher: string }\n\n/** Define a document composed of multiple redacted content segments */\nexport interface RedactedDocument {\n segments: RedactedDocSegment[]\n}\n\n/** Define options to encrypt text and specify patterns for redacting sensitive document content */\nexport interface BuildRedactedDocumentOptions {\n /** Encrypt one original span value. Wire it to `agent-app/crypto`\n * (`encryptWithKey` / `createFieldCrypto`). The cipher is what's stored. */\n encrypt: (plaintext: string) => string | Promise<string>\n /** Patterns to detect (default: {@link DEFAULT_REDACTION_PATTERNS}). */\n patterns?: readonly RedactionPattern[]\n}\n\n/**\n * Split `text` into text + redacted segments, encrypting each redacted span's\n * original. The result carries NO plaintext PII — only the masked structure and\n * ciphertext — so it is safe to ship to a client; reveal happens server-side via\n * {@link revealSpan}.\n */\nexport async function buildRedactedDocument(\n text: string,\n options: BuildRedactedDocumentOptions,\n): Promise<RedactedDocument> {\n const spans = detectSpans(text, options.patterns)\n const segments: RedactedDocSegment[] = []\n let pos = 0\n for (const span of spans) {\n if (span.start > pos) segments.push({ type: 'text', text: text.slice(pos, span.start) })\n segments.push({ type: 'redacted', id: span.id, kind: span.kind, cipher: await options.encrypt(span.text) })\n pos = span.end\n }\n if (pos < text.length) segments.push({ type: 'text', text: text.slice(pos) })\n return { segments }\n}\n\n/** Define options to decrypt, authorize, and audit the reveal of a span segment */\nexport interface RevealSpanOptions {\n /** Decrypt a span cipher. Wire to `agent-app/crypto` (`decryptWithKey`). */\n decrypt: (cipher: string) => string | Promise<string>\n /** Authorization gate — return false to deny the reveal (fail-closed). */\n canReveal: (segment: { id: string; kind: string }) => boolean | Promise<boolean>\n /** Audit hook — invoked only on a granted reveal (the caller records who/when). */\n onReveal?: (segment: { id: string; kind: string }) => void | Promise<void>\n}\n\n/** Describe the outcome of a reveal operation including success status, value, and failure reason */\nexport interface RevealResult {\n ok: boolean\n value?: string\n /** `not_found` | `forbidden` when `ok` is false. */\n reason?: string\n}\n\n/**\n * Reveal one redacted span's original, gated + audited. Fail-closed: an unknown\n * id or a denied `canReveal` returns `{ ok: false }` and never decrypts; a\n * granted reveal decrypts, fires `onReveal` for the audit trail, and returns the\n * value.\n */\nexport async function revealSpan(\n doc: RedactedDocument,\n spanId: string,\n options: RevealSpanOptions,\n): Promise<RevealResult> {\n const seg = doc.segments.find((s): s is Extract<RedactedDocSegment, { type: 'redacted' }> =>\n s.type === 'redacted' && s.id === spanId,\n )\n if (!seg) return { ok: false, reason: 'not_found' }\n const allowed = await options.canReveal({ id: seg.id, kind: seg.kind })\n if (!allowed) return { ok: false, reason: 'forbidden' }\n const value = await options.decrypt(seg.cipher)\n if (options.onReveal) await options.onReveal({ id: seg.id, kind: seg.kind })\n return { ok: true, value }\n}\n"],"mappings":"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","names":[]}
|