@clear-capabilities/agentic-security-scanner 0.186.2 → 0.188.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.
Files changed (39) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/1602.index.js +275 -19
  3. package/dist/{4d06c8944ae121d7085c.json → 92f78fd3e2dbe30fb58d.json} +5 -5
  4. package/dist/9560.index.js +61 -4
  5. package/dist/agentic-security.mjs +2 -2
  6. package/dist/agentic-security.mjs.sha256 +1 -1
  7. package/package.json +5 -5
  8. package/src/capabilities/decide.js +7 -1
  9. package/src/dataflow/CLAUDE.md +15 -0
  10. package/src/dataflow/catalog-haskell.js +7 -1
  11. package/src/dataflow/catalog.js +14 -0
  12. package/src/dataflow/engine.js +147 -10
  13. package/src/dataflow/privacy-catalog-haskell.js +2 -1
  14. package/src/engine.js +11 -3
  15. package/src/language/CLAUDE.md +26 -0
  16. package/src/language/compliance-map.js +6 -0
  17. package/src/language/context.js +20 -0
  18. package/src/language/haskell-comments.js +65 -0
  19. package/src/language/haskell-disclosure.js +16 -4
  20. package/src/language/haskell-findings.js +2 -1
  21. package/src/language/haskell-fix.js +118 -2
  22. package/src/language/haskell-guards.js +132 -5
  23. package/src/language/haskell-ir.js +228 -13
  24. package/src/language/haskell-models.js +108 -8
  25. package/src/language/haskell-security-rules.js +223 -41
  26. package/src/language/haskell-syntax.js +10 -1
  27. package/src/language/haskell-trust.js +268 -0
  28. package/src/language/haskell-web.js +46 -9
  29. package/src/language/support-registry.js +3 -2
  30. package/src/mcp/tools.js +54 -3
  31. package/src/posture/attack-taxonomy.js +10 -0
  32. package/src/posture/entrypoint-inventory.js +6 -2
  33. package/src/posture/proof-fabric/generate-haskell.js +266 -17
  34. package/src/posture/proof-fabric/generate.js +10 -3
  35. package/src/posture/proof-fabric/oracles.js +21 -0
  36. package/src/posture/stable-id.js +2 -1
  37. package/src/report/index.js +3 -0
  38. package/src/sast/cloud-iam.js +3 -1
  39. package/dist/2066.index.js +0 -851
@@ -721,8 +721,14 @@ const INTERPRETER_NAMES = new Set([
721
721
  'time', 'timeout', 'sudo', 'doas', 'su', 'ssh', 'osascript', 'launchctl', 'script', 'expect', 'tclsh', 'wish',
722
722
  'awk', 'gawk', 'mawk', 'nawk', 'java', 'jshell', 'deno', 'bun', 'nodejs', 'swift', 'make', 'gmake',
723
723
  'npm', 'npx', 'yarn', 'pnpm', 'pip', 'pip3', 'pipx', 'gem', 'bundle', 'cargo', 'go', 'gradle', 'mvn', 'watch',
724
+ // Haskell and Nix toolchains run code supplied by the project: GHC and cabal execute Template Haskell splices and a
725
+ // custom Setup.hs at build time, runghc/ghci interpret a source file, and the Nix evaluators run the expressions they
726
+ // are given (builders run the derivation's build script).
727
+ 'ghc', 'ghci', 'runghc', 'runhaskell', 'cabal', 'stack',
728
+ 'nix', 'nix-build', 'nix-shell', 'nix-instantiate', 'nix-env', 'nix-store',
724
729
  ]);
725
- const INTERPRETER_FAMILY = /^(?:python|pypy|ruby|perl|php|lua|luajit|node|tclsh)[0-9.]*$/;
730
+ // Versioned toolchain binaries (`ghc-9.14.1`, `runghc-9.14.1`, `cabal-3.14`) are the same executables under another name.
731
+ const INTERPRETER_FAMILY = /^(?:python|pypy|ruby|perl|php|lua|luajit|node|tclsh)[0-9.]*$|^(?:ghc|ghci|runghc|runhaskell|cabal|stack)-[0-9][0-9.]*$/;
726
732
 
727
733
  /**
728
734
  * What kind of executable this is. `interpreter` is true for a known shell or
@@ -3578,6 +3584,22 @@ const scan_diff = {
3578
3584
  },
3579
3585
  };
3580
3586
 
3587
+ // The ordered source-to-sink steps of a finding: its `trace` when the detector wrote one, else the `chain` the IR taint
3588
+ // engine records (file, line, label, kind, provenance). Null when the finding carries neither.
3589
+ function _flowSteps(f) {
3590
+ if (Array.isArray(f.trace) && f.trace.length) return f.trace;
3591
+ if (f.trace && !Array.isArray(f.trace)) return f.trace;
3592
+ if (Array.isArray(f.chain) && f.chain.length) {
3593
+ return f.chain.map((s) => ({ file: s.file ?? null, line: s.line ?? null, label: s.label ?? null, kind: s.kind || (s.provenance ? 'source' : 'step'), provenance: s.provenance ?? null }));
3594
+ }
3595
+ return null;
3596
+ }
3597
+ // Labels of the structured source, sink and rule, for matching only.
3598
+ function _flowEnds(f) {
3599
+ const lab = (x) => (x && typeof x === 'object' ? [x.label, x.family, x.kind].filter((v) => typeof v === 'string').join(' ') : '');
3600
+ return [lab(f.source), lab(f.sink), typeof f.rule === 'string' ? f.rule : ''].join(' ');
3601
+ }
3602
+
3581
3603
  // ─── query_taint ─────────────────────────────────────────────────────────────
3582
3604
  const query_taint = {
3583
3605
  name: 'query_taint',
@@ -3608,13 +3630,16 @@ const query_taint = {
3608
3630
  // dilutes its reasoning; the agent receives a bounded slice plus the
3609
3631
  // cursor to fetch the rest if it wants.
3610
3632
  const all = (scan.findings || []).filter(f => {
3611
- const hay = [f.description, f.title, f.vuln, f.snippet, JSON.stringify(f.trace || '')].join(' ').toLowerCase();
3633
+ // The structured flow counts too: an IR taint finding (Haskell included) records its path as `source`, `sink`, `rule`
3634
+ // and `chain`, and carries no free-text description or `trace`, so matching only the text fields would answer
3635
+ // "no path" for a path the scan did find.
3636
+ const hay = [f.description, f.title, f.vuln, f.snippet, JSON.stringify(_flowSteps(f) || ''), _flowEnds(f)].join(' ').toLowerCase();
3612
3637
  return hay.includes(srcL) && hay.includes(sinkL);
3613
3638
  });
3614
3639
  const page = all.slice(off, off + lim).map(f => (0,_redact_js__WEBPACK_IMPORTED_MODULE_21__/* .redactFinding */ .lE)({
3615
3640
  id: f.id, severity: f.severity, file: f.file, line: f.line,
3616
3641
  title: f.title || f.vuln, description: f.description,
3617
- trace: f.trace || null,
3642
+ trace: _flowSteps(f),
3618
3643
  }));
3619
3644
  return {
3620
3645
  _meta: META,
@@ -3654,7 +3679,7 @@ const explain_finding = {
3654
3679
  title: f.title || f.vuln, cwe: f.cwe,
3655
3680
  description: f.description, remediation: f.remediation,
3656
3681
  snippet: f.snippet || null,
3657
- trace: f.trace || null,
3682
+ trace: _flowSteps(f),
3658
3683
  });
3659
3684
  // Harness-anatomy #1: explain_finding's trace is the most-likely-large
3660
3685
  // field on a single finding. Offload when it crosses the threshold so
@@ -4314,6 +4339,9 @@ const synthesize_fix = {
4314
4339
  languageFix.applyWith = { finding_id: f.id, confirm: true, plan_digest: languageFix.planDigest };
4315
4340
  }
4316
4341
  else if (prev.ok) autofix = { deterministic: true, ruleId: f.rule || f.family || null, patch: prev.after, file: prev.file, label: prev.label || null, verified: true };
4342
+ } else if (lc.isLanguageDependencyFinding(f)) {
4343
+ // Named, not silent: a Hackage or flake dependency is fixed by raising a constraint, which has its own tool.
4344
+ languageFix = { status: 'use-sca-upgrade', ok: false, label: null, tier: null, reason: 'this is a dependency finding: use synthesize_sca_upgrade for the constraint edit (apply_sca_upgrade writes it)', diff: null, explanation: null, consequences: [] };
4317
4345
  }
4318
4346
  } catch { /* best-effort: the preview is advisory */ }
4319
4347
  }
@@ -4728,6 +4756,22 @@ const query_cache_telemetry = {
4728
4756
  },
4729
4757
  };
4730
4758
 
4759
+ async function _languageUpgrade(lc, f, ctx, apply) {
4760
+ const proj = lc.loadLanguageProject(ctx.sessionRoot);
4761
+ return lc.languageUpgradePreview(f, { ...proj.files, ...proj.manifests }, { apply, root: ctx.sessionRoot });
4762
+ }
4763
+ // The result shape both SCA upgrade tools return for a Hackage or flake dependency. `verified` is never true: the edit
4764
+ // changes a declared constraint or a ref, not the resolved version.
4765
+ function _languageUpgradeResult(f, r) {
4766
+ return {
4767
+ ok: true, mode: r.ok ? 'source-edit' : 'blocked', ecosystem: f.ecosystem, package: f.package || null,
4768
+ currentVersion: f.version || null, targetVersion: null, command: null,
4769
+ reason: r.ok ? null : (r.reason || 'no upgrade could be planned'),
4770
+ languageUpgrade: { status: r.status || null, tier: r.tier || null, label: r.label || null, file: r.file || null, diff: r.diff || null, explanation: r.explanation || null, consequences: r.consequences || [], backup: r.backup ? r.backup.id || null : null },
4771
+ verified: false,
4772
+ };
4773
+ }
4774
+
4731
4775
  // ─── synthesize_sca_upgrade ───────────────────────────────────────────────
4732
4776
  // Phase 3 / Item 5 of the SCA improvement plan. Read-only counterpart to
4733
4777
  // apply_sca_upgrade — produces a structured upgrade plan via the
@@ -4756,6 +4800,10 @@ const synthesize_sca_upgrade = {
4756
4800
  if (f.type !== 'vulnerable_dep') {
4757
4801
  return { _meta: META, ok: false, reason: 'finding is not an SCA vulnerable_dep — use synthesize_fix for SAST findings' };
4758
4802
  }
4803
+ // Hackage constraints and flake inputs are source edits the language planner owns; without this the generic planner
4804
+ // answers "no automated upgrade" for a dependency the CLI can fix. Read-only: nothing is written or run.
4805
+ const lc = await Promise.resolve(/* import() */).then(__webpack_require__.bind(__webpack_require__, 52603));
4806
+ if (lc.isLanguageDependencyFinding(f)) return { _meta: META, ..._languageUpgradeResult(f, await _languageUpgrade(lc, f, ctx, false)) };
4759
4807
  const { planScaUpgrade } = await _getScaUpgrade();
4760
4808
  const plan = await planScaUpgrade({ scanRoot: ctx.sessionRoot, finding: f });
4761
4809
  return { _meta: META, ...plan };
@@ -4796,6 +4844,15 @@ const apply_sca_upgrade = {
4796
4844
  if (f.type !== 'vulnerable_dep') {
4797
4845
  return { _meta: META, applied: false, reason: 'finding is not an SCA vulnerable_dep — use apply_fix for SAST findings' };
4798
4846
  }
4847
+ const lc = await Promise.resolve(/* import() */).then(__webpack_require__.bind(__webpack_require__, 52603));
4848
+ if (lc.isLanguageDependencyFinding(f)) {
4849
+ // Plan first, confine the one file it names, then write it with a backup and a history entry (`undo` reverts it).
4850
+ const plan = await _languageUpgrade(lc, f, ctx, false);
4851
+ if (!plan.ok) return { _meta: META, applied: false, ..._languageUpgradeResult(f, plan) };
4852
+ try { _confine(ctx.sessionRoot, plan.file, 'upgrade.file'); } catch (e) { return { _meta: META, applied: false, reason: String((e && e.message) || e) }; }
4853
+ const done = await _languageUpgrade(lc, f, ctx, true);
4854
+ return { _meta: META, applied: done.applied === true, ..._languageUpgradeResult(f, done), testsRun: false, testsNote: 'a constraint edit is not re-resolved or re-tested by this tool; resolve (for example `cabal build --dry-run`), rescan and run the project tests before relying on it' };
4855
+ }
4799
4856
  const { applyScaUpgrade } = await _getScaUpgrade();
4800
4857
  const result = await applyScaUpgrade({ scanRoot: ctx.sessionRoot, finding: f, runTests: run_tests });
4801
4858
  return { _meta: META, ...result };