@clear-capabilities/agentic-security-scanner 0.172.0 → 0.173.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.
- package/CHANGELOG.md +29 -0
- package/bin/agentic-security.js +37 -0
- package/dist/119.index.js +197 -0
- package/dist/128.index.js +129 -0
- package/dist/1293.index.js +5689 -0
- package/dist/1336.index.js +101 -101
- package/dist/1351.index.js +133 -0
- package/dist/1387.index.js +271 -0
- package/dist/1437.index.js +23 -0
- package/dist/1445.index.js +110 -0
- package/dist/1505.index.js +151 -0
- package/dist/1557.index.js +293 -0
- package/dist/1621.index.js +311 -0
- package/dist/1751.index.js +160 -0
- package/dist/1765.index.js +727 -0
- package/dist/1861.index.js +690 -0
- package/dist/1898.index.js +116 -0
- package/dist/1906.index.js +433 -0
- package/dist/1920.index.js +1 -291
- package/dist/208.index.js +1631 -0
- package/dist/2199.index.js +0 -239
- package/dist/2223.index.js +159 -0
- package/dist/2530.index.js +152 -0
- package/dist/2565.index.js +116 -0
- package/dist/2574.index.js +127 -0
- package/dist/2620.index.js +345 -0
- package/dist/2773.index.js +98 -0
- package/dist/2775.index.js +32 -0
- package/dist/2966.index.js +171 -0
- package/dist/2972.index.js +87 -0
- package/dist/2991.index.js +131 -0
- package/dist/3063.index.js +833 -0
- package/dist/3075.index.js +227 -0
- package/dist/3121.index.js +297 -0
- package/dist/3314.index.js +263 -0
- package/dist/3480.index.js +1822 -0
- package/dist/3481.index.js +24 -0
- package/dist/3506.index.js +268 -0
- package/dist/3533.index.js +591 -0
- package/dist/3653.index.js +450 -0
- package/dist/3822.index.js +131 -0
- package/dist/3868.index.js +292 -0
- package/dist/3917.index.js +152 -0
- package/dist/3918.index.js +192 -0
- package/dist/4090.index.js +288 -0
- package/dist/4121.index.js +1190 -0
- package/dist/4142.index.js +0 -239
- package/dist/4336.index.js +436 -0
- package/dist/4387.index.js +532 -0
- package/dist/4413.index.js +302 -0
- package/dist/4476.index.js +4510 -0
- package/dist/4491.index.js +133 -0
- package/dist/4538.index.js +185 -0
- package/dist/4684.index.js +319 -0
- package/dist/4740.index.js +165 -0
- package/dist/4780.index.js +209 -0
- package/dist/4802.index.js +225 -0
- package/dist/4812.index.js +379 -0
- package/dist/4835.index.js +126 -0
- package/dist/494.index.js +226 -0
- package/dist/4991.index.js +549 -0
- package/dist/5199.index.js +305 -0
- package/dist/5216.index.js +109 -0
- package/dist/5439.index.js +301 -0
- package/dist/5443.index.js +97 -0
- package/dist/5496.index.js +377 -0
- package/dist/550.index.js +2 -1
- package/dist/5801.index.js +588 -0
- package/dist/5858.index.js +223 -0
- package/dist/5933.index.js +143 -0
- package/dist/601.index.js +23 -0
- package/dist/6173.index.js +273 -0
- package/dist/6291.index.js +1871 -0
- package/dist/6312.index.js +666 -0
- package/dist/634.index.js +16182 -0
- package/dist/644.index.js +87 -0
- package/dist/6508.index.js +283 -0
- package/dist/6560.index.js +645 -0
- package/dist/6576.index.js +422 -0
- package/dist/6579.index.js +271 -0
- package/dist/6745.index.js +305 -0
- package/dist/6818.index.js +21642 -0
- package/dist/6972.index.js +167 -0
- package/dist/6995.index.js +163 -0
- package/dist/7249.index.js +191 -0
- package/dist/7372.index.js +1197 -0
- package/dist/7405.index.js +23 -6
- package/dist/7546.index.js +541 -0
- package/dist/76.index.js +448 -0
- package/dist/7879.index.js +163 -0
- package/dist/8025.index.js +130 -0
- package/dist/8054.index.js +248 -0
- package/dist/8097.index.js +201 -0
- package/dist/8126.index.js +191 -0
- package/dist/8178.index.js +78 -0
- package/dist/818.index.js +212 -0
- package/dist/8382.index.js +93 -0
- package/dist/8408.index.js +261 -0
- package/dist/8540.index.js +174 -0
- package/dist/8635.index.js +802 -0
- package/dist/8688.index.js +410 -0
- package/dist/8699.index.js +779 -0
- package/dist/8891.index.js +457 -0
- package/dist/8916.index.js +885 -0
- package/dist/9146.index.js +170 -0
- package/dist/9168.index.js +841 -0
- package/dist/9292.index.js +369 -0
- package/dist/9382.index.js +338 -48
- package/dist/9422.index.js +866 -0
- package/dist/9440.index.js +453 -0
- package/dist/9479.index.js +152 -0
- package/dist/9481.index.js +220 -0
- package/dist/9539.index.js +100 -0
- package/dist/9547.index.js +103 -0
- package/dist/9560.index.js +41 -61
- package/dist/9692.index.js +90 -0
- package/dist/9800.index.js +213 -1
- package/dist/9899.index.js +138 -0
- package/dist/9981.index.js +206 -0
- package/dist/999.index.js +117 -0
- package/dist/agentic-security.mjs +28 -28
- package/dist/agentic-security.mjs.sha256 +1 -1
- package/package.json +31 -6
- package/src/context-intelligence/CLAUDE.md +4 -0
- package/src/context-intelligence/adapters/haskell-hie.js +28 -1
- package/src/context-intelligence/candidates.js +2 -1
- package/src/context-intelligence/canonical.js +25 -0
- package/src/context-intelligence/capabilities.js +2 -1
- package/src/context-intelligence/cli.js +16 -10
- package/src/context-intelligence/config.js +4 -1
- package/src/context-intelligence/controls.js +108 -10
- package/src/context-intelligence/counterexample.js +200 -0
- package/src/context-intelligence/cross-route.js +111 -0
- package/src/context-intelligence/entrypoints.js +2 -2
- package/src/context-intelligence/evaluation/index.js +11 -4
- package/src/context-intelligence/evaluation/scripted-reader.js +12 -2
- package/src/context-intelligence/findings.js +166 -0
- package/src/context-intelligence/invariants.js +197 -0
- package/src/context-intelligence/queue.js +6 -1
- package/src/context-intelligence/reachability.js +10 -4
- package/src/context-intelligence/review.js +129 -16
- package/src/dataflow/access-paths.js +11 -0
- package/src/dataflow/analysis-limits.js +66 -0
- package/src/dataflow/engine.js +272 -20
- package/src/dataflow/service-edges.js +174 -0
- package/src/dataflow/summaries.js +7 -1
- package/src/discovery/judge.js +2 -1
- package/src/engine.js +82 -14
- package/src/fix/apply-fix-service.js +55 -8
- package/src/fix/batch-journal.js +84 -0
- package/src/fix/exploit-replay.js +51 -0
- package/src/fix/footprint.js +65 -0
- package/src/fix/patch-policy.js +49 -0
- package/src/fix/secret-persistence.js +39 -0
- package/src/fix/test-commands.js +84 -0
- package/src/fix/verification-adapters.js +97 -0
- package/src/fix/verification-record.js +243 -0
- package/src/fix/verify-patch.js +89 -0
- package/src/ir/CLAUDE.md +5 -0
- package/src/ir/callgraph.js +23 -1
- package/src/ir/module-resolver.js +315 -0
- package/src/ir/parser-js.js +21 -2
- package/src/ir/semantic-graph.js +245 -0
- package/src/lineage/build-attested-correlation.js +63 -0
- package/src/lineage/deployment-identity.js +76 -0
- package/src/mcp/tools.js +13 -34
- package/src/pipeline/canonical-output.js +64 -0
- package/src/pipeline/detector-runner.js +7 -0
- package/src/pipeline/scan-health.js +14 -0
- package/src/pipeline/supervised-analyzers.js +125 -0
- package/src/pipeline/supervised-child.js +17 -0
- package/src/pipeline/telemetry.js +79 -0
- package/src/posture/accuracy-scorecard.js +98 -9
- package/src/posture/artifact-registry.js +5 -0
- package/src/posture/assurance/evidence-registry.js +215 -0
- package/src/posture/assurance/release-package-verify.js +135 -0
- package/src/posture/assurance/release-package.js +118 -0
- package/src/posture/assurance/release-verdict.js +94 -0
- package/src/posture/assurance/waiver.js +200 -0
- package/src/posture/compliance-truth.js +100 -0
- package/src/posture/evaluation/scan-child.js +3 -1
- package/src/posture/evaluation/why-missed.js +3 -1
- package/src/posture/exploitability-evidence.js +141 -0
- package/src/posture/fix-verify.js +4 -2
- package/src/posture/fleet/exposure-ranking.js +115 -0
- package/src/posture/fleet/service-chain-graph.js +150 -0
- package/src/posture/graph-reconciliation.js +154 -0
- package/src/posture/provenance/coverage.js +103 -0
- package/src/posture/provenance/git-evidence.js +84 -0
- package/src/posture/provenance/origin-resolver.js +7 -1
- package/src/posture/provenance/predicate-replay.js +14 -4
- package/src/posture/runtime-correlation.js +61 -15
- package/src/posture/runtime-explanation.js +116 -0
- package/src/posture/runtime-trace-ingest.js +194 -0
- package/src/posture/scan-checkpoint.js +38 -10
- package/src/posture/waf-ingest.js +7 -2
- package/src/report/index.js +4 -0
- package/src/report/oscal.js +38 -0
- package/src/report/perspectives.js +150 -0
- package/src/sast/_comment-strip.js +8 -1
- package/src/sast/ruby.js +26 -0
- 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
|
+
};
|