@clear-capabilities/agentic-security-scanner 0.133.0 → 0.136.2
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 +299 -0
- package/bin/agentic-security-audit.js +2 -1
- package/bin/agentic-security-consistency.js +2 -1
- package/bin/agentic-security.js +348 -40
- package/dist/113.index.js +6 -6
- package/dist/117.index.js +3 -1
- package/dist/178.index.js +1 -1
- package/dist/220.index.js +5 -3
- package/dist/238.index.js +4 -4
- package/dist/317.index.js +270 -0
- package/dist/384.index.js +1 -1
- package/dist/435.index.js +13 -7
- package/dist/499.index.js +86 -0
- package/dist/513.index.js +7 -3
- package/dist/526.index.js +6 -6
- package/dist/609.index.js +741 -0
- package/dist/637.index.js +1 -1
- package/dist/675.index.js +7 -5
- package/dist/839.index.js +4 -3
- package/dist/905.index.js +1173 -0
- package/dist/agentic-security.mjs +56 -56
- package/dist/agentic-security.mjs.sha256 +1 -1
- package/dist/compliance-frameworks/ccpa.json +32 -0
- package/dist/compliance-frameworks/eu-ai-act.json +51 -0
- package/dist/compliance-frameworks/gdpr.json +45 -0
- package/dist/compliance-frameworks/hipaa-security-rule.json +56 -0
- package/dist/compliance-frameworks/nist-ai-600-1.json +51 -0
- package/dist/compliance-frameworks/nist-csf-2.json +73 -0
- package/dist/compliance-frameworks/nist-privacy-1-1.json +846 -0
- package/dist/compliance-frameworks/owasp-asvs-5.json +79 -0
- package/dist/compliance-frameworks/owasp-llm-top-10.json +69 -0
- package/package.json +14 -6
- package/src/badge.js +2 -1
- package/src/dataflow/cross-service-taint.js +2 -1
- package/src/dataflow/ifds-precise.js +6 -4
- package/src/dataflow/incremental.js +7 -5
- package/src/discovery/CLAUDE.md +48 -0
- package/src/discovery/confirm.js +47 -0
- package/src/discovery/disprove.js +79 -0
- package/src/discovery/hunter.js +116 -0
- package/src/discovery/index.js +331 -0
- package/src/discovery/judge.js +97 -0
- package/src/discovery/lenses.js +69 -0
- package/src/discovery/llm-invoke.js +120 -0
- package/src/discovery/memory.js +163 -0
- package/src/discovery/partition.js +92 -0
- package/src/engine.js +163 -7
- package/src/integrations/tickets.js +7 -6
- package/src/ir/ir-stats.js +1 -1
- package/src/ir/parser-cpp.js +1 -1
- package/src/leaderboard.js +3 -2
- package/src/llm-validator/consistency.js +6 -2
- package/src/llm-validator/index.js +30 -41
- package/src/llm-validator/providers.js +227 -0
- package/src/mcp/audit.js +2 -1
- package/src/mcp/tools.js +4 -3
- package/src/posture/CLAUDE.md +102 -1
- package/src/posture/accuracy-scorecard.js +59 -1
- package/src/posture/agents-memory.js +5 -3
- package/src/posture/auditor-walkthrough.js +9 -7
- package/src/posture/auth-posture-import.js +5 -4
- package/src/posture/autopilot.js +225 -0
- package/src/posture/comparison.js +181 -0
- package/src/posture/compliance-frameworks/nist-privacy-1-1.json +846 -0
- package/src/posture/compliance-policy.js +7 -9
- package/src/posture/custom-rules.js +7 -5
- package/src/posture/cve-alert-daemon.js +6 -5
- package/src/posture/dep-add-guard.js +2 -1
- package/src/posture/deterministic.js +3 -2
- package/src/posture/evidence-bundle.js +246 -0
- package/src/posture/execution-proof.js +25 -1
- package/src/posture/exploitability-probability.js +2 -1
- package/src/posture/feature-flags.js +3 -2
- package/src/posture/findings-memory.js +3 -3
- package/src/posture/fix-history.js +5 -2
- package/src/posture/fix-metrics.js +5 -5
- package/src/posture/fix-plan.js +2 -1
- package/src/posture/fleet.js +0 -0
- package/src/posture/grader-calibration.js +3 -4
- package/src/posture/intent-context.js +2 -1
- package/src/posture/learning.js +4 -3
- package/src/posture/license-attributions.js +5 -7
- package/src/posture/license-graph.js +2 -1
- package/src/posture/license-policy.js +2 -1
- package/src/posture/logic-claims.js +266 -0
- package/src/posture/model-rescan.js +4 -3
- package/src/posture/network-policy-import.js +3 -2
- package/src/posture/poc-inprocess.js +404 -2
- package/src/posture/pqc-migration-plan.js +7 -5
- package/src/posture/pr-augment.js +8 -5
- package/src/posture/privacy-framework.js +262 -0
- package/src/posture/proof-artifact.js +101 -0
- package/src/posture/prove-findings.js +28 -4
- package/src/posture/risk-dollars.js +2 -2
- package/src/posture/router.js +5 -4
- package/src/posture/ruleset-version.js +2 -2
- package/src/posture/runtime-correlation.js +2 -1
- package/src/posture/sbom-diff.js +12 -3
- package/src/posture/sca-policy.js +7 -4
- package/src/posture/scan-checkpoint.js +15 -0
- package/src/posture/state-dir.js +34 -0
- package/src/posture/telemetry-ingest.js +4 -3
- package/src/posture/threat-model-auto.js +4 -1
- package/src/posture/threat-model-grounding.js +11 -1
- package/src/posture/time-to-fix.js +3 -2
- package/src/posture/triage-memory.js +3 -2
- package/src/posture/waf-ingest.js +6 -5
- package/src/posture/watch-mode.js +4 -3
- package/src/report/index.js +9 -0
- package/src/sast/code-injection-multilang.js +29 -0
- package/src/sca/dep-confusion.js +2 -1
package/bin/agentic-security.js
CHANGED
|
@@ -30,6 +30,7 @@ import * as triage from '../src/posture/triage.js';
|
|
|
30
30
|
import { buildSlackDigest, buildDiscordDigest, postWebhook, buildJiraIssue, buildPrComment, buildSiemEvent, loadIntegrationConfig } from '../src/integrations/index.js';
|
|
31
31
|
import { globFiles } from '../src/util/glob.js';
|
|
32
32
|
|
|
33
|
+
import { stateDir, statePath } from '../src/posture/state-dir.js';
|
|
33
34
|
// last-scan.json integrity helpers — implementation in posture/integrity.js
|
|
34
35
|
// so the MCP server tools can share verification.
|
|
35
36
|
function _verifyLastScan(body, sigFile) {
|
|
@@ -65,6 +66,13 @@ Commands:
|
|
|
65
66
|
verify [--finding <id>] Re-run the verifier loop on last-scan findings (use --live --target <url> to execute PoCs)
|
|
66
67
|
reset [--yes] [--keep ...] Right-to-delete: wipe accumulated learned state under .agentic-security/ (preserves operator-authored config)
|
|
67
68
|
rule-synth [--dry-run] Auto-synthesise suppression rules from repeated FP verdicts (proposes — does not activate)
|
|
69
|
+
compliance [--privacy] Assess the last scan against NIST Privacy Framework 1.1
|
|
70
|
+
--list show bundled + BYO frameworks
|
|
71
|
+
--walkthrough <id> auditor narrative for any framework
|
|
72
|
+
--gap only the failing controls
|
|
73
|
+
--format cli|json|md (default cli)
|
|
74
|
+
--fail-on gap exit 1 when a control is failing
|
|
75
|
+
Reads .agentic-security/last-scan.json — run a scan first.
|
|
68
76
|
version Print version
|
|
69
77
|
banner [--full] Print the Patch-the-frog mascot + brand lockup
|
|
70
78
|
harness [path] [--include-home] Multi-harness config audit: scans .claude/,
|
|
@@ -290,20 +298,23 @@ function _bundleSha() {
|
|
|
290
298
|
}
|
|
291
299
|
|
|
292
300
|
async function writeMachineOutput(targetAbs, scan, meta, profile) {
|
|
293
|
-
const
|
|
294
|
-
const { isSafeStateDir: _isSafe } = await import('../src/posture/state-dir.js');
|
|
295
|
-
|
|
296
|
-
|
|
301
|
+
const stateDirPath = stateDir(targetAbs);
|
|
302
|
+
const { isSafeStateDir: _isSafe, stateWritesEnabled: _writesOn } = await import('../src/posture/state-dir.js');
|
|
303
|
+
// Read-only scan (NON_MUTATING_SCAN_PRD S1). These writes bypass
|
|
304
|
+
// safeWriteState because they are async, so the switch is checked here — the
|
|
305
|
+
// same refusal path the project-root check already uses.
|
|
306
|
+
if (!_writesOn() || !_isSafe(stateDirPath)) {
|
|
307
|
+
if (process.env.AGENTIC_SECURITY_DEBUG === '1') process.stderr.write(`[agentic-security] refusing to write machine output at ${stateDirPath} — no project marker\n`);
|
|
297
308
|
return;
|
|
298
309
|
}
|
|
299
|
-
await fsp.mkdir(
|
|
310
|
+
await fsp.mkdir(stateDirPath, { recursive: true });
|
|
300
311
|
// Always JSON (used by /security-fix and /security-report).
|
|
301
|
-
await fsp.writeFile(path.join(
|
|
312
|
+
await fsp.writeFile(path.join(stateDirPath, 'findings.json'),
|
|
302
313
|
JSON.stringify(toJSON(scan, meta), null, 2));
|
|
303
314
|
if (profile.profile === 'pro' || profile.machineOutput) {
|
|
304
|
-
await fsp.writeFile(path.join(
|
|
315
|
+
await fsp.writeFile(path.join(stateDirPath, 'findings.sarif'),
|
|
305
316
|
JSON.stringify(toSARIF(scan, meta), null, 2));
|
|
306
|
-
await fsp.writeFile(path.join(
|
|
317
|
+
await fsp.writeFile(path.join(stateDirPath, 'findings.csv'), toCSV(scan));
|
|
307
318
|
}
|
|
308
319
|
}
|
|
309
320
|
|
|
@@ -325,6 +336,11 @@ function parseArgs(argv) {
|
|
|
325
336
|
}
|
|
326
337
|
|
|
327
338
|
async function cmdScan(args) {
|
|
339
|
+
// NON_MUTATING_SCAN_PRD S1 — a scan is an observation; --no-state makes it one.
|
|
340
|
+
if (args.flags['no-state']) {
|
|
341
|
+
const { setStateWritesEnabled } = await import('../src/posture/state-dir.js');
|
|
342
|
+
setStateWritesEnabled(false);
|
|
343
|
+
}
|
|
328
344
|
const target = args._[1] || '.';
|
|
329
345
|
const targetAbs = path.resolve(target);
|
|
330
346
|
// Load persona profile (R1). Persona-aware defaults flow from here.
|
|
@@ -430,7 +446,7 @@ async function cmdScan(args) {
|
|
|
430
446
|
}
|
|
431
447
|
|
|
432
448
|
// --set-baseline: save current findings as baseline for future --since-baseline filtering
|
|
433
|
-
const baselinePath =
|
|
449
|
+
const baselinePath = statePath(target || '.', 'baseline.json');
|
|
434
450
|
if (args.flags['set-baseline']) {
|
|
435
451
|
const { normalizeFindings } = await import('../src/report/index.js');
|
|
436
452
|
const baselineIds = new Set(normalizeFindings(scan).map(f => f.stableId || f.id));
|
|
@@ -646,10 +662,10 @@ async function cmdScan(args) {
|
|
|
646
662
|
else process.stdout.write(body + '\n');
|
|
647
663
|
|
|
648
664
|
// Persist last scan for /security-fix and /security-report
|
|
649
|
-
const { isSafeStateDir: _isSafeStateDir } = await import('../src/posture/state-dir.js');
|
|
650
|
-
const
|
|
651
|
-
if (_isSafeStateDir(
|
|
652
|
-
await fsp.mkdir(
|
|
665
|
+
const { isSafeStateDir: _isSafeStateDir, stateWritesEnabled: _writesOnScan } = await import('../src/posture/state-dir.js');
|
|
666
|
+
const stateDirPath = stateDir(path.resolve(target));
|
|
667
|
+
if (_writesOnScan() && _isSafeStateDir(stateDirPath)) {
|
|
668
|
+
await fsp.mkdir(stateDirPath, { recursive: true });
|
|
653
669
|
const persistedScan = toJSON(scan, meta);
|
|
654
670
|
// #10 — MTTR: stamp firstSeenAt/lastSeenAt/ageDays from the PREVIOUS scan so
|
|
655
671
|
// every finding carries an age, SLA breaches can be surfaced, and the fix
|
|
@@ -660,7 +676,7 @@ async function cmdScan(args) {
|
|
|
660
676
|
const { stampFindingTimestamps, buildBaselineMap, renderSlaSummary } = await import('../src/posture/mttr.js');
|
|
661
677
|
let baselineMap = new Map();
|
|
662
678
|
try {
|
|
663
|
-
const prev = JSON.parse(await fsp.readFile(path.join(
|
|
679
|
+
const prev = JSON.parse(await fsp.readFile(path.join(stateDirPath, 'last-scan.json'), 'utf8'));
|
|
664
680
|
baselineMap = buildBaselineMap(prev);
|
|
665
681
|
} catch { /* first run — empty baseline, everything is firstSeen now */ }
|
|
666
682
|
const now = Date.now();
|
|
@@ -709,18 +725,18 @@ async function cmdScan(args) {
|
|
|
709
725
|
} catch { /* best-effort, offline-degrading — never block a scan */ }
|
|
710
726
|
}
|
|
711
727
|
const lastScanBody = JSON.stringify(persistedScan, null, 2);
|
|
712
|
-
await fsp.writeFile(path.join(
|
|
728
|
+
await fsp.writeFile(path.join(stateDirPath, 'last-scan.json'), lastScanBody);
|
|
713
729
|
try {
|
|
714
|
-
await fsp.writeFile(path.join(
|
|
730
|
+
await fsp.writeFile(path.join(stateDirPath, 'last-scan.json.sig'), _signLastScan(lastScanBody));
|
|
715
731
|
} catch { /* non-fatal — sig file is best-effort */ }
|
|
716
732
|
} else {
|
|
717
|
-
if (process.env.AGENTIC_SECURITY_DEBUG === '1') process.stderr.write(`[agentic-security] refusing to write state at ${
|
|
733
|
+
if (process.env.AGENTIC_SECURITY_DEBUG === '1') process.stderr.write(`[agentic-security] refusing to write state at ${stateDirPath} — no project marker in ${path.resolve(target)}\n`);
|
|
718
734
|
}
|
|
719
735
|
|
|
720
736
|
// 0.14.0 — update streak / achievements after every full scan. Suppress
|
|
721
737
|
// streak side effects when the user only wants raw JSON output (CI piping).
|
|
722
738
|
try {
|
|
723
|
-
const streak = recordScan(
|
|
739
|
+
const streak = recordScan(stateDirPath, persistedScan);
|
|
724
740
|
// Print celebration / streak line to stderr so it doesn't pollute --format json
|
|
725
741
|
if (process.stderr.isTTY && format !== 'json' && format !== 'sarif') {
|
|
726
742
|
const delta = formatGradeDelta(streak);
|
|
@@ -799,18 +815,18 @@ async function cmdCi(args) {
|
|
|
799
815
|
if (packNames.length) Object.assign(scan, applyPacks(scan, packNames));
|
|
800
816
|
|
|
801
817
|
// Persist the three CI artifacts.
|
|
802
|
-
const
|
|
803
|
-
const { isSafeStateDir: _isSafeCi } = await import('../src/posture/state-dir.js');
|
|
804
|
-
if (!_isSafeCi(
|
|
805
|
-
if (process.env.AGENTIC_SECURITY_DEBUG === '1') process.stderr.write(`[agentic-security] refusing to write CI artifacts at ${
|
|
818
|
+
const stateDirPath = stateDir(targetAbs);
|
|
819
|
+
const { isSafeStateDir: _isSafeCi, stateWritesEnabled: _writesOnCi } = await import('../src/posture/state-dir.js');
|
|
820
|
+
if (!_writesOnCi() || !_isSafeCi(stateDirPath)) {
|
|
821
|
+
if (process.env.AGENTIC_SECURITY_DEBUG === '1') process.stderr.write(`[agentic-security] refusing to write CI artifacts at ${stateDirPath} — no project marker\n`);
|
|
806
822
|
return;
|
|
807
823
|
}
|
|
808
|
-
await fsp.mkdir(
|
|
809
|
-
await fsp.writeFile(path.join(
|
|
824
|
+
await fsp.mkdir(stateDirPath, { recursive: true });
|
|
825
|
+
await fsp.writeFile(path.join(stateDirPath, 'findings.json'),
|
|
810
826
|
JSON.stringify(toJSON(scan, meta), null, 2));
|
|
811
|
-
await fsp.writeFile(path.join(
|
|
827
|
+
await fsp.writeFile(path.join(stateDirPath, 'findings.sarif'),
|
|
812
828
|
JSON.stringify(toSARIF(scan, meta), null, 2));
|
|
813
|
-
await fsp.writeFile(path.join(
|
|
829
|
+
await fsp.writeFile(path.join(stateDirPath, 'findings.junit.xml'),
|
|
814
830
|
toJUnit(scan, meta));
|
|
815
831
|
|
|
816
832
|
const scanCode = exitCodeFor(scan);
|
|
@@ -872,7 +888,7 @@ async function cmdAccept(args) {
|
|
|
872
888
|
const id = args.flags.finding;
|
|
873
889
|
if (!id) { console.error('--finding <id> required'); return 4; }
|
|
874
890
|
const reason = args.flags.reason || 'vibecoded for now';
|
|
875
|
-
const lastScanPath =
|
|
891
|
+
const lastScanPath = statePath(target, 'findings.json');
|
|
876
892
|
if (!fs.existsSync(lastScanPath)) { console.error('No prior scan found. Run `agentic-security scan` first.'); return 4; }
|
|
877
893
|
const last = JSON.parse(await fsp.readFile(lastScanPath, 'utf8'));
|
|
878
894
|
const f = (last.findings || []).find(x => x.id === id);
|
|
@@ -930,7 +946,7 @@ async function cmdTriage(args) {
|
|
|
930
946
|
}
|
|
931
947
|
const sub = args._[1];
|
|
932
948
|
// Sync first so list reflects the latest scan.
|
|
933
|
-
const lastScanPath =
|
|
949
|
+
const lastScanPath = statePath(target, 'findings.json');
|
|
934
950
|
if (fs.existsSync(lastScanPath)) {
|
|
935
951
|
const last = JSON.parse(await fsp.readFile(lastScanPath, 'utf8'));
|
|
936
952
|
triage.syncWithScan(target, last.findings || []);
|
|
@@ -1256,7 +1272,7 @@ async function cmdRule(args) {
|
|
|
1256
1272
|
async function cmdValidatorCache(args) {
|
|
1257
1273
|
const sub = args._[1] || 'help';
|
|
1258
1274
|
const root = path.resolve(args._[2] || '.');
|
|
1259
|
-
const cacheDir =
|
|
1275
|
+
const cacheDir = statePath(root, 'llm-cache');
|
|
1260
1276
|
if (!fs.existsSync(cacheDir)) {
|
|
1261
1277
|
console.log(`No validator cache at ${cacheDir}`);
|
|
1262
1278
|
return 0;
|
|
@@ -1319,7 +1335,7 @@ async function cmdValidatorCache(args) {
|
|
|
1319
1335
|
// FR-VER-7 fail-closed: any error → cannot-verify, never silent drop.
|
|
1320
1336
|
async function cmdVerify(args) {
|
|
1321
1337
|
const scanRoot = path.resolve(args.flags.root || '.');
|
|
1322
|
-
const lastScanPath =
|
|
1338
|
+
const lastScanPath = statePath(scanRoot, 'last-scan.json');
|
|
1323
1339
|
if (!fs.existsSync(lastScanPath)) {
|
|
1324
1340
|
console.error(`No prior scan found at ${lastScanPath}. Run \`agentic-security scan\` first.`);
|
|
1325
1341
|
return 4;
|
|
@@ -1421,9 +1437,9 @@ async function cmdVerify(args) {
|
|
|
1421
1437
|
// --yes to skip the confirmation prompt (for scripted use).
|
|
1422
1438
|
async function cmdReset(args) {
|
|
1423
1439
|
const scanRoot = path.resolve(args.flags.root || '.');
|
|
1424
|
-
const
|
|
1425
|
-
if (!fs.existsSync(
|
|
1426
|
-
console.log(`No state to reset at ${
|
|
1440
|
+
const stateDirPath = stateDir(scanRoot);
|
|
1441
|
+
if (!fs.existsSync(stateDirPath)) {
|
|
1442
|
+
console.log(`No state to reset at ${stateDirPath}`);
|
|
1427
1443
|
return 0;
|
|
1428
1444
|
}
|
|
1429
1445
|
const WIPE = new Set([
|
|
@@ -1448,17 +1464,17 @@ async function cmdReset(args) {
|
|
|
1448
1464
|
]);
|
|
1449
1465
|
const keep = new Set((args.flags.keep || '').split(',').filter(Boolean));
|
|
1450
1466
|
const targets = [];
|
|
1451
|
-
for (const entry of await fsp.readdir(
|
|
1467
|
+
for (const entry of await fsp.readdir(stateDirPath, { withFileTypes: true })) {
|
|
1452
1468
|
if (keep.has(entry.name)) continue;
|
|
1453
1469
|
if (WIPE.has(entry.name) || WIPE_DIRS.has(entry.name)) {
|
|
1454
1470
|
targets.push({ name: entry.name, dir: entry.isDirectory() });
|
|
1455
1471
|
}
|
|
1456
1472
|
}
|
|
1457
1473
|
if (!targets.length) {
|
|
1458
|
-
console.log(`Nothing to reset under ${
|
|
1474
|
+
console.log(`Nothing to reset under ${stateDirPath}.`);
|
|
1459
1475
|
return 0;
|
|
1460
1476
|
}
|
|
1461
|
-
console.log(`agentic-security reset — will remove from ${
|
|
1477
|
+
console.log(`agentic-security reset — will remove from ${stateDirPath}:`);
|
|
1462
1478
|
for (const t of targets) console.log(` ${t.name}${t.dir ? '/' : ''}`);
|
|
1463
1479
|
console.log('');
|
|
1464
1480
|
console.log('Preserving operator-authored config: rules.yml, rules/, license-policy.yml, trusted-keys.json, ruleset-version.json');
|
|
@@ -1468,7 +1484,7 @@ async function cmdReset(args) {
|
|
|
1468
1484
|
return 0;
|
|
1469
1485
|
}
|
|
1470
1486
|
for (const t of targets) {
|
|
1471
|
-
const p = path.join(
|
|
1487
|
+
const p = path.join(stateDirPath, t.name);
|
|
1472
1488
|
try {
|
|
1473
1489
|
if (t.dir) await fsp.rm(p, { recursive: true, force: true });
|
|
1474
1490
|
else await fsp.rm(p, { force: true });
|
|
@@ -1485,6 +1501,294 @@ async function cmdReset(args) {
|
|
|
1485
1501
|
// FR-LEARN-6: read triage-feedback.json, group repeated FP verdicts by
|
|
1486
1502
|
// (family, dir prefix), and propose a suppression YAML when ≥ threshold
|
|
1487
1503
|
// (default 5) verdicts cluster. Writes to .agentic-security/rules-proposed/.
|
|
1504
|
+
// Languages the Layer-1 IR parses. Anything else cannot be partitioned into a
|
|
1505
|
+
// call-graph focus area, so feeding it to a hunter would spend tokens on files
|
|
1506
|
+
// the confirmation gate can never corroborate.
|
|
1507
|
+
const HUNT_EXTS = /\.(?:js|jsx|mjs|cjs|ts|tsx|py|java|cs|kt|go|php|rb)$/i;
|
|
1508
|
+
const HUNT_IGNORE = ['node_modules/**', '.git/**', 'dist/**', 'build/**', 'vendor/**', '**/.agentic-security/**'];
|
|
1509
|
+
const HUNT_MAX_FILES = 2000;
|
|
1510
|
+
|
|
1511
|
+
async function cmdHunt(args) {
|
|
1512
|
+
const scanRoot = path.resolve(args.flags.root || args._[1] || '.');
|
|
1513
|
+
const { listFiles } = await import('../src/util/glob.js');
|
|
1514
|
+
const { buildProjectIR } = await import('../src/ir/index.js');
|
|
1515
|
+
const { runDiscovery } = await import('../src/discovery/index.js');
|
|
1516
|
+
const { LENSES } = await import('../src/discovery/lenses.js');
|
|
1517
|
+
|
|
1518
|
+
const rels = (await listFiles(scanRoot, { ignore: HUNT_IGNORE })).filter(f => HUNT_EXTS.test(f));
|
|
1519
|
+
if (rels.length > HUNT_MAX_FILES) {
|
|
1520
|
+
console.error(`agentic-security: ${rels.length} source files exceeds the ${HUNT_MAX_FILES}-file hunt cap.`);
|
|
1521
|
+
console.error('Narrow the scope with --root <subdir>. Discovery is token-expensive and');
|
|
1522
|
+
console.error('unbounded fan-out on a large repository is the wrong default.');
|
|
1523
|
+
return 2;
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
const fileContents = {};
|
|
1527
|
+
for (const rel of rels) {
|
|
1528
|
+
try { fileContents[rel] = fs.readFileSync(path.join(scanRoot, rel), 'utf8'); } catch { /* unreadable — skip */ }
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
const { perFile, callGraph } = buildProjectIR(fileContents);
|
|
1532
|
+
|
|
1533
|
+
// Prior scan and triage verdicts feed the judge so a hunt does not re-report
|
|
1534
|
+
// what the rule engine already found or what a human already dismissed.
|
|
1535
|
+
let priorScan = null, triageFeedback = null;
|
|
1536
|
+
try { priorScan = JSON.parse(fs.readFileSync(statePath(scanRoot, 'last-scan.json'), 'utf8')); } catch {}
|
|
1537
|
+
try { triageFeedback = JSON.parse(fs.readFileSync(statePath(scanRoot, 'triage-feedback.json'), 'utf8')); } catch {}
|
|
1538
|
+
|
|
1539
|
+
const lenses = args.flags.lens ? String(args.flags.lens).split(',').map(s => s.trim()).filter(Boolean) : undefined;
|
|
1540
|
+
const intFlag = (name) => (args.flags[name] ? parseInt(args.flags[name], 10) : undefined);
|
|
1541
|
+
const numFlag = (name) => (args.flags[name] ? Number(args.flags[name]) : undefined);
|
|
1542
|
+
const report = await runDiscovery(
|
|
1543
|
+
{ perFileIR: perFile, callGraph, fileContents, priorScan, triageFeedback },
|
|
1544
|
+
{
|
|
1545
|
+
lenses,
|
|
1546
|
+
maxAreas: intFlag('max-areas'),
|
|
1547
|
+
// PRD Phase 0 / C3. Defaults live in discovery/index.js so the library is
|
|
1548
|
+
// bounded even when driven by something other than this CLI.
|
|
1549
|
+
maxLlmCalls: intFlag('max-llm-calls'),
|
|
1550
|
+
maxWallMs: intFlag('max-wall-ms'),
|
|
1551
|
+
maxCandidates: intFlag('max-candidates'),
|
|
1552
|
+
maxCostUsd: numFlag('max-cost-usd'),
|
|
1553
|
+
// PRD C4 — persist and consult cross-run memory.
|
|
1554
|
+
scanRoot: args.flags['no-memory'] ? undefined : scanRoot,
|
|
1555
|
+
// PRD C2 — a comma-separated endpoint list turns on consensus.
|
|
1556
|
+
endpoints: args.flags.endpoints,
|
|
1557
|
+
costPerCallUsd: numFlag('cost-per-call-usd'),
|
|
1558
|
+
},
|
|
1559
|
+
);
|
|
1560
|
+
|
|
1561
|
+
const c = report.coverage;
|
|
1562
|
+
console.log('');
|
|
1563
|
+
console.log(`Discovery — ${rels.length} file(s), ${c.areasPlanned} focus area(s), ${c.lensesPerArea} lens(es) each`);
|
|
1564
|
+
console.log(` areas hunted: ${c.areasHunted}/${c.areasPlanned} (fully: ${c.areasFullyHunted}) degraded runs: ${c.degradedRuns}/${report.runs.length}`);
|
|
1565
|
+
console.log(` budget: ${c.llmCalls}/${c.maxLlmCalls} LLM calls${c.budgetExhausted ? ' ← EXHAUSTED, run is incomplete' : ''}` +
|
|
1566
|
+
`${c.candidatesCapped ? ` ${c.candidatesCapped} candidate(s) capped` : ''}` +
|
|
1567
|
+
`${c.callsPerFinding !== null && c.callsPerFinding !== undefined ? ` ${c.callsPerFinding} calls/finding` : ''}`);
|
|
1568
|
+
if (c.priorRuns !== null && c.priorRuns !== undefined) {
|
|
1569
|
+
console.log(` memory: ${c.priorRuns} prior run(s)` +
|
|
1570
|
+
`${c.rememberedRefutals ? `, ${c.rememberedRefutals} candidate(s) skipped as already refuted` : ''}`);
|
|
1571
|
+
if (c.nextWave?.summary) console.log(` next wave: ${c.nextWave.summary}`);
|
|
1572
|
+
}
|
|
1573
|
+
console.log(` confirmation: ${JSON.stringify(c.confirmedByTier)} panels: ${c.panelsRun} (undecided ${c.undecidedPanels})`);
|
|
1574
|
+
console.log('');
|
|
1575
|
+
|
|
1576
|
+
if (report.fresh.length === 0) {
|
|
1577
|
+
console.log('No new candidates survived confirmation and refutation.');
|
|
1578
|
+
} else {
|
|
1579
|
+
console.log(`${report.fresh.length} new candidate finding(s):`);
|
|
1580
|
+
for (const f of report.fresh) {
|
|
1581
|
+
console.log(` [${f.severity}] ${f.file}:${f.line} ${f.vuln}`);
|
|
1582
|
+
console.log(` lens=${f.discovery.lens} confirmation=${f.discovery.confirmation?.tier || 'unknown'}`);
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
if (report.duplicates.length || report.suppressed.length || report.refutedCandidates.length) {
|
|
1586
|
+
console.log('');
|
|
1587
|
+
console.log(` (${report.duplicates.length} already known, ${report.suppressed.length} previously marked false positive, ` +
|
|
1588
|
+
`${report.refutedCandidates.length} refuted by the panel)`);
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
// PRD C5 — variant analysis. A confirmed finding is rarely alone: the same
|
|
1592
|
+
// root cause is usually copy-pasted, and the siblings often do not trip a
|
|
1593
|
+
// rule. sweepRootCauses already does this with exact accounting for scan
|
|
1594
|
+
// findings; discovery findings simply never reached it until now.
|
|
1595
|
+
if (report.fresh.length && !args.flags['no-variants']) {
|
|
1596
|
+
try {
|
|
1597
|
+
const { sweepRootCauses, formatSweepLedger } = await import('../src/posture/root-cause-sweep.js');
|
|
1598
|
+
const sweep = sweepRootCauses(report.fresh, fileContents, { confirmedOnly: false });
|
|
1599
|
+
const ledger = formatSweepLedger(sweep);
|
|
1600
|
+
if (ledger) {
|
|
1601
|
+
console.log('');
|
|
1602
|
+
console.log('Variant sweep (siblings of the findings above):');
|
|
1603
|
+
console.log(` ${ledger}`);
|
|
1604
|
+
}
|
|
1605
|
+
} catch { /* the sweep is additive; never let it sink the report */ }
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
// Degradation is part of the output. A half-failed hunt must never read as a
|
|
1609
|
+
// clean one, so every reason is printed rather than summarised away.
|
|
1610
|
+
if (c.reasons.length) {
|
|
1611
|
+
console.log('');
|
|
1612
|
+
console.log('Coverage gaps:');
|
|
1613
|
+
for (const r of c.reasons) console.log(` · ${r}`);
|
|
1614
|
+
}
|
|
1615
|
+
if (report.runs.length && c.degradedRuns === report.runs.length) {
|
|
1616
|
+
console.log('');
|
|
1617
|
+
console.log('EVERY hunter run degraded — this result says nothing about the code.');
|
|
1618
|
+
console.log('Set AGENTIC_SECURITY_LLM_ENDPOINT to enable discovery.');
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
// Advisory by design: discovery never gates. Exit 0 unless the scope was bad.
|
|
1622
|
+
console.log('');
|
|
1623
|
+
console.log(`Lenses available: ${LENSES.map(l => l.key).join(', ')} (--lens a,b to narrow)`);
|
|
1624
|
+
return 0;
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
// PRD D2 — emit signed, per-finding evidence bundles, and verify them.
|
|
1628
|
+
//
|
|
1629
|
+
// `attest` needs our private key. `verify-attestation` needs ONLY a public key,
|
|
1630
|
+
// which is the entire point: a buyer or auditor checks the artefact without ever
|
|
1631
|
+
// having had access to anything of ours.
|
|
1632
|
+
/**
|
|
1633
|
+
* `compliance` — framework assessment from the CLI.
|
|
1634
|
+
*
|
|
1635
|
+
* Reads the LAST SCAN rather than running a new one. A compliance answer is a
|
|
1636
|
+
* statement about a scan that happened, and silently re-scanning here would
|
|
1637
|
+
* make the number depend on when you asked rather than on what was measured —
|
|
1638
|
+
* the same class of confusion as a benchmark that mutates its own corpus. If
|
|
1639
|
+
* there is no scan to read, this says so and exits non-zero instead of
|
|
1640
|
+
* assessing an empty project, which would report controls as satisfied on the
|
|
1641
|
+
* strength of having looked at nothing.
|
|
1642
|
+
*/
|
|
1643
|
+
async function cmdCompliance(args) {
|
|
1644
|
+
const scanRoot = path.resolve(args.flags.root || '.');
|
|
1645
|
+
const fmt = String(args.flags.format || 'cli');
|
|
1646
|
+
const {
|
|
1647
|
+
assessPrivacyFramework, PRIVACY_FRAMEWORK_ID, BUCKETS,
|
|
1648
|
+
} = await import('../src/posture/privacy-framework.js');
|
|
1649
|
+
const { listFrameworks, loadFramework, evaluateFramework, renderWalkthrough } =
|
|
1650
|
+
await import('../src/posture/auditor-walkthrough.js');
|
|
1651
|
+
|
|
1652
|
+
if (args.flags.list) {
|
|
1653
|
+
const fws = listFrameworks(scanRoot);
|
|
1654
|
+
if (fmt === 'json') { console.log(JSON.stringify(fws, null, 2)); return 0; }
|
|
1655
|
+
for (const f of fws) console.log(` ${f.id.padEnd(20)} ${f.name} [${f.source}]`);
|
|
1656
|
+
return 0;
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
let scan;
|
|
1660
|
+
try { scan = JSON.parse(fs.readFileSync(statePath(scanRoot, 'last-scan.json'), 'utf8')); }
|
|
1661
|
+
catch {
|
|
1662
|
+
console.error('No .agentic-security/last-scan.json — run `agentic-security scan .` first.');
|
|
1663
|
+
console.error('Refusing to assess an unscanned project: every control would report as');
|
|
1664
|
+
console.error('unassessed, which is correct but useless, and one flag away from looking clean.');
|
|
1665
|
+
return 2;
|
|
1666
|
+
}
|
|
1667
|
+
// The engine records this two ways depending on the emit path; take either.
|
|
1668
|
+
scan.filesScanned = scan._scanMeta?.filesScanned ?? scan.scanned?.files ?? 0;
|
|
1669
|
+
|
|
1670
|
+
const wt = args.flags.walkthrough;
|
|
1671
|
+
if (wt && wt !== true) {
|
|
1672
|
+
const fw = loadFramework(scanRoot, String(wt));
|
|
1673
|
+
if (!fw) {
|
|
1674
|
+
console.error(`Unknown framework "${wt}". Try --list.`);
|
|
1675
|
+
return 2;
|
|
1676
|
+
}
|
|
1677
|
+
console.log(renderWalkthrough(fw, evaluateFramework(scanRoot, fw, scan), {}));
|
|
1678
|
+
return 0;
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
// Default mode is --privacy: it is the only framework with a remediation
|
|
1682
|
+
// layer, so it is the only one where a CLI exit code means anything.
|
|
1683
|
+
const r = assessPrivacyFramework(scanRoot, scan);
|
|
1684
|
+
if (!r) { console.error(`Framework ${PRIVACY_FRAMEWORK_ID} could not be loaded.`); return 2; }
|
|
1685
|
+
|
|
1686
|
+
const gapsOnly = !!args.flags.gap;
|
|
1687
|
+
if (fmt === 'json') {
|
|
1688
|
+
console.log(JSON.stringify(gapsOnly ? { ...r, controls: r.controls.filter(c => c.bucket === 'gap') } : r, null, 2));
|
|
1689
|
+
} else if (fmt === 'md') {
|
|
1690
|
+
console.log(fs.readFileSync(statePath(scanRoot, 'privacy-framework.md'), 'utf8'));
|
|
1691
|
+
} else {
|
|
1692
|
+
console.log(`\n${r.frameworkName}\n`);
|
|
1693
|
+
console.log(` ${r.interpretation}\n`);
|
|
1694
|
+
for (const b of BUCKETS) {
|
|
1695
|
+
const rows = r.controls.filter(c => c.bucket === b);
|
|
1696
|
+
if (!rows.length || (gapsOnly && b !== 'gap')) continue;
|
|
1697
|
+
console.log(` ${b} (${rows.length})`);
|
|
1698
|
+
for (const c of rows) {
|
|
1699
|
+
console.log(` ${c.id.padEnd(11)} ${c.summary.slice(0, 84)}`);
|
|
1700
|
+
const f = r.findings.find(x => x.id === `privacy-framework:${c.id}`);
|
|
1701
|
+
if (f) console.log(` → ${f.remediation}`);
|
|
1702
|
+
}
|
|
1703
|
+
console.log('');
|
|
1704
|
+
}
|
|
1705
|
+
console.log(' A control that is "manual" or "engine-gap" is NOT evidence of compliance.');
|
|
1706
|
+
console.log(' This organizes scanner evidence; a licensed assessor owns the attestation.\n');
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
// --fail-on gap is opt-in. A compliance opinion becomes a build failure only
|
|
1710
|
+
// when someone asks for it; defaulting to non-zero would break every pipeline
|
|
1711
|
+
// that adds this command to see the report.
|
|
1712
|
+
if (args.flags['fail-on'] === 'gap' && r.summary.gap > 0) return 1;
|
|
1713
|
+
return 0;
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
async function cmdAttest(args) {
|
|
1717
|
+
const scanRoot = path.resolve(args.flags.root || '.');
|
|
1718
|
+
const {
|
|
1719
|
+
ensureKeyPair, buildEvidenceBundle, signEvidenceBundle,
|
|
1720
|
+
} = await import('../src/posture/evidence-bundle.js');
|
|
1721
|
+
|
|
1722
|
+
let scan;
|
|
1723
|
+
try { scan = JSON.parse(fs.readFileSync(statePath(scanRoot, 'last-scan.json'), 'utf8')); }
|
|
1724
|
+
catch { console.error('No .agentic-security/last-scan.json — run a scan first.'); return 2; }
|
|
1725
|
+
|
|
1726
|
+
const findings = scan.findings || [];
|
|
1727
|
+
const wanted = args.flags.id;
|
|
1728
|
+
const subset = wanted ? findings.filter(f => f.id === wanted || f.stableId === wanted) : findings;
|
|
1729
|
+
if (!subset.length) {
|
|
1730
|
+
console.error(wanted ? `No finding matching "${wanted}".` : 'No findings to attest.');
|
|
1731
|
+
return 2;
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
const kp = ensureKeyPair();
|
|
1735
|
+
if (kp.created) console.error(`Generated a new signing key at ${kp.privateKey} (public: ${kp.publicKey}).`);
|
|
1736
|
+
|
|
1737
|
+
const outDir = statePath(scanRoot, 'attestations');
|
|
1738
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
1739
|
+
const meta = {
|
|
1740
|
+
engineVersion: scan.engineVersion || null,
|
|
1741
|
+
rulesetVersion: scan.rulesetVersion || null,
|
|
1742
|
+
bundleSha: scan.bundleSha || null,
|
|
1743
|
+
commit: scan.commit || null,
|
|
1744
|
+
};
|
|
1745
|
+
|
|
1746
|
+
let n = 0;
|
|
1747
|
+
for (const f of subset) {
|
|
1748
|
+
const bundle = signEvidenceBundle(buildEvidenceBundle(f, meta), kp.privateKeyPem);
|
|
1749
|
+
const name = `${(f.stableId || f.id || `finding-${n}`)}.json`.replace(/[^\w.-]/g, '_');
|
|
1750
|
+
fs.writeFileSync(path.join(outDir, name), JSON.stringify(bundle, null, 2) + '\n');
|
|
1751
|
+
n++;
|
|
1752
|
+
}
|
|
1753
|
+
console.log(`Signed ${n} evidence bundle(s) → ${path.relative(scanRoot, outDir)}/`);
|
|
1754
|
+
console.log(`Public key (share this with whoever verifies): ${kp.publicKey}`);
|
|
1755
|
+
console.log('');
|
|
1756
|
+
console.log('A bundle proves its contents are unmodified since signing. It does NOT');
|
|
1757
|
+
console.log('prove the finding is real — read evidence.proofTier for that.');
|
|
1758
|
+
return 0;
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
async function cmdVerifyAttestation(args) {
|
|
1762
|
+
const { verifyEvidenceBundle, keyPaths } = await import('../src/posture/evidence-bundle.js');
|
|
1763
|
+
// `args._[0]` is the command name itself — the same convention cmdScan uses.
|
|
1764
|
+
const file = args.flags.bundle || args._[1];
|
|
1765
|
+
if (!file) { console.error('Usage: agentic-security verify-attestation <bundle.json> [--public-key <path>]'); return 2; }
|
|
1766
|
+
|
|
1767
|
+
let bundle;
|
|
1768
|
+
try { bundle = JSON.parse(fs.readFileSync(path.resolve(file), 'utf8')); }
|
|
1769
|
+
catch (e) { console.error(`Could not read bundle: ${e.message}`); return 2; }
|
|
1770
|
+
|
|
1771
|
+
const keyFile = args.flags['public-key'] || keyPaths().publicKey;
|
|
1772
|
+
let publicKeyPem = null;
|
|
1773
|
+
try { publicKeyPem = fs.readFileSync(path.resolve(keyFile), 'utf8'); }
|
|
1774
|
+
catch { console.error(`Could not read public key at ${keyFile}. Pass --public-key <path>.`); return 2; }
|
|
1775
|
+
|
|
1776
|
+
const r = verifyEvidenceBundle(bundle, publicKeyPem);
|
|
1777
|
+
if (!r.ok) {
|
|
1778
|
+
console.error(`✗ INVALID — ${r.reason}`);
|
|
1779
|
+
return 1;
|
|
1780
|
+
}
|
|
1781
|
+
const f = bundle.finding || {};
|
|
1782
|
+
console.log('✓ VALID — the bundle is exactly what the signer attested.');
|
|
1783
|
+
console.log('');
|
|
1784
|
+
console.log(` ${f.severity ? `[${f.severity}] ` : ''}${f.vuln || '(no title)'} ${f.file || '?'}:${f.line ?? '?'}`);
|
|
1785
|
+
console.log(` cwe ${f.cwe || 'n/a'} parser ${f.parser || 'n/a'} proof tier ${bundle.evidence?.proofTier || 'n/a'}`);
|
|
1786
|
+
console.log('');
|
|
1787
|
+
console.log(` proves: ${bundle.proves}`);
|
|
1788
|
+
console.log(` does NOT prove: ${bundle.doesNotProve}`);
|
|
1789
|
+
return 0;
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1488
1792
|
async function cmdRuleSynth(args) {
|
|
1489
1793
|
const scanRoot = path.resolve(args.flags.root || '.');
|
|
1490
1794
|
const { synthesizeRules } = await import('../src/posture/rule-synthesis.js');
|
|
@@ -1521,7 +1825,7 @@ async function cmdPacks(args) {
|
|
|
1521
1825
|
async function cmdDigest(args) {
|
|
1522
1826
|
const target = path.resolve(args._[1] || '.');
|
|
1523
1827
|
const profile = loadProfile(target);
|
|
1524
|
-
const lastScanPath =
|
|
1828
|
+
const lastScanPath = statePath(target, 'findings.json');
|
|
1525
1829
|
if (!fs.existsSync(lastScanPath)) { console.error('No prior scan found.'); return 4; }
|
|
1526
1830
|
const last = JSON.parse(await fsp.readFile(lastScanPath, 'utf8'));
|
|
1527
1831
|
const findings = (last.findings || []).filter(f => f.severity === 'critical' || f.severity === 'high');
|
|
@@ -1549,7 +1853,7 @@ async function cmdFix(args) {
|
|
|
1549
1853
|
const isApply = !!args.flags.apply;
|
|
1550
1854
|
const scanRoot = path.resolve(args.flags.root || '.');
|
|
1551
1855
|
if (!id) { console.error('--finding <id> required'); return 4; }
|
|
1552
|
-
const lastScanPath =
|
|
1856
|
+
const lastScanPath = statePath(scanRoot, 'last-scan.json');
|
|
1553
1857
|
if (!fs.existsSync(lastScanPath)) { console.error('No prior scan found. Run `agentic-security scan` first.'); return 4; }
|
|
1554
1858
|
const lastScanBody = await fsp.readFile(lastScanPath, 'utf8');
|
|
1555
1859
|
const sigVerified = _verifyLastScan(lastScanBody, lastScanPath + '.sig');
|
|
@@ -1777,6 +2081,10 @@ async function main() {
|
|
|
1777
2081
|
case 'validator-cache': process.exit(await cmdValidatorCache(args));
|
|
1778
2082
|
case 'verify': process.exit(await cmdVerify(args));
|
|
1779
2083
|
case 'reset': process.exit(await cmdReset(args));
|
|
2084
|
+
case 'hunt': process.exit(await cmdHunt(args));
|
|
2085
|
+
case 'compliance': process.exit(await cmdCompliance(args));
|
|
2086
|
+
case 'attest': process.exit(await cmdAttest(args));
|
|
2087
|
+
case 'verify-attestation': process.exit(await cmdVerifyAttestation(args));
|
|
1780
2088
|
case 'rule-synth': process.exit(await cmdRuleSynth(args));
|
|
1781
2089
|
case 'digest': process.exit(await cmdDigest(args));
|
|
1782
2090
|
case 'setup': process.exit(await cmdSetup(args));
|
|
@@ -1799,7 +2107,7 @@ async function main() {
|
|
|
1799
2107
|
const result = analyzeTranscript({ transcriptPath: args.flags.transcript, projectDir });
|
|
1800
2108
|
if (result.ok) {
|
|
1801
2109
|
try {
|
|
1802
|
-
const dir =
|
|
2110
|
+
const dir = stateDir(projectDir);
|
|
1803
2111
|
fs.mkdirSync(dir, { recursive: true });
|
|
1804
2112
|
fs.writeFileSync(path.join(dir, 'cache-telemetry.json'),
|
|
1805
2113
|
JSON.stringify({ updatedAt: new Date().toISOString(), metrics: result.metrics, leaks: result.leaks }, null, 2));
|
package/dist/113.index.js
CHANGED
|
@@ -52,7 +52,6 @@ export const modules = {
|
|
|
52
52
|
|
|
53
53
|
|
|
54
54
|
|
|
55
|
-
const STATE_DIR = '.agentic-security';
|
|
56
55
|
const LOG_FILE = 'fix-metrics.jsonl';
|
|
57
56
|
|
|
58
57
|
// Below this many samples a percentile is an artifact of the sample, not a
|
|
@@ -65,7 +64,7 @@ const RELIABLE_N = 10;
|
|
|
65
64
|
const FIX_STAGES = Object.freeze(['rescan', 'lint', 'tests', 'honesty', 'poc']);
|
|
66
65
|
|
|
67
66
|
function _logPath(scanRoot) {
|
|
68
|
-
return
|
|
67
|
+
return (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__/* .statePath */ .BQ)(scanRoot, LOG_FILE);
|
|
69
68
|
}
|
|
70
69
|
|
|
71
70
|
/**
|
|
@@ -77,9 +76,10 @@ function _logPath(scanRoot) {
|
|
|
77
76
|
function recordFixAttempt(scanRoot, record) {
|
|
78
77
|
if (!scanRoot || !record || typeof record !== 'object') return false;
|
|
79
78
|
try {
|
|
80
|
-
const dir =
|
|
79
|
+
const dir = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__/* .stateDir */ .Pn)(scanRoot);
|
|
81
80
|
if (!(0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__.isSafeStateDir)(dir)) return false;
|
|
82
|
-
|
|
81
|
+
if (!(0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__.stateWritesEnabled)()) return;
|
|
82
|
+
node_fs__WEBPACK_IMPORTED_MODULE_0__.mkdirSync(dir, { recursive: true });
|
|
83
83
|
// One writeSync of one newline-terminated line: a concurrent reader sees
|
|
84
84
|
// whole records or nothing, and a torn tail is dropped on read.
|
|
85
85
|
node_fs__WEBPACK_IMPORTED_MODULE_0__.appendFileSync(_logPath(scanRoot), JSON.stringify(record) + '\n', 'utf8');
|
|
@@ -406,8 +406,8 @@ var external_node_child_process_ = __webpack_require__(1421);
|
|
|
406
406
|
var external_node_fs_ = __webpack_require__(3024);
|
|
407
407
|
// EXTERNAL MODULE: external "node:path"
|
|
408
408
|
var external_node_path_ = __webpack_require__(6760);
|
|
409
|
-
// EXTERNAL MODULE: ./src/engine.js +
|
|
410
|
-
var engine = __webpack_require__(
|
|
409
|
+
// EXTERNAL MODULE: ./src/engine.js + 499 modules
|
|
410
|
+
var engine = __webpack_require__(2912);
|
|
411
411
|
;// CONCATENATED MODULE: ./src/posture/fix-honesty-gate.js
|
|
412
412
|
// Deterministic honesty gates on fix / finding output (#7).
|
|
413
413
|
//
|
package/dist/117.index.js
CHANGED
|
@@ -12,6 +12,7 @@ export const modules = {
|
|
|
12
12
|
/* unused harmony exports badgeFromScanRoot, renderSvg, _internal */
|
|
13
13
|
/* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3024);
|
|
14
14
|
/* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6760);
|
|
15
|
+
/* harmony import */ var _posture_state_dir_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(1174);
|
|
15
16
|
// Live SVG badge generator (v0.72).
|
|
16
17
|
//
|
|
17
18
|
// Every repo can drop a badge in its README pulling from the latest scan:
|
|
@@ -42,6 +43,7 @@ export const modules = {
|
|
|
42
43
|
|
|
43
44
|
|
|
44
45
|
|
|
46
|
+
|
|
45
47
|
const COLORS = {
|
|
46
48
|
critical: '#e05d44', // red
|
|
47
49
|
high: '#fe7d37', // orange
|
|
@@ -56,7 +58,7 @@ const SEVERITIES = ['critical', 'high', 'medium', 'low', 'info'];
|
|
|
56
58
|
|
|
57
59
|
function _readLastScan(scanRoot) {
|
|
58
60
|
if (!scanRoot) return null;
|
|
59
|
-
const fp =
|
|
61
|
+
const fp = (0,_posture_state_dir_js__WEBPACK_IMPORTED_MODULE_2__/* .statePath */ .BQ)(scanRoot, 'last-scan.json');
|
|
60
62
|
if (!node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(fp)) return null;
|
|
61
63
|
try { return JSON.parse(node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(fp, 'utf8')); }
|
|
62
64
|
catch { return null; }
|
package/dist/178.index.js
CHANGED
|
@@ -13,7 +13,7 @@ export const modules = {
|
|
|
13
13
|
/* harmony import */ var node_child_process__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1421);
|
|
14
14
|
/* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(3024);
|
|
15
15
|
/* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(6760);
|
|
16
|
-
/* harmony import */ var _engine_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(
|
|
16
|
+
/* harmony import */ var _engine_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(2912);
|
|
17
17
|
// Time-travel + counterfactual scanning (v0.68).
|
|
18
18
|
//
|
|
19
19
|
// Two new modes that exploit the pure-input shape of runFullScan:
|