agent-inspect 6.31.2 → 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 +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 +406 -93
  10. package/packages/cli/dist/index.cjs.map +1 -1
  11. package/packages/cli/dist/index.mjs +314 -16
  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 +224 -8
  38. package/packages/core/dist/exporters.cjs.map +1 -1
  39. package/packages/core/dist/exporters.mjs +125 -11
  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.2";
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,
@@ -18588,6 +18715,11 @@ init_helpers();
18588
18715
  function hexFrom2(seed, byteLen) {
18589
18716
  return crypto__default.default.createHash("sha256").update(seed, "utf8").digest("hex").slice(0, byteLen * 2);
18590
18717
  }
18718
+ function unixNano2(ms) {
18719
+ const whole = Math.trunc(ms);
18720
+ const fractionNs = Math.round((ms - whole) * 1e6);
18721
+ return BigInt(whole) * 1000000n + BigInt(fractionNs);
18722
+ }
18591
18723
  function stringAttr2(key, value) {
18592
18724
  return { key, value: { stringValue: value } };
18593
18725
  }
@@ -18621,10 +18753,10 @@ function exportOtlpJson(tree, options) {
18621
18753
  const ev = n.event;
18622
18754
  const spanId = hexFrom2(`${tree.runId}:${ev.eventId}`, 8);
18623
18755
  const parentSpanId = ev.parentId ? hexFrom2(`${tree.runId}:${ev.parentId}`, 8) : void 0;
18624
- const startNs = String(Math.round(ev.timestamp * 1e6));
18756
+ const startNs = String(unixNano2(ev.timestamp));
18625
18757
  let endNs;
18626
18758
  if (ev.durationMs !== void 0 && Number.isFinite(ev.durationMs)) {
18627
- endNs = String(Math.round(ev.timestamp * 1e6 + ev.durationMs * 1e6));
18759
+ endNs = String(unixNano2(ev.timestamp) + unixNano2(ev.durationMs));
18628
18760
  }
18629
18761
  const attrs = [
18630
18762
  stringAttr2("agent_inspect.kind", ev.kind),
@@ -20145,7 +20277,7 @@ function isCredentialSensitiveKey2(key, sensitiveKeys) {
20145
20277
  }
20146
20278
 
20147
20279
  // packages/redact/src/key-value-secret.ts
20148
- 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,})`;
20149
20281
  var KEY_VALUE_SECRET_PATTERN2 = new RegExp(
20150
20282
  KEY_VALUE_SECRET_PATTERN_SOURCE,
20151
20283
  "i"
@@ -20155,6 +20287,99 @@ function valueContainsKeyValueSecret(value) {
20155
20287
  return KEY_VALUE_SECRET_PATTERN2.test(value);
20156
20288
  }
20157
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
+
20158
20383
  // packages/redact/src/index.ts
20159
20384
  var DEFAULT_REDACT_KEYS2 = [
20160
20385
  "authorization",
@@ -20221,7 +20446,9 @@ function findCompiledKeyRule(key, rules) {
20221
20446
  const exact = toKey2(key);
20222
20447
  const direct = rules.find((candidate) => candidate.key === exact);
20223
20448
  if (direct) return direct;
20224
- return rules.find((candidate) => isCredentialSensitiveKey2(key, [candidate.key]));
20449
+ return rules.find(
20450
+ (candidate) => isCredentialSensitiveKey2(key, [candidate.raw ?? candidate.key])
20451
+ );
20225
20452
  }
20226
20453
  function stableHash2(value) {
20227
20454
  const hash = crypto__default.default.createHash("sha256").update(value, "utf8").digest("hex");
@@ -20420,7 +20647,7 @@ function compileRules2(rules, extraKeys) {
20420
20647
  const out = /* @__PURE__ */ new Map();
20421
20648
  const set = (rule) => {
20422
20649
  const key = toKey2(rule.key);
20423
- out.set(key, { ...rule, key });
20650
+ out.set(key, { ...rule, key, raw: rule.raw ?? rule.key });
20424
20651
  };
20425
20652
  for (const key of DEFAULT_REDACT_KEYS2) {
20426
20653
  set({ key, strategy: "full" });
@@ -20499,6 +20726,7 @@ function createRedactionProfile(profile = "local") {
20499
20726
  }
20500
20727
  var Redactor2 = class {
20501
20728
  #rules;
20729
+ #sensitiveKeys;
20502
20730
  #detectors;
20503
20731
  #profile;
20504
20732
  #replacement;
@@ -20511,6 +20739,7 @@ var Redactor2 = class {
20511
20739
  ...resolved.extraKeys,
20512
20740
  ...options?.extraKeys ?? []
20513
20741
  ]);
20742
+ this.#sensitiveKeys = this.#rules.map((rule) => rule.raw ?? rule.key);
20514
20743
  this.#detectors = [
20515
20744
  ...builtInDetectorsForProfile(this.#profile),
20516
20745
  ...options?.detectors ?? []
@@ -20544,6 +20773,14 @@ var Redactor2 = class {
20544
20773
  #recordFinding(state, finding) {
20545
20774
  if (this.#collectFindings) state.findings.push(finding);
20546
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
+ }
20547
20784
  #redactValue(value, key, path44, depth, state) {
20548
20785
  if (depth > this.#maxDepth) {
20549
20786
  this.#recordFinding(
@@ -20562,6 +20799,43 @@ var Redactor2 = class {
20562
20799
  return applyRule(rule, value, this.#replacement);
20563
20800
  }
20564
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
+ }
20565
20839
  for (const detector of this.#detectors) {
20566
20840
  const detections = detector.detect({ path: path44, key, value });
20567
20841
  for (const detection of detections) {
@@ -28577,7 +28851,7 @@ function envCheck(name, optional = true) {
28577
28851
  async function traceDirWritable(traceDir) {
28578
28852
  const resolved = path33__default.default.resolve(traceDir);
28579
28853
  try {
28580
- await promises.mkdir(resolved, { recursive: true });
28854
+ await promises.mkdir(resolved, { recursive: true, mode: 448 });
28581
28855
  await promises.access(resolved, promises.constants.W_OK);
28582
28856
  return {
28583
28857
  id: "trace-dir-writable",
@@ -28595,6 +28869,44 @@ async function traceDirWritable(traceDir) {
28595
28869
  };
28596
28870
  }
28597
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
+ }
28598
28910
  function readPackageVersionNearEntry(entryPath, packageName) {
28599
28911
  let dir = path33__default.default.dirname(path33__default.default.resolve(entryPath));
28600
28912
  const { root } = path33__default.default.parse(dir);
@@ -28726,6 +29038,7 @@ async function runDoctorChecks(options = {}) {
28726
29038
  evidence: version
28727
29039
  },
28728
29040
  await traceDirWritable(traceDir),
29041
+ await traceDirPermissions(traceDir),
28729
29042
  envCheck("AGENT_INSPECT"),
28730
29043
  envCheck("AGENT_INSPECT_TRACE_DIR"),
28731
29044
  versionMismatchCheck(cwd)