@yiaany/ghostapi 0.1.9 → 0.2.1

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 (292) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/CONTRIBUTING.md +132 -119
  3. package/LICENSE +21 -21
  4. package/PROVENANCE.md +13 -0
  5. package/README.md +78 -84
  6. package/SECURITY.md +56 -53
  7. package/dist/actions/gateway.js +320 -72
  8. package/dist/actions/gateway.js.map +1 -1
  9. package/dist/actions/index.d.ts +2 -2
  10. package/dist/actions/index.js +1 -1
  11. package/dist/actions/index.js.map +1 -1
  12. package/dist/agents/agentPrompt.js +7 -5
  13. package/dist/agents/agentPrompt.js.map +1 -1
  14. package/dist/ai/aiClient.js +5 -5
  15. package/dist/ai/aiClient.js.map +1 -1
  16. package/dist/ai/aiGenerator.js +66 -26
  17. package/dist/ai/aiGenerator.js.map +1 -1
  18. package/dist/ai/genericInference.js +151 -36
  19. package/dist/ai/genericInference.js.map +1 -1
  20. package/dist/ai/prompts.js +55 -49
  21. package/dist/ai/prompts.js.map +1 -1
  22. package/dist/approvals/approvalInbox.js +649 -133
  23. package/dist/approvals/approvalInbox.js.map +1 -1
  24. package/dist/approvals/index.d.ts +2 -2
  25. package/dist/approvals/index.js +1 -1
  26. package/dist/approvals/index.js.map +1 -1
  27. package/dist/behavior/behaviorStore.js +34 -14
  28. package/dist/behavior/behaviorStore.js.map +1 -1
  29. package/dist/cache/index.js +14 -5
  30. package/dist/cache/index.js.map +1 -1
  31. package/dist/cli/index.js +226 -93
  32. package/dist/cli/index.js.map +1 -1
  33. package/dist/cli/openUrl.js +17 -3
  34. package/dist/cli/openUrl.js.map +1 -1
  35. package/dist/cli/parser.js +79 -18
  36. package/dist/cli/parser.js.map +1 -1
  37. package/dist/config/dataPaths.js +4 -2
  38. package/dist/config/dataPaths.js.map +1 -1
  39. package/dist/config/localConfig.js +5 -2
  40. package/dist/config/localConfig.js.map +1 -1
  41. package/dist/config/serverConfig.js +11 -5
  42. package/dist/config/serverConfig.js.map +1 -1
  43. package/dist/contracts/contract.js +311 -83
  44. package/dist/contracts/contract.js.map +1 -1
  45. package/dist/contracts/index.d.ts +2 -2
  46. package/dist/contracts/index.js +1 -1
  47. package/dist/contracts/index.js.map +1 -1
  48. package/dist/credentials/credentialBroker.js +391 -79
  49. package/dist/credentials/credentialBroker.js.map +1 -1
  50. package/dist/credentials/index.d.ts +2 -2
  51. package/dist/credentials/index.js +1 -1
  52. package/dist/credentials/index.js.map +1 -1
  53. package/dist/dashboard/app.js +823 -685
  54. package/dist/dashboard/dashboard.js.map +1 -1
  55. package/dist/dashboard/index.html +704 -267
  56. package/dist/dashboard/styles.css +1040 -349
  57. package/dist/egress/capabilities.js +55 -25
  58. package/dist/egress/capabilities.js.map +1 -1
  59. package/dist/egress/linuxBootstrap.js +47 -11
  60. package/dist/egress/linuxBootstrap.js.map +1 -1
  61. package/dist/egress/run.js +170 -31
  62. package/dist/egress/run.js.map +1 -1
  63. package/dist/errors/errorEngine.js +15 -3
  64. package/dist/errors/errorEngine.js.map +1 -1
  65. package/dist/errors/providerErrors.js.map +1 -1
  66. package/dist/evals/evals.js +304 -78
  67. package/dist/evals/evals.js.map +1 -1
  68. package/dist/evals/index.d.ts +2 -2
  69. package/dist/evals/index.js +1 -1
  70. package/dist/evals/index.js.map +1 -1
  71. package/dist/evidence/index.d.ts +2 -2
  72. package/dist/evidence/index.js +1 -1
  73. package/dist/evidence/index.js.map +1 -1
  74. package/dist/evidence/report.d.ts +1 -1
  75. package/dist/evidence/report.js +263 -79
  76. package/dist/evidence/report.js.map +1 -1
  77. package/dist/fault/faultLab.js +24 -9
  78. package/dist/fault/faultLab.js.map +1 -1
  79. package/dist/index.d.ts +42 -42
  80. package/dist/index.js +21 -21
  81. package/dist/index.js.map +1 -1
  82. package/dist/inventory/analysis.d.ts +1 -1
  83. package/dist/inventory/analysis.js +174 -50
  84. package/dist/inventory/analysis.js.map +1 -1
  85. package/dist/inventory/index.d.ts +4 -4
  86. package/dist/inventory/index.js +3 -3
  87. package/dist/inventory/index.js.map +1 -1
  88. package/dist/inventory/inventoryController.js +362 -115
  89. package/dist/inventory/inventoryController.js.map +1 -1
  90. package/dist/inventory/types.js +680 -140
  91. package/dist/inventory/types.js.map +1 -1
  92. package/dist/ledger/actionLedger.js +355 -78
  93. package/dist/ledger/actionLedger.js.map +1 -1
  94. package/dist/ledger/index.d.ts +2 -2
  95. package/dist/ledger/index.js +1 -1
  96. package/dist/ledger/index.js.map +1 -1
  97. package/dist/mcp/server.js +26 -11
  98. package/dist/mcp/server.js.map +1 -1
  99. package/dist/policy/index.d.ts +2 -2
  100. package/dist/policy/index.js +1 -1
  101. package/dist/policy/index.js.map +1 -1
  102. package/dist/policy/policy.js +112 -32
  103. package/dist/policy/policy.js.map +1 -1
  104. package/dist/productTelemetry/index.js +71 -20
  105. package/dist/productTelemetry/index.js.map +1 -1
  106. package/dist/providers/conformance.js +10 -5
  107. package/dist/providers/conformance.js.map +1 -1
  108. package/dist/providers/discord.js +2 -2
  109. package/dist/providers/generic.js +3 -3
  110. package/dist/providers/github.js +2 -2
  111. package/dist/providers/index.d.ts +3 -3
  112. package/dist/providers/index.js +2 -2
  113. package/dist/providers/index.js.map +1 -1
  114. package/dist/providers/openai.js +3 -3
  115. package/dist/providers/packs/resendPack.js +51 -25
  116. package/dist/providers/packs/resendPack.js.map +1 -1
  117. package/dist/providers/packs/stripePack.js +590 -121
  118. package/dist/providers/packs/stripePack.js.map +1 -1
  119. package/dist/providers/registry.js +4 -4
  120. package/dist/providers/registry.js.map +1 -1
  121. package/dist/providers/runtime.js +7 -6
  122. package/dist/providers/runtime.js.map +1 -1
  123. package/dist/providers/stripeWebhook.js +39 -10
  124. package/dist/providers/stripeWebhook.js.map +1 -1
  125. package/dist/providers/twilio.js +2 -2
  126. package/dist/proxy/cacheKey.js +9 -2
  127. package/dist/proxy/cacheKey.js.map +1 -1
  128. package/dist/proxy/providerDetector.js +22 -9
  129. package/dist/proxy/providerDetector.js.map +1 -1
  130. package/dist/proxy/proxyHandler.js +57 -22
  131. package/dist/proxy/proxyHandler.js.map +1 -1
  132. package/dist/proxy/requestNormalizer.js +4 -2
  133. package/dist/proxy/requestNormalizer.js.map +1 -1
  134. package/dist/reliability/costGovernance.js +211 -49
  135. package/dist/reliability/costGovernance.js.map +1 -1
  136. package/dist/reliability/index.d.ts +8 -8
  137. package/dist/reliability/index.js +4 -4
  138. package/dist/reliability/index.js.map +1 -1
  139. package/dist/reliability/reconciliation.js +274 -65
  140. package/dist/reliability/reconciliation.js.map +1 -1
  141. package/dist/reliability/runtimeHealth.js +127 -35
  142. package/dist/reliability/runtimeHealth.js.map +1 -1
  143. package/dist/reliability/slo.js +257 -50
  144. package/dist/reliability/slo.js.map +1 -1
  145. package/dist/report/safetyReport.d.ts +6 -0
  146. package/dist/report/safetyReport.js +56 -12
  147. package/dist/report/safetyReport.js.map +1 -1
  148. package/dist/rules/aiRules.js +5 -5
  149. package/dist/rules/aiRules.js.map +1 -1
  150. package/dist/safety/index.d.ts +2 -2
  151. package/dist/safety/index.js +1 -1
  152. package/dist/safety/index.js.map +1 -1
  153. package/dist/safety/safetyController.js +675 -165
  154. package/dist/safety/safetyController.js.map +1 -1
  155. package/dist/scenarios/scenarioBundle.js +204 -71
  156. package/dist/scenarios/scenarioBundle.js.map +1 -1
  157. package/dist/scenarios/scenarioStore.js +88 -29
  158. package/dist/scenarios/scenarioStore.js.map +1 -1
  159. package/dist/security/headerSanitizer.js +52 -10
  160. package/dist/security/headerSanitizer.js.map +1 -1
  161. package/dist/security/secrets.js +10 -5
  162. package/dist/security/secrets.js.map +1 -1
  163. package/dist/server/accessControl.js +37 -10
  164. package/dist/server/accessControl.js.map +1 -1
  165. package/dist/server/createServer.js +1 -1
  166. package/dist/server/createServer.js.map +1 -1
  167. package/dist/server/eventsStore.js +15 -5
  168. package/dist/server/eventsStore.js.map +1 -1
  169. package/dist/server/routes.js +61 -19
  170. package/dist/server/routes.js.map +1 -1
  171. package/dist/server/sse.js +1 -1
  172. package/dist/server/sse.js.map +1 -1
  173. package/dist/setup/sdkDetector.js +7 -2
  174. package/dist/setup/sdkDetector.js.map +1 -1
  175. package/dist/setup/setupGenerator.js +136 -48
  176. package/dist/setup/setupGenerator.js.map +1 -1
  177. package/dist/state/stateExtractor.js +3 -1
  178. package/dist/state/stateExtractor.js.map +1 -1
  179. package/dist/state/stateResolver.js +10 -7
  180. package/dist/state/stateResolver.js.map +1 -1
  181. package/dist/state/stateStore.js +13 -4
  182. package/dist/state/stateStore.js.map +1 -1
  183. package/dist/storage/fileStore.js +12 -4
  184. package/dist/storage/fileStore.js.map +1 -1
  185. package/dist/teamControl/controlPlane.js +846 -174
  186. package/dist/teamControl/controlPlane.js.map +1 -1
  187. package/dist/teamControl/deployment.js +19 -5
  188. package/dist/teamControl/deployment.js.map +1 -1
  189. package/dist/teamControl/index.d.ts +4 -4
  190. package/dist/teamControl/index.js +2 -2
  191. package/dist/teamControl/index.js.map +1 -1
  192. package/dist/tests/testGenerator.js +15 -7
  193. package/dist/tests/testGenerator.js.map +1 -1
  194. package/dist/trust/index.d.ts +2 -2
  195. package/dist/trust/index.js +1 -1
  196. package/dist/trust/index.js.map +1 -1
  197. package/dist/trust/trustLadder.js +309 -63
  198. package/dist/trust/trustLadder.js.map +1 -1
  199. package/dist/worlds/index.d.ts +2 -2
  200. package/dist/worlds/index.js +1 -1
  201. package/dist/worlds/index.js.map +1 -1
  202. package/dist/worlds/worlds.js +364 -65
  203. package/dist/worlds/worlds.js.map +1 -1
  204. package/docs/assets/dashboard.png +0 -0
  205. package/docs/ci.md +49 -49
  206. package/docs/github-actions.md +84 -84
  207. package/docs/mcp.md +58 -52
  208. package/docs/policy.md +63 -61
  209. package/docs/providers/authoring-packs.md +180 -175
  210. package/docs/providers/stripe-core-pack.md +84 -82
  211. package/docs/releases/README.md +13 -0
  212. package/docs/security/action-gateway-threat-model.md +34 -34
  213. package/docs/security/action-ledger-incident-replay-threat-model.md +42 -42
  214. package/docs/security/approval-inbox-threat-model.md +35 -35
  215. package/docs/security/credential-broker-threat-model.md +52 -52
  216. package/docs/security/egress-threat-model.md +126 -126
  217. package/docs/security/inventory-threat-model.md +104 -104
  218. package/docs/security/kill-switch-budgets-threat-model.md +37 -37
  219. package/docs/security/reliability-threat-model.md +111 -111
  220. package/docs/security/trust-ladder-threat-model.md +33 -33
  221. package/docs/telemetry.md +14 -0
  222. package/docs/usage.md +680 -571
  223. package/examples/README.md +10 -10
  224. package/examples/agent-instructions/README.md +39 -39
  225. package/examples/ci-smoke/README.md +21 -21
  226. package/examples/ci-smoke/ghostapi.policy.yaml +22 -22
  227. package/examples/ci-smoke/package.json +9 -9
  228. package/examples/ci-smoke/production-egress.mjs +4 -2
  229. package/examples/ci-smoke/safe.mjs +21 -17
  230. package/examples/evals/README.md +24 -24
  231. package/examples/evals/retry-after.eval.json +71 -52
  232. package/examples/generic-rest/README.md +27 -27
  233. package/examples/github-fetch/README.md +38 -35
  234. package/examples/openai-streaming/README.md +16 -16
  235. package/examples/openai-streaming/streaming-tool-call.mjs +44 -31
  236. package/examples/policy/ghostapi.policy.yaml +27 -27
  237. package/examples/record-replay/README.md +19 -19
  238. package/examples/record-replay/replay-requests.json +22 -22
  239. package/examples/record-replay/stripe-checkout.har.json +30 -30
  240. package/examples/resend-node/README.md +22 -22
  241. package/examples/stripe-node/README.md +35 -35
  242. package/examples/stripe-node/checkout-flow.mjs +87 -61
  243. package/examples/twilio-node/README.md +21 -21
  244. package/examples/worlds/README.md +19 -19
  245. package/examples/worlds/subscription-recovery.mjs +21 -12
  246. package/package.json +28 -6
  247. package/dist/landing/assets/index-BXR41fAD.css +0 -1
  248. package/dist/landing/assets/index-CxWsopzP.css +0 -1
  249. package/dist/landing/assets/index-D8_ggMRE.js +0 -55
  250. package/dist/landing/assets/index-DZlpyhqd.js +0 -55
  251. package/dist/landing/assets/index-Dk-Xezs3.js +0 -55
  252. package/dist/landing/index.html +0 -15
  253. package/dist/landing/landing.d.ts +0 -3
  254. package/dist/landing/landing.js +0 -12
  255. package/dist/landing/landing.js.map +0 -1
  256. package/docs/commercial/README.md +0 -29
  257. package/docs/commercial/data-inventory.md +0 -36
  258. package/docs/commercial/manual-invoicing.md +0 -44
  259. package/docs/commercial/metrics.md +0 -51
  260. package/docs/commercial/pricing.md +0 -103
  261. package/docs/commercial/privacy-policy-draft.md +0 -41
  262. package/docs/commercial/terms-topics-for-counsel.md +0 -51
  263. package/docs/design-partners/README.md +0 -52
  264. package/docs/design-partners/demo-narratives.md +0 -33
  265. package/docs/design-partners/design-partner-offer.md +0 -39
  266. package/docs/design-partners/discovery-questions.md +0 -28
  267. package/docs/design-partners/feedback-capture-template.md +0 -48
  268. package/docs/design-partners/icp-one-pager.md +0 -43
  269. package/docs/design-partners/onboarding-checklist.md +0 -33
  270. package/docs/design-partners/pilot-success-criteria.md +0 -27
  271. package/docs/design-partners/pricing-interview-script.md +0 -18
  272. package/docs/design-partners/telemetry-plan.md +0 -48
  273. package/docs/development/baseline.md +0 -114
  274. package/docs/development/onboarding-smoke.md +0 -37
  275. package/docs/development/verification-0.1.8.md +0 -25
  276. package/docs/enterprise-product-roadmap-ru.md +0 -1879
  277. package/docs/fundraising/README.md +0 -87
  278. package/docs/fundraising/data-room-checklist.md +0 -72
  279. package/docs/fundraising/demo-script.md +0 -52
  280. package/docs/fundraising/design-partners-50.md +0 -74
  281. package/docs/fundraising/launch-posts.md +0 -37
  282. package/docs/fundraising/metrics-and-evidence.md +0 -30
  283. package/docs/fundraising/roadmap-12-month.md +0 -25
  284. package/docs/fundraising/technical-due-diligence-index.md +0 -37
  285. package/docs/fundraising/yc-application.md +0 -25
  286. package/docs/hosted-pilot.md +0 -111
  287. package/docs/operations/disaster-recovery-runbook.md +0 -81
  288. package/docs/operations/kill-switch-runbook.md +0 -32
  289. package/docs/release-checklist.md +0 -83
  290. package/docs/release-migration-and-rollback.md +0 -63
  291. package/docs/release-readiness.md +0 -39
  292. package/docs/team-control-plane.md +0 -72
@@ -1,13 +1,13 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { createRequire } from "node:module";
3
3
  import { lstat, mkdir, readFile, readdir, rm } from "node:fs/promises";
4
- import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
4
+ import { basename, dirname, isAbsolute, join, relative, resolve, sep, } from "node:path";
5
5
  import { getDataPaths } from "../config/dataPaths.js";
6
6
  import { detectEgressCapabilities } from "../egress/capabilities.js";
7
- import { evaluatePolicy, loadPolicyFile } from "../policy/index.js";
8
- import { sanitizeSecrets, sanitizeSecretString, isSecretFieldName } from "../security/secrets.js";
9
- import { atomicWriteJson, ensurePrivateDirectory } from "../storage/fileStore.js";
10
- import { diffContracts, loadContract } from "../contracts/index.js";
7
+ import { evaluatePolicy, loadPolicyFile, } from "../policy/index.js";
8
+ import { sanitizeSecrets, sanitizeSecretString, isSecretFieldName, } from "../security/secrets.js";
9
+ import { atomicWriteJson, ensurePrivateDirectory, } from "../storage/fileStore.js";
10
+ import { diffContracts, loadContract, } from "../contracts/index.js";
11
11
  const require = createRequire(import.meta.url);
12
12
  const packageJson = require("../../package.json");
13
13
  const MAX_EVENTS_FOR_REPORT = 1_000;
@@ -16,7 +16,19 @@ const MAX_REPORT_BYTES = 512 * 1024;
16
16
  const MAX_RUN_EVIDENCE_BYTES = 128 * 1024;
17
17
  const MAX_REPORTS_RETAINED = 20;
18
18
  const MAX_ATTEMPTS = 500;
19
- const SENSITIVE_QUERY_KEYS = new Set(["api_key", "apikey", "access_token", "refresh_token", "token", "secret", "key", "client_secret", "password", "authorization", "cookie"]);
19
+ const SENSITIVE_QUERY_KEYS = new Set([
20
+ "api_key",
21
+ "apikey",
22
+ "access_token",
23
+ "refresh_token",
24
+ "token",
25
+ "secret",
26
+ "key",
27
+ "client_secret",
28
+ "password",
29
+ "authorization",
30
+ "cookie",
31
+ ]);
20
32
  export class EvidenceReportError extends Error {
21
33
  constructor(message) {
22
34
  super(message);
@@ -33,21 +45,27 @@ export async function generateEvidenceReport(options = {}) {
33
45
  events,
34
46
  policy: loadedPolicy?.policy,
35
47
  policyHash: loadedPolicy?.hash ?? readString(run?.evidence.policy?.policyHash),
36
- requiredScenarios: loadedPolicy?.policy.requiredScenarios ?? readStringArray(run?.evidence.policy?.requiredScenarios),
48
+ requiredScenarios: loadedPolicy?.policy.requiredScenarios ??
49
+ readStringArray(run?.evidence.policy?.requiredScenarios),
37
50
  runEvidence: run?.evidence ?? null,
38
51
  contractDiff,
39
52
  generatedAt: options.generatedAt ?? new Date().toISOString(),
40
- ghostApiVersion: options.ghostApiVersion ?? readPackageVersion()
53
+ ghostApiVersion: options.ghostApiVersion ?? readPackageVersion(),
41
54
  });
42
55
  const path = await writeEvidenceReport(report, projectRoot, options.outPath);
43
56
  return { report, path };
44
57
  }
45
58
  export function buildEvidenceReport(input) {
46
- const sanitizedEvents = input.events.slice(0, MAX_EVENTS_FOR_REPORT).map((event) => sanitizeEvent(event));
59
+ const sanitizedEvents = input.events
60
+ .slice(0, MAX_EVENTS_FOR_REPORT)
61
+ .map((event) => sanitizeEvent(event));
47
62
  const capability = detectEgressCapabilities();
48
- const runEvents = Array.isArray(input.runEvidence?.events) ? input.runEvidence.events : [];
63
+ const runEvents = Array.isArray(input.runEvidence?.events)
64
+ ? input.runEvidence.events
65
+ : [];
49
66
  const runStatus = readRunStatus(input.runEvidence?.status);
50
- const runUsedNamespace = input.runEvidence?.backend === "linux-network-namespace" && runStatus === "finished";
67
+ const runUsedNamespace = input.runEvidence?.backend === "linux-network-namespace" &&
68
+ runStatus === "finished";
51
69
  const completedScenarios = detectScenarios(sanitizedEvents);
52
70
  const requiredScenarios = uniqueSorted(input.requiredScenarios ?? []);
53
71
  const providers = uniqueSorted(sanitizedEvents.map((event) => event.provider).filter(Boolean));
@@ -61,87 +79,183 @@ export function buildEvidenceReport(input) {
61
79
  }
62
80
  const runOutputSecretMatches = readNonNegativeInteger(input.runEvidence?.output?.secretMatches) ?? 0;
63
81
  secretMatches += runOutputSecretMatches;
64
- const allowedAttempts = sanitizedEvents.map((event) => ({
82
+ const allowedAttempts = sanitizedEvents
83
+ .map((event) => ({
65
84
  provider: limitText(event.provider),
66
85
  method: limitText(event.method),
67
86
  path: sanitizePath(event.path),
68
87
  statusCode: event.statusCode,
69
- source: limitText(event.source)
70
- })).slice(0, MAX_ATTEMPTS);
71
- const failures = allowedAttempts.filter((attempt) => attempt.statusCode >= 400).map((attempt) => ({ provider: attempt.provider, method: attempt.method, path: attempt.path, statusCode: attempt.statusCode }));
88
+ source: limitText(event.source),
89
+ }))
90
+ .slice(0, MAX_ATTEMPTS);
91
+ const failures = allowedAttempts
92
+ .filter((attempt) => attempt.statusCode >= 400)
93
+ .map((attempt) => ({
94
+ provider: attempt.provider,
95
+ method: attempt.method,
96
+ path: attempt.path,
97
+ statusCode: attempt.statusCode,
98
+ }));
72
99
  const retryCount = sanitizedEvents.filter((event) => event.statusCode === 429 || hasRetryAfter(event)).length;
73
- const productionAttempts = 0;
100
+ const productionAttempts = null;
74
101
  const contractDrift = input.contractDiff === undefined
75
102
  ? { breaking: 0, nonBreaking: 0, uncertain: 0 }
76
- : { baselineHash: input.contractDiff.baselineHash, candidateHash: input.contractDiff.candidateHash, ...input.contractDiff.summary };
103
+ : {
104
+ baselineHash: input.contractDiff.baselineHash,
105
+ candidateHash: input.contractDiff.candidateHash,
106
+ ...input.contractDiff.summary,
107
+ };
77
108
  const findings = [];
78
109
  const warnings = [];
79
110
  if (sanitizedEvents.length < input.events.length)
80
111
  warnings.push(`Event evidence was truncated to ${MAX_EVENTS_FOR_REPORT} events.`);
81
112
  if (input.runEvidence === null)
82
113
  warnings.push("Run evidence is missing; report cannot prove launcher lifecycle or blocked kernel egress attempts.");
114
+ warnings.push("Production egress attempts are not measured; the current backend records namespace lifecycle and GhostAPI-local traffic only.");
83
115
  if (readBoolean(input.runEvidence?.output?.timedOut))
84
116
  warnings.push("Target exceeded the configured run timeout.");
85
117
  if (readBoolean(input.runEvidence?.output?.limitExceeded))
86
118
  warnings.push("Target exceeded the configured output limit.");
87
119
  if (runUsedNamespace)
88
- findings.push({ id: "enforcement.isolated", severity: "pass", message: "Run evidence reports a completed Linux namespace boundary." });
120
+ findings.push({
121
+ id: "enforcement.isolated",
122
+ severity: "pass",
123
+ message: "Run evidence reports a completed Linux namespace boundary.",
124
+ });
89
125
  else
90
- findings.push({ id: "enforcement.degraded", severity: "warning", message: "Egress enforcement is degraded or unavailable on this host." });
126
+ findings.push({
127
+ id: "enforcement.degraded",
128
+ severity: "warning",
129
+ message: "Egress enforcement is degraded or unavailable on this host.",
130
+ });
91
131
  if (input.runEvidence?.status === "finished")
92
- findings.push({ id: "run.finished", severity: "pass", message: "Run evidence reached finished status." });
132
+ findings.push({
133
+ id: "run.finished",
134
+ severity: "pass",
135
+ message: "Run evidence reached finished status.",
136
+ });
93
137
  else
94
- findings.push({ id: "run.incomplete", severity: "warning", message: "Run evidence is absent or incomplete." });
138
+ findings.push({
139
+ id: "run.incomplete",
140
+ severity: "warning",
141
+ message: "Run evidence is absent or incomplete.",
142
+ });
95
143
  for (const scenarioId of requiredScenarios) {
96
- const decision = input.policy === undefined ? { allowed: completedScenarios.includes(scenarioId) } : evaluatePolicy(input.policy, { type: "scenario", scenarioId, completedScenarioIds: completedScenarios });
144
+ const decision = input.policy === undefined
145
+ ? { allowed: completedScenarios.includes(scenarioId) }
146
+ : evaluatePolicy(input.policy, {
147
+ type: "scenario",
148
+ scenarioId,
149
+ completedScenarioIds: completedScenarios,
150
+ });
97
151
  findings.push(decision.allowed
98
- ? { id: `scenario.${scenarioId}`, severity: "pass", message: `Required scenario completed: ${scenarioId}` }
99
- : { id: `scenario.${scenarioId}`, severity: "fail", message: `Required scenario missing: ${scenarioId}` });
152
+ ? {
153
+ id: `scenario.${scenarioId}`,
154
+ severity: "pass",
155
+ message: `Required scenario completed: ${scenarioId}`,
156
+ }
157
+ : {
158
+ id: `scenario.${scenarioId}`,
159
+ severity: "fail",
160
+ message: `Required scenario missing: ${scenarioId}`,
161
+ });
100
162
  }
101
- const reportDecision = input.policy === undefined ? { allowed: productionAttempts === 0 && secretMatches === 0 && contractDrift.breaking === 0 } : evaluatePolicy(input.policy, { type: "report", productionEgressAttempts: productionAttempts, forbiddenCredentialMatches: secretMatches, breakingContractChanges: contractDrift.breaking });
163
+ const reportDecision = input.policy === undefined
164
+ ? { allowed: secretMatches === 0 && contractDrift.breaking === 0 }
165
+ : {
166
+ allowed: secretMatches <=
167
+ input.policy.reports.maxForbiddenCredentialMatches &&
168
+ contractDrift.breaking <=
169
+ input.policy.reports.maxBreakingContractChanges,
170
+ };
102
171
  findings.push(reportDecision.allowed
103
- ? { id: "policy.report-thresholds", severity: "pass", message: "Policy report thresholds are satisfied." }
104
- : { id: "policy.report-thresholds", severity: "fail", message: "Policy report thresholds are exceeded." });
172
+ ? {
173
+ id: "policy.report-thresholds",
174
+ severity: "pass",
175
+ message: "Policy report thresholds are satisfied.",
176
+ }
177
+ : {
178
+ id: "policy.report-thresholds",
179
+ severity: "fail",
180
+ message: "Policy report thresholds are exceeded.",
181
+ });
105
182
  if (secretMatches > 0)
106
- findings.push({ id: "secrets.detected", severity: "fail", message: "Secret-shaped values were detected and summarized without raw values.", count: secretMatches });
183
+ findings.push({
184
+ id: "secrets.detected",
185
+ severity: "fail",
186
+ message: "Secret-shaped values were detected and summarized without raw values.",
187
+ count: secretMatches,
188
+ });
107
189
  if (input.contractDiff !== undefined) {
108
190
  if (contractDrift.breaking > 0)
109
- findings.push({ id: "contract-drift.breaking", severity: "fail", message: "Breaking contract drift was detected.", count: contractDrift.breaking });
191
+ findings.push({
192
+ id: "contract-drift.breaking",
193
+ severity: "fail",
194
+ message: "Breaking contract drift was detected.",
195
+ count: contractDrift.breaking,
196
+ });
110
197
  if (contractDrift.uncertain > 0)
111
- findings.push({ id: "contract-drift.uncertain", severity: "warning", message: "Contract changes need compatibility review because client tolerance is unknown.", count: contractDrift.uncertain });
198
+ findings.push({
199
+ id: "contract-drift.uncertain",
200
+ severity: "warning",
201
+ message: "Contract changes need compatibility review because client tolerance is unknown.",
202
+ count: contractDrift.uncertain,
203
+ });
112
204
  }
113
205
  if (failures.length > 0)
114
- findings.push({ id: "provider.failures", severity: "warning", message: "Provider failures were observed.", count: failures.length });
206
+ findings.push({
207
+ id: "provider.failures",
208
+ severity: "warning",
209
+ message: "Provider failures were observed.",
210
+ count: failures.length,
211
+ });
115
212
  const reportWithoutHash = {
116
213
  schemaVersion: 1,
117
- artifact: { generatedAt: input.generatedAt, logicalHash: "", canonicalization: "json-stable-sorted-keys-v1" },
214
+ artifact: {
215
+ generatedAt: input.generatedAt,
216
+ logicalHash: "",
217
+ canonicalization: "json-stable-sorted-keys-v1",
218
+ },
118
219
  run: {
119
220
  id: readString(input.runEvidence?.runId) ?? "unknown",
120
221
  status: runStatus,
121
222
  startedAt: firstTimestamp(runEvents),
122
223
  finishedAt: lastTimestamp(runEvents),
123
- exitCode: lastExitCode(runEvents)
224
+ exitCode: lastExitCode(runEvents),
124
225
  },
125
226
  ghostapi: { version: input.ghostApiVersion ?? "unknown" },
126
227
  enforcement: {
127
228
  capability: capability.summary,
128
- mode: input.runEvidence?.backend === "linux-network-namespace" ? "linux-network-namespace" : "proxy-guidance",
229
+ mode: input.runEvidence?.backend === "linux-network-namespace"
230
+ ? "linux-network-namespace"
231
+ : "proxy-guidance",
129
232
  isolated: runUsedNamespace,
130
- degraded: !runUsedNamespace
233
+ degraded: !runUsedNamespace,
131
234
  },
132
235
  policy: { hash: input.policyHash, requiredScenarios },
133
236
  coverage: { providers, scenarios: completedScenarios },
134
237
  contractDrift,
135
238
  egress: { allowedAttempts, blockedAttempts: [], productionAttempts },
136
- secrets: { categories: [...secretCategories].sort(), matches: secretMatches },
137
- retriesAndFailures: { retryCount, failureCount: failures.length, failures: failures.slice(0, MAX_ATTEMPTS) },
239
+ secrets: {
240
+ categories: [...secretCategories].sort(),
241
+ matches: secretMatches,
242
+ },
243
+ retriesAndFailures: {
244
+ retryCount,
245
+ failureCount: failures.length,
246
+ failures: failures.slice(0, MAX_ATTEMPTS),
247
+ },
138
248
  findings: findings.sort((left, right) => left.id.localeCompare(right.id)),
139
249
  warnings: warnings.map(limitText).sort(),
140
- summary: { passed: false, failCount: 0, warningCount: 0 }
250
+ summary: { passed: false, failCount: 0, warningCount: 0 },
141
251
  };
142
252
  const failCount = reportWithoutHash.findings.filter((finding) => finding.severity === "fail").length;
143
253
  const warningCount = reportWithoutHash.findings.filter((finding) => finding.severity === "warning").length + reportWithoutHash.warnings.length;
144
- reportWithoutHash.summary = { passed: failCount === 0, failCount, warningCount };
254
+ reportWithoutHash.summary = {
255
+ passed: failCount === 0,
256
+ failCount,
257
+ warningCount,
258
+ };
145
259
  reportWithoutHash.artifact.logicalHash = hashLogicalReport(reportWithoutHash);
146
260
  return reportWithoutHash;
147
261
  }
@@ -179,13 +293,13 @@ export function formatEvidenceReport(report) {
179
293
  `Contract drift: ${(report.contractDrift ?? emptyContractDrift()).breaking} breaking, ${(report.contractDrift ?? emptyContractDrift()).nonBreaking} non-breaking, ${(report.contractDrift ?? emptyContractDrift()).uncertain} uncertain`,
180
294
  `Allowed egress attempts: ${report.egress.allowedAttempts.length}`,
181
295
  `Blocked egress attempts: ${report.egress.blockedAttempts.length}`,
182
- `Production egress attempts: ${report.egress.productionAttempts}`,
296
+ `Production egress attempts: ${report.egress.productionAttempts ?? "not measured"}`,
183
297
  `Secret categories: ${report.secrets.categories.length > 0 ? report.secrets.categories.map(escapeTerminal).join(", ") : "none"}`,
184
298
  `Retries: ${report.retriesAndFailures.retryCount}`,
185
299
  `Failures: ${report.retriesAndFailures.failureCount}`,
186
300
  `Findings: ${report.summary.failCount} fail, ${report.summary.warningCount} warning`,
187
301
  ...report.findings.map((finding) => ` ${finding.severity.toUpperCase()} ${escapeTerminal(finding.id)}: ${escapeTerminal(finding.message)}`),
188
- ...report.warnings.map((warning) => ` WARNING evidence: ${escapeTerminal(warning)}`)
302
+ ...report.warnings.map((warning) => ` WARNING evidence: ${escapeTerminal(warning)}`),
189
303
  ].join("\n");
190
304
  }
191
305
  export function compareEvidenceReports(left, right) {
@@ -200,21 +314,34 @@ export function compareEvidenceReports(left, right) {
200
314
  differences.push("egress.productionAttempts differs");
201
315
  if (left.secrets.matches !== right.secrets.matches)
202
316
  differences.push("secrets.matches differs");
203
- if (stableStringify(left.contractDrift ?? emptyContractDrift()) !== stableStringify(right.contractDrift ?? emptyContractDrift()))
317
+ if (stableStringify(left.contractDrift ?? emptyContractDrift()) !==
318
+ stableStringify(right.contractDrift ?? emptyContractDrift()))
204
319
  differences.push("contractDrift differs");
205
320
  if (stableStringify(left.coverage) !== stableStringify(right.coverage))
206
321
  differences.push("coverage differs");
207
322
  if (stableStringify(left.findings) !== stableStringify(right.findings))
208
323
  differences.push("findings differ");
209
- return { equal: differences.length === 0, leftHash: left.artifact.logicalHash, rightHash: right.artifact.logicalHash, differences };
324
+ return {
325
+ equal: differences.length === 0,
326
+ leftHash: left.artifact.logicalHash,
327
+ rightHash: right.artifact.logicalHash,
328
+ differences,
329
+ };
210
330
  }
211
331
  export function formatEvidenceCompare(result) {
212
332
  if (result.equal)
213
333
  return `Evidence reports match: ${result.leftHash}`;
214
- return [`Evidence reports differ:`, `Left: ${result.leftHash}`, `Right: ${result.rightHash}`, ...result.differences.map((difference) => ` - ${escapeTerminal(difference)}`)].join("\n");
334
+ return [
335
+ `Evidence reports differ:`,
336
+ `Left: ${result.leftHash}`,
337
+ `Right: ${result.rightHash}`,
338
+ ...result.differences.map((difference) => ` - ${escapeTerminal(difference)}`),
339
+ ].join("\n");
215
340
  }
216
341
  async function writeEvidenceReport(report, projectRoot, outPath) {
217
- const target = outPath === undefined ? join(getDataPaths().reports, `${report.run.id === "unknown" ? "latest" : report.run.id}.json`) : await resolveOutputPath(outPath, projectRoot);
342
+ const target = outPath === undefined
343
+ ? join(getDataPaths().reports, `${report.run.id === "unknown" ? "latest" : report.run.id}.json`)
344
+ : await resolveOutputPath(outPath, projectRoot);
218
345
  await ensurePrivateDirectory(dirname(target));
219
346
  await atomicWriteJson(target, report);
220
347
  const source = await readFile(target, "utf8");
@@ -260,10 +387,12 @@ async function readPersistedEvents(runEvidencePath) {
260
387
  return events;
261
388
  }
262
389
  async function readRunEvidence(runPath, projectRoot) {
263
- const path = runPath ?? await findLatestRunEvidence();
390
+ const path = runPath ?? (await findLatestRunEvidence());
264
391
  if (path === undefined)
265
392
  return null;
266
- const resolved = runPath === undefined ? path : await resolveExistingReportPath(runPath, projectRoot);
393
+ const resolved = runPath === undefined
394
+ ? path
395
+ : await resolveExistingReportPath(runPath, projectRoot);
267
396
  const source = await readFile(resolved, "utf8");
268
397
  if (Buffer.byteLength(source, "utf8") > MAX_RUN_EVIDENCE_BYTES)
269
398
  throw new EvidenceReportError(`Run evidence exceeds ${MAX_RUN_EVIDENCE_BYTES} bytes.`);
@@ -280,11 +409,16 @@ async function loadContractDiff(baselinePath, candidatePath, projectRoot) {
280
409
  if (baselinePath === undefined || candidatePath === undefined)
281
410
  throw new EvidenceReportError("Contract drift evidence requires both --contract-baseline and --contract-candidate.");
282
411
  try {
283
- const [baseline, candidate] = await Promise.all([loadContract(baselinePath, projectRoot), loadContract(candidatePath, projectRoot)]);
412
+ const [baseline, candidate] = await Promise.all([
413
+ loadContract(baselinePath, projectRoot),
414
+ loadContract(candidatePath, projectRoot),
415
+ ]);
284
416
  return diffContracts(baseline, candidate);
285
417
  }
286
418
  catch (error) {
287
- throw new EvidenceReportError(error instanceof Error ? `Contract drift input is invalid: ${error.message}` : "Contract drift input is invalid.");
419
+ throw new EvidenceReportError(error instanceof Error
420
+ ? `Contract drift input is invalid: ${error.message}`
421
+ : "Contract drift input is invalid.");
288
422
  }
289
423
  }
290
424
  async function findLatestRunEvidence() {
@@ -300,7 +434,8 @@ async function findLatestRunEvidence() {
300
434
  if (info?.isFile())
301
435
  candidates.push({ path: evidencePath, mtimeMs: info.mtimeMs });
302
436
  }
303
- return candidates.sort((left, right) => right.mtimeMs - left.mtimeMs)[0]?.path;
437
+ return candidates.sort((left, right) => right.mtimeMs - left.mtimeMs)[0]
438
+ ?.path;
304
439
  }
305
440
  catch (error) {
306
441
  if (isErrorCode(error, "ENOENT"))
@@ -310,7 +445,9 @@ async function findLatestRunEvidence() {
310
445
  }
311
446
  async function pruneReports() {
312
447
  try {
313
- const entries = await readdir(getDataPaths().reports, { withFileTypes: true });
448
+ const entries = await readdir(getDataPaths().reports, {
449
+ withFileTypes: true,
450
+ });
314
451
  const reports = [];
315
452
  for (const entry of entries) {
316
453
  if (!entry.isFile() || !entry.name.endsWith(".json"))
@@ -320,7 +457,9 @@ async function pruneReports() {
320
457
  if (!info.isSymbolicLink())
321
458
  reports.push({ path, mtimeMs: info.mtimeMs });
322
459
  }
323
- for (const report of reports.sort((left, right) => right.mtimeMs - left.mtimeMs).slice(MAX_REPORTS_RETAINED))
460
+ for (const report of reports
461
+ .sort((left, right) => right.mtimeMs - left.mtimeMs)
462
+ .slice(MAX_REPORTS_RETAINED))
324
463
  await rm(report.path, { force: true });
325
464
  }
326
465
  catch (error) {
@@ -329,7 +468,9 @@ async function pruneReports() {
329
468
  }
330
469
  }
331
470
  async function resolveOutputPath(outPath, projectRoot) {
332
- const target = isAbsolute(outPath) ? resolve(outPath) : resolve(projectRoot, outPath);
471
+ const target = isAbsolute(outPath)
472
+ ? resolve(outPath)
473
+ : resolve(projectRoot, outPath);
333
474
  assertPathInsideAllowedRoots(projectRoot, target);
334
475
  await mkdir(dirname(target), { recursive: true, mode: 0o700 });
335
476
  const parent = await lstat(dirname(target));
@@ -354,17 +495,27 @@ function normalizeEvidenceReport(value) {
354
495
  const report = value;
355
496
  if (report.schemaVersion !== 1)
356
497
  throw new EvidenceReportError("Unsupported evidence report schema version.");
357
- if (typeof report.artifact?.logicalHash !== "string" || !/^[a-f0-9]{64}$/.test(report.artifact.logicalHash))
498
+ if (typeof report.artifact?.logicalHash !== "string" ||
499
+ !/^[a-f0-9]{64}$/.test(report.artifact.logicalHash))
358
500
  throw new EvidenceReportError("Evidence report is missing a valid logical hash.");
359
501
  if (report.artifact.canonicalization !== "json-stable-sorted-keys-v1")
360
502
  throw new EvidenceReportError("Unsupported evidence report canonicalization.");
361
- if (typeof report.run?.id !== "string" || typeof report.summary?.passed !== "boolean" || !Array.isArray(report.findings))
503
+ if (typeof report.run?.id !== "string" ||
504
+ typeof report.summary?.passed !== "boolean" ||
505
+ !Array.isArray(report.findings))
362
506
  throw new EvidenceReportError("Evidence report has an invalid schema.");
363
507
  return report;
364
508
  }
365
509
  function sanitizeEvent(event) {
366
510
  const sanitized = sanitizeSecrets(event);
367
- return { ...sanitized, provider: limitText(sanitized.provider), method: limitText(sanitized.method), path: sanitizePath(sanitized.path), request: sanitizeSecrets(sanitized.request), response: sanitizeSecrets(sanitized.response) };
511
+ return {
512
+ ...sanitized,
513
+ provider: limitText(sanitized.provider),
514
+ method: limitText(sanitized.method),
515
+ path: sanitizePath(sanitized.path),
516
+ request: sanitizeSecrets(sanitized.request),
517
+ response: sanitizeSecrets(sanitized.response),
518
+ };
368
519
  }
369
520
  function sanitizePath(path) {
370
521
  const sanitized = sanitizeSecretString(path);
@@ -373,7 +524,9 @@ function sanitizePath(path) {
373
524
  return limitText(pathname ?? "");
374
525
  const params = query.split("&").map((part) => {
375
526
  const [key = "", value = ""] = part.split("=", 2);
376
- return SENSITIVE_QUERY_KEYS.has(decodeURIComponentSafe(key).toLowerCase()) ? `${key}=***` : `${key}=${sanitizeSecretString(value)}`;
527
+ return SENSITIVE_QUERY_KEYS.has(decodeURIComponentSafe(key).toLowerCase())
528
+ ? `${key}=***`
529
+ : `${key}=${sanitizeSecretString(value)}`;
377
530
  });
378
531
  return limitText(`${pathname}?${params.join("&")}`);
379
532
  }
@@ -393,7 +546,9 @@ function collectScenarioStrings(value, scenarios) {
393
546
  }
394
547
  if (value !== null && typeof value === "object") {
395
548
  for (const [key, entry] of Object.entries(value)) {
396
- if (key.toLowerCase() === "scenario" && typeof entry === "string" && /^[a-z0-9][a-z0-9._-]{0,127}$/.test(entry))
549
+ if (key.toLowerCase() === "scenario" &&
550
+ typeof entry === "string" &&
551
+ /^[a-z0-9][a-z0-9._-]{0,127}$/.test(entry))
397
552
  scenarios.add(entry);
398
553
  collectScenarioStrings(entry, scenarios);
399
554
  }
@@ -449,7 +604,9 @@ function hashLogicalReport(report) {
449
604
  const logical = structuredClone(report);
450
605
  logical.artifact.logicalHash = "";
451
606
  logical.artifact.generatedAt = "";
452
- return createHash("sha256").update(stableStringify(logical), "utf8").digest("hex");
607
+ return createHash("sha256")
608
+ .update(stableStringify(logical), "utf8")
609
+ .digest("hex");
453
610
  }
454
611
  function stableStringify(value) {
455
612
  if (value === undefined)
@@ -458,32 +615,58 @@ function stableStringify(value) {
458
615
  return JSON.stringify(value);
459
616
  if (Array.isArray(value))
460
617
  return `[${value.map(stableStringify).join(",")}]`;
461
- const entries = Object.entries(value).filter(([, entry]) => entry !== undefined).sort(([left], [right]) => left.localeCompare(right));
618
+ const entries = Object.entries(value)
619
+ .filter(([, entry]) => entry !== undefined)
620
+ .sort(([left], [right]) => left.localeCompare(right));
462
621
  return `{${entries.map(([key, entry]) => `${JSON.stringify(key)}:${stableStringify(entry)}`).join(",")}}`;
463
622
  }
464
623
  function firstTimestamp(events) {
465
- return events.map((event) => readString(event.timestamp)).filter((value) => value !== undefined).sort()[0];
624
+ return events
625
+ .map((event) => readString(event.timestamp))
626
+ .filter((value) => value !== undefined)
627
+ .sort()[0];
466
628
  }
467
629
  function lastTimestamp(events) {
468
- return events.map((event) => readString(event.timestamp)).filter((value) => value !== undefined).sort().at(-1);
630
+ return events
631
+ .map((event) => readString(event.timestamp))
632
+ .filter((value) => value !== undefined)
633
+ .sort()
634
+ .at(-1);
469
635
  }
470
636
  function lastExitCode(events) {
471
- return events.map((event) => event.exitCode).filter((value) => Number.isInteger(value)).at(-1);
637
+ return events
638
+ .map((event) => event.exitCode)
639
+ .filter((value) => Number.isInteger(value))
640
+ .at(-1);
472
641
  }
473
642
  function hasRetryAfter(event) {
474
- return JSON.stringify(event.response).toLowerCase().includes("retry-after") || JSON.stringify(event.request).toLowerCase().includes("retry-after");
643
+ return (JSON.stringify(event.response).toLowerCase().includes("retry-after") ||
644
+ JSON.stringify(event.request).toLowerCase().includes("retry-after"));
475
645
  }
476
646
  function readRunStatus(value) {
477
- return value === "preparing" || value === "running" || value === "failed-to-start" || value === "finished" ? value : "unknown";
647
+ return value === "preparing" ||
648
+ value === "running" ||
649
+ value === "failed-to-start" ||
650
+ value === "finished"
651
+ ? value
652
+ : "unknown";
478
653
  }
479
654
  function readString(value) {
480
- return typeof value === "string" && value.trim() !== "" ? limitText(sanitizeSecretString(value)) : undefined;
655
+ return typeof value === "string" && value.trim() !== ""
656
+ ? limitText(sanitizeSecretString(value))
657
+ : undefined;
481
658
  }
482
659
  function readStringArray(value) {
483
- return Array.isArray(value) ? uniqueSorted(value.filter((entry) => typeof entry === "string").map(limitText)) : [];
660
+ return Array.isArray(value)
661
+ ? uniqueSorted(value
662
+ .filter((entry) => typeof entry === "string")
663
+ .map(limitText))
664
+ : [];
484
665
  }
485
666
  function readNonNegativeInteger(value) {
486
- return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : undefined;
667
+ return typeof value === "number" && Number.isInteger(value) && value >= 0
668
+ ? value
669
+ : undefined;
487
670
  }
488
671
  function readBoolean(value) {
489
672
  return value === true;
@@ -496,10 +679,14 @@ function emptyContractDrift() {
496
679
  }
497
680
  function limitText(value) {
498
681
  const sanitized = sanitizeSecretString(value).replace(/[\r\n\t]/g, " ");
499
- return sanitized.length > 2_000 ? `${sanitized.slice(0, 2_000)}[truncated]` : sanitized;
682
+ return sanitized.length > 2_000
683
+ ? `${sanitized.slice(0, 2_000)}[truncated]`
684
+ : sanitized;
500
685
  }
501
686
  function escapeTerminal(value) {
502
- return value.replace(/[\u001B\u009B][[\]()#;?]*(?:(?:(?:[a-zA-Z\d]*(?:;[a-zA-Z\d]*)*)?\u0007)|(?:(?:\d{1,4}(?:;\d{0,4})*)?[\dA-PR-TZcf-nq-uy=><~]))/g, "").replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "");
687
+ return value
688
+ .replace(/[\u001B\u009B][[\]()#;?]*(?:(?:(?:[a-zA-Z\d]*(?:;[a-zA-Z\d]*)*)?\u0007)|(?:(?:\d{1,4}(?:;\d{0,4})*)?[\dA-PR-TZcf-nq-uy=><~]))/g, "")
689
+ .replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "");
503
690
  }
504
691
  function secretCategory(key) {
505
692
  const normalized = key.toLowerCase();
@@ -516,7 +703,9 @@ function secretCategory(key) {
516
703
  return "secret-field";
517
704
  }
518
705
  function readPackageVersion() {
519
- return typeof packageJson.version === "string" ? packageJson.version : "unknown";
706
+ return typeof packageJson.version === "string"
707
+ ? packageJson.version
708
+ : "unknown";
520
709
  }
521
710
  async function realProjectRoot(projectRoot) {
522
711
  const root = resolve(projectRoot);
@@ -525,13 +714,6 @@ async function realProjectRoot(projectRoot) {
525
714
  throw new EvidenceReportError("Project root must be a real directory, not a symlink.");
526
715
  return root;
527
716
  }
528
- function assertPathInside(root, target) {
529
- const relativePath = relative(root, target);
530
- if (relativePath === ".." || relativePath.startsWith(`..${sep}`) || isAbsolute(relativePath))
531
- throw new EvidenceReportError("Evidence path traversal outside the project root is not allowed.");
532
- if (basename(target).trim() === "")
533
- throw new EvidenceReportError("Evidence path must include a file name.");
534
- }
535
717
  function assertPathInsideAllowedRoots(projectRoot, target) {
536
718
  if (!isInside(projectRoot, target) && !isInside(getDataPaths().root, target))
537
719
  throw new EvidenceReportError("Evidence path traversal outside the project root or GHOSTAPI_DATA_DIR is not allowed.");
@@ -540,7 +722,9 @@ function assertPathInsideAllowedRoots(projectRoot, target) {
540
722
  }
541
723
  function isInside(root, target) {
542
724
  const relativePath = relative(resolve(root), target);
543
- return relativePath !== ".." && !relativePath.startsWith(`..${sep}`) && !isAbsolute(relativePath);
725
+ return (relativePath !== ".." &&
726
+ !relativePath.startsWith(`..${sep}`) &&
727
+ !isAbsolute(relativePath));
544
728
  }
545
729
  function decodeURIComponentSafe(value) {
546
730
  try {