@clear-capabilities/agentic-security-scanner 0.142.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 +399 -0
- package/bin/agentic-security.js +530 -54
- 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 +83 -18
- package/src/report/oscal.js +635 -0
- 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;
|
|
@@ -736,6 +769,26 @@ export function toMarkdown(scan, meta={}){
|
|
|
736
769
|
return lines.join('\n');
|
|
737
770
|
}
|
|
738
771
|
|
|
772
|
+
// The load-bearing caveats every machine-readable artifact must carry, in one
|
|
773
|
+
// place. A consumer that ingests our output without seeing these will read an
|
|
774
|
+
// ordinal priority score as a probability, and will read a benchmark-tuned F1
|
|
775
|
+
// as a general-purpose quality claim. Both are wrong, and both are the kind of
|
|
776
|
+
// wrong that only shows up in somebody else's risk-acceptance decision.
|
|
777
|
+
export const TOOL_CAVEATS = Object.freeze([
|
|
778
|
+
{
|
|
779
|
+
id: 'scores-are-ordinal',
|
|
780
|
+
name: 'ScoresAreOrdinal',
|
|
781
|
+
shortDescription: 'priority/exploitability scores are ordinal, not calibrated probabilities',
|
|
782
|
+
fullDescription: 'The properties.exploitability and properties.confidence fields on each result are ORDINAL priority scores used to rank findings within a scan. They are NOT calibrated probabilities; do not render them as percentages or feed them into pricing / risk-acceptance decisions. Use the tier labels (critical/high/medium/low) for coarse bucketing. See bench/README.md for the open calibration work.',
|
|
783
|
+
},
|
|
784
|
+
{
|
|
785
|
+
id: 'owasp-benchmark-tuning',
|
|
786
|
+
name: 'OwaspBenchmarkTuning',
|
|
787
|
+
shortDescription: 'engine ships OWASP-Benchmark-shape precision lifters; F1 numbers do not generalize',
|
|
788
|
+
fullDescription: 'The engine includes precision lifters (sast/primary-cwe-java.js, sast/java-constant-fold.js) whose heuristics are tuned to OWASP Benchmark v1.2 file shape (servlet-style files <=300 LoC, canonical variable names). F1 numbers cited against OWASP Benchmark do NOT generalize to arbitrary Java code. Expect higher FP rates on real-world codebases until per-customer tuning lands. See bench/README.md.',
|
|
789
|
+
},
|
|
790
|
+
]);
|
|
791
|
+
|
|
739
792
|
export function toSARIF(scan, meta={}){
|
|
740
793
|
const findings = normalizeFindings(scan);
|
|
741
794
|
const ruleMap = new Map();
|
|
@@ -758,22 +811,18 @@ export function toSARIF(scan, meta={}){
|
|
|
758
811
|
// SARIF run itself so machine consumers see them. Without these, a CI that
|
|
759
812
|
// ingests SARIF treats "confidence: 0.9" as a probability and the
|
|
760
813
|
// benchmark-tuned 0.907 number as quality evidence.
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
{
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
defaultConfiguration: { level: 'note' },
|
|
774
|
-
fullDescription: { text: 'The engine includes precision lifters (sast/primary-cwe-java.js, sast/java-constant-fold.js) whose heuristics are tuned to OWASP Benchmark v1.2 file shape (servlet-style files <=300 LoC, canonical variable names). F1 numbers cited against OWASP Benchmark do NOT generalize to arbitrary Java code. Expect higher FP rates on real-world codebases until per-customer tuning lands. See bench/README.md.' },
|
|
775
|
-
},
|
|
776
|
-
];
|
|
814
|
+
//
|
|
815
|
+
// The texts live in TOOL_CAVEATS (module scope) because SARIF is no longer
|
|
816
|
+
// the only machine format that has to carry them: report/oscal.js emits the
|
|
817
|
+
// same list as back-matter resources. Two copies of a caveat is one copy
|
|
818
|
+
// that goes stale, and the stale one is always the one somebody reads.
|
|
819
|
+
const SARIF_NOTIFICATIONS = TOOL_CAVEATS.map(c => ({
|
|
820
|
+
id: c.id,
|
|
821
|
+
name: c.name,
|
|
822
|
+
shortDescription: { text: c.shortDescription },
|
|
823
|
+
defaultConfiguration: { level: 'note' },
|
|
824
|
+
fullDescription: { text: c.fullDescription },
|
|
825
|
+
}));
|
|
777
826
|
return {
|
|
778
827
|
$schema: 'https://json.schemastore.org/sarif-2.1.0.json',
|
|
779
828
|
version: '2.1.0',
|
|
@@ -1268,17 +1317,33 @@ export function toShipVerdict(scan, options = {}) {
|
|
|
1268
1317
|
const confirmedCount = findings.filter(f => f.validated === true || f.confirmed === true).length;
|
|
1269
1318
|
const cats = categoryScores(findings);
|
|
1270
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
|
+
|
|
1271
1329
|
const lines = [];
|
|
1272
1330
|
const bar = '─────────────────────────────────────────';
|
|
1273
1331
|
// Patch the mascot reacts to the result — APPROVE if clean, ALERT if findings.
|
|
1274
|
-
|
|
1332
|
+
const clean = actionable.length === 0 && !scanIncomplete;
|
|
1333
|
+
lines.push(clean ? approveFace({ color }) : alertFace({ color }));
|
|
1275
1334
|
lines.push(bar);
|
|
1276
|
-
if (
|
|
1335
|
+
if (clean) {
|
|
1277
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));
|
|
1278
1339
|
} else {
|
|
1279
1340
|
lines.push(c(' ❌ Not safe to deploy', SEV_COLOR.critical + BOLD));
|
|
1280
1341
|
}
|
|
1281
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
|
+
}
|
|
1282
1347
|
lines.push(` • ${sev.critical} critical · ${sev.high} high · ${advisoryCount} advisory`);
|
|
1283
1348
|
// Per-category 0..100 score bars — Secrets / Permissions / Hooks / MCP / Agents.
|
|
1284
1349
|
// Only render when at least one category has been contributed to (skip the
|