@clear-capabilities/agentic-security-scanner 0.143.0 → 0.145.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 +555 -0
- package/bin/agentic-security.js +770 -49
- package/dist/1.index.js +223 -0
- package/dist/113.index.js +117 -18
- package/dist/144.index.js +163 -0
- package/dist/178.index.js +24 -6
- package/dist/238.index.js +3 -2
- package/dist/265.index.js +191 -0
- package/dist/271.index.js +165 -0
- package/dist/384.index.js +1 -1
- package/dist/435.index.js +187 -52
- package/dist/444.index.js +11 -2
- package/dist/449.index.js +76 -12
- package/dist/526.index.js +117 -18
- package/dist/552.index.js +97 -0
- package/dist/637.index.js +27 -5
- package/dist/730.index.js +311 -0
- package/dist/736.index.js +301 -0
- package/dist/824.index.js +7 -0
- package/dist/905.index.js +88 -22
- package/dist/920.index.js +491 -0
- package/dist/970.index.js +173 -0
- package/dist/agentic-security.mjs +13 -13
- package/dist/agentic-security.mjs.sha256 +1 -1
- package/dist/calibration-seed.json +2 -0
- package/package.json +23 -9
- package/src/compare.js +6 -1
- package/src/dataflow/CLAUDE.md +1 -1
- package/src/dataflow/index.js +18 -0
- package/src/dataflow/privacy-catalog.js +290 -0
- package/src/dataflow/privacy-deep-walker.js +515 -0
- package/src/dataflow/privacy-governance.js +126 -0
- package/src/dataflow/privacy-inventory.js +154 -0
- package/src/dataflow/privacy-sink-policy.js +125 -0
- package/src/dataflow/privacy-taint.js +115 -54
- package/src/dataflow/privacy-taxonomy.js +233 -0
- package/src/discovery/disprove.js +7 -3
- package/src/discovery/hunter.js +9 -5
- package/src/discovery/index.js +2 -2
- package/src/discovery/llm-invoke.js +69 -13
- package/src/egress/audit.js +147 -0
- package/src/egress/policy.js +313 -0
- package/src/egress/redact.js +180 -0
- package/src/engine.js +1048 -302
- package/src/fix/apply-fix-service.js +404 -0
- package/src/fix/approver-registry.js +157 -0
- package/src/history-scan.js +22 -5
- package/src/ir/CLAUDE.md +1 -1
- package/src/llm-validator/index.js +86 -9
- package/src/llm-validator/model-status.js +66 -0
- package/src/lsp/server.js +49 -2
- package/src/mcp/tools.js +177 -50
- package/src/pipeline/analyzer-supervisor.js +93 -0
- package/src/pipeline/analyzer-worker.js +26 -0
- package/src/pipeline/annotator-runner.js +33 -0
- package/src/pipeline/assurance-mode.js +154 -0
- package/src/pipeline/cascade-worker-pool.js +172 -0
- package/src/pipeline/cascade-worker.js +43 -0
- package/src/pipeline/coverage-ledger.js +0 -0
- package/src/pipeline/detector-runner.js +51 -0
- package/src/pipeline/enrichment-completion.js +58 -0
- package/src/pipeline/evidence-provenance.js +91 -0
- package/src/pipeline/finding-schema.js +108 -0
- package/src/pipeline/legacy-compat.js +101 -0
- package/src/pipeline/producer-collector.js +48 -0
- package/src/pipeline/producer-registry.js +112 -0
- package/src/pipeline/scan-health.js +144 -0
- package/src/posture/CLAUDE.md +123 -0
- package/src/posture/accuracy-scorecard.js +156 -1
- package/src/posture/adversary-agent.js +15 -3
- package/src/posture/artifact-registry.js +241 -0
- package/src/posture/auditor-walkthrough.js +186 -21
- package/src/posture/calibration-feedback.js +201 -0
- package/src/posture/calibration-seed.json +2 -0
- package/src/posture/calibration.js +25 -0
- package/src/posture/compliance-evidence-signing.js +131 -0
- package/src/posture/compliance-policy.js +324 -17
- package/src/posture/cross-repo-memory.js +7 -2
- package/src/posture/custom-rules.js +36 -0
- package/src/posture/deterministic.js +8 -1
- package/src/posture/encryption-provider.js +205 -0
- package/src/posture/evidence-grade-wording.js +71 -0
- package/src/posture/fix-history.js +137 -20
- package/src/posture/fix-honesty-gate.js +47 -6
- package/src/posture/fix-verify.js +65 -8
- package/src/posture/fleet.js +0 -0
- package/src/posture/flow-narration.js +7 -2
- package/src/posture/git-history.js +13 -5
- package/src/posture/legal-hold.js +140 -0
- package/src/posture/llm-redteam.js +10 -1
- package/src/posture/material-change.js +111 -2
- package/src/posture/mttr.js +75 -12
- package/src/posture/policy-bundle.js +274 -0
- package/src/posture/pre-incident-archaeology.js +39 -7
- package/src/posture/privacy-framework.js +47 -6
- package/src/posture/production-feedback.js +179 -0
- package/src/posture/provenance/ai-authorship.js +68 -0
- package/src/posture/provenance/branch-entry.js +80 -0
- package/src/posture/provenance/cache.js +143 -0
- package/src/posture/provenance/confidence.js +36 -0
- package/src/posture/provenance/coordinator.js +786 -0
- package/src/posture/provenance/dag-walk.js +249 -0
- package/src/posture/provenance/evidence-attribution.js +59 -0
- package/src/posture/provenance/git-evidence.js +310 -0
- package/src/posture/provenance/lifecycle.js +208 -0
- package/src/posture/provenance/missing-control-resolver.js +137 -0
- package/src/posture/provenance/origin-resolver.js +342 -0
- package/src/posture/provenance/predicate-replay.js +133 -0
- package/src/posture/provenance/providers/config.js +39 -0
- package/src/posture/provenance/providers/github.js +62 -0
- package/src/posture/provenance/providers/gitlab.js +58 -0
- package/src/posture/provenance/repo-lineage.js +74 -0
- package/src/posture/provenance/sca-origin.js +139 -0
- package/src/posture/provenance/schema.js +255 -0
- package/src/posture/provenance/transitive-sca.js +147 -0
- package/src/posture/provenance/validate.js +30 -0
- package/src/posture/provenance-evidence-bundle.js +144 -0
- package/src/posture/retention-policy.js +132 -0
- package/src/posture/risk-dollars.js +216 -26
- package/src/posture/sbom-diff.js +15 -2
- package/src/posture/scan-checkpoint.js +176 -31
- package/src/posture/secret-history.js +10 -2
- package/src/posture/state-dir.js +64 -5
- package/src/posture/state-lifecycle-report.js +77 -0
- package/src/posture/suppressions.js +59 -3
- package/src/posture/vuln-archaeology.js +8 -2
- package/src/pr-delta.js +25 -4
- package/src/privacy/ir-adapter.js +380 -0
- package/src/report/index.js +248 -5
- package/src/report/oscal.js +7 -2
- package/src/runScan.js +34 -5
- package/src/sast/cpp.js +3 -14
- package/src/sast/rate-limit.js +33 -3
- package/src/sca/llm-function-extract.js +6 -0
- package/src/util/git-hardening.js +128 -0
package/dist/905.index.js
CHANGED
|
@@ -111,6 +111,8 @@ function partitionCallGraph(callGraph, opts = {}) {
|
|
|
111
111
|
|
|
112
112
|
// EXTERNAL MODULE: ./src/discovery/lenses.js
|
|
113
113
|
var discovery_lenses = __webpack_require__(3499);
|
|
114
|
+
// EXTERNAL MODULE: ./src/egress/policy.js
|
|
115
|
+
var policy = __webpack_require__(5712);
|
|
114
116
|
;// CONCATENATED MODULE: ./src/discovery/llm-invoke.js
|
|
115
117
|
//
|
|
116
118
|
// Shared LLM endpoint caller. Both the hunter and the refutation panel need
|
|
@@ -119,6 +121,8 @@ var discovery_lenses = __webpack_require__(3499);
|
|
|
119
121
|
// other does not, the bug stays buried in one direction.
|
|
120
122
|
//
|
|
121
123
|
|
|
124
|
+
|
|
125
|
+
|
|
122
126
|
const DEFAULT_TIMEOUT_MS = 60000;
|
|
123
127
|
|
|
124
128
|
async function defaultLlmInvoke(prompt, opts = {}) {
|
|
@@ -202,12 +206,27 @@ function consensusOf(responses) {
|
|
|
202
206
|
* An llmInvoke that queries several endpoints and returns the consensus answer.
|
|
203
207
|
* Returns null when no endpoint answered — the callers already treat a null or
|
|
204
208
|
* a throw as degradation, so an all-providers-down run degrades honestly.
|
|
209
|
+
*
|
|
210
|
+
* FR-605 (assurance-hardening PRD): each endpoint gets its OWN egress
|
|
211
|
+
* decision before being included — `mode: local-only` / `deniedProviders`
|
|
212
|
+
* must not be smuggled past just because the SINGLE-endpoint path already
|
|
213
|
+
* checks it. A policy-denied endpoint is EXCLUDED from the vote, the exact
|
|
214
|
+
* same treatment an unreachable endpoint already gets a few lines below
|
|
215
|
+
* (never counted as dissent) — the module's own established pattern
|
|
216
|
+
* extended to a second exclusion reason. Returns `{invoke, decisions}`:
|
|
217
|
+
* `invoke` is null only when EVERY endpoint was denied (the all-down
|
|
218
|
+
* equivalent); `decisions` is the full per-endpoint array for a caller
|
|
219
|
+
* that wants it, kept alongside the single aggregate `decision` the
|
|
220
|
+
* pre-existing callers already read.
|
|
205
221
|
*/
|
|
206
|
-
function makeConsensusInvoke(endpoints, { timeoutMs } = {}) {
|
|
222
|
+
function makeConsensusInvoke(endpoints, { timeoutMs, scanRoot, purpose } = {}) {
|
|
207
223
|
const list = parseEndpoints(endpoints);
|
|
208
|
-
if (list.length === 0) return null;
|
|
209
|
-
|
|
210
|
-
|
|
224
|
+
if (list.length === 0) return { invoke: null, decisions: [] };
|
|
225
|
+
const decisions = list.map(url => (0,policy/* evaluateEgress */.nn)({ scanRoot, purpose: purpose || 'discovery-consensus', endpoint: url }));
|
|
226
|
+
const allowedList = list.filter((_, i) => decisions[i].allowed);
|
|
227
|
+
if (allowedList.length === 0) return { invoke: null, decisions };
|
|
228
|
+
const invoke = async (prompt) => {
|
|
229
|
+
const answers = await Promise.all(allowedList.map(async (url) => {
|
|
211
230
|
try { return await defaultLlmInvoke(prompt, { timeoutMs, endpoint: url }); }
|
|
212
231
|
catch { return null; } // excluded from the vote, never counted as dissent
|
|
213
232
|
}));
|
|
@@ -215,22 +234,61 @@ function makeConsensusInvoke(endpoints, { timeoutMs } = {}) {
|
|
|
215
234
|
if (value === null) throw new Error('no LLM endpoint answered');
|
|
216
235
|
return value;
|
|
217
236
|
};
|
|
237
|
+
return { invoke, decisions };
|
|
218
238
|
}
|
|
219
239
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
240
|
+
// FR-601: egress policy is evaluated here, BEFORE returning a callable and
|
|
241
|
+
// therefore before either caller (hunter.js, disprove.js) builds a prompt —
|
|
242
|
+
// a denied decision makes this resolve to `invoke: null`, which both callers
|
|
243
|
+
// already treat as "nothing to call" via their existing degrade path, so a
|
|
244
|
+
// denial produces no network request through the exact same code path a
|
|
245
|
+
// missing endpoint always has. `decision` carries the machine-readable
|
|
246
|
+
// reason so callers can distinguish "not configured" from "configured but
|
|
247
|
+
// policy-denied" in their own degrade message, rather than reporting a
|
|
248
|
+
// generic "not set" that would be actively wrong once policy is what
|
|
249
|
+
// blocked the call.
|
|
250
|
+
//
|
|
251
|
+
// FR-605 (assurance-hardening PRD): consensus mode (multiple endpoints) is
|
|
252
|
+
// now egress-filtered per endpoint, same as the single-endpoint path below —
|
|
253
|
+
// this is what closes the actual "a remote URL cannot be smuggled into
|
|
254
|
+
// local-only configuration" gap the paragraph below used to describe as
|
|
255
|
+
// open. Per-endpoint CONSTRAINT dimensions beyond allow/deny/local-only
|
|
256
|
+
// (role/region/repository/path/data-class — one provider allowed, another
|
|
257
|
+
// denied for a REASON beyond the deny-list) remain FR-602's separate scope;
|
|
258
|
+
// what changed here is that the single allow/deny/local-only gate FR-601
|
|
259
|
+
// already built is no longer bypassable just by using multiple endpoints
|
|
260
|
+
// instead of one.
|
|
261
|
+
function resolveLlmInvokeWithDecision(opts = {}) {
|
|
262
|
+
// An injected callback is a test/consumer-controlled escape hatch — it
|
|
263
|
+
// bypasses egress the same way it already bypasses endpoint resolution,
|
|
264
|
+
// because there is no real endpoint here for a policy to evaluate.
|
|
265
|
+
if (opts.llmInvoke) return { invoke: opts.llmInvoke, decision: null };
|
|
225
266
|
|
|
226
267
|
const multi = opts.endpoints || process.env[DEFAULT_CONSENSUS_ENV];
|
|
227
268
|
if (multi) {
|
|
228
|
-
const
|
|
229
|
-
|
|
269
|
+
const { invoke, decisions } = makeConsensusInvoke(multi, { timeoutMs: opts.timeoutMs, scanRoot: opts.scanRoot, purpose: opts.purpose });
|
|
270
|
+
// A single aggregate `decision` for the pre-existing callers (hunter.js,
|
|
271
|
+
// disprove.js), which only ever read `.reason` when `invoke` is null —
|
|
272
|
+
// the all-denied case. The full per-endpoint detail is on `decisions`
|
|
273
|
+
// for a caller that wants it.
|
|
274
|
+
const decision = decisions.find(d => !d.allowed) || decisions[0] || null;
|
|
275
|
+
return { invoke, decision, decisions };
|
|
230
276
|
}
|
|
231
277
|
|
|
232
|
-
|
|
233
|
-
|
|
278
|
+
const endpoint = process.env.AGENTIC_SECURITY_LLM_ENDPOINT;
|
|
279
|
+
if (!endpoint) return { invoke: null, decision: null };
|
|
280
|
+
|
|
281
|
+
const decision = (0,policy/* evaluateEgress */.nn)({ scanRoot: opts.scanRoot, purpose: opts.purpose || 'discovery', endpoint });
|
|
282
|
+
if (!decision.allowed) return { invoke: null, decision };
|
|
283
|
+
|
|
284
|
+
return { invoke: (prompt) => defaultLlmInvoke(prompt, { timeoutMs: opts.timeoutMs }), decision };
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function resolveLlmInvoke(opts = {}) {
|
|
288
|
+
// Precedence, most explicit first: an injected callback beats configuration,
|
|
289
|
+
// and a multi-endpoint list beats a single endpoint. A caller who supplied
|
|
290
|
+
// their own function must always get exactly that function.
|
|
291
|
+
return resolveLlmInvokeWithDecision(opts).invoke;
|
|
234
292
|
}
|
|
235
293
|
|
|
236
294
|
;// CONCATENATED MODULE: ./src/discovery/hunter.js
|
|
@@ -311,12 +369,16 @@ async function runHunter(focusArea, lens, ctx = {}, opts = {}) {
|
|
|
311
369
|
const transcript = [];
|
|
312
370
|
const lensKey = lens?.key || 'unknown';
|
|
313
371
|
const base = { focusAreaId: focusArea.id, lens: lensKey, transcript };
|
|
314
|
-
const llmInvoke =
|
|
372
|
+
const { invoke: llmInvoke, decision: egressDecision } = resolveLlmInvokeWithDecision({ ...opts, purpose: 'discovery-hunter' });
|
|
315
373
|
|
|
316
374
|
if (typeof llmInvoke !== 'function') {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
375
|
+
// FR-601: distinguish "policy denied a configured endpoint" from "nothing
|
|
376
|
+
// was configured at all" — the reason must reflect which actually happened.
|
|
377
|
+
const reason = egressDecision
|
|
378
|
+
? `egress policy denied this call: ${egressDecision.reason}`
|
|
379
|
+
: 'no llmInvoke supplied and AGENTIC_SECURITY_LLM_ENDPOINT not set';
|
|
380
|
+
appendEntry(transcript, { phase: 'init', reason, egressDecision: egressDecision || undefined });
|
|
381
|
+
return { ...base, candidates: [], degraded: true, reason, egressDecision };
|
|
320
382
|
}
|
|
321
383
|
|
|
322
384
|
let prompt;
|
|
@@ -454,7 +516,11 @@ function parseVote(raw) {
|
|
|
454
516
|
|
|
455
517
|
async function disproveCandidate(candidate, opts = {}) {
|
|
456
518
|
const angles = Array.isArray(opts.angles) && opts.angles.length ? opts.angles : DEFAULT_ANGLES;
|
|
457
|
-
|
|
519
|
+
// FR-601: an egress-policy denial resolves llmInvoke to null the same way a
|
|
520
|
+
// missing endpoint always has, so it falls straight into this module's own
|
|
521
|
+
// pre-existing rule — "silence never refutes" — with zero votes cast and no
|
|
522
|
+
// prompt ever built for a denied endpoint.
|
|
523
|
+
const { invoke: llmInvoke, decision: egressDecision } = resolveLlmInvokeWithDecision({ ...opts, purpose: 'discovery-disprove' });
|
|
458
524
|
|
|
459
525
|
const votes = [];
|
|
460
526
|
if (typeof llmInvoke === 'function') {
|
|
@@ -469,7 +535,7 @@ async function disproveCandidate(candidate, opts = {}) {
|
|
|
469
535
|
const refuteCount = votes.filter(v => v.refuted).length;
|
|
470
536
|
const undecided = voterCount === 0;
|
|
471
537
|
const refuted = !undecided && refuteCount * 2 > voterCount;
|
|
472
|
-
return { ...candidate, refutation: { votes, voterCount, refuteCount, refuted, undecided } };
|
|
538
|
+
return { ...candidate, refutation: { votes, voterCount, refuteCount, refuted, undecided, egressDecision: egressDecision || undefined } };
|
|
473
539
|
}
|
|
474
540
|
|
|
475
541
|
async function disprovePanel(candidates, opts = {}) {
|
|
@@ -800,7 +866,7 @@ function makeTaintProbe(perFileIR, callGraph) {
|
|
|
800
866
|
|
|
801
867
|
async function runDeepAnalysisSafe(perFileIR, callGraph) {
|
|
802
868
|
try {
|
|
803
|
-
const { runDeepAnalysis } = await Promise.resolve(/* import() */).then(__webpack_require__.bind(__webpack_require__,
|
|
869
|
+
const { runDeepAnalysis } = await Promise.resolve(/* import() */).then(__webpack_require__.bind(__webpack_require__, 7165));
|
|
804
870
|
return runDeepAnalysis(perFileIR, callGraph, {});
|
|
805
871
|
} catch {
|
|
806
872
|
return null;
|
|
@@ -925,7 +991,7 @@ async function runDiscovery(ctx = {}, opts = {}) {
|
|
|
925
991
|
for (const area of areas) {
|
|
926
992
|
let areaDegradedCount = 0;
|
|
927
993
|
for (const lens of lenses) {
|
|
928
|
-
const run = await runHunter(area, lens, { fileContents: ctx.fileContents || {} }, { llmInvoke });
|
|
994
|
+
const run = await runHunter(area, lens, { fileContents: ctx.fileContents || {} }, { llmInvoke, scanRoot: opts.scanRoot });
|
|
929
995
|
runs.push({ focusAreaId: run.focusAreaId, lens: run.lens, degraded: run.degraded, reason: run.reason, candidateCount: run.candidates.length });
|
|
930
996
|
if (run.degraded && run.reason) reasons.push(`${area.label} × ${lens.key}: ${run.reason}`);
|
|
931
997
|
if (run.degraded) areaDegradedCount += 1;
|
|
@@ -987,7 +1053,7 @@ async function runDiscovery(ctx = {}, opts = {}) {
|
|
|
987
1053
|
'every candidate is reported unconfirmed and severity is not evidence-derived');
|
|
988
1054
|
}
|
|
989
1055
|
const confirmed = await confirmAll(candidates, { taintProbe });
|
|
990
|
-
const { survivors, refuted } = await disprovePanel(confirmed, { llmInvoke });
|
|
1056
|
+
const { survivors, refuted } = await disprovePanel(confirmed, { llmInvoke, scanRoot: opts.scanRoot });
|
|
991
1057
|
const { fresh, duplicates, suppressed } = judgeCandidates(survivors, ctx.priorScan, ctx.triageFeedback);
|
|
992
1058
|
|
|
993
1059
|
// A spent budget is a coverage gap, stated once at the top level rather than
|