@clear-capabilities/agentic-security-scanner 0.172.1 → 0.174.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 (201) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/bin/agentic-security.js +37 -0
  3. package/dist/119.index.js +197 -0
  4. package/dist/128.index.js +129 -0
  5. package/dist/1293.index.js +5689 -0
  6. package/dist/1351.index.js +133 -0
  7. package/dist/1387.index.js +271 -0
  8. package/dist/1437.index.js +23 -0
  9. package/dist/1445.index.js +110 -0
  10. package/dist/1505.index.js +151 -0
  11. package/dist/1557.index.js +293 -0
  12. package/dist/1621.index.js +311 -0
  13. package/dist/1751.index.js +160 -0
  14. package/dist/1765.index.js +727 -0
  15. package/dist/1861.index.js +690 -0
  16. package/dist/1898.index.js +116 -0
  17. package/dist/1906.index.js +433 -0
  18. package/dist/1920.index.js +1 -291
  19. package/dist/208.index.js +1631 -0
  20. package/dist/2199.index.js +0 -239
  21. package/dist/2223.index.js +159 -0
  22. package/dist/2530.index.js +152 -0
  23. package/dist/2565.index.js +116 -0
  24. package/dist/2574.index.js +127 -0
  25. package/dist/2620.index.js +345 -0
  26. package/dist/2773.index.js +98 -0
  27. package/dist/2775.index.js +32 -0
  28. package/dist/2966.index.js +171 -0
  29. package/dist/2972.index.js +87 -0
  30. package/dist/2991.index.js +131 -0
  31. package/dist/3063.index.js +833 -0
  32. package/dist/3075.index.js +227 -0
  33. package/dist/3121.index.js +297 -0
  34. package/dist/3314.index.js +263 -0
  35. package/dist/3480.index.js +1822 -0
  36. package/dist/3481.index.js +24 -0
  37. package/dist/3506.index.js +268 -0
  38. package/dist/3533.index.js +591 -0
  39. package/dist/3653.index.js +450 -0
  40. package/dist/3822.index.js +131 -0
  41. package/dist/3868.index.js +292 -0
  42. package/dist/3917.index.js +152 -0
  43. package/dist/3918.index.js +192 -0
  44. package/dist/4090.index.js +288 -0
  45. package/dist/4121.index.js +1190 -0
  46. package/dist/4142.index.js +0 -239
  47. package/dist/4336.index.js +436 -0
  48. package/dist/4387.index.js +532 -0
  49. package/dist/4413.index.js +302 -0
  50. package/dist/4476.index.js +4510 -0
  51. package/dist/4491.index.js +133 -0
  52. package/dist/4538.index.js +185 -0
  53. package/dist/4684.index.js +319 -0
  54. package/dist/4740.index.js +165 -0
  55. package/dist/4780.index.js +209 -0
  56. package/dist/4802.index.js +225 -0
  57. package/dist/4812.index.js +379 -0
  58. package/dist/4835.index.js +126 -0
  59. package/dist/494.index.js +226 -0
  60. package/dist/4991.index.js +549 -0
  61. package/dist/5199.index.js +305 -0
  62. package/dist/5216.index.js +109 -0
  63. package/dist/5439.index.js +301 -0
  64. package/dist/5443.index.js +97 -0
  65. package/dist/5496.index.js +377 -0
  66. package/dist/550.index.js +2 -1
  67. package/dist/5801.index.js +588 -0
  68. package/dist/5858.index.js +223 -0
  69. package/dist/5933.index.js +143 -0
  70. package/dist/601.index.js +23 -0
  71. package/dist/6173.index.js +273 -0
  72. package/dist/6291.index.js +1871 -0
  73. package/dist/6312.index.js +666 -0
  74. package/dist/634.index.js +16182 -0
  75. package/dist/644.index.js +87 -0
  76. package/dist/6508.index.js +283 -0
  77. package/dist/6560.index.js +645 -0
  78. package/dist/6576.index.js +422 -0
  79. package/dist/6579.index.js +271 -0
  80. package/dist/6745.index.js +305 -0
  81. package/dist/6818.index.js +21642 -0
  82. package/dist/6972.index.js +167 -0
  83. package/dist/6995.index.js +163 -0
  84. package/dist/7249.index.js +191 -0
  85. package/dist/7372.index.js +1197 -0
  86. package/dist/7546.index.js +541 -0
  87. package/dist/76.index.js +448 -0
  88. package/dist/7879.index.js +163 -0
  89. package/dist/8025.index.js +130 -0
  90. package/dist/8054.index.js +248 -0
  91. package/dist/8097.index.js +201 -0
  92. package/dist/8126.index.js +191 -0
  93. package/dist/8178.index.js +78 -0
  94. package/dist/818.index.js +212 -0
  95. package/dist/8382.index.js +93 -0
  96. package/dist/8408.index.js +261 -0
  97. package/dist/8540.index.js +174 -0
  98. package/dist/8635.index.js +802 -0
  99. package/dist/8688.index.js +410 -0
  100. package/dist/8699.index.js +779 -0
  101. package/dist/8891.index.js +457 -0
  102. package/dist/8916.index.js +885 -0
  103. package/dist/9146.index.js +170 -0
  104. package/dist/9168.index.js +841 -0
  105. package/dist/9292.index.js +369 -0
  106. package/dist/9382.index.js +135 -11
  107. package/dist/9422.index.js +866 -0
  108. package/dist/9440.index.js +453 -0
  109. package/dist/9479.index.js +152 -0
  110. package/dist/9481.index.js +220 -0
  111. package/dist/9539.index.js +100 -0
  112. package/dist/9547.index.js +103 -0
  113. package/dist/9560.index.js +41 -61
  114. package/dist/9692.index.js +90 -0
  115. package/dist/9800.index.js +213 -1
  116. package/dist/9899.index.js +138 -0
  117. package/dist/9981.index.js +206 -0
  118. package/dist/999.index.js +117 -0
  119. package/dist/agentic-security.mjs +27 -27
  120. package/dist/agentic-security.mjs.sha256 +1 -1
  121. package/package.json +31 -6
  122. package/src/context-intelligence/candidates.js +2 -1
  123. package/src/context-intelligence/capabilities.js +2 -1
  124. package/src/context-intelligence/cli.js +2 -0
  125. package/src/context-intelligence/config.js +3 -1
  126. package/src/context-intelligence/controls.js +49 -1
  127. package/src/context-intelligence/counterexample.js +200 -0
  128. package/src/context-intelligence/cross-route.js +111 -0
  129. package/src/context-intelligence/findings.js +166 -0
  130. package/src/context-intelligence/invariants.js +197 -0
  131. package/src/context-intelligence/queue.js +6 -1
  132. package/src/context-intelligence/reachability.js +2 -2
  133. package/src/context-intelligence/review.js +68 -4
  134. package/src/dataflow/access-paths.js +11 -0
  135. package/src/dataflow/analysis-limits.js +66 -0
  136. package/src/dataflow/engine.js +282 -24
  137. package/src/dataflow/service-edges.js +174 -0
  138. package/src/dataflow/summaries.js +7 -1
  139. package/src/discovery/judge.js +2 -1
  140. package/src/engine.js +82 -14
  141. package/src/fix/apply-fix-service.js +55 -8
  142. package/src/fix/batch-journal.js +84 -0
  143. package/src/fix/exploit-replay.js +51 -0
  144. package/src/fix/footprint.js +65 -0
  145. package/src/fix/patch-policy.js +49 -0
  146. package/src/fix/secret-persistence.js +39 -0
  147. package/src/fix/test-commands.js +84 -0
  148. package/src/fix/verification-adapters.js +97 -0
  149. package/src/fix/verification-record.js +243 -0
  150. package/src/fix/verify-patch.js +89 -0
  151. package/src/ir/CLAUDE.md +5 -0
  152. package/src/ir/callgraph.js +23 -1
  153. package/src/ir/module-resolver.js +315 -0
  154. package/src/ir/parser-js.js +21 -2
  155. package/src/ir/semantic-graph.js +245 -0
  156. package/src/language/haskell-findings.js +2 -0
  157. package/src/language/haskell-guards.js +1 -1
  158. package/src/language/haskell-ir.js +3 -1
  159. package/src/language/haskell-models.js +94 -0
  160. package/src/language/haskell-security-rules.js +20 -0
  161. package/src/language/nixos-hardening.js +72 -0
  162. package/src/lineage/build-attested-correlation.js +63 -0
  163. package/src/lineage/deployment-identity.js +76 -0
  164. package/src/mcp/tools.js +13 -34
  165. package/src/pipeline/canonical-output.js +64 -0
  166. package/src/pipeline/detector-runner.js +7 -0
  167. package/src/pipeline/scan-health.js +14 -0
  168. package/src/pipeline/supervised-analyzers.js +125 -0
  169. package/src/pipeline/supervised-child.js +17 -0
  170. package/src/pipeline/telemetry.js +79 -0
  171. package/src/posture/accuracy-scorecard.js +98 -9
  172. package/src/posture/artifact-registry.js +5 -0
  173. package/src/posture/assurance/evidence-registry.js +215 -0
  174. package/src/posture/assurance/release-package-verify.js +135 -0
  175. package/src/posture/assurance/release-package.js +118 -0
  176. package/src/posture/assurance/release-verdict.js +94 -0
  177. package/src/posture/assurance/waiver.js +200 -0
  178. package/src/posture/compliance-truth.js +100 -0
  179. package/src/posture/evaluation/scan-child.js +3 -1
  180. package/src/posture/evaluation/why-missed.js +3 -1
  181. package/src/posture/exploitability-evidence.js +141 -0
  182. package/src/posture/fix-verify.js +4 -2
  183. package/src/posture/fleet/exposure-ranking.js +115 -0
  184. package/src/posture/fleet/service-chain-graph.js +150 -0
  185. package/src/posture/graph-reconciliation.js +154 -0
  186. package/src/posture/provenance/coverage.js +103 -0
  187. package/src/posture/provenance/git-evidence.js +84 -0
  188. package/src/posture/provenance/origin-resolver.js +7 -1
  189. package/src/posture/provenance/predicate-replay.js +14 -4
  190. package/src/posture/runtime-correlation.js +61 -15
  191. package/src/posture/runtime-explanation.js +116 -0
  192. package/src/posture/runtime-trace-ingest.js +194 -0
  193. package/src/posture/scan-checkpoint.js +38 -10
  194. package/src/posture/waf-ingest.js +7 -2
  195. package/src/report/index.js +4 -0
  196. package/src/report/oscal.js +38 -0
  197. package/src/report/perspectives.js +150 -0
  198. package/src/sast/_comment-strip.js +8 -1
  199. package/src/sast/ruby.js +26 -0
  200. package/src/sast/zip-slip.js +79 -0
  201. package/src/util/string-memo.js +38 -0
@@ -0,0 +1,305 @@
1
+ export const id = 5199;
2
+ export const ids = [5199];
3
+ export const modules = {
4
+
5
+ /***/ 45199:
6
+ /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
7
+
8
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
9
+ /* harmony export */ ASSURANCE_MODES: () => (/* binding */ ASSURANCE_MODES),
10
+ /* harmony export */ DEFAULT_ASSURANCE_MODE: () => (/* binding */ DEFAULT_ASSURANCE_MODE),
11
+ /* harmony export */ evaluateAssuranceMode: () => (/* binding */ evaluateAssuranceMode)
12
+ /* harmony export */ });
13
+ /* unused harmony export _internals */
14
+ /* harmony import */ var _posture_provenance_schema_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(49171);
15
+ // Assurance modes (assurance-hardening PRD FR-204).
16
+ //
17
+ // "Add assurance modes: advisory, standard, and strict | Strict mode fails
18
+ // when a required analyzer fails, times out, is unavailable, or is
19
+ // silently skipped." Section 12.1 names the CLI surface:
20
+ // `--assurance advisory|standard|strict`.
21
+ //
22
+ // Built directly on FR-203's coverage ledger (pipeline/coverage-ledger.js)
23
+ // and FR-206's scan-health.js -- this module adds no new signal collection
24
+ // of its own, only a POLICY over signals that already exist. That is
25
+ // deliberate: assurance modes decide how strict to be about incomplete
26
+ // analysis; they must never be the thing that DEFINES what "incomplete"
27
+ // means, or the two concepts would drift out of sync.
28
+ //
29
+ // THREE MODES:
30
+ // advisory — never fails the gate over scan health, regardless of what
31
+ // scanHealth reports. Purely informational (surfaced in scanHealth /
32
+ // toShipVerdict / `ci`'s stderr, per FR-206's fix -- this mode does not
33
+ // suppress that, it just does not ADD a build-failing consequence).
34
+ // standard (the DEFAULT, matching this codebase's behavior before and
35
+ // after FR-204) — same as advisory for gate purposes: an incomplete
36
+ // scan is surfaced, never silently hidden, but does not itself fail a
37
+ // build independent of the ordinary --fail-on severity threshold. The
38
+ // distinction from advisory is one of INTENT/reporting emphasis, not
39
+ // mechanism -- see the module-level note below on why this codebase
40
+ // does not invent a mechanical difference the PRD text does not ask
41
+ // for.
42
+ // strict — an incomplete scan (any analyzer failed, timed out, or was
43
+ // skipped by policy -- the ledger's own three non-"completed" outcomes)
44
+ // is a HARD FAILURE, independent of and IN ADDITION TO --fail-on,
45
+ // mirroring cmdCi's own existing precedent for its --policy gate
46
+ // ("Policy runs ALONGSIDE the --fail-on threshold; either gate can
47
+ // fail the build").
48
+ //
49
+ // WHY ADVISORY AND STANDARD ARE MECHANICALLY IDENTICAL HERE: the PRD's own
50
+ // one-line acceptance criterion only specifies STRICT mode's behavior in
51
+ // full ("fails when..."); it does not name a distinct mechanical
52
+ // consequence for standard beyond "the default, not strict." Inventing an
53
+ // intermediate failure condition neither named in the PRD nor requested
54
+ // would be exactly the kind of unrequested scope-widening this codebase's
55
+ // own conventions warn against. If a future requirement needs standard
56
+ // mode to behave differently from advisory, that is a deliberate, separate
57
+ // decision -- not something to guess at here.
58
+
59
+
60
+
61
+ const ASSURANCE_MODES = Object.freeze(['advisory', 'standard', 'strict']);
62
+ const DEFAULT_ASSURANCE_MODE = 'standard';
63
+
64
+ function _isValidMode(mode) {
65
+ return ASSURANCE_MODES.includes(mode);
66
+ }
67
+
68
+ // A real user hit this: `agentic-security ci <a directory downloaded as a
69
+ // GitHub zip, no .git present> --assurance strict` failed with the bare
70
+ // count this function used to produce alone — "1210 finding(s) have status
71
+ // outside [complete, uncommitted]" — with no indication that all 1210
72
+ // findings failed for the exact same, simple, fixable reason
73
+ // (`coordinator.js`'s `annotateGitProvenance` already knows and records it,
74
+ // in `finding.findingProvenance.limitations[0]`, but that reason never
75
+ // reached this message). A user reading "1210 problems" reasonably assumes
76
+ // their CODE has 1210 problems, not that their DIRECTORY isn't a git
77
+ // repository. This surfaces the dominant recorded reason instead of a bare
78
+ // count, and gives the two most common, fully-fixable reasons ("not a Git
79
+ // repository" from a zip download instead of `git clone`; a shallow CI
80
+ // checkout) a one-line, specific remedy — the same specificity the
81
+ // scanHealth branch above already gives for a stale-EPSS-cache failure.
82
+ function _provenanceFailureReason(badProvenance, totalFindings) {
83
+ const counts = new Map();
84
+ for (const f of badProvenance) {
85
+ const reason = f?.findingProvenance?.limitations?.[0] || f?.findingProvenance?.status || 'unknown';
86
+ counts.set(reason, (counts.get(reason) || 0) + 1);
87
+ }
88
+ const ranked = [...counts.entries()].sort((a, b) => b[1] - a[1]);
89
+ const base = `strict mode requires complete finding provenance; ${badProvenance.length}/${totalFindings} finding(s) have status outside [complete, uncommitted]`;
90
+
91
+ const gitReasons = ranked.filter(([r]) => r === 'not a Git repository' || r === 'repository state unavailable');
92
+ const gitCount = gitReasons.reduce((s, [, n]) => s + n, 0);
93
+ // engine.js's own comment on this branch: "unpinned_dep / no_lockfile...
94
+ // describe the ABSENCE of a declaration, so 'which commit introduced this
95
+ // version' is not a question that has an answer to defer ... this is a
96
+ // known, disclosed limitation, not a bug... strict mode WILL fail on
97
+ // nearly any real project that has a package.json." That disclosure lived
98
+ // only in a source comment nobody hits this error reads — the README's
99
+ // own quickstart explicitly invites pointing --assurance strict at "your
100
+ // own project," where this is the single most likely outcome. Named here
101
+ // so the person who hits it learns it is expected and permanent, not
102
+ // something to keep investigating. This prefix is deliberately narrower
103
+ // than "every non-vulnerable_dep supply-chain entry" — engine.js's
104
+ // provenance-stamping loop only uses it for unpinned_dep/no_lockfile,
105
+ // which genuinely have no origin commit; cdn_no_integrity/dynamic_require
106
+ // carry a real file:line and get a DIFFERENT string precisely so they
107
+ // never land in this "permanent, give up" bucket (adversarial premortem
108
+ // R2, 2026-09-07 — conflating the two told a user a resolvable coverage
109
+ // gap was an unfixable, by-design limitation).
110
+ const supplyChainReasons = ranked.filter(([r]) => r.startsWith('origin resolution does not apply to a'));
111
+ const supplyChainCount = supplyChainReasons.reduce((s, [, n]) => s + n, 0);
112
+ // Third known bucket (S1, adversarial premortem third pass, 2026-09-07):
113
+ // cdn_no_integrity/dynamic_require's "not yet wired" string (see the
114
+ // engine.js comment this file's `supplyChainReasons` block already
115
+ // references) was previously falling through to the generic `otherReasons`
116
+ // path below — honest, but verbose, and with no recommended next step,
117
+ // unlike every other named bucket here. Giving it its own bucket closes
118
+ // that inconsistency without touching the two already-fixed buckets.
119
+ const notYetWiredReasons = ranked.filter(([r]) => r.startsWith('origin resolution is not yet wired for a'));
120
+ const notYetWiredCount = notYetWiredReasons.reduce((s, [, n]) => s + n, 0);
121
+ const knownReasonSet = new Set([...gitReasons, ...supplyChainReasons, ...notYetWiredReasons].map(([r]) => r));
122
+ const otherReasons = ranked.filter(([r]) => !knownReasonSet.has(r));
123
+ const otherCount = badProvenance.length - gitCount - supplyChainCount - notYetWiredCount;
124
+ const knownCategoryCount = (gitCount > 0 ? 1 : 0) + (supplyChainCount > 0 ? 1 : 0) + (notYetWiredCount > 0 ? 1 : 0);
125
+
126
+ // Exactly one KNOWN category, and nothing outside it — the shape every
127
+ // caller before this fix assumed was the only shape, and the one every
128
+ // existing test was written against. Kept as tight, single-topic prose
129
+ // rather than the multi-segment form below.
130
+ if (knownCategoryCount === 0) {
131
+ if (otherReasons.length === 1) {
132
+ return `${base} — all ${badProvenance.length} share the same reason: "${otherReasons[0][0]}".`;
133
+ }
134
+ const breakdown = otherReasons.slice(0, 5).map(([reason, n]) => `${n}× "${reason}"`).join(', ');
135
+ return `${base} — breakdown: ${breakdown}${otherReasons.length > 5 ? ', …' : ''}.`;
136
+ }
137
+ if (knownCategoryCount === 1 && otherCount === 0) {
138
+ if (gitCount > 0) {
139
+ const gitReasonNames = gitReasons.map(([r]) => `"${r}"`).join(' and ');
140
+ return `${base} — reason: ${gitCount === badProvenance.length ? 'all of them are' : `${gitCount} of them are`} ${gitReasonNames}. ` +
141
+ `strict mode resolves finding provenance from git history, so it requires a real git repository ` +
142
+ `(a GitHub "Download ZIP" extracts without one). Run \`git init && git add -A && git commit -m init\` in ` +
143
+ `the scanned directory, point the scan at a real \`git clone\`, or drop --assurance strict for standard/advisory.`;
144
+ }
145
+ if (supplyChainCount > 0) {
146
+ return `${base} — ${supplyChainCount} of them describe an ABSENT dependency declaration ` +
147
+ `(an unpinned version, a missing lockfile) that has no origin commit to resolve, by design. This is a ` +
148
+ `known, permanent limitation: strict mode cannot pass while any are present, on any real project with ` +
149
+ `such a dependency. Fix the underlying SCA finding(s) (pin the version / add a lockfile) if you want ` +
150
+ `strict to pass, or use --assurance standard/advisory for a project you don't control the dependencies of.`;
151
+ }
152
+ return `${base} — ${notYetWiredCount} of them point at a real source location (a CDN script tag, a dynamic ` +
153
+ `require) this engine can't yet trace back to the commit that introduced it — unlike the ABSENT-declaration ` +
154
+ `case above, this is an ordinary coverage gap, not a permanent limitation, but it isn't fixable from your ` +
155
+ `side either. Use --assurance standard/advisory if you need this scan to pass today.`;
156
+ }
157
+
158
+ // Two or more independently-blocking categories on the SAME scan — the
159
+ // defect this closes (adversarial premortem R1, 2026-09-07): the old
160
+ // code picked whichever category had the most findings and silently
161
+ // dropped every other one, so a user could "fix" the reported problem,
162
+ // rerun, and hit a second wall the first run already had full information
163
+ // about but never mentioned — the same "the tool knew and didn't tell me"
164
+ // complaint this whole function exists to fix, recurring in a milder form.
165
+ //
166
+ // Rendered as a bulleted, newline-separated list rather than one
167
+ // semicolon-joined paragraph (S2, adversarial premortem third pass,
168
+ // 2026-09-07) — each bullet is independently actionable, and a wall of
169
+ // clauses buried the fact that they are SEPARATE problems, each with its
170
+ // own fix, rather than one problem described three ways.
171
+ const segments = [];
172
+ if (gitCount > 0) {
173
+ const gitReasonNames = gitReasons.map(([r]) => `"${r}"`).join(' and ');
174
+ segments.push(`${gitCount} of them are ${gitReasonNames} — strict mode requires a real git repository; ` +
175
+ `run \`git init && git add -A && git commit\`, or scan a real \`git clone\`.`);
176
+ }
177
+ if (supplyChainCount > 0) {
178
+ segments.push(`${supplyChainCount} of them describe an ABSENT dependency declaration (unpinned version / ` +
179
+ `missing lockfile) with no origin commit to resolve — a known, permanent limitation, not something a ` +
180
+ `rerun will fix.`);
181
+ }
182
+ if (notYetWiredCount > 0) {
183
+ segments.push(`${notYetWiredCount} of them point at a real source location this engine can't yet trace ` +
184
+ `back to a commit — an ordinary coverage gap, not a permanent limitation, but not fixable from your side.`);
185
+ }
186
+ if (otherCount > 0) {
187
+ if (otherReasons.length === 1) {
188
+ segments.push(`${otherCount} share the reason "${otherReasons[0][0]}".`);
189
+ } else {
190
+ const breakdown = otherReasons.slice(0, 5).map(([reason, n]) => `${n}× "${reason}"`).join(', ');
191
+ segments.push(`${otherCount} break down as: ${breakdown}${otherReasons.length > 5 ? ', …' : ''}.`);
192
+ }
193
+ }
194
+ const bullets = segments.map((s) => ` - ${s}`).join('\n');
195
+ return `${base} — MULTIPLE distinct reasons, not just one:\n${bullets}\nEvery category above must be ` +
196
+ `resolved for strict to pass (or drop to --assurance standard/advisory) — fixing only one will surface the ` +
197
+ `next on your following run.`;
198
+ }
199
+
200
+ /**
201
+ * @param {string} mode - one of ASSURANCE_MODES; invalid/missing degrades to the default.
202
+ * @param {object|null} scanHealth - the engine's computed scan.scanHealth (FR-206).
203
+ * @returns {{ok: boolean, mode: string, reason: string|null, conditions: string[]}}
204
+ * ok:false only ever happens in strict mode; advisory/standard always ok:true
205
+ * (they report, they do not gate).
206
+ */
207
+ function evaluateAssuranceMode(mode, scanHealth, findings = []) {
208
+ const effectiveMode = _isValidMode(mode) ? mode : DEFAULT_ASSURANCE_MODE;
209
+ const conditions = Array.isArray(scanHealth?.conditions) ? scanHealth.conditions : [];
210
+
211
+ if (effectiveMode !== 'strict') {
212
+ return { ok: true, mode: effectiveMode, reason: null, conditions };
213
+ }
214
+
215
+ // Strict: any of the ledger's three non-"completed" analyzer outcomes,
216
+ // OR an annotator error / deep-mode failure (scanHealth's OTHER,
217
+ // non-analyzer conditions) -- "a required analyzer... is unavailable, or
218
+ // is silently skipped" covers more than just coverage-ledger.js's own
219
+ // per-detector accounting; a scan whose ANY known-good signal degraded
220
+ // is not "complete" under strict's own definition.
221
+ const status = scanHealth?.status;
222
+ if (!scanHealth || status !== 'complete') {
223
+ const a = scanHealth?.analyzers;
224
+ const parts = [];
225
+ if (a?.failed > 0) parts.push(`${a.failed} analyzer(s) failed`);
226
+ if (a?.timedOut > 0) parts.push(`${a.timedOut} analyzer(s) timed out`);
227
+ if (a?.skippedByPolicy > 0) parts.push(`${a.skippedByPolicy} analyzer(s) silently skipped by policy`);
228
+ const analyzerSummary = parts.length ? ` (${parts.join(', ')})` : '';
229
+ return {
230
+ ok: false,
231
+ mode: 'strict',
232
+ reason: `strict mode requires a fully complete scan; scanHealth.status is '${status ?? 'unknown'}'${analyzerSummary}`,
233
+ conditions,
234
+ };
235
+ }
236
+
237
+ // M2 §2.5: strict cares about overall scan completeness, which now
238
+ // explicitly includes PROVENANCE completeness, not just detector/analyzer
239
+ // completeness. A finding whose findingProvenance status is outside
240
+ // ['complete','uncommitted'] — including a finding with NO
241
+ // findingProvenance at all, e.g. --no-provenance was used — means strict
242
+ // cannot vouch for this scan's provenance the same way it already refuses
243
+ // to vouch for a scan with a failed analyzer.
244
+ //
245
+ // KNOWN INTERACTION: scan.secrets/scan.logicVulns are unconditionally
246
+ // stamped not_available today (M0+M1 deliberately deferred real origin
247
+ // resolution for those two channels — see the M2/M3/M4 design spec's
248
+ // §2.6). Any real secret or logic finding therefore fails strict mode
249
+ // until that resolution work lands. This is the literal, intended
250
+ // consequence of "never false certainty" applied to strict's own
251
+ // definition, not an oversight — a strict-mode operator with secrets
252
+ // findings should expect this until M3+ closes that gap.
253
+ //
254
+ // This list is INCOMPLETE without scan.supplyChain, and the omission
255
+ // matters more than the secrets/logic one above because it hits nearly
256
+ // every real project. engine.js stamps every supplyChain entry
257
+ // not_available too (see the loop over `supplyChain` right after the
258
+ // `annotateGitProvenance` calls), and that bucket covers three distinct
259
+ // populations, not one:
260
+ //
261
+ // - transitive `vulnerable_dep` findings: a genuine, if currently
262
+ // unresolved, DEFERRAL — same shape as secrets/logicVulns above. The
263
+ // vulnerable version was never declared in this repo's own manifests,
264
+ // so there is no local commit to walk yet, but one could exist to
265
+ // resolve in a later phase.
266
+ // - `unpinned_dep` / `no_lockfile` findings: a CATEGORY ERROR, not a
267
+ // deferral. These describe an ABSENT state (a version range with no
268
+ // pin, a manifest with no lockfile) — there is no "commit that
269
+ // introduced a missing lockfile" for any future resolver to find,
270
+ // because the finding is about the absence of an event, not an event
271
+ // itself. No amount of future engineering work makes these resolvable.
272
+ //
273
+ // Direct `vulnerable_dep` findings DO go through real origin resolution
274
+ // (`resolveDirectSCAOrigin`, gated on `isDirect`) and are not part of this
275
+ // limitation.
276
+ //
277
+ // Net effect: because `unpinned_dep`/`no_lockfile` findings are a category
278
+ // error rather than a deferral, `--assurance strict` will fail on nearly
279
+ // any real project that has a `package.json` (or equivalent manifest)
280
+ // today — an unpinned or unlocked dependency is common, and this check has
281
+ // no way to ever resolve one. This is a known, disclosed limitation of the
282
+ // current implementation, not a bug, and it is not something this check
283
+ // should route around: exempting these finding types from the strict-mode
284
+ // gate was considered and deliberately deferred to a future milestone
285
+ // rather than done here, so strict mode keeps refusing to vouch for
286
+ // provenance it cannot actually speak to.
287
+ const badProvenance = (Array.isArray(findings) ? findings : []).filter((f) => !(0,_posture_provenance_schema_js__WEBPACK_IMPORTED_MODULE_0__/* .isProvenanceHealthy */ .lL)(f?.findingProvenance));
288
+ if (badProvenance.length > 0) {
289
+ return {
290
+ ok: false,
291
+ mode: 'strict',
292
+ reason: _provenanceFailureReason(badProvenance, findings.length),
293
+ conditions,
294
+ };
295
+ }
296
+
297
+ return { ok: true, mode: 'strict', reason: null, conditions };
298
+ }
299
+
300
+ const _internals = { _isValidMode, _provenanceFailureReason };
301
+
302
+
303
+ /***/ })
304
+
305
+ };
@@ -0,0 +1,109 @@
1
+ export const id = 5216;
2
+ export const ids = [5216];
3
+ export const modules = {
4
+
5
+ /***/ 25216:
6
+ /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
7
+
8
+ __webpack_require__.r(__webpack_exports__);
9
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
10
+ /* harmony export */ findingKey: () => (/* binding */ findingKey),
11
+ /* harmony export */ latestProof: () => (/* binding */ latestProof),
12
+ /* harmony export */ listProofs: () => (/* binding */ listProofs),
13
+ /* harmony export */ saveProof: () => (/* binding */ saveProof),
14
+ /* harmony export */ scrubDeep: () => (/* binding */ scrubDeep)
15
+ /* harmony export */ });
16
+ /* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(73024);
17
+ /* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(76760);
18
+ /* harmony import */ var _state_dir_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(69259);
19
+ /* harmony import */ var _encryption_provider_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(71425);
20
+ /* harmony import */ var _llm_validator_redact_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(98039);
21
+ /* harmony import */ var _reproducer_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(82034);
22
+ // Proof artifact store: `.agentic-security/proofs/<findingKey>/<recordDigest>.json`.
23
+ //
24
+ // Reuses the existing state machinery rather than adding another: `safeWriteState` (refuses unsafe dirs and symlinks),
25
+ // `maybeEncryptForWrite` / `maybeDecryptForRead` (the `proofs` artifact is registered `confidential`), retention
26
+ // (evidence class) and legal holds (a hold on `proofs` blocks purge). Nothing persisted contains a plaintext credential:
27
+ // records are passed through a recursive secret filter first, and the runner never returns its per-run nonce or secrets.
28
+
29
+
30
+
31
+
32
+
33
+
34
+
35
+ const PROOF_RECORD_SCHEMA = 'agentic-security/proof-record';
36
+ const PROOFS_DIR = 'proofs';
37
+ const MAX_RECORD_BYTES = 512 * 1024;
38
+
39
+ const findingKey = (id) => `${String(id).replace(/[^A-Za-z0-9._-]/g, '_').slice(0, 80)}-${(0,_reproducer_js__WEBPACK_IMPORTED_MODULE_5__/* .sha256 */ .sc)(String(id)).slice(0, 8)}`;
40
+
41
+ // The shared redactor catches key=value shapes; bare tokens need their own pass, so persisted evidence cannot carry one.
42
+ const BARE_SECRET_RES = [
43
+ /\b(?:ghp|gho|ghu|ghs|ghr|github_pat)_[A-Za-z0-9_]{20,}\b/g, /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/g, /\bsk-[A-Za-z0-9_-]{20,}\b/g,
44
+ /\bxox[abprs]-[A-Za-z0-9-]{10,}\b/g, /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g,
45
+ /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?(?:-----END [A-Z ]*PRIVATE KEY-----|$)/g, /\bSECRET-[0-9a-f]{24}-[a-z0-9_-]+\b/g,
46
+ ];
47
+ function scrubString(text) {
48
+ let t = String(text);
49
+ try { t = (0,_llm_validator_redact_js__WEBPACK_IMPORTED_MODULE_4__/* .redactSecrets */ .f)(t).text; } catch { return '[unredactable]'; }
50
+ for (const re of BARE_SECRET_RES) t = t.replace(re, '[REDACTED-SECRET]');
51
+ return t;
52
+ }
53
+
54
+ /** Deep-copy `v` with every string passed through the secret redactor. */
55
+ function scrubDeep(v) {
56
+ if (typeof v === 'string') return scrubString(v);
57
+ if (Array.isArray(v)) return v.map(scrubDeep);
58
+ if (v && typeof v === 'object') return Object.fromEntries(Object.entries(v).map(([k, x]) => [k, scrubDeep(x)]));
59
+ return v;
60
+ }
61
+
62
+ function recordDigest(record) {
63
+ const { recordDigest: _omit, ...rest } = record;
64
+ return (0,_reproducer_js__WEBPACK_IMPORTED_MODULE_5__/* .sha256 */ .sc)((0,_reproducer_js__WEBPACK_IMPORTED_MODULE_5__/* .canonicalize */ .dw)(rest));
65
+ }
66
+
67
+ function saveProof(scanRoot, record) {
68
+ const safe = scrubDeep(record);
69
+ safe.schema = PROOF_RECORD_SCHEMA;
70
+ safe.recordDigest = recordDigest(safe);
71
+ const text = JSON.stringify(safe, null, 2);
72
+ if (Buffer.byteLength(text) > MAX_RECORD_BYTES) return { ok: false, reason: 'proof record exceeds the size limit' };
73
+ if (!(0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__/* .ensureStateDir */ .xH)(scanRoot)) return { ok: false, reason: 'no safe state directory for this project' };
74
+ const enc = (0,_encryption_provider_js__WEBPACK_IMPORTED_MODULE_3__/* .maybeEncryptForWrite */ .v2)(scanRoot, PROOFS_DIR, text);
75
+ if (!enc.ok) return { ok: false, reason: enc.reason };
76
+ const file = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__.statePath)(scanRoot, PROOFS_DIR, findingKey(record.findingId), `${safe.recordDigest}.json`);
77
+ if (!(0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__.safeWriteState)(file, enc.content)) return { ok: false, reason: 'state write refused' };
78
+ return { ok: true, file, digest: safe.recordDigest, encrypted: enc.encrypted };
79
+ }
80
+
81
+ function readRecord(file) {
82
+ const text = (0,_encryption_provider_js__WEBPACK_IMPORTED_MODULE_3__.maybeDecryptForRead)(node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(file, 'utf8'));
83
+ const rec = JSON.parse(text);
84
+ // A record whose content no longer matches its own digest was modified after it was written.
85
+ if (rec.recordDigest !== recordDigest(rec)) return { tampered: true, record: rec };
86
+ return { tampered: false, record: rec };
87
+ }
88
+
89
+ /** Newest-first records for a finding. Tampered records are returned flagged, never silently dropped or trusted. */
90
+ function listProofs(scanRoot, findingId) {
91
+ const dir = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__.statePath)(scanRoot, PROOFS_DIR, findingKey(findingId));
92
+ let names = [];
93
+ try { names = node_fs__WEBPACK_IMPORTED_MODULE_0__.readdirSync(dir).filter((n) => n.endsWith('.json')); } catch { return []; }
94
+ const out = [];
95
+ for (const n of names) {
96
+ const f = node_path__WEBPACK_IMPORTED_MODULE_1__.join(dir, n);
97
+ try { const { tampered, record } = readRecord(f); out.push({ file: f, tampered, record, mtime: node_fs__WEBPACK_IMPORTED_MODULE_0__.statSync(f).mtimeMs }); } catch { out.push({ file: f, tampered: true, record: null, mtime: 0 }); }
98
+ }
99
+ return out.sort((a, b) => b.mtime - a.mtime);
100
+ }
101
+
102
+ function latestProof(scanRoot, findingId) {
103
+ return listProofs(scanRoot, findingId).find((p) => p.record) ?? null;
104
+ }
105
+
106
+
107
+ /***/ })
108
+
109
+ };