agent-inspect 6.31.3 → 6.31.4

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 +8 -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 +399 -91
  10. package/packages/cli/dist/index.cjs.map +1 -1
  11. package/packages/cli/dist/index.mjs +307 -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 +217 -6
  38. package/packages/core/dist/exporters.cjs.map +1 -1
  39. package/packages/core/dist/exporters.mjs +118 -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
@@ -13,6 +13,7 @@ var readline = require('readline');
13
13
  var url = require('url');
14
14
  var module$1 = require('module');
15
15
  var commander = require('commander');
16
+ var string_decoder = require('string_decoder');
16
17
  var child_process = require('child_process');
17
18
  var util = require('util');
18
19
  var http = require('http');
@@ -66,6 +67,83 @@ var init_correlation_metadata = __esm({
66
67
  ];
67
68
  }
68
69
  });
70
+
71
+ // packages/core/src/safety/sensitive-key.ts
72
+ function keyHasExplicitSeparator(value) {
73
+ return /[_\-.]/.test(value);
74
+ }
75
+ function normalizeSensitiveKey(value) {
76
+ return value.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
77
+ }
78
+ function isTokenCredentialKey(normalized) {
79
+ if (NON_CREDENTIAL_TOKEN_CONFIG_KEYS.has(normalized)) return false;
80
+ if (normalized === "token") return true;
81
+ if (normalized.endsWith("tokens")) return false;
82
+ return normalized.endsWith("token");
83
+ }
84
+ function isCredentialSensitiveKey(key, sensitiveKeys = DEFAULT_CREDENTIAL_SENSITIVE_KEYS) {
85
+ if (!key) return false;
86
+ const normalized = normalizeSensitiveKey(key);
87
+ if (!normalized) return false;
88
+ if (NON_CREDENTIAL_TOKEN_CONFIG_KEYS.has(normalized)) return false;
89
+ const allowPrefixCompound = keyHasExplicitSeparator(key);
90
+ for (const sensitive of sensitiveKeys) {
91
+ const s = normalizeSensitiveKey(sensitive);
92
+ if (!s) continue;
93
+ if (s === "token") {
94
+ if (isTokenCredentialKey(normalized)) return true;
95
+ continue;
96
+ }
97
+ if (normalized === s) return true;
98
+ if (normalized.endsWith(`_${s}`)) return true;
99
+ if (allowPrefixCompound && normalized.startsWith(`${s}_`)) return true;
100
+ }
101
+ return false;
102
+ }
103
+ var NON_CREDENTIAL_TOKEN_CONFIG_KEYS, DEFAULT_CREDENTIAL_SENSITIVE_KEYS;
104
+ var init_sensitive_key = __esm({
105
+ "packages/core/src/safety/sensitive-key.ts"() {
106
+ NON_CREDENTIAL_TOKEN_CONFIG_KEYS = new Set(
107
+ [
108
+ "tokens",
109
+ "max_tokens",
110
+ "min_tokens",
111
+ "ls_max_tokens",
112
+ "token_count",
113
+ "token_limit",
114
+ "token_budget",
115
+ "input_tokens",
116
+ "output_tokens",
117
+ "total_tokens",
118
+ "cached_tokens",
119
+ "prompt_tokens",
120
+ "completion_tokens"
121
+ ].map(normalizeSensitiveKey)
122
+ );
123
+ DEFAULT_CREDENTIAL_SENSITIVE_KEYS = [
124
+ "authorization",
125
+ "cookie",
126
+ "token",
127
+ "access_token",
128
+ "accesstoken",
129
+ "auth_token",
130
+ "authtoken",
131
+ "refresh_token",
132
+ "refreshtoken",
133
+ "id_token",
134
+ "idtoken",
135
+ "bearer_token",
136
+ "bearertoken",
137
+ "api_token",
138
+ "apitoken",
139
+ "apikey",
140
+ "api_key",
141
+ "password",
142
+ "secret",
143
+ "email"
144
+ ];
145
+ }
146
+ });
69
147
  function isRecord(v) {
70
148
  return typeof v === "object" && v !== null && !Array.isArray(v);
71
149
  }
@@ -80,7 +158,7 @@ function compileRules(rules, extraKeys) {
80
158
  const out = /* @__PURE__ */ new Map();
81
159
  const set = (r) => {
82
160
  const k = toKey(r.key);
83
- out.set(k, { ...r, key: k });
161
+ out.set(k, { ...r, key: k, raw: r.raw ?? r.key });
84
162
  };
85
163
  for (const k of DEFAULT_REDACT_KEYS) {
86
164
  set({ key: k, strategy: "full" });
@@ -107,6 +185,7 @@ function compileRules(rules, extraKeys) {
107
185
  var DEFAULT_REDACT_KEYS, Redactor;
108
186
  var init_redactor = __esm({
109
187
  "packages/core/src/logs/redactor.ts"() {
188
+ init_sensitive_key();
110
189
  DEFAULT_REDACT_KEYS = [
111
190
  "authorization",
112
191
  "cookie",
@@ -121,9 +200,13 @@ var init_redactor = __esm({
121
200
  constructor(options) {
122
201
  this.#rules = compileRules(options?.rules, options?.extraKeys);
123
202
  }
203
+ /** Field names this redactor treats as sensitive, for value-level helpers. */
204
+ get sensitiveKeys() {
205
+ return this.#rules.map((rule) => rule.raw ?? rule.key);
206
+ }
124
207
  redactValue(key, value) {
125
208
  const k = toKey(key);
126
- const rule = this.#rules.find((r) => r.key === k);
209
+ const rule = this.#rules.find((r) => r.key === k) ?? this.#rules.find((r) => isCredentialSensitiveKey(key, [r.raw ?? r.key]));
127
210
  if (!rule) {
128
211
  return this.#redactNested(value);
129
212
  }
@@ -178,7 +261,7 @@ function stringContainsHighConfidenceCredential(value) {
178
261
  var KEY_VALUE_SECRET_PATTERN, HIGH_CONFIDENCE_CREDENTIAL_PATTERNS;
179
262
  var init_credential_value_patterns = __esm({
180
263
  "packages/core/src/safety/credential-value-patterns.ts"() {
181
- KEY_VALUE_SECRET_PATTERN = /\b(?:api[_-]?key|internal[_-]?token|access[_-]?token|auth[_-]?token|password|secret|token)=([^\s"'\\]{8,})/i;
264
+ 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;
182
265
  HIGH_CONFIDENCE_CREDENTIAL_PATTERNS = [
183
266
  /^(?:basic|bearer|digest|apikey)\s+[a-z0-9._~+/=-]+$/i,
184
267
  /\bbearer\s+[a-z0-9._~+/=-]{12,}\b/i,
@@ -829,6 +912,10 @@ var init_nanoid = __esm({
829
912
  POOL_SIZE_MULTIPLIER = 128;
830
913
  }
831
914
  });
915
+ var init_trace_filesystem = __esm({
916
+ "packages/core/src/trace-filesystem.ts"() {
917
+ }
918
+ });
832
919
 
833
920
  // packages/core/src/utils/duration.ts
834
921
  function parseDuration(duration) {
@@ -979,6 +1066,7 @@ function warn(message, error) {
979
1066
  var DEFAULT_TRACE_DIR_NAME, RUNS_DIR_NAME, FALLBACK_TRACE_DIR, MAX_NAME_LENGTH;
980
1067
  var init_utils = __esm({
981
1068
  "packages/core/src/utils.ts"() {
1069
+ init_trace_filesystem();
982
1070
  init_duration();
983
1071
  DEFAULT_TRACE_DIR_NAME = ".agent-inspect";
984
1072
  RUNS_DIR_NAME = "runs";
@@ -1149,6 +1237,7 @@ var init_storage = __esm({
1149
1237
  init_types();
1150
1238
  init_trace_event_safety();
1151
1239
  init_read_trace();
1240
+ init_trace_filesystem();
1152
1241
  init_utils();
1153
1242
  }
1154
1243
  });
@@ -4662,83 +4751,6 @@ var init_programmatic = __esm({
4662
4751
  }
4663
4752
  });
4664
4753
 
4665
- // packages/core/src/safety/sensitive-key.ts
4666
- function keyHasExplicitSeparator(value) {
4667
- return /[_\-.]/.test(value);
4668
- }
4669
- function normalizeSensitiveKey(value) {
4670
- return value.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
4671
- }
4672
- function isTokenCredentialKey(normalized) {
4673
- if (NON_CREDENTIAL_TOKEN_CONFIG_KEYS.has(normalized)) return false;
4674
- if (normalized === "token") return true;
4675
- if (normalized.endsWith("tokens")) return false;
4676
- return normalized.endsWith("token");
4677
- }
4678
- function isCredentialSensitiveKey(key, sensitiveKeys = DEFAULT_CREDENTIAL_SENSITIVE_KEYS) {
4679
- if (!key) return false;
4680
- const normalized = normalizeSensitiveKey(key);
4681
- if (!normalized) return false;
4682
- if (NON_CREDENTIAL_TOKEN_CONFIG_KEYS.has(normalized)) return false;
4683
- const allowPrefixCompound = keyHasExplicitSeparator(key);
4684
- for (const sensitive of sensitiveKeys) {
4685
- const s = normalizeSensitiveKey(sensitive);
4686
- if (!s) continue;
4687
- if (s === "token") {
4688
- if (isTokenCredentialKey(normalized)) return true;
4689
- continue;
4690
- }
4691
- if (normalized === s) return true;
4692
- if (normalized.endsWith(`_${s}`)) return true;
4693
- if (allowPrefixCompound && normalized.startsWith(`${s}_`)) return true;
4694
- }
4695
- return false;
4696
- }
4697
- var NON_CREDENTIAL_TOKEN_CONFIG_KEYS, DEFAULT_CREDENTIAL_SENSITIVE_KEYS;
4698
- var init_sensitive_key = __esm({
4699
- "packages/core/src/safety/sensitive-key.ts"() {
4700
- NON_CREDENTIAL_TOKEN_CONFIG_KEYS = new Set(
4701
- [
4702
- "tokens",
4703
- "max_tokens",
4704
- "min_tokens",
4705
- "ls_max_tokens",
4706
- "token_count",
4707
- "token_limit",
4708
- "token_budget",
4709
- "input_tokens",
4710
- "output_tokens",
4711
- "total_tokens",
4712
- "cached_tokens",
4713
- "prompt_tokens",
4714
- "completion_tokens"
4715
- ].map(normalizeSensitiveKey)
4716
- );
4717
- DEFAULT_CREDENTIAL_SENSITIVE_KEYS = [
4718
- "authorization",
4719
- "cookie",
4720
- "token",
4721
- "access_token",
4722
- "accesstoken",
4723
- "auth_token",
4724
- "authtoken",
4725
- "refresh_token",
4726
- "refreshtoken",
4727
- "id_token",
4728
- "idtoken",
4729
- "bearer_token",
4730
- "bearertoken",
4731
- "api_token",
4732
- "apitoken",
4733
- "apikey",
4734
- "api_key",
4735
- "password",
4736
- "secret",
4737
- "email"
4738
- ];
4739
- }
4740
- });
4741
-
4742
4754
  // packages/core/src/checks/logical-events.ts
4743
4755
  function isRecord7(value) {
4744
4756
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -7350,7 +7362,7 @@ var init_checks2 = __esm({
7350
7362
  // Keep in sync with packages/redact/src/key-value-secret.ts (KEY_VALUE_SECRET_PATTERN_SOURCE).
7351
7363
  {
7352
7364
  id: "key-value-secret",
7353
- pattern: /\b(?:api[_-]?key|internal[_-]?token|access[_-]?token|auth[_-]?token|password|secret|token)=([^\s"'\\]{8,})/i
7365
+ pattern: /\b(?:api[_-]?key|internal[_-]?token|access[_-]?token|auth[_-]?token|password|secret|token)=(?!\[(?:REDACTED(?::[^\]]*)?|HASH:[0-9a-f]{8})\](?![^\s"'\\&#?;/]))([^\s"'\\]{8,})/i
7354
7366
  }
7355
7367
  ];
7356
7368
  }
@@ -16153,7 +16165,7 @@ var init_src = __esm({
16153
16165
  });
16154
16166
 
16155
16167
  // package.json
16156
- var version = "6.31.3";
16168
+ var version = "6.31.4";
16157
16169
 
16158
16170
  // packages/cli/src/list.ts
16159
16171
  init_advanced();
@@ -17757,6 +17769,7 @@ async function followFile(filePath, options, onLine, shouldStop) {
17757
17769
  await readFileOnce(filePath, onLine);
17758
17770
  return;
17759
17771
  }
17772
+ let decoder = new string_decoder.StringDecoder("utf-8");
17760
17773
  pos = st.size;
17761
17774
  while (!shouldStop()) {
17762
17775
  let next;
@@ -17770,6 +17783,7 @@ async function followFile(filePath, options, onLine, shouldStop) {
17770
17783
  pos = 0;
17771
17784
  carry = "";
17772
17785
  lineNumber = 0;
17786
+ decoder = new string_decoder.StringDecoder("utf-8");
17773
17787
  }
17774
17788
  if (next.size > pos) {
17775
17789
  const fh = await promises.open(filePath, "r");
@@ -17778,7 +17792,7 @@ async function followFile(filePath, options, onLine, shouldStop) {
17778
17792
  const buf = Buffer.allocUnsafe(Number(len));
17779
17793
  const { bytesRead } = await fh.read(buf, 0, buf.length, pos);
17780
17794
  pos += bytesRead;
17781
- const chunk = carry + buf.toString("utf-8", 0, bytesRead);
17795
+ const chunk = carry + decoder.write(buf.subarray(0, bytesRead));
17782
17796
  const parts = chunk.split(/\r?\n/);
17783
17797
  carry = parts.pop() ?? "";
17784
17798
  for (const p of parts) {
@@ -17943,6 +17957,103 @@ var EXPORT_PAYLOAD_VERSION = "0.1.2";
17943
17957
 
17944
17958
  // packages/core/src/exporters/redact-export.ts
17945
17959
  init_redactor();
17960
+ init_credential_value_patterns();
17961
+
17962
+ // packages/core/src/safety/url-redaction.ts
17963
+ init_sensitive_key();
17964
+ var HTTP_URL = /^https?:\/\/[^\s]+$/i;
17965
+ var UUID_SEGMENT = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
17966
+ var HEX_SEGMENT = /^[0-9a-f]{12,}$/i;
17967
+ var NUMERIC_SEGMENT = /^\d{5,}$/;
17968
+ var OPAQUE_SEGMENT = /^[A-Za-z0-9_-]{20,}$/;
17969
+ function looksLikeHttpUrl(value) {
17970
+ return HTTP_URL.test(value.trim());
17971
+ }
17972
+ function parseHttpUrl(value) {
17973
+ try {
17974
+ const url = new URL(value.trim());
17975
+ return url.protocol === "http:" || url.protocol === "https:" ? url : void 0;
17976
+ } catch {
17977
+ return void 0;
17978
+ }
17979
+ }
17980
+ function isIdentifierSegment(segment) {
17981
+ if (UUID_SEGMENT.test(segment)) return true;
17982
+ if (HEX_SEGMENT.test(segment)) return true;
17983
+ if (NUMERIC_SEGMENT.test(segment)) return true;
17984
+ return OPAQUE_SEGMENT.test(segment) && /\d/.test(segment) && /[A-Za-z]/.test(segment);
17985
+ }
17986
+ function decodeParam(value) {
17987
+ try {
17988
+ return decodeURIComponent(value.replace(/\+/g, " "));
17989
+ } catch {
17990
+ return value;
17991
+ }
17992
+ }
17993
+ function redactParams(raw, options, replacement) {
17994
+ let redacted = false;
17995
+ const parts = raw.split("&").map((part) => {
17996
+ if (part.length === 0) return part;
17997
+ const separator = part.indexOf("=");
17998
+ if (separator < 0) return part;
17999
+ const rawKey = part.slice(0, separator);
18000
+ const rawValue = part.slice(separator + 1);
18001
+ const key = decodeParam(rawKey);
18002
+ const value = decodeParam(rawValue);
18003
+ if (isCredentialSensitiveKey(key, options.sensitiveKeys) || options.isSensitiveValue(value)) {
18004
+ redacted = true;
18005
+ return `${rawKey}=${replacement}`;
18006
+ }
18007
+ return part;
18008
+ });
18009
+ return { value: parts.join("&"), redacted };
18010
+ }
18011
+ function redactUrlString(value, options) {
18012
+ const unchanged = {
18013
+ value,
18014
+ credentialRedacted: false,
18015
+ identifiersMasked: false
18016
+ };
18017
+ if (!looksLikeHttpUrl(value)) return unchanged;
18018
+ const url = parseHttpUrl(value);
18019
+ if (!url) return unchanged;
18020
+ const replacement = options.replacement ?? "[REDACTED]";
18021
+ const idPlaceholder = options.idPlaceholder ?? "[id]";
18022
+ let credentialRedacted = url.username.length > 0 || url.password.length > 0;
18023
+ let identifiersMasked = false;
18024
+ let pathname = url.pathname;
18025
+ if (options.maskPathIds === true) {
18026
+ pathname = url.pathname.split("/").map((segment) => {
18027
+ if (segment.length === 0 || !isIdentifierSegment(decodeParam(segment))) {
18028
+ return segment;
18029
+ }
18030
+ identifiersMasked = true;
18031
+ return idPlaceholder;
18032
+ }).join("/");
18033
+ }
18034
+ let search = "";
18035
+ if (url.search.length > 1) {
18036
+ const params = redactParams(url.search.slice(1), options, replacement);
18037
+ if (params.redacted) credentialRedacted = true;
18038
+ search = `?${params.value}`;
18039
+ } else if (url.search.length === 1) {
18040
+ search = "?";
18041
+ }
18042
+ let hash = url.hash;
18043
+ if (url.hash.includes("=")) {
18044
+ const params = redactParams(url.hash.slice(1), options, replacement);
18045
+ if (params.redacted) credentialRedacted = true;
18046
+ hash = `#${params.value}`;
18047
+ }
18048
+ if (!credentialRedacted && !identifiersMasked) return unchanged;
18049
+ return {
18050
+ value: `${url.protocol}//${url.host}${pathname}${search}${hash}`,
18051
+ credentialRedacted,
18052
+ identifiersMasked
18053
+ };
18054
+ }
18055
+
18056
+ // packages/core/src/exporters/redact-export.ts
17946
18057
  init_redaction_profiles();
17947
18058
  function isRecord15(value) {
17948
18059
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -17960,21 +18071,34 @@ function deepClone(value) {
17960
18071
  }
17961
18072
  return out;
17962
18073
  }
17963
- function boundAttributeValues(record, maxMetadataValueLength, maxPreviewLength, seen, depth) {
18074
+ function boundAttributeValues(record, sensitiveKeys, maxMetadataValueLength, maxPreviewLength, seen, depth) {
17964
18075
  if (depth > 32) {
17965
18076
  return { truncated: true, reason: "maxDepth" };
17966
18077
  }
17967
18078
  const out = {};
17968
18079
  for (const [key, value] of Object.entries(record)) {
17969
- out[key] = boundValue(value, key, maxMetadataValueLength, maxPreviewLength, seen, depth);
18080
+ out[key] = boundValue(
18081
+ value,
18082
+ key,
18083
+ sensitiveKeys,
18084
+ maxMetadataValueLength,
18085
+ maxPreviewLength,
18086
+ seen,
18087
+ depth
18088
+ );
17970
18089
  }
17971
18090
  return out;
17972
18091
  }
17973
- function boundValue(value, key, maxMetadataValueLength, maxPreviewLength, seen, depth) {
18092
+ function boundValue(value, key, sensitiveKeys, maxMetadataValueLength, maxPreviewLength, seen, depth) {
17974
18093
  if (value === null || typeof value !== "object") {
17975
18094
  if (typeof value === "string") {
18095
+ const rewritten = redactUrlString(value, {
18096
+ sensitiveKeys,
18097
+ isSensitiveValue: stringContainsHighConfidenceCredential,
18098
+ maskPathIds: true
18099
+ }).value;
17976
18100
  return truncateStringForProfile(
17977
- value,
18101
+ stringContainsHighConfidenceCredential(rewritten) ? "[REDACTED]" : rewritten,
17978
18102
  key,
17979
18103
  maxMetadataValueLength,
17980
18104
  maxPreviewLength
@@ -17989,6 +18113,7 @@ function boundValue(value, key, maxMetadataValueLength, maxPreviewLength, seen,
17989
18113
  (item, index) => boundValue(
17990
18114
  item,
17991
18115
  String(index),
18116
+ sensitiveKeys,
17992
18117
  maxMetadataValueLength,
17993
18118
  maxPreviewLength,
17994
18119
  seen,
@@ -17998,6 +18123,7 @@ function boundValue(value, key, maxMetadataValueLength, maxPreviewLength, seen,
17998
18123
  }
17999
18124
  return boundAttributeValues(
18000
18125
  value,
18126
+ sensitiveKeys,
18001
18127
  maxMetadataValueLength,
18002
18128
  maxPreviewLength,
18003
18129
  seen,
@@ -18012,6 +18138,7 @@ function redactEventAttributes(attrs, redactor, maxMetadataValueLength, maxPrevi
18012
18138
  const seen = /* @__PURE__ */ new WeakSet();
18013
18139
  const bounded = boundAttributeValues(
18014
18140
  redacted,
18141
+ [...DEFAULT_REDACT_KEYS, ...redactor.sensitiveKeys],
18015
18142
  maxMetadataValueLength,
18016
18143
  maxPreviewLength,
18017
18144
  seen,
@@ -20150,7 +20277,7 @@ function isCredentialSensitiveKey2(key, sensitiveKeys) {
20150
20277
  }
20151
20278
 
20152
20279
  // packages/redact/src/key-value-secret.ts
20153
- var KEY_VALUE_SECRET_PATTERN_SOURCE = String.raw`\b(?:api[_-]?key|internal[_-]?token|access[_-]?token|auth[_-]?token|password|secret|token)=([^\s"'\\]{8,})`;
20280
+ var KEY_VALUE_SECRET_PATTERN_SOURCE = String.raw`\b(?:api[_-]?key|internal[_-]?token|access[_-]?token|auth[_-]?token|password|secret|token)=(?!\[(?:REDACTED(?::[^\]]*)?|HASH:[0-9a-f]{8})\](?![^\s"'\\&#?;/]))([^\s"'\\]{8,})`;
20154
20281
  var KEY_VALUE_SECRET_PATTERN2 = new RegExp(
20155
20282
  KEY_VALUE_SECRET_PATTERN_SOURCE,
20156
20283
  "i"
@@ -20160,6 +20287,99 @@ function valueContainsKeyValueSecret(value) {
20160
20287
  return KEY_VALUE_SECRET_PATTERN2.test(value);
20161
20288
  }
20162
20289
 
20290
+ // packages/redact/src/url-redaction.ts
20291
+ var HTTP_URL2 = /^https?:\/\/[^\s]+$/i;
20292
+ var UUID_SEGMENT2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
20293
+ var HEX_SEGMENT2 = /^[0-9a-f]{12,}$/i;
20294
+ var NUMERIC_SEGMENT2 = /^\d{5,}$/;
20295
+ var OPAQUE_SEGMENT2 = /^[A-Za-z0-9_-]{20,}$/;
20296
+ function looksLikeHttpUrl2(value) {
20297
+ return HTTP_URL2.test(value.trim());
20298
+ }
20299
+ function parseHttpUrl2(value) {
20300
+ try {
20301
+ const url = new URL(value.trim());
20302
+ return url.protocol === "http:" || url.protocol === "https:" ? url : void 0;
20303
+ } catch {
20304
+ return void 0;
20305
+ }
20306
+ }
20307
+ function isIdentifierSegment2(segment) {
20308
+ if (UUID_SEGMENT2.test(segment)) return true;
20309
+ if (HEX_SEGMENT2.test(segment)) return true;
20310
+ if (NUMERIC_SEGMENT2.test(segment)) return true;
20311
+ return OPAQUE_SEGMENT2.test(segment) && /\d/.test(segment) && /[A-Za-z]/.test(segment);
20312
+ }
20313
+ function decodeParam2(value) {
20314
+ try {
20315
+ return decodeURIComponent(value.replace(/\+/g, " "));
20316
+ } catch {
20317
+ return value;
20318
+ }
20319
+ }
20320
+ function redactParams2(raw, options, replacement) {
20321
+ let redacted = false;
20322
+ const parts = raw.split("&").map((part) => {
20323
+ if (part.length === 0) return part;
20324
+ const separator = part.indexOf("=");
20325
+ if (separator < 0) return part;
20326
+ const rawKey = part.slice(0, separator);
20327
+ const rawValue = part.slice(separator + 1);
20328
+ const key = decodeParam2(rawKey);
20329
+ const value = decodeParam2(rawValue);
20330
+ if (isCredentialSensitiveKey2(key, options.sensitiveKeys) || options.isSensitiveValue(value)) {
20331
+ redacted = true;
20332
+ return `${rawKey}=${replacement}`;
20333
+ }
20334
+ return part;
20335
+ });
20336
+ return { value: parts.join("&"), redacted };
20337
+ }
20338
+ function redactUrlString2(value, options) {
20339
+ const unchanged = {
20340
+ value,
20341
+ credentialRedacted: false,
20342
+ identifiersMasked: false
20343
+ };
20344
+ if (!looksLikeHttpUrl2(value)) return unchanged;
20345
+ const url = parseHttpUrl2(value);
20346
+ if (!url) return unchanged;
20347
+ const replacement = options.replacement ?? "[REDACTED]";
20348
+ const idPlaceholder = options.idPlaceholder ?? "[id]";
20349
+ let credentialRedacted = url.username.length > 0 || url.password.length > 0;
20350
+ let identifiersMasked = false;
20351
+ let pathname = url.pathname;
20352
+ if (options.maskPathIds === true) {
20353
+ pathname = url.pathname.split("/").map((segment) => {
20354
+ if (segment.length === 0 || !isIdentifierSegment2(decodeParam2(segment))) {
20355
+ return segment;
20356
+ }
20357
+ identifiersMasked = true;
20358
+ return idPlaceholder;
20359
+ }).join("/");
20360
+ }
20361
+ let search = "";
20362
+ if (url.search.length > 1) {
20363
+ const params = redactParams2(url.search.slice(1), options, replacement);
20364
+ if (params.redacted) credentialRedacted = true;
20365
+ search = `?${params.value}`;
20366
+ } else if (url.search.length === 1) {
20367
+ search = "?";
20368
+ }
20369
+ let hash = url.hash;
20370
+ if (url.hash.includes("=")) {
20371
+ const params = redactParams2(url.hash.slice(1), options, replacement);
20372
+ if (params.redacted) credentialRedacted = true;
20373
+ hash = `#${params.value}`;
20374
+ }
20375
+ if (!credentialRedacted && !identifiersMasked) return unchanged;
20376
+ return {
20377
+ value: `${url.protocol}//${url.host}${pathname}${search}${hash}`,
20378
+ credentialRedacted,
20379
+ identifiersMasked
20380
+ };
20381
+ }
20382
+
20163
20383
  // packages/redact/src/index.ts
20164
20384
  var DEFAULT_REDACT_KEYS2 = [
20165
20385
  "authorization",
@@ -20226,7 +20446,9 @@ function findCompiledKeyRule(key, rules) {
20226
20446
  const exact = toKey2(key);
20227
20447
  const direct = rules.find((candidate) => candidate.key === exact);
20228
20448
  if (direct) return direct;
20229
- return rules.find((candidate) => isCredentialSensitiveKey2(key, [candidate.key]));
20449
+ return rules.find(
20450
+ (candidate) => isCredentialSensitiveKey2(key, [candidate.raw ?? candidate.key])
20451
+ );
20230
20452
  }
20231
20453
  function stableHash2(value) {
20232
20454
  const hash = crypto__default.default.createHash("sha256").update(value, "utf8").digest("hex");
@@ -20425,7 +20647,7 @@ function compileRules2(rules, extraKeys) {
20425
20647
  const out = /* @__PURE__ */ new Map();
20426
20648
  const set = (rule) => {
20427
20649
  const key = toKey2(rule.key);
20428
- out.set(key, { ...rule, key });
20650
+ out.set(key, { ...rule, key, raw: rule.raw ?? rule.key });
20429
20651
  };
20430
20652
  for (const key of DEFAULT_REDACT_KEYS2) {
20431
20653
  set({ key, strategy: "full" });
@@ -20504,6 +20726,7 @@ function createRedactionProfile(profile = "local") {
20504
20726
  }
20505
20727
  var Redactor2 = class {
20506
20728
  #rules;
20729
+ #sensitiveKeys;
20507
20730
  #detectors;
20508
20731
  #profile;
20509
20732
  #replacement;
@@ -20516,6 +20739,7 @@ var Redactor2 = class {
20516
20739
  ...resolved.extraKeys,
20517
20740
  ...options?.extraKeys ?? []
20518
20741
  ]);
20742
+ this.#sensitiveKeys = this.#rules.map((rule) => rule.raw ?? rule.key);
20519
20743
  this.#detectors = [
20520
20744
  ...builtInDetectorsForProfile(this.#profile),
20521
20745
  ...options?.detectors ?? []
@@ -20549,6 +20773,14 @@ var Redactor2 = class {
20549
20773
  #recordFinding(state, finding) {
20550
20774
  if (this.#collectFindings) state.findings.push(finding);
20551
20775
  }
20776
+ #detect(value, key, path44) {
20777
+ for (const detector of this.#detectors) {
20778
+ for (const detection of detector.detect({ path: path44, key, value })) {
20779
+ if ((detection.action ?? "replace") !== "keep") return { detector, detection };
20780
+ }
20781
+ }
20782
+ return void 0;
20783
+ }
20552
20784
  #redactValue(value, key, path44, depth, state) {
20553
20785
  if (depth > this.#maxDepth) {
20554
20786
  this.#recordFinding(
@@ -20567,6 +20799,43 @@ var Redactor2 = class {
20567
20799
  return applyRule(rule, value, this.#replacement);
20568
20800
  }
20569
20801
  }
20802
+ if (typeof value === "string" && looksLikeHttpUrl2(value)) {
20803
+ const url = redactUrlString2(value, {
20804
+ sensitiveKeys: this.#sensitiveKeys,
20805
+ isSensitiveValue: (candidate) => this.#detect(candidate, key, path44) !== void 0,
20806
+ replacement: this.#replacement,
20807
+ maskPathIds: this.#profile !== "local"
20808
+ });
20809
+ if (url.value !== value) {
20810
+ this.#recordFinding(
20811
+ state,
20812
+ makeFinding(
20813
+ path44,
20814
+ url.credentialRedacted ? "value.urlCredential" : "value.urlIdentifier",
20815
+ "replace",
20816
+ "value",
20817
+ url.credentialRedacted ? "error" : "warning"
20818
+ )
20819
+ );
20820
+ const residual = this.#detect(
20821
+ url.value.split(this.#replacement).join(""),
20822
+ key,
20823
+ path44
20824
+ );
20825
+ if (residual === void 0) return url.value;
20826
+ this.#recordFinding(
20827
+ state,
20828
+ makeFinding(
20829
+ path44,
20830
+ residual.detector.id,
20831
+ "replace",
20832
+ residual.detection.matchKind ?? residual.detector.matchKind ?? "custom",
20833
+ residual.detection.severity ?? residual.detector.severity ?? "warning"
20834
+ )
20835
+ );
20836
+ return this.#replacement;
20837
+ }
20838
+ }
20570
20839
  for (const detector of this.#detectors) {
20571
20840
  const detections = detector.detect({ path: path44, key, value });
20572
20841
  for (const detection of detections) {
@@ -28582,7 +28851,7 @@ function envCheck(name, optional = true) {
28582
28851
  async function traceDirWritable(traceDir) {
28583
28852
  const resolved = path33__default.default.resolve(traceDir);
28584
28853
  try {
28585
- await promises.mkdir(resolved, { recursive: true });
28854
+ await promises.mkdir(resolved, { recursive: true, mode: 448 });
28586
28855
  await promises.access(resolved, promises.constants.W_OK);
28587
28856
  return {
28588
28857
  id: "trace-dir-writable",
@@ -28600,6 +28869,44 @@ async function traceDirWritable(traceDir) {
28600
28869
  };
28601
28870
  }
28602
28871
  }
28872
+ async function traceDirPermissions(traceDir) {
28873
+ const resolved = path33__default.default.resolve(traceDir);
28874
+ if (process3__default.default.platform === "win32") {
28875
+ return {
28876
+ id: "trace-dir-permissions",
28877
+ status: "skipped",
28878
+ message: "POSIX confidentiality mode check skipped on Windows.",
28879
+ evidence: resolved
28880
+ };
28881
+ }
28882
+ try {
28883
+ const st = await promises.stat(resolved);
28884
+ const mode = st.mode & 511;
28885
+ const octal = mode.toString(8).padStart(3, "0");
28886
+ if ((mode & 63) !== 0) {
28887
+ return {
28888
+ id: "trace-dir-permissions",
28889
+ status: "warn",
28890
+ message: `Trace directory allows group/other access (mode ${octal}): ${resolved}`,
28891
+ remediation: `Restrict with chmod 700 on the directory and review existing JSONL files with chmod 600. AgentInspect does not rewrite existing modes. See ${DOCS_BASE}/GETTING-STARTED.md`,
28892
+ evidence: `mode=${octal}`
28893
+ };
28894
+ }
28895
+ return {
28896
+ id: "trace-dir-permissions",
28897
+ status: "pass",
28898
+ message: `Trace directory mode is restrictive (${octal}): ${resolved}`,
28899
+ evidence: `mode=${octal}`
28900
+ };
28901
+ } catch (error) {
28902
+ return {
28903
+ id: "trace-dir-permissions",
28904
+ status: "skipped",
28905
+ message: `Could not inspect trace directory permissions: ${resolved}`,
28906
+ evidence: error instanceof Error ? error.message : String(error)
28907
+ };
28908
+ }
28909
+ }
28603
28910
  function readPackageVersionNearEntry(entryPath, packageName) {
28604
28911
  let dir = path33__default.default.dirname(path33__default.default.resolve(entryPath));
28605
28912
  const { root } = path33__default.default.parse(dir);
@@ -28731,6 +29038,7 @@ async function runDoctorChecks(options = {}) {
28731
29038
  evidence: version
28732
29039
  },
28733
29040
  await traceDirWritable(traceDir),
29041
+ await traceDirPermissions(traceDir),
28734
29042
  envCheck("AGENT_INSPECT"),
28735
29043
  envCheck("AGENT_INSPECT_TRACE_DIR"),
28736
29044
  versionMismatchCheck(cwd)