agent-inspect 6.8.0 → 6.9.0

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 (29) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +1 -1
  3. package/docs/CLI.md +10 -3
  4. package/package.json +1 -1
  5. package/packages/cli/dist/{chunk-TO4VENHV.mjs → chunk-WQ5XMFH2.mjs} +109 -17
  6. package/packages/cli/dist/chunk-WQ5XMFH2.mjs.map +1 -0
  7. package/packages/cli/dist/index.cjs +357 -66
  8. package/packages/cli/dist/index.cjs.map +1 -1
  9. package/packages/cli/dist/index.mjs +252 -53
  10. package/packages/cli/dist/index.mjs.map +1 -1
  11. package/packages/cli/dist/{src-Z5W27YCW.mjs → src-C4EUHTER.mjs} +3 -3
  12. package/packages/cli/dist/{src-Z5W27YCW.mjs.map → src-C4EUHTER.mjs.map} +1 -1
  13. package/packages/core/dist/advanced.cjs +15 -6
  14. package/packages/core/dist/advanced.cjs.map +1 -1
  15. package/packages/core/dist/advanced.d.cts +4 -1
  16. package/packages/core/dist/advanced.d.ts +4 -1
  17. package/packages/core/dist/advanced.mjs +3 -2
  18. package/packages/core/dist/advanced.mjs.map +1 -1
  19. package/packages/core/dist/checks.cjs +105 -14
  20. package/packages/core/dist/checks.cjs.map +1 -1
  21. package/packages/core/dist/checks.d.cts +2 -2
  22. package/packages/core/dist/checks.d.ts +2 -2
  23. package/packages/core/dist/checks.mjs +1 -1
  24. package/packages/core/dist/{chunk-EQVXDGGU.mjs → chunk-QT6CQ2XA.mjs} +107 -16
  25. package/packages/core/dist/chunk-QT6CQ2XA.mjs.map +1 -0
  26. package/packages/core/dist/{index-BblJEdYZ.d.ts → index-BO7l0iAe.d.ts} +44 -1
  27. package/packages/core/dist/{index-DyKk7iR9.d.cts → index-mdFcxSOR.d.cts} +44 -1
  28. package/packages/cli/dist/chunk-TO4VENHV.mjs.map +0 -1
  29. package/packages/core/dist/chunk-EQVXDGGU.mjs.map +0 -1
@@ -4105,8 +4105,9 @@ function buildBundleSummaryMarkdown(parts) {
4105
4105
  ""
4106
4106
  ];
4107
4107
  for (const run of checks2.runs) {
4108
+ const source = run.sourceStatus !== void 0 && run.sourceStatus !== run.status ? `; source ${run.sourceStatus}` : "";
4108
4109
  lines.push(
4109
- `- \`${run.runId}\`: ${run.status} (${run.findings} finding(s), ${run.errors} error(s), ${run.warnings} warning(s))`
4110
+ `- \`${run.runId}\`: artifact ${run.status}${source} (${run.findings} finding(s), ${run.errors} error(s), ${run.warnings} warning(s))`
4110
4111
  );
4111
4112
  }
4112
4113
  lines.push("", "## Redaction", "", `Total findings: ${redaction.totalFindings}`, "");
@@ -4691,7 +4692,11 @@ function normalizeFinding(rule, finding) {
4691
4692
  message: finding.message,
4692
4693
  ...finding.expected !== void 0 ? { expected: finding.expected } : {},
4693
4694
  ...finding.actual !== void 0 ? { actual: finding.actual } : {},
4694
- evidence: [...finding.evidence ?? []]
4695
+ evidence: [...finding.evidence ?? []],
4696
+ ...finding.category !== void 0 ? { category: finding.category } : {},
4697
+ ...finding.confidence !== void 0 ? { confidence: finding.confidence } : {},
4698
+ ...finding.detector !== void 0 ? { detector: finding.detector } : {},
4699
+ ...finding.action !== void 0 ? { action: finding.action } : {}
4695
4700
  };
4696
4701
  }
4697
4702
  function summarize(findings, diagnostics) {
@@ -4749,15 +4754,19 @@ function eventEvidence(event, path37) {
4749
4754
  function runEvidence(run) {
4750
4755
  return run ? [{ runId: run.runId, name: run.name, status: run.status }] : [];
4751
4756
  }
4752
- function failFinding(ruleId, message, evidence, expected, actual) {
4757
+ function failFinding(ruleId, message, evidence, expected, actual, meta2) {
4753
4758
  return {
4754
4759
  ruleId,
4755
- severity: "error",
4756
- status: "fail",
4760
+ severity: meta2?.severity ?? "error",
4761
+ status: meta2?.status ?? "fail",
4757
4762
  message,
4758
4763
  ...expected !== void 0 ? { expected } : {},
4759
4764
  ...actual !== void 0 ? { actual } : {},
4760
- evidence: [...evidence]
4765
+ evidence: [...evidence],
4766
+ ...meta2?.category !== void 0 ? { category: meta2.category } : {},
4767
+ ...meta2?.confidence !== void 0 ? { confidence: meta2.confidence } : {},
4768
+ ...meta2?.detector !== void 0 ? { detector: meta2.detector } : {},
4769
+ ...meta2?.action !== void 0 ? { action: meta2.action } : {}
4761
4770
  };
4762
4771
  }
4763
4772
  function toolName(event) {
@@ -4878,6 +4887,19 @@ function isRawContentKey(key, forbiddenKeys) {
4878
4887
  const normalized = normalizedKey(key);
4879
4888
  return forbiddenKeys.some((forbidden) => normalized === normalizedKey(forbidden));
4880
4889
  }
4890
+ function isSafeRawContentMetricPath(path37, key, safePathPrefixes) {
4891
+ const leaf = normalizedKey(key ?? lastPathSegment(path37));
4892
+ if (!SAFE_USAGE_LEAF_KEYS.has(leaf)) return false;
4893
+ const parts = path37.split(".").filter(Boolean);
4894
+ if (parts.length < 2) return false;
4895
+ const parent = parts[parts.length - 2] ?? "";
4896
+ const parentNorm = normalizedKey(parent);
4897
+ return safePathPrefixes.some((prefix) => parentNorm === normalizedKey(prefix));
4898
+ }
4899
+ function isRawContentPath(path37, key, forbiddenKeys, safePathPrefixes) {
4900
+ if (isSafeRawContentMetricPath(path37, key, safePathPrefixes)) return false;
4901
+ return isRawContentKey(key ?? lastPathSegment(path37), forbiddenKeys);
4902
+ }
4881
4903
  function parentMarkedUnresolved(event) {
4882
4904
  if (booleanAttr(event, [
4883
4905
  "parentUnresolved",
@@ -5517,7 +5539,13 @@ function createSafetyRedactionRule(options = {}) {
5517
5539
  `Sensitive-looking field at ${entry.path} is not redacted.`,
5518
5540
  [eventEvidence(event, entry.path)],
5519
5541
  "redaction marker",
5520
- { path: entry.path, valueType: valueType(entry.value) }
5542
+ { path: entry.path, valueType: valueType(entry.value) },
5543
+ {
5544
+ category: "credential",
5545
+ confidence: "high",
5546
+ detector: "safety.redaction",
5547
+ action: "redact"
5548
+ }
5521
5549
  )
5522
5550
  );
5523
5551
  }
@@ -5528,6 +5556,7 @@ function createSafetyRedactionRule(options = {}) {
5528
5556
  }
5529
5557
  function createSafetyRawContentRule(options = {}) {
5530
5558
  const forbiddenKeys = options.forbiddenKeys ?? DEFAULT_RAW_CONTENT_KEYS;
5559
+ const safePathPrefixes = options.safePathPrefixes ?? DEFAULT_SAFE_RAW_CONTENT_PATH_PREFIXES;
5531
5560
  return {
5532
5561
  id: "safety.rawPrompt",
5533
5562
  category: "safety",
@@ -5537,14 +5566,20 @@ function createSafetyRawContentRule(options = {}) {
5537
5566
  for (const event of context.events) {
5538
5567
  for (const entry of eventValueEntries(event, { includeSummaries: options.includeSummaries })) {
5539
5568
  const key = entry.key ?? lastPathSegment(entry.path);
5540
- if (!isRawContentKey(key, forbiddenKeys)) continue;
5569
+ if (!isRawContentPath(entry.path, key, forbiddenKeys, safePathPrefixes)) continue;
5541
5570
  findings.push(
5542
5571
  failFinding(
5543
5572
  "safety.rawPrompt",
5544
5573
  `Raw content-like field ${entry.path} is present.`,
5545
5574
  [eventEvidence(event, entry.path)],
5546
5575
  "metadata-only trace fields",
5547
- { path: entry.path, valueType: valueType(entry.value) }
5576
+ { path: entry.path, valueType: valueType(entry.value) },
5577
+ {
5578
+ category: "raw-content",
5579
+ confidence: "high",
5580
+ detector: "safety.rawPrompt",
5581
+ action: "redact-or-omit"
5582
+ }
5548
5583
  )
5549
5584
  );
5550
5585
  }
@@ -5576,7 +5611,13 @@ function createSafetySecretPatternRule(options = {}) {
5576
5611
  `Secret-like pattern ${pattern.id} matched at ${entry.path}.`,
5577
5612
  [eventEvidence(event, entry.path)],
5578
5613
  "no secret-like strings",
5579
- { pattern: pattern.id, path: entry.path }
5614
+ { pattern: pattern.id, path: entry.path },
5615
+ {
5616
+ category: "credential",
5617
+ confidence: "high",
5618
+ detector: pattern.id,
5619
+ action: "redact"
5620
+ }
5580
5621
  )
5581
5622
  );
5582
5623
  break;
@@ -5603,7 +5644,13 @@ function createSafetyOversizedAttributeRule(options) {
5603
5644
  `String at ${entry.path} exceeds ${options.maxStringLength} characters.`,
5604
5645
  [eventEvidence(event, entry.path)],
5605
5646
  { maxStringLength: options.maxStringLength },
5606
- { path: entry.path, length: entry.value.length }
5647
+ { path: entry.path, length: entry.value.length },
5648
+ {
5649
+ category: "size",
5650
+ confidence: "high",
5651
+ detector: "safety.oversizedAttribute",
5652
+ action: "truncate-or-omit"
5653
+ }
5607
5654
  )
5608
5655
  );
5609
5656
  }
@@ -5614,7 +5661,13 @@ function createSafetyOversizedAttributeRule(options) {
5614
5661
  `Array at ${entry.path} exceeds ${options.maxArrayLength} items.`,
5615
5662
  [eventEvidence(event, entry.path)],
5616
5663
  { maxArrayLength: options.maxArrayLength },
5617
- { path: entry.path, length: entry.value.length }
5664
+ { path: entry.path, length: entry.value.length },
5665
+ {
5666
+ category: "size",
5667
+ confidence: "high",
5668
+ detector: "safety.oversizedAttribute",
5669
+ action: "truncate-or-omit"
5670
+ }
5618
5671
  )
5619
5672
  );
5620
5673
  }
@@ -5625,7 +5678,13 @@ function createSafetyOversizedAttributeRule(options) {
5625
5678
  `Object at ${entry.path} exceeds ${options.maxObjectKeys} keys.`,
5626
5679
  [eventEvidence(event, entry.path)],
5627
5680
  { maxObjectKeys: options.maxObjectKeys },
5628
- { path: entry.path, keys: Object.keys(entry.value).length }
5681
+ { path: entry.path, keys: Object.keys(entry.value).length },
5682
+ {
5683
+ category: "size",
5684
+ confidence: "high",
5685
+ detector: "safety.oversizedAttribute",
5686
+ action: "truncate-or-omit"
5687
+ }
5629
5688
  )
5630
5689
  );
5631
5690
  }
@@ -5638,7 +5697,13 @@ function createSafetyOversizedAttributeRule(options) {
5638
5697
  `Value at ${entry.path} exceeds ${options.maxSerializedBytes} serialized bytes.`,
5639
5698
  [eventEvidence(event, entry.path)],
5640
5699
  { maxSerializedBytes: options.maxSerializedBytes },
5641
- { path: entry.path, bytes }
5700
+ { path: entry.path, bytes },
5701
+ {
5702
+ category: "size",
5703
+ confidence: "high",
5704
+ detector: "safety.oversizedAttribute",
5705
+ action: "truncate-or-omit"
5706
+ }
5642
5707
  )
5643
5708
  );
5644
5709
  }
@@ -5855,7 +5920,7 @@ function runTraceChecks(input3, options = {}) {
5855
5920
  diagnostics
5856
5921
  };
5857
5922
  }
5858
- var SEVERITY_RANK, STATUS_RANK, CONFIDENCE_RANK, DEFAULT_SENSITIVE_KEYS, DEFAULT_RAW_CONTENT_KEYS, DEFAULT_SECRET_PATTERNS;
5923
+ var SEVERITY_RANK, STATUS_RANK, CONFIDENCE_RANK, DEFAULT_SENSITIVE_KEYS, DEFAULT_RAW_CONTENT_KEYS, DEFAULT_SAFE_RAW_CONTENT_PATH_PREFIXES, SAFE_USAGE_LEAF_KEYS, DEFAULT_SECRET_PATTERNS;
5859
5924
  var init_checks2 = __esm({
5860
5925
  "packages/core/src/checks/index.ts"() {
5861
5926
  init_outcomes();
@@ -5905,8 +5970,35 @@ var init_checks2 = __esm({
5905
5970
  "toolinput",
5906
5971
  "tool_input",
5907
5972
  "tooloutput",
5908
- "tool_output"
5973
+ "tool_output",
5974
+ // Framework / agent metadata that carries user or task text
5975
+ "currenttask",
5976
+ "current_task",
5977
+ "task",
5978
+ "userinput",
5979
+ "user_input",
5980
+ "requesttext",
5981
+ "request_text",
5982
+ "conversationtext",
5983
+ "conversation_text"
5909
5984
  ];
5985
+ DEFAULT_SAFE_RAW_CONTENT_PATH_PREFIXES = ["tokenUsage", "usage"];
5986
+ SAFE_USAGE_LEAF_KEYS = /* @__PURE__ */ new Set([
5987
+ "input",
5988
+ "output",
5989
+ "total",
5990
+ "cached",
5991
+ "input_tokens",
5992
+ "inputtokens",
5993
+ "output_tokens",
5994
+ "outputtokens",
5995
+ "total_tokens",
5996
+ "totaltokens",
5997
+ "prompt_tokens",
5998
+ "prompttokens",
5999
+ "completion_tokens",
6000
+ "completiontokens"
6001
+ ]);
5910
6002
  DEFAULT_SECRET_PATTERNS = [
5911
6003
  { id: "bearer-token", pattern: /Bearer\s+[A-Za-z0-9._~+/-]{12,}=*/ },
5912
6004
  { id: "openai-key", pattern: /sk-[A-Za-z0-9_-]{16,}/ },
@@ -10069,7 +10161,7 @@ var init_src = __esm({
10069
10161
  });
10070
10162
 
10071
10163
  // package.json
10072
- var version = "6.8.0";
10164
+ var version = "6.9.0";
10073
10165
 
10074
10166
  // packages/cli/src/list.ts
10075
10167
  init_advanced();
@@ -14450,7 +14542,12 @@ var SHARE_PROFILE_EXTRA_KEYS2 = [
14450
14542
  "organizationId",
14451
14543
  "traceId",
14452
14544
  "spanId",
14453
- "parentSpanId"
14545
+ "parentSpanId",
14546
+ "currentTask",
14547
+ "task",
14548
+ "userInput",
14549
+ "requestText",
14550
+ "conversationText"
14454
14551
  ];
14455
14552
  var STRICT_PROFILE_EXTRA_KEYS2 = [
14456
14553
  "prompt",
@@ -14518,6 +14615,61 @@ function passesLuhn(value) {
14518
14615
  }
14519
14616
  return sum % 10 === 0;
14520
14617
  }
14618
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
14619
+ var EPOCH_MS_RE = /^1[0-9]{12}$/;
14620
+ var EMAIL_RE = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi;
14621
+ function pathSuggestsNonCard(path37) {
14622
+ const normalized = path37.toLowerCase().replace(/[^a-z0-9._]/g, "");
14623
+ return /(^|\.)(tokenusage|usage)(\.|$)/.test(normalized) || /(startedat|endedat|durationms|timestamp|createdat|updatedat)(\.|$)/.test(normalized) || /(^|\.)(runid|traceid|spanid|eventid|sessionid|userid|parentid|requestid|correlationid)(\.|$)/.test(
14624
+ normalized
14625
+ ) || /(^|\.)ts(\.|$)/.test(normalized);
14626
+ }
14627
+ function isUuidLike(value) {
14628
+ return UUID_RE.test(value.trim());
14629
+ }
14630
+ function isPlausibleCardCandidate(candidate, fullValue) {
14631
+ const digits = digitsOnly(candidate);
14632
+ if (digits.length < 13 || digits.length > 19) return false;
14633
+ if (!passesLuhn(candidate)) return false;
14634
+ const trimmed = fullValue.trim();
14635
+ if (isUuidLike(trimmed)) return false;
14636
+ if (EPOCH_MS_RE.test(digits) && digitsOnly(trimmed) === digits && /^\d+$/.test(trimmed)) {
14637
+ return false;
14638
+ }
14639
+ const start = fullValue.indexOf(candidate);
14640
+ if (start >= 0) {
14641
+ const before = fullValue[start - 1];
14642
+ const after = fullValue[start + candidate.length];
14643
+ if (before && /[A-Za-z0-9_]/.test(before)) return false;
14644
+ if (after && /[A-Za-z0-9_]/.test(after)) return false;
14645
+ }
14646
+ return true;
14647
+ }
14648
+ function looksLikePathOrPackageOrUrl(value) {
14649
+ const trimmed = value.trim();
14650
+ if (/^(?:[a-z][a-z0-9+.-]*:)?\/\//i.test(trimmed)) return true;
14651
+ if (/^webpack:/i.test(trimmed)) return true;
14652
+ if (/^@[A-Za-z0-9_.-]+\/[A-Za-z0-9_.@/-]+$/.test(trimmed)) return true;
14653
+ if (/^[A-Za-z]:[\\/]/.test(trimmed) || /^[/\\]/.test(trimmed)) return true;
14654
+ if (/[\\/]/.test(trimmed) && !/\s/.test(trimmed)) return true;
14655
+ return false;
14656
+ }
14657
+ function emailMatchIsPathAdjacent(value, index, length) {
14658
+ const before = value[index - 1];
14659
+ const after = value[index + length];
14660
+ return before === "/" || before === "\\" || after === "/" || after === "\\";
14661
+ }
14662
+ function valueContainsEmail(value) {
14663
+ if (looksLikePathOrPackageOrUrl(value)) return false;
14664
+ EMAIL_RE.lastIndex = 0;
14665
+ for (const match of value.matchAll(EMAIL_RE)) {
14666
+ const text = match[0] ?? "";
14667
+ const index = match.index ?? 0;
14668
+ if (emailMatchIsPathAdjacent(value, index, text.length)) continue;
14669
+ return true;
14670
+ }
14671
+ return false;
14672
+ }
14521
14673
  var credentialDetectors = [
14522
14674
  patternDetector({
14523
14675
  id: "value.authorizationHeader",
@@ -14565,10 +14717,12 @@ var credentialDetectors = [
14565
14717
  matchKind: "value",
14566
14718
  detect(input3) {
14567
14719
  if (typeof input3.value !== "string") return [];
14720
+ if (pathSuggestsNonCard(input3.path)) return [];
14721
+ if (isUuidLike(input3.value)) return [];
14568
14722
  const candidatePattern = /(?:\d[ -]?){13,19}/g;
14569
14723
  for (const match of input3.value.matchAll(candidatePattern)) {
14570
14724
  const candidate = match[0] ?? "";
14571
- if (passesLuhn(candidate)) {
14725
+ if (isPlausibleCardCandidate(candidate, input3.value)) {
14572
14726
  return [{ action: "replace", severity: "error", matchKind: "value" }];
14573
14727
  }
14574
14728
  }
@@ -14577,10 +14731,15 @@ var credentialDetectors = [
14577
14731
  }
14578
14732
  ];
14579
14733
  var identifierDetectors = [
14580
- patternDetector({
14734
+ {
14581
14735
  id: "value.email",
14582
- pattern: /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/i
14583
- }),
14736
+ severity: "warning",
14737
+ matchKind: "value",
14738
+ detect(input3) {
14739
+ if (typeof input3.value !== "string") return [];
14740
+ return valueContainsEmail(input3.value) ? [{ action: "replace", severity: "warning", matchKind: "value" }] : [];
14741
+ }
14742
+ },
14584
14743
  patternDetector({
14585
14744
  id: "value.phone",
14586
14745
  pattern: /\b(?:\+\d{1,3}[\s.-]?)?(?:\(?\d{3}\)?[\s.-])\d{3}[\s.-]\d{4}\b/
@@ -14904,6 +15063,16 @@ function redactJsonlText(content, profile) {
14904
15063
  };
14905
15064
  }
14906
15065
  function redactDocument(content, profile) {
15066
+ const trimmed = content.trim();
15067
+ if (trimmed.startsWith("{")) {
15068
+ try {
15069
+ const parsed = JSON.parse(trimmed);
15070
+ if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) && ("schemaVersion" in parsed || "eventId" in parsed || "runId" in parsed)) {
15071
+ return redactJsonlText(content, profile);
15072
+ }
15073
+ } catch {
15074
+ }
15075
+ }
14907
15076
  try {
14908
15077
  return redactJsonText(content, profile);
14909
15078
  } catch {
@@ -18399,8 +18568,6 @@ async function evalCommand(target, options = {}, stdin = process.stdin) {
18399
18568
  else if (options.markdown) console.log(renderEvalMarkdown(result).trimEnd());
18400
18569
  else printHuman2(result);
18401
18570
  }
18402
-
18403
- // packages/cli/src/safety.ts
18404
18571
  var BEST_EFFORT_NOTE = "Best-effort local safety verification only; not a compliance, privacy, security, or regulatory certification.";
18405
18572
  var DEFAULT_MAX_STRING_LENGTH = 16384;
18406
18573
  var DEFAULT_MAX_ARRAY_LENGTH = 1e3;
@@ -18458,7 +18625,7 @@ function resultFromParts(parts) {
18458
18625
  const diagnostics = [...parts.diagnostics ?? []];
18459
18626
  const warnings = [...parts.warnings ?? []];
18460
18627
  const unsupportedFields = [...parts.unsupportedFields ?? []];
18461
- const status = statusFrom(findings, diagnostics);
18628
+ const status = parts.status ?? statusFrom(findings, diagnostics);
18462
18629
  return {
18463
18630
  ok: status === "SAFE" || status === "SAFE WITH WARNINGS",
18464
18631
  command: parts.command,
@@ -18474,7 +18641,17 @@ function resultFromParts(parts) {
18474
18641
  diagnostics,
18475
18642
  warnings,
18476
18643
  unsupportedFields,
18477
- note: BEST_EFFORT_NOTE
18644
+ note: BEST_EFFORT_NOTE,
18645
+ ...parts.sourceAssessment !== void 0 ? { sourceAssessment: parts.sourceAssessment } : {},
18646
+ ...parts.artifactAssessment !== void 0 ? { artifactAssessment: parts.artifactAssessment } : {},
18647
+ ...parts.redactionSummary !== void 0 ? { redactionSummary: parts.redactionSummary } : {}
18648
+ };
18649
+ }
18650
+ function layerFromResult(result) {
18651
+ return {
18652
+ status: result.status,
18653
+ summary: result.summary,
18654
+ findings: result.findings
18478
18655
  };
18479
18656
  }
18480
18657
  function readErrorResult3(command, error) {
@@ -18538,6 +18715,21 @@ function flattenNodes2(nodes) {
18538
18715
  function detectorSeverity(finding) {
18539
18716
  return finding.severity;
18540
18717
  }
18718
+ function classifyRedactionDetector(detector) {
18719
+ if (detector === "value.creditCard" || detector === "value.email" || detector === "value.phone") {
18720
+ return { category: "personal-data", confidence: "high" };
18721
+ }
18722
+ if (detector === "value.ipv4" || detector === "value.ipv6") {
18723
+ return { category: "identifier", confidence: "medium" };
18724
+ }
18725
+ if (detector.startsWith("value.") && (detector.includes("Token") || detector.includes("Key") || detector.includes("jwt") || detector.includes("authorization") || detector.includes("bearer") || detector.includes("cookie") || detector.includes("privateKey") || detector.includes("github") || detector.includes("aws") || detector.includes("provider"))) {
18726
+ return { category: "credential", confidence: "high" };
18727
+ }
18728
+ if (detector.startsWith("key.")) {
18729
+ return { category: "credential", confidence: "medium" };
18730
+ }
18731
+ return { category: "credential", confidence: "medium" };
18732
+ }
18541
18733
  function redactionDetectorFindings(read, runId) {
18542
18734
  const runs = runId === void 0 ? read.runs : read.runs.filter((run) => run.runId === runId);
18543
18735
  const out = [];
@@ -18548,6 +18740,7 @@ function redactionDetectorFindings(read, runId) {
18548
18740
  const result = redact(attrs, { profile: "share" });
18549
18741
  for (const finding of result.findings) {
18550
18742
  if (finding.action === "keep") continue;
18743
+ const taxonomy = classifyRedactionDetector(finding.detector);
18551
18744
  out.push({
18552
18745
  ruleId: "safety.redactDetector",
18553
18746
  severity: detectorSeverity(finding),
@@ -18565,7 +18758,11 @@ function redactionDetectorFindings(read, runId) {
18565
18758
  ...node.event.status !== void 0 ? { status: node.event.status } : {},
18566
18759
  path: `attributes.${finding.path.replace(/^\$\.?/, "")}`
18567
18760
  }
18568
- ]
18761
+ ],
18762
+ category: taxonomy.category,
18763
+ confidence: taxonomy.confidence,
18764
+ detector: finding.detector,
18765
+ action: finding.action
18569
18766
  });
18570
18767
  }
18571
18768
  }
@@ -18581,13 +18778,49 @@ function exitCodeFor3(result) {
18581
18778
  if (result.status === "UNSAFE") return 1;
18582
18779
  return 2;
18583
18780
  }
18584
- function printJson3(result) {
18585
- console.log(JSON.stringify(stable4(result), null, 2));
18781
+ function explainFinding(finding, blocksBundle) {
18782
+ const path37 = finding.evidence[0]?.path ?? "(unknown path)";
18783
+ const category = finding.category ?? "structure";
18784
+ const confidence = finding.confidence ?? "medium";
18785
+ const detector = finding.detector ?? finding.ruleId;
18786
+ const action = finding.action ?? "review";
18787
+ const redactionHint = category === "credential" || category === "personal-data" || category === "raw-content" || action.includes("redact") ? "Usually removable by share/strict redaction before bundling." : "May require omitting the field, lowering limits, or an explicit local override.";
18788
+ return [
18789
+ ` Matched: detector=${detector}; path=${path37}; category=${category}`,
18790
+ ` Why: ${finding.message}`,
18791
+ ` Confidence: ${confidence}`,
18792
+ ` Redaction: ${redactionHint}`,
18793
+ ` Override: configure a custom redaction/detector rule locally (see docs/SAFETY-POLICY.md); do not weaken defaults globally.`,
18794
+ ` Bundle gate: ${blocksBundle ? "blocks share-safe bundle unless --allow-unsafe" : "does not block by itself (warning/info)"}`
18795
+ ];
18796
+ }
18797
+ function findingExplanation(finding) {
18798
+ const blocks = finding.severity === "error" || finding.status === "fail";
18799
+ return {
18800
+ ruleId: finding.ruleId,
18801
+ detector: finding.detector ?? finding.ruleId,
18802
+ path: finding.evidence[0]?.path,
18803
+ category: finding.category,
18804
+ confidence: finding.confidence,
18805
+ action: finding.action,
18806
+ blocksBundle: blocks,
18807
+ // Never include matched secret/PII values.
18808
+ message: finding.message
18809
+ };
18586
18810
  }
18587
- function printHuman3(result) {
18811
+ function printHuman3(result, explain = false) {
18588
18812
  console.log(`Safety status: ${result.status}`);
18589
18813
  console.log(`Format: ${result.format}`);
18590
18814
  if (result.runId !== void 0) console.log(`Run: ${result.runId}`);
18815
+ if (result.sourceAssessment !== void 0 && result.artifactAssessment !== void 0) {
18816
+ console.log(`Source assessment: ${result.sourceAssessment.status}`);
18817
+ console.log(`Artifact assessment: ${result.artifactAssessment.status}`);
18818
+ if (result.redactionSummary !== void 0) {
18819
+ console.log(
18820
+ `Redaction: profile=${result.redactionSummary.profile}, findings=${result.redactionSummary.findings}`
18821
+ );
18822
+ }
18823
+ }
18591
18824
  console.log(
18592
18825
  `Summary: ${result.summary.findings} finding(s), ${result.summary.warnings} warning(s), ${result.summary.errors} error(s)`
18593
18826
  );
@@ -18596,45 +18829,82 @@ function printHuman3(result) {
18596
18829
  }
18597
18830
  for (const finding of result.findings) {
18598
18831
  const path37 = finding.evidence[0]?.path;
18599
- console.log(`- ${finding.ruleId}: ${finding.message}${path37 ? ` (${path37})` : ""}`);
18832
+ const taxonomy = finding.category !== void 0 || finding.confidence !== void 0 ? ` [${[finding.category, finding.confidence].filter(Boolean).join("/")}]` : "";
18833
+ console.log(`- ${finding.ruleId}: ${finding.message}${path37 ? ` (${path37})` : ""}${taxonomy}`);
18834
+ if (explain) {
18835
+ const blocks = finding.severity === "error" || finding.status === "fail";
18836
+ for (const line of explainFinding(finding, blocks)) {
18837
+ console.log(line);
18838
+ }
18839
+ }
18600
18840
  }
18601
18841
  console.log(`Note: ${result.note}`);
18602
18842
  }
18843
+ function printJson3(result, explain = false) {
18844
+ const payload = explain === true ? { ...result, explanations: result.findings.map(findingExplanation) } : result;
18845
+ console.log(JSON.stringify(stable4(payload), null, 2));
18846
+ }
18603
18847
  async function safetyCommand(command, target, options, stdin) {
18604
18848
  let result;
18605
18849
  try {
18606
- const rules = buildSafetyRules(options);
18607
18850
  const input3 = await inputFromTarget(target, options, stdin);
18608
18851
  const read = await openTrace(input3, {
18609
18852
  ...options.format !== void 0 ? { format: options.format } : {}
18610
18853
  });
18611
- const checkResult = runTraceChecks(
18612
- { read },
18613
- {
18614
- rules,
18615
- ...options.run !== void 0 ? { runId: options.run } : {}
18616
- }
18617
- );
18618
- const detectorFindings = checkResult.diagnostics.length === 0 ? redactionDetectorFindings(read, checkResult.runId) : [];
18619
- result = resultFromParts({
18620
- command,
18621
- format: checkResult.format,
18622
- runId: checkResult.runId,
18623
- findings: [...checkResult.findings, ...detectorFindings],
18624
- diagnostics: [
18625
- ...checkResult.diagnostics.map(diagnosticFromCheck),
18626
- ...warningDiagnostics(read.warnings, read.unsupportedFields)
18627
- ],
18628
- warnings: read.warnings,
18629
- unsupportedFields: read.unsupportedFields
18854
+ const source = assessOpenedTrace(read, {
18855
+ ...options,
18856
+ ...options.run !== void 0 ? { runId: options.run } : {}
18630
18857
  });
18858
+ if (command === "scan") {
18859
+ result = { ...source, command: "scan" };
18860
+ } else {
18861
+ const profile = options.redactionProfile ?? "share";
18862
+ const rawContent = input3.type === "string" ? input3.content : input3.type === "file" ? await promises.readFile(input3.path, "utf-8") : void 0;
18863
+ if (rawContent === void 0) {
18864
+ result = {
18865
+ ...source,
18866
+ command: "verify-safe",
18867
+ sourceAssessment: layerFromResult(source)
18868
+ };
18869
+ } else {
18870
+ const redacted = redactTraceContent(rawContent, profile);
18871
+ const artifactRead = await openTrace(
18872
+ { type: "string", content: redacted.content },
18873
+ { format: options.format ?? "agent-inspect-jsonl" }
18874
+ );
18875
+ const artifact = assessOpenedTrace(artifactRead, {
18876
+ ...options,
18877
+ ...options.run !== void 0 ? { runId: options.run } : {}
18878
+ });
18879
+ const detectors = [
18880
+ ...new Set(redacted.findings.map((finding) => finding.detector))
18881
+ ].sort((a, b) => a.localeCompare(b));
18882
+ result = resultFromParts({
18883
+ command: "verify-safe",
18884
+ format: artifact.format,
18885
+ runId: artifact.runId ?? source.runId,
18886
+ findings: artifact.findings,
18887
+ diagnostics: artifact.diagnostics,
18888
+ warnings: artifact.warnings,
18889
+ unsupportedFields: artifact.unsupportedFields,
18890
+ status: artifact.status,
18891
+ sourceAssessment: layerFromResult(source),
18892
+ artifactAssessment: layerFromResult(artifact),
18893
+ redactionSummary: {
18894
+ profile,
18895
+ findings: redacted.findings.length,
18896
+ detectors
18897
+ }
18898
+ });
18899
+ }
18900
+ }
18631
18901
  } catch (error) {
18632
18902
  const message = error instanceof Error ? error.message : String(error);
18633
18903
  result = message.startsWith("--") ? invalidArgumentResult(command, error) : readErrorResult3(command, error);
18634
18904
  }
18635
18905
  process.exitCode = exitCodeFor3(result);
18636
- if (options.json) printJson3(result);
18637
- else printHuman3(result);
18906
+ if (options.json) printJson3(result, options.explain === true);
18907
+ else printHuman3(result, options.explain === true);
18638
18908
  }
18639
18909
  function scanCommand(target, options = {}, stdin = process.stdin) {
18640
18910
  return safetyCommand("scan", target, options, stdin);
@@ -19116,15 +19386,8 @@ async function bundleCommand(runIdArg, options = {}) {
19116
19386
  process.exitCode = 1;
19117
19387
  return;
19118
19388
  }
19119
- const safety = assessOpenedTrace(read, { run: runId });
19120
- const status = safetyStatusFromAssess(safety.status);
19121
- checkRuns.push({
19122
- runId,
19123
- status,
19124
- errors: safety.summary.errors,
19125
- warnings: safety.summary.warnings,
19126
- findings: safety.summary.findings
19127
- });
19389
+ const sourceSafety = assessOpenedTrace(read, { run: runId });
19390
+ const sourceStatus = safetyStatusFromAssess(sourceSafety.status);
19128
19391
  const redacted = redactTraceContent(rawContent, toReportProfile(profile));
19129
19392
  redactedJsonlByRun.set(runId, redacted.content);
19130
19393
  combinedJsonl += redacted.content.endsWith("\n") ? redacted.content : `${redacted.content}
@@ -19137,6 +19400,34 @@ async function bundleCommand(runIdArg, options = {}) {
19137
19400
  findings: redacted.findings.length,
19138
19401
  detectors
19139
19402
  });
19403
+ let artifactSafety = sourceSafety;
19404
+ try {
19405
+ const artifactRead = await openTrace(
19406
+ { type: "string", content: redacted.content },
19407
+ { format: "agent-inspect-jsonl" }
19408
+ );
19409
+ artifactSafety = assessOpenedTrace(artifactRead, { run: runId });
19410
+ } catch {
19411
+ artifactSafety = {
19412
+ ...sourceSafety,
19413
+ status: "UNKNOWN",
19414
+ ok: false,
19415
+ summary: {
19416
+ findings: sourceSafety.summary.findings,
19417
+ warnings: sourceSafety.summary.warnings,
19418
+ errors: Math.max(1, sourceSafety.summary.errors)
19419
+ }
19420
+ };
19421
+ }
19422
+ const status = safetyStatusFromAssess(artifactSafety.status);
19423
+ checkRuns.push({
19424
+ runId,
19425
+ status,
19426
+ sourceStatus,
19427
+ errors: artifactSafety.summary.errors,
19428
+ warnings: artifactSafety.summary.warnings,
19429
+ findings: artifactSafety.summary.findings
19430
+ });
19140
19431
  const selected = read.runs.find((run) => run.runId === runId);
19141
19432
  const runReport = exportRunTree(selected ?? read.runs[0], {
19142
19433
  format: "html",
@@ -19165,13 +19456,13 @@ async function bundleCommand(runIdArg, options = {}) {
19165
19456
  console.log(
19166
19457
  writeJson4({
19167
19458
  ok: false,
19168
- error: `Bundle safety status is ${checks2.aggregateStatus}. Pass --allow-unsafe to override.`,
19459
+ error: `Bundle artifact safety status is ${checks2.aggregateStatus}. Pass --allow-unsafe to override.`,
19169
19460
  checks: checks2
19170
19461
  }).trimEnd()
19171
19462
  );
19172
19463
  } else {
19173
19464
  console.error(
19174
- `[AgentInspect] bundle refused: safety status is ${checks2.aggregateStatus}. Pass --allow-unsafe to override.`
19465
+ `[AgentInspect] bundle refused: artifact safety status is ${checks2.aggregateStatus}. Pass --allow-unsafe to override.`
19175
19466
  );
19176
19467
  }
19177
19468
  process.exitCode = 1;
@@ -21516,7 +21807,7 @@ function createCliProgram() {
21516
21807
  "openinference-json",
21517
21808
  "otlp-json"
21518
21809
  ])
21519
- ).option("--run <run-id>", "select a run when the trace contains multiple runs").option("--json", "print deterministic JSON safety result").option("--max-string-length <number>", "unsafe threshold for string values").option("--max-array-length <number>", "unsafe threshold for array values").option("--max-object-keys <number>", "unsafe threshold for object key counts").option("--max-serialized-bytes <number>", "unsafe threshold for serialized values").action((target, opts) => {
21810
+ ).option("--run <run-id>", "select a run when the trace contains multiple runs").option("--json", "print deterministic JSON safety result").option("--explain", "explain each finding (path, confidence, redaction, override, bundle gate)").option("--max-string-length <number>", "unsafe threshold for string values").option("--max-array-length <number>", "unsafe threshold for array values").option("--max-object-keys <number>", "unsafe threshold for object key counts").option("--max-serialized-bytes <number>", "unsafe threshold for serialized values").action((target, opts) => {
21520
21811
  runCommand(() => scanCommand(target, opts));
21521
21812
  });
21522
21813
  program.command("verify-safe").description("Best-effort local trace safety verification").argument("<trace-path-or-run-id>", "trace file, directory, stdin -, or run id").option("--dir <path>", "trace directory for run-id lookup").addOption(
@@ -21525,7 +21816,7 @@ function createCliProgram() {
21525
21816
  "openinference-json",
21526
21817
  "otlp-json"
21527
21818
  ])
21528
- ).option("--run <run-id>", "select a run when the trace contains multiple runs").option("--json", "print deterministic JSON safety result").option("--max-string-length <number>", "unsafe threshold for string values").option("--max-array-length <number>", "unsafe threshold for array values").option("--max-object-keys <number>", "unsafe threshold for object key counts").option("--max-serialized-bytes <number>", "unsafe threshold for serialized values").action((target, opts) => {
21819
+ ).option("--run <run-id>", "select a run when the trace contains multiple runs").option("--json", "print deterministic JSON safety result").option("--explain", "explain each finding (path, confidence, redaction, override, bundle gate)").option("--max-string-length <number>", "unsafe threshold for string values").option("--max-array-length <number>", "unsafe threshold for array values").option("--max-object-keys <number>", "unsafe threshold for object key counts").option("--max-serialized-bytes <number>", "unsafe threshold for serialized values").action((target, opts) => {
21529
21820
  runCommand(() => verifySafeCommand(target, opts));
21530
21821
  });
21531
21822
  program.command("artifacts").description("Create safe local CI trace artifacts").argument("<trace-path-or-run-id>", "trace file, directory, stdin -, or run id").requiredOption("--output-dir <path>", "directory for generated artifacts").option("--dir <path>", "trace directory for run-id lookup").addOption(