@clear-capabilities/agentic-security-scanner 0.143.0 → 0.144.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 +304 -0
- package/bin/agentic-security.js +477 -47
- package/dist/1.index.js +223 -0
- package/dist/113.index.js +108 -17
- package/dist/144.index.js +163 -0
- package/dist/178.index.js +1 -1
- package/dist/238.index.js +3 -2
- package/dist/265.index.js +191 -0
- package/dist/384.index.js +1 -1
- package/dist/435.index.js +165 -52
- package/dist/526.index.js +108 -17
- package/dist/552.index.js +97 -0
- package/dist/637.index.js +1 -1
- 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 +109 -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 +19 -11
- 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 +575 -288
- package/src/fix/apply-fix-service.js +403 -0
- package/src/fix/approver-registry.js +157 -0
- package/src/llm-validator/index.js +86 -9
- package/src/llm-validator/model-status.js +66 -0
- package/src/mcp/tools.js +157 -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 +91 -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 +101 -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 +2 -0
- package/src/posture/accuracy-scorecard.js +96 -1
- package/src/posture/adversary-agent.js +15 -3
- package/src/posture/artifact-registry.js +217 -0
- package/src/posture/auditor-walkthrough.js +70 -8
- 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 +314 -17
- 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 +113 -19
- package/src/posture/fix-honesty-gate.js +47 -6
- package/src/posture/fix-verify.js +56 -7
- package/src/posture/fleet.js +0 -0
- package/src/posture/flow-narration.js +7 -2
- package/src/posture/legal-hold.js +140 -0
- package/src/posture/llm-redteam.js +10 -1
- package/src/posture/material-change.js +90 -0
- package/src/posture/policy-bundle.js +274 -0
- package/src/posture/privacy-framework.js +33 -6
- package/src/posture/production-feedback.js +179 -0
- package/src/posture/retention-policy.js +132 -0
- package/src/posture/risk-dollars.js +216 -26
- package/src/posture/scan-checkpoint.js +176 -31
- package/src/posture/state-dir.js +36 -1
- package/src/posture/state-lifecycle-report.js +77 -0
- package/src/posture/suppressions.js +59 -3
- package/src/privacy/ir-adapter.js +380 -0
- package/src/report/index.js +51 -2
- package/src/report/oscal.js +7 -2
- package/src/sast/cpp.js +3 -14
- package/src/sca/llm-function-extract.js +6 -0
package/src/report/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { _isCustomSuppressed } from '../engine.js';
|
|
|
4
4
|
import { alertFace, approveFace } from './mascot.js';
|
|
5
5
|
import { SCANNER_VERSION } from '../posture/version.js';
|
|
6
6
|
import { proofBlock } from '../posture/proof-artifact.js';
|
|
7
|
+
import { applyLegacyCompat, legacyFieldDeprecationNotice } from '../pipeline/legacy-compat.js';
|
|
7
8
|
|
|
8
9
|
const SEV_RANK = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
|
|
9
10
|
const SEV_TO_SARIF = { critical: 'error', high: 'error', medium: 'warning', low: 'note', info: 'none' };
|
|
@@ -426,6 +427,12 @@ export function normalizeFindings(scan){
|
|
|
426
427
|
toxicityLabel: sc.toxicityLabel || null,
|
|
427
428
|
});
|
|
428
429
|
}
|
|
430
|
+
// FR-108: backfill deprecated field names from their current replacements
|
|
431
|
+
// before this shape reaches ANY consumer (JSON/SARIF/HTML/CSV/JUnit/MCP
|
|
432
|
+
// all derive from this one function) — one place, covers every output
|
|
433
|
+
// format, matches this function's own role as "the canonical shape".
|
|
434
|
+
for (const f of out) applyLegacyCompat(f);
|
|
435
|
+
|
|
429
436
|
// Sort by severity tier, then within a tier by EPSS percentile (desc) so that
|
|
430
437
|
// CVEs with active in-the-wild abuse float above theoretical CVEs.
|
|
431
438
|
return out.sort((a, b) => {
|
|
@@ -516,6 +523,13 @@ export function toJSON(scan, meta={}, opts={}){
|
|
|
516
523
|
// threw and were skipped. The findings still ship; downstream consumers
|
|
517
524
|
// see the gap.
|
|
518
525
|
annotatorErrors: Array.isArray(scan.annotatorErrors) ? scan.annotatorErrors : [],
|
|
526
|
+
// FR-201 (assurance-hardening PRD, E2): per-file SAST/logic/secrets
|
|
527
|
+
// detector errors, captured by pipeline/detector-runner.js's runDetector()
|
|
528
|
+
// isolation wrapper. Empty array means clean; entries here mean one or
|
|
529
|
+
// more detectors threw on a specific file and were skipped -- other
|
|
530
|
+
// detectors and other files still ran (that isolation is the acceptance
|
|
531
|
+
// criterion itself, not just this field's presence).
|
|
532
|
+
detectorErrors: Array.isArray(scan.detectorErrors) ? scan.detectorErrors : [],
|
|
519
533
|
// R4 — run attestation: a stable, order-independent digest over this
|
|
520
534
|
// finding set bound to the engine/ruleset/bundle that produced it.
|
|
521
535
|
// Attached by the CLI (posture/attestation.js); null when not computed.
|
|
@@ -529,6 +543,10 @@ export function toJSON(scan, meta={}, opts={}){
|
|
|
529
543
|
// render an empty one. An AUTHORISED suppression is reported too — the
|
|
530
544
|
// signature proves who asked for it, not that the results are absent.
|
|
531
545
|
suppressedRules: scan.suppressedRules || null,
|
|
546
|
+
// FR-108: null in the (eventual, intended) steady state where no finding
|
|
547
|
+
// used a deprecated field name; present only when normalizeFindings()
|
|
548
|
+
// actually backfilled a legacy alias, naming exactly which and why.
|
|
549
|
+
legacyFieldNotice: legacyFieldDeprecationNotice(findings),
|
|
532
550
|
_scanMeta: scan._scanMeta || null,
|
|
533
551
|
// S7: engine.js computes these on every scan with components, and their
|
|
534
552
|
// own findings already flow into `findings` above — but the structured
|
|
@@ -546,6 +564,21 @@ export function toJSON(scan, meta={}, opts={}){
|
|
|
546
564
|
rootCauseSweep: scan.rootCauseSweep || null,
|
|
547
565
|
attackTaxonomy: scan.attackTaxonomy || null,
|
|
548
566
|
privacyFramework: scan.privacyFramework || null,
|
|
567
|
+
// FR-405: `false` is a real, meaningful value here (privacy analysis
|
|
568
|
+
// ran but was not IR-backed) — must not collapse to null via `||`,
|
|
569
|
+
// which would make it indistinguishable from "never ran at all".
|
|
570
|
+
privacyIrBacked: scan.privacyIrBacked ?? null,
|
|
571
|
+
// FR-402: which privacy data-classification taxonomy version actually
|
|
572
|
+
// classified this scan (built-in, or built-in+custom when an operator's
|
|
573
|
+
// .agentic-security/privacy-taxonomy.json extended it) — see
|
|
574
|
+
// dataflow/privacy-taxonomy.js. Null means the annotator never ran.
|
|
575
|
+
privacyTaxonomyVersion: scan.privacyTaxonomyVersion ?? null,
|
|
576
|
+
// Assurance-hardening PRD FR-206 (Milestone 0): additive scan-health
|
|
577
|
+
// summary (pipeline/scan-health.js) — separates "no findings" from
|
|
578
|
+
// "analysis complete". Null when the engine did not compute one (e.g. a
|
|
579
|
+
// hand-built `scan` object in a test), same convention as the S7 fields
|
|
580
|
+
// above; a real scan always computes it.
|
|
581
|
+
scanHealth: scan.scanHealth || null,
|
|
549
582
|
};
|
|
550
583
|
if (opts.includeSuppressed) out.suppressed = scan.suppressions||[];
|
|
551
584
|
return out;
|
|
@@ -1284,17 +1317,33 @@ export function toShipVerdict(scan, options = {}) {
|
|
|
1284
1317
|
const confirmedCount = findings.filter(f => f.validated === true || f.confirmed === true).length;
|
|
1285
1318
|
const cats = categoryScores(findings);
|
|
1286
1319
|
|
|
1320
|
+
// FR-206: "a partial zero-finding scan never says clean." `scanHealth`
|
|
1321
|
+
// (computeScanHealth, scan-health.js) was already computed correctly —
|
|
1322
|
+
// an annotator error, a file timeout, or a skipped analyzer already
|
|
1323
|
+
// demoted `status` to 'partial' — but nothing in this, the actual
|
|
1324
|
+
// human-facing one-screen verdict, ever consulted it. Zero actionable
|
|
1325
|
+
// findings on a scan that didn't finish cleanly is "we don't know," not
|
|
1326
|
+
// "safe."
|
|
1327
|
+
const scanIncomplete = scan.scanHealth && scan.scanHealth.status && scan.scanHealth.status !== 'complete';
|
|
1328
|
+
|
|
1287
1329
|
const lines = [];
|
|
1288
1330
|
const bar = '─────────────────────────────────────────';
|
|
1289
1331
|
// Patch the mascot reacts to the result — APPROVE if clean, ALERT if findings.
|
|
1290
|
-
|
|
1332
|
+
const clean = actionable.length === 0 && !scanIncomplete;
|
|
1333
|
+
lines.push(clean ? approveFace({ color }) : alertFace({ color }));
|
|
1291
1334
|
lines.push(bar);
|
|
1292
|
-
if (
|
|
1335
|
+
if (clean) {
|
|
1293
1336
|
lines.push(c(' ✅ Safe to deploy', SEV_COLOR.low + BOLD));
|
|
1337
|
+
} else if (actionable.length === 0) {
|
|
1338
|
+
lines.push(c(' ⚠️ Scan incomplete — cannot confirm safe to deploy', SEV_COLOR.high + BOLD));
|
|
1294
1339
|
} else {
|
|
1295
1340
|
lines.push(c(' ❌ Not safe to deploy', SEV_COLOR.critical + BOLD));
|
|
1296
1341
|
}
|
|
1297
1342
|
lines.push(bar);
|
|
1343
|
+
if (scanIncomplete) {
|
|
1344
|
+
const conditions = Array.isArray(scan.scanHealth.conditions) ? scan.scanHealth.conditions : [];
|
|
1345
|
+
lines.push(c(` ${scan.scanHealth.status}: ${conditions[0] || 'analysis did not complete cleanly'}${conditions.length > 1 ? ` (+${conditions.length - 1} more)` : ''}`, SEV_COLOR.high));
|
|
1346
|
+
}
|
|
1298
1347
|
lines.push(` • ${sev.critical} critical · ${sev.high} high · ${advisoryCount} advisory`);
|
|
1299
1348
|
// Per-category 0..100 score bars — Secrets / Permissions / Hooks / MCP / Agents.
|
|
1300
1349
|
// Only render when at least one category has been contributed to (skip the
|
package/src/report/oscal.js
CHANGED
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
import crypto from 'node:crypto';
|
|
51
51
|
import { isDeterministic, SCANNER_VERSION } from '../posture/deterministic.js';
|
|
52
52
|
import { normalizeFindings, TOOL_CAVEATS } from './index.js';
|
|
53
|
+
import { EVIDENCE_GRADE_DISCLAIMER } from '../posture/evidence-grade-wording.js';
|
|
53
54
|
|
|
54
55
|
// The OSCAL release these documents declare conformance to. Bump deliberately:
|
|
55
56
|
// `oscal-version` is a claim a validator checks the rest of the document
|
|
@@ -620,9 +621,13 @@ export function toOSCALCompliance(framework, rows, meta = {}) {
|
|
|
620
621
|
observations,
|
|
621
622
|
...(findings.length ? { findings } : {}),
|
|
622
623
|
...(risks.length ? { risks } : {}),
|
|
624
|
+
// FR-507: names all three assurance tiers explicitly, not just "not
|
|
625
|
+
// certified" — see evidence-grade-wording.js for why that distinction
|
|
626
|
+
// matters (a reader who has never heard "attestation" used correctly
|
|
627
|
+
// has no way to know a real one is a different, valid artifact they
|
|
628
|
+
// might separately need).
|
|
623
629
|
remarks:
|
|
624
|
-
|
|
625
|
-
+ 'of compliance and no licensed assessor reviewed it. '
|
|
630
|
+
`${EVIDENCE_GRADE_DISCLAIMER} No licensed assessor reviewed this document. `
|
|
626
631
|
+ TOOL_CAVEATS.map(c => `${c.id}: ${c.shortDescription}`).join(' | '),
|
|
627
632
|
}],
|
|
628
633
|
'back-matter': { resources: [frameworkResource, ...caveats] },
|
package/src/sast/cpp.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
// - hardcoded hardcoded user/password in fopen / connect calls
|
|
17
17
|
|
|
18
18
|
import { blankComments } from './_comment-strip.js';
|
|
19
|
+
import { classifySecretCandidate as _entropyClassifySecret } from './_secret-entropy.js';
|
|
19
20
|
|
|
20
21
|
// ── context detectors ───────────────────────────────────────────────────────
|
|
21
22
|
|
|
@@ -279,14 +280,8 @@ const FINDINGS = [
|
|
|
279
280
|
const val = m && m[2];
|
|
280
281
|
if (!val) return false;
|
|
281
282
|
try {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
// eslint-disable-next-line no-unused-vars
|
|
285
|
-
const { classifySecretCandidate } = _entropyMod || {};
|
|
286
|
-
if (classifySecretCandidate) {
|
|
287
|
-
const r = classifySecretCandidate(val);
|
|
288
|
-
if (r.skip) return false;
|
|
289
|
-
}
|
|
283
|
+
const r = _entropyClassifySecret(val);
|
|
284
|
+
if (r.skip) return false;
|
|
290
285
|
} catch { /* fail open */ }
|
|
291
286
|
return true;
|
|
292
287
|
},
|
|
@@ -319,12 +314,6 @@ const FINDINGS = [
|
|
|
319
314
|
},
|
|
320
315
|
];
|
|
321
316
|
|
|
322
|
-
// Late-bound entropy module — imported via a dynamic require shim so the
|
|
323
|
-
// rule table can lazy-call it from inside gate functions without creating
|
|
324
|
-
// a circular import at module load time.
|
|
325
|
-
let _entropyMod = null;
|
|
326
|
-
import('./_secret-entropy.js').then(m => { _entropyMod = m; }).catch(() => { _entropyMod = null; });
|
|
327
|
-
|
|
328
317
|
function lineOf(raw, idx) { return raw.substring(0, idx).split('\n').length; }
|
|
329
318
|
|
|
330
319
|
export function scanCpp(fp, raw) {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import * as crypto from 'node:crypto';
|
|
10
10
|
import * as fs from 'node:fs';
|
|
11
11
|
import * as path from 'node:path';
|
|
12
|
+
import { evaluateEgress } from '../egress/policy.js';
|
|
12
13
|
|
|
13
14
|
const CACHE_DIR = process.env.XDG_CONFIG_HOME
|
|
14
15
|
? path.join(process.env.XDG_CONFIG_HOME, 'agentic-security', 'llm-sca-cache')
|
|
@@ -63,6 +64,11 @@ export async function extractVulnFunctionsViaLLM(supplyChain, opts = {}) {
|
|
|
63
64
|
if (!isLlmScaEnabled()) return [];
|
|
64
65
|
const config = _endpointConfig();
|
|
65
66
|
if (!config) return [];
|
|
67
|
+
// FR-601: one decision for the whole batch (same endpoint for every CVE in
|
|
68
|
+
// this run) — evaluated before any of the per-candidate prompts below are
|
|
69
|
+
// built.
|
|
70
|
+
const egressDecision = evaluateEgress({ scanRoot: opts.scanRoot, purpose: 'sca-llm-function-extract', endpoint: config.endpoint });
|
|
71
|
+
if (!egressDecision.allowed) return [];
|
|
66
72
|
|
|
67
73
|
const enriched = [];
|
|
68
74
|
const candidates = (supplyChain || []).filter(sc =>
|