agent-inspect 6.31.3 → 6.31.5

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 (60) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +1 -1
  3. package/SECURITY.md +2 -0
  4. package/docs/GETTING-STARTED.md +2 -0
  5. package/docs/SAFE-TRACE-SHARING.md +1 -0
  6. package/package.json +1 -1
  7. package/packages/cli/dist/{chunk-SJ6FRNOE.mjs → chunk-LKEPABND.mjs} +86 -80
  8. package/packages/cli/dist/chunk-LKEPABND.mjs.map +1 -0
  9. package/packages/cli/dist/index.cjs +449 -91
  10. package/packages/cli/dist/index.cjs.map +1 -1
  11. package/packages/cli/dist/index.mjs +357 -14
  12. package/packages/cli/dist/index.mjs.map +1 -1
  13. package/packages/cli/dist/{src-YNEJOPRA.mjs → src-4WYF5RFI.mjs} +3 -3
  14. package/packages/cli/dist/{src-YNEJOPRA.mjs.map → src-4WYF5RFI.mjs.map} +1 -1
  15. package/packages/core/dist/advanced.cjs +97 -30
  16. package/packages/core/dist/advanced.cjs.map +1 -1
  17. package/packages/core/dist/advanced.mjs +13 -13
  18. package/packages/core/dist/checks.cjs +51 -49
  19. package/packages/core/dist/checks.cjs.map +1 -1
  20. package/packages/core/dist/checks.mjs +5 -5
  21. package/packages/core/dist/{chunk-PBNBQRXV.mjs → chunk-2KJR6X4V.mjs} +9 -9
  22. package/packages/core/dist/chunk-2KJR6X4V.mjs.map +1 -0
  23. package/packages/core/dist/{chunk-26YJSETN.mjs → chunk-7DHSWDKB.mjs} +19 -6
  24. package/packages/core/dist/chunk-7DHSWDKB.mjs.map +1 -0
  25. package/packages/core/dist/{chunk-BGF5JDJN.mjs → chunk-INE5IWLN.mjs} +3 -3
  26. package/packages/core/dist/{chunk-BGF5JDJN.mjs.map → chunk-INE5IWLN.mjs.map} +1 -1
  27. package/packages/core/dist/{chunk-ZFJ2ICTC.mjs → chunk-MTMTXTJL.mjs} +5 -5
  28. package/packages/core/dist/{chunk-ZFJ2ICTC.mjs.map → chunk-MTMTXTJL.mjs.map} +1 -1
  29. package/packages/core/dist/{chunk-KWBO36PH.mjs → chunk-NY4ILMXK.mjs} +4 -4
  30. package/packages/core/dist/{chunk-KWBO36PH.mjs.map → chunk-NY4ILMXK.mjs.map} +1 -1
  31. package/packages/core/dist/{chunk-WN5PT4GV.mjs → chunk-OSGNGRHF.mjs} +3 -3
  32. package/packages/core/dist/{chunk-WN5PT4GV.mjs.map → chunk-OSGNGRHF.mjs.map} +1 -1
  33. package/packages/core/dist/{chunk-CBPWLHNQ.mjs → chunk-PKDBFBJ7.mjs} +5 -76
  34. package/packages/core/dist/chunk-PKDBFBJ7.mjs.map +1 -0
  35. package/packages/core/dist/{chunk-VU6O5QAH.mjs → chunk-VLVCLAPB.mjs} +83 -5
  36. package/packages/core/dist/chunk-VLVCLAPB.mjs.map +1 -0
  37. package/packages/core/dist/exporters.cjs +242 -6
  38. package/packages/core/dist/exporters.cjs.map +1 -1
  39. package/packages/core/dist/exporters.mjs +143 -9
  40. package/packages/core/dist/exporters.mjs.map +1 -1
  41. package/packages/core/dist/index.cjs +97 -8
  42. package/packages/core/dist/index.cjs.map +1 -1
  43. package/packages/core/dist/index.mjs +6 -6
  44. package/packages/core/dist/logs.cjs +80 -2
  45. package/packages/core/dist/logs.cjs.map +1 -1
  46. package/packages/core/dist/logs.d.cts +2 -0
  47. package/packages/core/dist/logs.d.ts +2 -0
  48. package/packages/core/dist/logs.mjs +2 -2
  49. package/packages/core/dist/readers.cjs +2 -0
  50. package/packages/core/dist/readers.cjs.map +1 -1
  51. package/packages/core/dist/readers.mjs +2 -2
  52. package/packages/core/dist/writers.cjs +96 -8
  53. package/packages/core/dist/writers.cjs.map +1 -1
  54. package/packages/core/dist/writers.mjs +7 -8
  55. package/packages/core/dist/writers.mjs.map +1 -1
  56. package/packages/cli/dist/chunk-SJ6FRNOE.mjs.map +0 -1
  57. package/packages/core/dist/chunk-26YJSETN.mjs.map +0 -1
  58. package/packages/core/dist/chunk-CBPWLHNQ.mjs.map +0 -1
  59. package/packages/core/dist/chunk-PBNBQRXV.mjs.map +0 -1
  60. package/packages/core/dist/chunk-VU6O5QAH.mjs.map +0 -1
@@ -1,5 +1,79 @@
1
1
  import crypto from 'crypto';
2
2
 
3
+ // packages/core/src/logs/redactor.ts
4
+
5
+ // packages/core/src/safety/sensitive-key.ts
6
+ function keyHasExplicitSeparator(value) {
7
+ return /[_\-.]/.test(value);
8
+ }
9
+ function normalizeSensitiveKey(value) {
10
+ return value.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
11
+ }
12
+ var NON_CREDENTIAL_TOKEN_CONFIG_KEYS = new Set(
13
+ [
14
+ "tokens",
15
+ "max_tokens",
16
+ "min_tokens",
17
+ "ls_max_tokens",
18
+ "token_count",
19
+ "token_limit",
20
+ "token_budget",
21
+ "input_tokens",
22
+ "output_tokens",
23
+ "total_tokens",
24
+ "cached_tokens",
25
+ "prompt_tokens",
26
+ "completion_tokens"
27
+ ].map(normalizeSensitiveKey)
28
+ );
29
+ var DEFAULT_CREDENTIAL_SENSITIVE_KEYS = [
30
+ "authorization",
31
+ "cookie",
32
+ "token",
33
+ "access_token",
34
+ "accesstoken",
35
+ "auth_token",
36
+ "authtoken",
37
+ "refresh_token",
38
+ "refreshtoken",
39
+ "id_token",
40
+ "idtoken",
41
+ "bearer_token",
42
+ "bearertoken",
43
+ "api_token",
44
+ "apitoken",
45
+ "apikey",
46
+ "api_key",
47
+ "password",
48
+ "secret",
49
+ "email"
50
+ ];
51
+ function isTokenCredentialKey(normalized) {
52
+ if (NON_CREDENTIAL_TOKEN_CONFIG_KEYS.has(normalized)) return false;
53
+ if (normalized === "token") return true;
54
+ if (normalized.endsWith("tokens")) return false;
55
+ return normalized.endsWith("token");
56
+ }
57
+ function isCredentialSensitiveKey(key, sensitiveKeys = DEFAULT_CREDENTIAL_SENSITIVE_KEYS) {
58
+ if (!key) return false;
59
+ const normalized = normalizeSensitiveKey(key);
60
+ if (!normalized) return false;
61
+ if (NON_CREDENTIAL_TOKEN_CONFIG_KEYS.has(normalized)) return false;
62
+ const allowPrefixCompound = keyHasExplicitSeparator(key);
63
+ for (const sensitive of sensitiveKeys) {
64
+ const s = normalizeSensitiveKey(sensitive);
65
+ if (!s) continue;
66
+ if (s === "token") {
67
+ if (isTokenCredentialKey(normalized)) return true;
68
+ continue;
69
+ }
70
+ if (normalized === s) return true;
71
+ if (normalized.endsWith(`_${s}`)) return true;
72
+ if (allowPrefixCompound && normalized.startsWith(`${s}_`)) return true;
73
+ }
74
+ return false;
75
+ }
76
+
3
77
  // packages/core/src/logs/redactor.ts
4
78
  var DEFAULT_REDACT_KEYS = [
5
79
  "authorization",
@@ -24,7 +98,7 @@ function compileRules(rules, extraKeys) {
24
98
  const out = /* @__PURE__ */ new Map();
25
99
  const set = (r) => {
26
100
  const k = toKey(r.key);
27
- out.set(k, { ...r, key: k });
101
+ out.set(k, { ...r, key: k, raw: r.raw ?? r.key });
28
102
  };
29
103
  for (const k of DEFAULT_REDACT_KEYS) {
30
104
  set({ key: k, strategy: "full" });
@@ -53,9 +127,13 @@ var Redactor = class {
53
127
  constructor(options) {
54
128
  this.#rules = compileRules(options?.rules, options?.extraKeys);
55
129
  }
130
+ /** Field names this redactor treats as sensitive, for value-level helpers. */
131
+ get sensitiveKeys() {
132
+ return this.#rules.map((rule) => rule.raw ?? rule.key);
133
+ }
56
134
  redactValue(key, value) {
57
135
  const k = toKey(key);
58
- const rule = this.#rules.find((r) => r.key === k);
136
+ const rule = this.#rules.find((r) => r.key === k) ?? this.#rules.find((r) => isCredentialSensitiveKey(key, [r.raw ?? r.key]));
59
137
  if (!rule) {
60
138
  return this.#redactNested(value);
61
139
  }
@@ -94,6 +172,6 @@ var Redactor = class {
94
172
  }
95
173
  };
96
174
 
97
- export { DEFAULT_REDACT_KEYS, Redactor };
98
- //# sourceMappingURL=chunk-VU6O5QAH.mjs.map
99
- //# sourceMappingURL=chunk-VU6O5QAH.mjs.map
175
+ export { DEFAULT_CREDENTIAL_SENSITIVE_KEYS, DEFAULT_REDACT_KEYS, Redactor, isCredentialSensitiveKey };
176
+ //# sourceMappingURL=chunk-VLVCLAPB.mjs.map
177
+ //# sourceMappingURL=chunk-VLVCLAPB.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/safety/sensitive-key.ts","../src/logs/redactor.ts"],"names":[],"mappings":"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","file":"chunk-VLVCLAPB.mjs","sourcesContent":["/**\n * Contextual sensitive-key classification for safety checks and redaction.\n *\n * Avoids treating model token-configuration fields (ls_max_tokens, max_tokens,\n * token_count, …) as credentials solely because they contain the substring\n * \"token\". Value-based secret detectors remain separate.\n *\n * @experimental Shared classifier used by checks (6.14.2+); parity across\n * surfaces lands in the same patch train.\n */\n\n/** True when the original key already uses an explicit separator. */\nfunction keyHasExplicitSeparator(value: string): boolean {\n return /[_\\-.]/.test(value);\n}\n\n/**\n * Canonicalize field names so camelCase / kebab / dot / snake forms share a\n * snake_case shape the compound matcher can recognize (`userPassword` →\n * `user_password`).\n */\nexport function normalizeSensitiveKey(value: string): string {\n return value\n .replace(/([a-z0-9])([A-Z])/g, \"$1_$2\")\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"_\")\n .replace(/^_+|_+$/g, \"\");\n}\n\n/** Model / usage configuration keys that are not credentials by key alone. */\nexport const NON_CREDENTIAL_TOKEN_CONFIG_KEYS: ReadonlySet<string> = new Set(\n [\n \"tokens\",\n \"max_tokens\",\n \"min_tokens\",\n \"ls_max_tokens\",\n \"token_count\",\n \"token_limit\",\n \"token_budget\",\n \"input_tokens\",\n \"output_tokens\",\n \"total_tokens\",\n \"cached_tokens\",\n \"prompt_tokens\",\n \"completion_tokens\",\n ].map(normalizeSensitiveKey),\n);\n\nexport const DEFAULT_CREDENTIAL_SENSITIVE_KEYS = [\n \"authorization\",\n \"cookie\",\n \"token\",\n \"access_token\",\n \"accesstoken\",\n \"auth_token\",\n \"authtoken\",\n \"refresh_token\",\n \"refreshtoken\",\n \"id_token\",\n \"idtoken\",\n \"bearer_token\",\n \"bearertoken\",\n \"api_token\",\n \"apitoken\",\n \"apikey\",\n \"api_key\",\n \"password\",\n \"secret\",\n \"email\",\n] as const;\n\nfunction isTokenCredentialKey(normalized: string): boolean {\n if (NON_CREDENTIAL_TOKEN_CONFIG_KEYS.has(normalized)) return false;\n if (normalized === \"token\") return true;\n // access_token / refreshToken → *token, but not *tokens (max_tokens → max_tokens)\n if (normalized.endsWith(\"tokens\")) return false;\n return normalized.endsWith(\"token\");\n}\n\n/**\n * True when the field name looks credential-sensitive under the shared policy.\n *\n * Suffix compounds (`user_password`, `userPassword`) always match. Prefix\n * compounds (`email_note`) match only when the original key already contained\n * an explicit separator, so camelCase topic fields like `emailNote` stay for\n * value detectors instead of key redaction.\n */\nexport function isCredentialSensitiveKey(\n key: string | undefined,\n sensitiveKeys: readonly string[] = DEFAULT_CREDENTIAL_SENSITIVE_KEYS,\n): boolean {\n if (!key) return false;\n const normalized = normalizeSensitiveKey(key);\n if (!normalized) return false;\n if (NON_CREDENTIAL_TOKEN_CONFIG_KEYS.has(normalized)) return false;\n\n const allowPrefixCompound = keyHasExplicitSeparator(key);\n\n for (const sensitive of sensitiveKeys) {\n const s = normalizeSensitiveKey(sensitive);\n if (!s) continue;\n if (s === \"token\") {\n if (isTokenCredentialKey(normalized)) return true;\n continue;\n }\n if (normalized === s) return true;\n // Compound keys with explicit separators, plus camelCase owner+secret suffixes.\n if (normalized.endsWith(`_${s}`)) return true;\n if (allowPrefixCompound && normalized.startsWith(`${s}_`)) return true;\n }\n return false;\n}\n","import crypto from \"node:crypto\";\n\nimport { isCredentialSensitiveKey } from \"../safety/sensitive-key.js\";\n\nimport type { RedactionRule } from \"../types/log-config.js\";\n\nexport const DEFAULT_REDACT_KEYS = [\n \"authorization\",\n \"cookie\",\n \"token\",\n \"apiKey\",\n \"password\",\n \"secret\",\n \"email\",\n] as const;\n\nexport interface RedactorOptions {\n rules?: RedactionRule[];\n /** Additional exact keys (case-insensitive) to redact in addition to defaults. */\n extraKeys?: readonly string[];\n}\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n\nfunction toKey(s: string): string {\n return s.toLowerCase();\n}\n\nfunction stableHash(value: string): string {\n const h = crypto.createHash(\"sha256\").update(value, \"utf8\").digest(\"hex\");\n return h.slice(0, 8);\n}\n\ntype CompiledRule = (\n | { key: string; strategy: \"full\" }\n | { key: string; strategy: \"prefix\"; keep: number }\n | { key: string; strategy: \"hash\" }\n) & { raw?: string };\n\nfunction compileRules(\n rules?: RedactionRule[],\n extraKeys?: readonly string[],\n): CompiledRule[] {\n const out = new Map<string, CompiledRule>();\n\n const set = (r: CompiledRule) => {\n const k = toKey(r.key);\n out.set(k, { ...r, key: k, raw: r.raw ?? r.key } as CompiledRule);\n };\n\n for (const k of DEFAULT_REDACT_KEYS) {\n set({ key: k, strategy: \"full\" });\n }\n\n for (const k of extraKeys ?? []) {\n if (typeof k === \"string\" && k.length > 0) {\n set({ key: k, strategy: \"full\" });\n }\n }\n\n for (const r of rules ?? []) {\n if (typeof r === \"string\") {\n set({ key: r, strategy: \"full\" });\n continue;\n }\n const key = r.key;\n if (r.strategy === \"full\") set({ key, strategy: \"full\" });\n if (r.strategy === \"hash\") set({ key, strategy: \"hash\" });\n if (r.strategy === \"prefix\") {\n set({ key, strategy: \"prefix\", keep: typeof r.keep === \"number\" ? r.keep : 8 });\n }\n }\n\n return [...out.values()];\n}\n\nexport class Redactor {\n readonly #rules: CompiledRule[];\n\n constructor(options?: RedactorOptions) {\n this.#rules = compileRules(options?.rules, options?.extraKeys);\n }\n\n /** Field names this redactor treats as sensitive, for value-level helpers. */\n get sensitiveKeys(): readonly string[] {\n return this.#rules.map((rule) => rule.raw ?? rule.key);\n }\n\n redactValue(key: string, value: unknown): unknown {\n const k = toKey(key);\n const rule =\n this.#rules.find((r) => r.key === k) ??\n this.#rules.find((r) => isCredentialSensitiveKey(key, [r.raw ?? r.key]));\n if (!rule) {\n return this.#redactNested(value);\n }\n\n if (rule.strategy === \"full\") return \"[REDACTED]\";\n const asString =\n typeof value === \"string\"\n ? value\n : typeof value === \"number\" || typeof value === \"boolean\" || typeof value === \"bigint\"\n ? String(value)\n : undefined;\n\n if (rule.strategy === \"prefix\") {\n if (asString === undefined) return \"[REDACTED]\";\n const keep = Math.max(0, Math.floor(rule.keep));\n return asString.length <= keep ? `${asString}…` : `${asString.slice(0, keep)}…`;\n }\n\n if (rule.strategy === \"hash\") {\n if (asString === undefined) return \"[HASH:unknown]\";\n return `[HASH:${stableHash(asString)}]`;\n }\n\n return this.#redactNested(value);\n }\n\n redactRecord(record: Record<string, unknown>): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(record)) {\n out[k] = this.redactValue(k, v);\n }\n return out;\n }\n\n #redactNested(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.map((v) => this.#redactNested(v));\n }\n if (isRecord(value)) {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value)) {\n out[k] = this.redactValue(k, v);\n }\n return out;\n }\n return value;\n }\n}\n\n"]}
@@ -13,6 +13,80 @@ var os__default = /*#__PURE__*/_interopDefault(os);
13
13
 
14
14
  // packages/core/src/exporters/types.ts
15
15
  var EXPORT_PAYLOAD_VERSION = "0.1.2";
16
+
17
+ // packages/core/src/safety/sensitive-key.ts
18
+ function keyHasExplicitSeparator(value) {
19
+ return /[_\-.]/.test(value);
20
+ }
21
+ function normalizeSensitiveKey(value) {
22
+ return value.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
23
+ }
24
+ var NON_CREDENTIAL_TOKEN_CONFIG_KEYS = new Set(
25
+ [
26
+ "tokens",
27
+ "max_tokens",
28
+ "min_tokens",
29
+ "ls_max_tokens",
30
+ "token_count",
31
+ "token_limit",
32
+ "token_budget",
33
+ "input_tokens",
34
+ "output_tokens",
35
+ "total_tokens",
36
+ "cached_tokens",
37
+ "prompt_tokens",
38
+ "completion_tokens"
39
+ ].map(normalizeSensitiveKey)
40
+ );
41
+ var DEFAULT_CREDENTIAL_SENSITIVE_KEYS = [
42
+ "authorization",
43
+ "cookie",
44
+ "token",
45
+ "access_token",
46
+ "accesstoken",
47
+ "auth_token",
48
+ "authtoken",
49
+ "refresh_token",
50
+ "refreshtoken",
51
+ "id_token",
52
+ "idtoken",
53
+ "bearer_token",
54
+ "bearertoken",
55
+ "api_token",
56
+ "apitoken",
57
+ "apikey",
58
+ "api_key",
59
+ "password",
60
+ "secret",
61
+ "email"
62
+ ];
63
+ function isTokenCredentialKey(normalized) {
64
+ if (NON_CREDENTIAL_TOKEN_CONFIG_KEYS.has(normalized)) return false;
65
+ if (normalized === "token") return true;
66
+ if (normalized.endsWith("tokens")) return false;
67
+ return normalized.endsWith("token");
68
+ }
69
+ function isCredentialSensitiveKey(key, sensitiveKeys = DEFAULT_CREDENTIAL_SENSITIVE_KEYS) {
70
+ if (!key) return false;
71
+ const normalized = normalizeSensitiveKey(key);
72
+ if (!normalized) return false;
73
+ if (NON_CREDENTIAL_TOKEN_CONFIG_KEYS.has(normalized)) return false;
74
+ const allowPrefixCompound = keyHasExplicitSeparator(key);
75
+ for (const sensitive of sensitiveKeys) {
76
+ const s = normalizeSensitiveKey(sensitive);
77
+ if (!s) continue;
78
+ if (s === "token") {
79
+ if (isTokenCredentialKey(normalized)) return true;
80
+ continue;
81
+ }
82
+ if (normalized === s) return true;
83
+ if (normalized.endsWith(`_${s}`)) return true;
84
+ if (allowPrefixCompound && normalized.startsWith(`${s}_`)) return true;
85
+ }
86
+ return false;
87
+ }
88
+
89
+ // packages/core/src/logs/redactor.ts
16
90
  var DEFAULT_REDACT_KEYS = [
17
91
  "authorization",
18
92
  "cookie",
@@ -36,7 +110,7 @@ function compileRules(rules, extraKeys) {
36
110
  const out = /* @__PURE__ */ new Map();
37
111
  const set = (r) => {
38
112
  const k = toKey(r.key);
39
- out.set(k, { ...r, key: k });
113
+ out.set(k, { ...r, key: k, raw: r.raw ?? r.key });
40
114
  };
41
115
  for (const k of DEFAULT_REDACT_KEYS) {
42
116
  set({ key: k, strategy: "full" });
@@ -65,9 +139,13 @@ var Redactor = class {
65
139
  constructor(options) {
66
140
  this.#rules = compileRules(options?.rules, options?.extraKeys);
67
141
  }
142
+ /** Field names this redactor treats as sensitive, for value-level helpers. */
143
+ get sensitiveKeys() {
144
+ return this.#rules.map((rule) => rule.raw ?? rule.key);
145
+ }
68
146
  redactValue(key, value) {
69
147
  const k = toKey(key);
70
- const rule = this.#rules.find((r) => r.key === k);
148
+ const rule = this.#rules.find((r) => r.key === k) ?? this.#rules.find((r) => isCredentialSensitiveKey(key, [r.raw ?? r.key]));
71
149
  if (!rule) {
72
150
  return this.#redactNested(value);
73
151
  }
@@ -106,6 +184,148 @@ var Redactor = class {
106
184
  }
107
185
  };
108
186
 
187
+ // packages/core/src/safety/credential-value-patterns.ts
188
+ var KEY_VALUE_SECRET_PATTERN = /\b(?:api[_-]?key|internal[_-]?token|access[_-]?token|auth[_-]?token|password|secret|token)=(?!\[(?:REDACTED(?::[^\]]*)?|HASH:[0-9a-f]{8})\](?![^\s"'\\&#?;/]))([^\s"'\\]{8,})/i;
189
+ var HIGH_CONFIDENCE_CREDENTIAL_PATTERNS = [
190
+ /^(?:basic|bearer|digest|apikey)\s+[a-z0-9._~+/=-]+$/i,
191
+ /\bbearer\s+[a-z0-9._~+/=-]{12,}\b/i,
192
+ /\b[a-z0-9_.-]+=[^;\s]+(?:;\s*[a-z0-9_.-]+=[^;\s]+)+/i,
193
+ /\beyJ[a-zA-Z0-9_-]{8,}\.[a-zA-Z0-9_-]{8,}\.[a-zA-Z0-9_-]{8,}\b/,
194
+ /\b(?:sk-(?:proj-)?[a-zA-Z0-9_-]{16,}|sk-ant-[a-zA-Z0-9_-]{16,}|AIza[0-9A-Za-z_-]{20,})\b/,
195
+ /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{20,}\b/,
196
+ /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/,
197
+ /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*-----END [A-Z ]*PRIVATE KEY-----/,
198
+ KEY_VALUE_SECRET_PATTERN
199
+ ];
200
+ function stringContainsHighConfidenceCredential(value) {
201
+ for (const pattern of HIGH_CONFIDENCE_CREDENTIAL_PATTERNS) {
202
+ pattern.lastIndex = 0;
203
+ if (pattern.test(value)) {
204
+ pattern.lastIndex = 0;
205
+ return true;
206
+ }
207
+ }
208
+ return false;
209
+ }
210
+
211
+ // packages/core/src/safety/url-redaction.ts
212
+ var HTTP_URL = /^https?:\/\/[^\s]+$/i;
213
+ var CONNECTION_PROTOCOLS = /* @__PURE__ */ new Set([
214
+ "postgres:",
215
+ "postgresql:",
216
+ "mysql:",
217
+ "mariadb:",
218
+ "mongodb:",
219
+ "mongodb+srv:",
220
+ "redis:",
221
+ "rediss:",
222
+ "amqp:",
223
+ "amqps:"
224
+ ]);
225
+ var CONNECTION_URI = /^(?:postgres|postgresql|mysql|mariadb|mongodb(?:\+srv)?|rediss?|amqps?):\/\/[^\s]+$/i;
226
+ var UUID_SEGMENT = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
227
+ var HEX_SEGMENT = /^[0-9a-f]{12,}$/i;
228
+ var NUMERIC_SEGMENT = /^\d{5,}$/;
229
+ var OPAQUE_SEGMENT = /^[A-Za-z0-9_-]{20,}$/;
230
+ function looksLikeHttpUrl(value) {
231
+ return HTTP_URL.test(value.trim());
232
+ }
233
+ function looksLikeConnectionUri(value) {
234
+ return CONNECTION_URI.test(value.trim());
235
+ }
236
+ function looksLikeUrlForRedaction(value) {
237
+ return looksLikeHttpUrl(value) || looksLikeConnectionUri(value);
238
+ }
239
+ function parseRedactableUrl(value) {
240
+ try {
241
+ const url = new URL(value.trim());
242
+ if (url.protocol === "http:" || url.protocol === "https:") return url;
243
+ if (CONNECTION_PROTOCOLS.has(url.protocol)) return url;
244
+ return void 0;
245
+ } catch {
246
+ return void 0;
247
+ }
248
+ }
249
+ function isHttpProtocol(protocol) {
250
+ return protocol === "http:" || protocol === "https:";
251
+ }
252
+ function isIdentifierSegment(segment) {
253
+ if (UUID_SEGMENT.test(segment)) return true;
254
+ if (HEX_SEGMENT.test(segment)) return true;
255
+ if (NUMERIC_SEGMENT.test(segment)) return true;
256
+ return OPAQUE_SEGMENT.test(segment) && /\d/.test(segment) && /[A-Za-z]/.test(segment);
257
+ }
258
+ function decodeParam(value) {
259
+ try {
260
+ return decodeURIComponent(value.replace(/\+/g, " "));
261
+ } catch {
262
+ return value;
263
+ }
264
+ }
265
+ function redactParams(raw, options, replacement) {
266
+ let redacted = false;
267
+ const parts = raw.split("&").map((part) => {
268
+ if (part.length === 0) return part;
269
+ const separator = part.indexOf("=");
270
+ if (separator < 0) return part;
271
+ const rawKey = part.slice(0, separator);
272
+ const rawValue = part.slice(separator + 1);
273
+ const key = decodeParam(rawKey);
274
+ const value = decodeParam(rawValue);
275
+ if (isCredentialSensitiveKey(key, options.sensitiveKeys) || options.isSensitiveValue(value)) {
276
+ redacted = true;
277
+ return `${rawKey}=${replacement}`;
278
+ }
279
+ return part;
280
+ });
281
+ return { value: parts.join("&"), redacted };
282
+ }
283
+ function redactUrlString(value, options) {
284
+ const unchanged = {
285
+ value,
286
+ credentialRedacted: false,
287
+ identifiersMasked: false
288
+ };
289
+ if (!looksLikeUrlForRedaction(value)) return unchanged;
290
+ const url = parseRedactableUrl(value);
291
+ if (!url) return unchanged;
292
+ const replacement = options.replacement ?? "[REDACTED]";
293
+ const idPlaceholder = options.idPlaceholder ?? "[id]";
294
+ const http = isHttpProtocol(url.protocol);
295
+ let credentialRedacted = url.username.length > 0 || url.password.length > 0;
296
+ let identifiersMasked = false;
297
+ let pathname = url.pathname;
298
+ if (http && options.maskPathIds === true) {
299
+ pathname = url.pathname.split("/").map((segment) => {
300
+ if (segment.length === 0 || !isIdentifierSegment(decodeParam(segment))) {
301
+ return segment;
302
+ }
303
+ identifiersMasked = true;
304
+ return idPlaceholder;
305
+ }).join("/");
306
+ }
307
+ let search = "";
308
+ if (url.search.length > 1) {
309
+ const params = redactParams(url.search.slice(1), options, replacement);
310
+ if (params.redacted) credentialRedacted = true;
311
+ search = `?${params.value}`;
312
+ } else if (url.search.length === 1) {
313
+ search = "?";
314
+ }
315
+ let hash = url.hash;
316
+ if (url.hash.includes("=")) {
317
+ const params = redactParams(url.hash.slice(1), options, replacement);
318
+ if (params.redacted) credentialRedacted = true;
319
+ hash = `#${params.value}`;
320
+ }
321
+ if (!credentialRedacted && !identifiersMasked) return unchanged;
322
+ return {
323
+ value: `${url.protocol}//${url.host}${pathname}${search}${hash}`,
324
+ credentialRedacted,
325
+ identifiersMasked
326
+ };
327
+ }
328
+
109
329
  // packages/core/src/redaction-profiles.ts
110
330
  var SHARE_PROFILE_EXTRA_KEYS = [
111
331
  "userEmail",
@@ -208,21 +428,34 @@ function deepClone(value) {
208
428
  }
209
429
  return out;
210
430
  }
211
- function boundAttributeValues(record, maxMetadataValueLength, maxPreviewLength, seen, depth) {
431
+ function boundAttributeValues(record, sensitiveKeys, maxMetadataValueLength, maxPreviewLength, seen, depth) {
212
432
  if (depth > 32) {
213
433
  return { truncated: true, reason: "maxDepth" };
214
434
  }
215
435
  const out = {};
216
436
  for (const [key, value] of Object.entries(record)) {
217
- out[key] = boundValue(value, key, maxMetadataValueLength, maxPreviewLength, seen, depth);
437
+ out[key] = boundValue(
438
+ value,
439
+ key,
440
+ sensitiveKeys,
441
+ maxMetadataValueLength,
442
+ maxPreviewLength,
443
+ seen,
444
+ depth
445
+ );
218
446
  }
219
447
  return out;
220
448
  }
221
- function boundValue(value, key, maxMetadataValueLength, maxPreviewLength, seen, depth) {
449
+ function boundValue(value, key, sensitiveKeys, maxMetadataValueLength, maxPreviewLength, seen, depth) {
222
450
  if (value === null || typeof value !== "object") {
223
451
  if (typeof value === "string") {
452
+ const rewritten = redactUrlString(value, {
453
+ sensitiveKeys,
454
+ isSensitiveValue: stringContainsHighConfidenceCredential,
455
+ maskPathIds: true
456
+ }).value;
224
457
  return truncateStringForProfile(
225
- value,
458
+ stringContainsHighConfidenceCredential(rewritten) ? "[REDACTED]" : rewritten,
226
459
  key,
227
460
  maxMetadataValueLength,
228
461
  maxPreviewLength
@@ -237,6 +470,7 @@ function boundValue(value, key, maxMetadataValueLength, maxPreviewLength, seen,
237
470
  (item, index) => boundValue(
238
471
  item,
239
472
  String(index),
473
+ sensitiveKeys,
240
474
  maxMetadataValueLength,
241
475
  maxPreviewLength,
242
476
  seen,
@@ -246,6 +480,7 @@ function boundValue(value, key, maxMetadataValueLength, maxPreviewLength, seen,
246
480
  }
247
481
  return boundAttributeValues(
248
482
  value,
483
+ sensitiveKeys,
249
484
  maxMetadataValueLength,
250
485
  maxPreviewLength,
251
486
  seen,
@@ -260,6 +495,7 @@ function redactEventAttributes(attrs, redactor, maxMetadataValueLength, maxPrevi
260
495
  const seen = /* @__PURE__ */ new WeakSet();
261
496
  const bounded = boundAttributeValues(
262
497
  redacted,
498
+ [...DEFAULT_REDACT_KEYS, ...redactor.sensitiveKeys],
263
499
  maxMetadataValueLength,
264
500
  maxPreviewLength,
265
501
  seen,