@clear-capabilities/agentic-security-scanner 0.79.0 → 0.80.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 (91) hide show
  1. package/dist/178.index.js +1 -1
  2. package/dist/333.index.js +283 -0
  3. package/dist/384.index.js +1 -1
  4. package/dist/637.index.js +1 -1
  5. package/dist/838.index.js +1 -1
  6. package/dist/985.index.js +90 -1
  7. package/dist/agentic-security.mjs +83 -83
  8. package/dist/agentic-security.mjs.sha256 +1 -1
  9. package/package.json +6 -4
  10. package/src/.agentic-security/findings.json +104638 -0
  11. package/src/.agentic-security/last-scan.json +104638 -0
  12. package/src/.agentic-security/last-scan.json.sig +1 -0
  13. package/src/.agentic-security/scan-history.json +12562 -0
  14. package/src/.agentic-security/streak.json +21 -0
  15. package/src/dataflow/.agentic-security/findings.json +6086 -0
  16. package/src/dataflow/.agentic-security/last-scan.json +6086 -0
  17. package/src/dataflow/.agentic-security/last-scan.json.sig +1 -0
  18. package/src/dataflow/.agentic-security/scan-history.json +250 -0
  19. package/src/dataflow/.agentic-security/streak.json +21 -0
  20. package/src/dataflow/cross-service-taint.js +201 -0
  21. package/src/dataflow/formal-verify.js +204 -0
  22. package/src/dataflow/ifds-precise.js +222 -0
  23. package/src/dataflow/k2-summary-cache.js +153 -0
  24. package/src/dataflow/lib-taint-summaries.js +198 -0
  25. package/src/dataflow/privacy-taint.js +205 -0
  26. package/src/dataflow/smt-feasibility.js +189 -0
  27. package/src/engine.js +784 -127
  28. package/src/ir/.agentic-security/findings.json +4011 -0
  29. package/src/ir/.agentic-security/last-scan.json +4011 -0
  30. package/src/ir/.agentic-security/last-scan.json.sig +1 -0
  31. package/src/ir/.agentic-security/scan-history.json +193 -0
  32. package/src/ir/.agentic-security/streak.json +20 -0
  33. package/src/ir/cpp-preprocessor.js +142 -0
  34. package/src/ir/csharp-ir.js +604 -0
  35. package/src/ir/universal-ir.js +403 -0
  36. package/src/mcp/.agentic-security/findings.json +8632 -0
  37. package/src/mcp/.agentic-security/last-scan.json +8632 -0
  38. package/src/mcp/.agentic-security/last-scan.json.sig +1 -0
  39. package/src/mcp/.agentic-security/scan-history.json +143 -0
  40. package/src/mcp/.agentic-security/streak.json +20 -0
  41. package/src/mcp/tools.js +90 -1
  42. package/src/posture/.agentic-security/findings.json +64004 -0
  43. package/src/posture/.agentic-security/last-scan.json +64004 -0
  44. package/src/posture/.agentic-security/last-scan.json.sig +1 -0
  45. package/src/posture/.agentic-security/scan-history.json +7162 -0
  46. package/src/posture/.agentic-security/streak.json +21 -0
  47. package/src/posture/api-contract.js +193 -0
  48. package/src/posture/attack-taxonomy.js +227 -0
  49. package/src/posture/compliance-policy.js +218 -0
  50. package/src/posture/composite-risk.js +122 -0
  51. package/src/posture/csharp-analysis.js +330 -0
  52. package/src/posture/exploit-bundle.js +210 -0
  53. package/src/posture/federated-learning.js +172 -0
  54. package/src/posture/license-attributions.js +94 -0
  55. package/src/posture/license-graph.js +238 -0
  56. package/src/posture/pqc-migration-plan.js +158 -0
  57. package/src/posture/reachability-filter.js +33 -2
  58. package/src/posture/realtime-cve-monitor.js +214 -0
  59. package/src/posture/runtime-correlation.js +174 -0
  60. package/src/posture/sbom-diff.js +171 -0
  61. package/src/posture/sca-policy.js +235 -0
  62. package/src/posture/sca-upgrade.js +259 -0
  63. package/src/posture/threat-model-auto.js +268 -0
  64. package/src/posture/triage-learning.js +170 -0
  65. package/src/posture/triage.js +26 -1
  66. package/src/sast/.agentic-security/findings.json +6154 -0
  67. package/src/sast/.agentic-security/last-scan.json +6154 -0
  68. package/src/sast/.agentic-security/last-scan.json.sig +1 -0
  69. package/src/sast/.agentic-security/scan-history.json +941 -0
  70. package/src/sast/.agentic-security/streak.json +22 -0
  71. package/src/sast/_secret-entropy.js +145 -0
  72. package/src/sast/cloud-iam.js +312 -0
  73. package/src/sast/cpp.js +138 -4
  74. package/src/sast/crypto-protocol.js +388 -0
  75. package/src/sast/csharp-tokenizer.js +392 -0
  76. package/src/sast/csharp.js +924 -138
  77. package/src/sast/dapp-frontend.js +200 -0
  78. package/src/sast/k8s-admission.js +271 -0
  79. package/src/sast/llm-app.js +272 -0
  80. package/src/sast/ml-supply-chain.js +259 -0
  81. package/src/sast/mobile.js +224 -0
  82. package/src/sast/post-quantum-crypto.js +348 -0
  83. package/src/sast/web3-advanced.js +375 -0
  84. package/src/sca/.agentic-security/findings.json +7460 -0
  85. package/src/sca/.agentic-security/last-scan.json +7460 -0
  86. package/src/sca/.agentic-security/last-scan.json.sig +1 -0
  87. package/src/sca/.agentic-security/scan-history.json +113 -0
  88. package/src/sca/.agentic-security/streak.json +21 -0
  89. package/src/sca/CLAUDE.md +161 -0
  90. package/src/sca/binary-metadata.js +37 -15
  91. package/src/sca/sigstore-verify.js +215 -0
@@ -0,0 +1,170 @@
1
+ // Continuous learning from triage decisions — Recommendation #8 of the
2
+ // world-class roadmap.
3
+ //
4
+ // Every triage transition (open → fixed | wont-fix | false-positive)
5
+ // auto-tunes a per-(project, family, file-glob, sink-method) calibration
6
+ // store. The store directly modifies finding confidence scores on
7
+ // subsequent scans, so the scanner's precision on each individual
8
+ // codebase improves monotonically the longer it runs.
9
+ //
10
+ // Calibration shape:
11
+ //
12
+ // { "global": { // per-(family, sink-method) prior across all projects
13
+ // "sql-injection|.executeQuery": { tp: 142, fp: 7, lastUpdated: "..." }
14
+ // },
15
+ // "perProject": { // per-(file-glob, family) project-specific delta
16
+ // "src/admin/**|hardcoded-secret": { tp: 0, fp: 23, lastUpdated: "..." }
17
+ // }
18
+ // }
19
+ //
20
+ // Update rule on triage:
21
+ // transition → 'fixed' / 'wont-fix-because-not-exploitable' counts as TP+1
22
+ // transition → 'false-positive' counts as FP+1
23
+ //
24
+ // Application rule at scan time:
25
+ // confidence *= bayesianFactor(prior, project)
26
+ // where bayesianFactor uses a beta-distribution update of the priors.
27
+
28
+ import * as fs from 'node:fs';
29
+ import * as path from 'node:path';
30
+ import { statePath, safeWriteState } from './state-dir.js';
31
+
32
+ const CALIBRATION_FILE = 'triage-calibration.json';
33
+
34
+ // Minimum sample size before per-project calibration takes effect. With
35
+ // fewer than this many triage decisions, we fall back to global priors.
36
+ const MIN_PROJECT_SAMPLES = 5;
37
+
38
+ // Bayesian prior — beta(α, β) over the precision rate. α=1, β=1 is a
39
+ // uniform prior (no opinion); α=2, β=1 mildly favors precision.
40
+ const PRIOR_ALPHA = 2;
41
+ const PRIOR_BETA = 1;
42
+
43
+ function _storePath(scanRoot) {
44
+ return statePath(scanRoot, CALIBRATION_FILE);
45
+ }
46
+
47
+ export function loadCalibration(scanRoot) {
48
+ const fp = _storePath(scanRoot);
49
+ if (!fs.existsSync(fp)) return { global: {}, perProject: {} };
50
+ try { return JSON.parse(fs.readFileSync(fp, 'utf8')); }
51
+ catch { return { global: {}, perProject: {} }; }
52
+ }
53
+
54
+ function _save(scanRoot, data) {
55
+ const fp = _storePath(scanRoot);
56
+ safeWriteState(fp, JSON.stringify(data, null, 2));
57
+ }
58
+
59
+ function _bucketKey(family, sinkMethod) {
60
+ return `${family || 'unknown'}|${sinkMethod || ''}`;
61
+ }
62
+
63
+ function _projectKey(fileGlob, family) {
64
+ return `${fileGlob || '*'}|${family || 'unknown'}`;
65
+ }
66
+
67
+ /**
68
+ * Record a triage decision. Called by the triage transition path.
69
+ * verdict ∈ { 'true-positive', 'false-positive' }
70
+ */
71
+ export function recordTriageDecision(scanRoot, finding, verdict) {
72
+ if (!finding || !verdict) return null;
73
+ const data = loadCalibration(scanRoot);
74
+ const family = finding.family;
75
+ const sinkMethod = _extractSinkMethod(finding);
76
+ const fileGlob = _fileGlobFor(finding.file);
77
+ const now = new Date().toISOString();
78
+
79
+ const gk = _bucketKey(family, sinkMethod);
80
+ data.global[gk] ||= { tp: 0, fp: 0, lastUpdated: now };
81
+ if (verdict === 'true-positive') data.global[gk].tp++;
82
+ if (verdict === 'false-positive') data.global[gk].fp++;
83
+ data.global[gk].lastUpdated = now;
84
+
85
+ const pk = _projectKey(fileGlob, family);
86
+ data.perProject[pk] ||= { tp: 0, fp: 0, lastUpdated: now };
87
+ if (verdict === 'true-positive') data.perProject[pk].tp++;
88
+ if (verdict === 'false-positive') data.perProject[pk].fp++;
89
+ data.perProject[pk].lastUpdated = now;
90
+
91
+ _save(scanRoot, data);
92
+ return { gk, pk, data };
93
+ }
94
+
95
+ /**
96
+ * Apply learned calibration to a fresh batch of findings. Modifies
97
+ * confidence in place. Returns { adjusted: int, suppressed: int }
98
+ * — findings whose adjusted confidence drops below `suppressThreshold`
99
+ * are filtered out (added to the suppression log instead).
100
+ */
101
+ export function applyLearnedCalibration(scanRoot, findings, opts = {}) {
102
+ if (!Array.isArray(findings)) return { adjusted: 0, suppressed: 0 };
103
+ const data = loadCalibration(scanRoot);
104
+ const suppressThreshold = opts.suppressThreshold ?? 0.2;
105
+ let adjusted = 0;
106
+ const suppressedList = [];
107
+ for (const f of findings) {
108
+ const factor = _learnedFactor(data, f);
109
+ if (factor === 1.0) continue;
110
+ const before = typeof f.confidence === 'number' ? f.confidence : 0.85;
111
+ const after = Math.max(0.01, Math.min(0.99, before * factor));
112
+ f.confidence = after;
113
+ f._learnedCalibration = { factor, before, samples: _sampleCount(data, f) };
114
+ adjusted++;
115
+ if (after < suppressThreshold) {
116
+ f._suppressed_by = 'triage-learning';
117
+ suppressedList.push(f);
118
+ }
119
+ }
120
+ return { adjusted, suppressed: suppressedList.length, suppressedList, data };
121
+ }
122
+
123
+ function _learnedFactor(data, finding) {
124
+ const family = finding.family;
125
+ const sinkMethod = _extractSinkMethod(finding);
126
+ const fileGlob = _fileGlobFor(finding.file);
127
+
128
+ // Per-project: beta-distribution precision estimate when N is large enough.
129
+ const pk = _projectKey(fileGlob, family);
130
+ const proj = data.perProject?.[pk];
131
+ if (proj && proj.tp + proj.fp >= MIN_PROJECT_SAMPLES) {
132
+ const p = (PRIOR_ALPHA + proj.tp) / (PRIOR_ALPHA + PRIOR_BETA + proj.tp + proj.fp);
133
+ return p / 0.5; // 0.5 = neutral prior precision
134
+ }
135
+ // Global: same formula, less aggressive scaling.
136
+ const gk = _bucketKey(family, sinkMethod);
137
+ const glob = data.global?.[gk];
138
+ if (glob && glob.tp + glob.fp >= MIN_PROJECT_SAMPLES) {
139
+ const p = (PRIOR_ALPHA + glob.tp) / (PRIOR_ALPHA + PRIOR_BETA + glob.tp + glob.fp);
140
+ return (0.7 * (p / 0.5)) + 0.3; // weight: 70% global, 30% neutral
141
+ }
142
+ return 1.0;
143
+ }
144
+
145
+ function _sampleCount(data, finding) {
146
+ const family = finding.family;
147
+ const sinkMethod = _extractSinkMethod(finding);
148
+ const fileGlob = _fileGlobFor(finding.file);
149
+ const pk = _projectKey(fileGlob, family);
150
+ const gk = _bucketKey(family, sinkMethod);
151
+ const proj = data.perProject?.[pk] || { tp: 0, fp: 0 };
152
+ const glob = data.global?.[gk] || { tp: 0, fp: 0 };
153
+ return { perProject: proj.tp + proj.fp, global: glob.tp + glob.fp };
154
+ }
155
+
156
+ function _extractSinkMethod(finding) {
157
+ if (finding.sink?.method) return finding.sink.method;
158
+ // Try to parse from the vuln string ("SQL Injection — executeQuery").
159
+ const m = (finding.vuln || '').match(/\b([A-Za-z_]\w*)\s*\(/);
160
+ return m ? m[1] : '';
161
+ }
162
+
163
+ function _fileGlobFor(file) {
164
+ if (!file) return '*';
165
+ const parts = file.split('/');
166
+ if (parts.length <= 2) return file;
167
+ return parts.slice(0, 2).join('/') + '/**';
168
+ }
169
+
170
+ export const _internals = { _bucketKey, _projectKey, _extractSinkMethod, _fileGlobFor, MIN_PROJECT_SAMPLES };
@@ -5,6 +5,7 @@
5
5
  import * as fs from 'node:fs';
6
6
  import * as path from 'node:path';
7
7
  import { statePath, safeWriteState } from './state-dir.js';
8
+ import { appendAcceptRiskFromTriage } from './sca-policy.js';
8
9
 
9
10
  export const STATES = ['open', 'in-progress', 'fixed', 'wont-fix', 'false-positive'];
10
11
 
@@ -44,6 +45,15 @@ export function syncWithScan(scanRoot, findings) {
44
45
  assignee: null,
45
46
  opened_at: now,
46
47
  comments: [],
48
+ // Phase 4 / Item 7: capture SCA-relevant fields so the
49
+ // triage → sca-policy bridge has enough data to materialize an
50
+ // accept-risk entry on wont-fix. No-ops for SAST findings.
51
+ type: f.type || null,
52
+ name: f.name || null,
53
+ version: f.version || null,
54
+ ecosystem: f.ecosystem || null,
55
+ osvId: f.osvId || null,
56
+ cveAliases: Array.isArray(f.cveAliases) ? f.cveAliases : [],
47
57
  };
48
58
  data.transitions.push({ id, from: null, to: 'open', at: now });
49
59
  }
@@ -80,7 +90,22 @@ export function transition(scanRoot, id, toState, comment) {
80
90
  if (toState === 'fixed') cur.fixed_at = new Date().toISOString();
81
91
  data.transitions.push({ id, from, to: toState, at: new Date().toISOString(), comment });
82
92
  _save(scanRoot, data);
83
- return { ok: true };
93
+
94
+ // Phase 4 / Item 7 of the SCA improvement plan — bridge wont-fix
95
+ // transitions on SCA findings into sca-policy.yml accept-risk entries
96
+ // so the suppression is durable across rescans. The finding object on
97
+ // the triage store has a `type` field when it was synced from a scan
98
+ // via syncWithScan; we only bridge type === 'vulnerable_dep'.
99
+ let policyBridge = null;
100
+ if (toState === 'wont-fix' && cur.type === 'vulnerable_dep') {
101
+ try {
102
+ const reason = comment || `Marked wont-fix in triage on ${new Date().toISOString().slice(0,10)}`;
103
+ policyBridge = appendAcceptRiskFromTriage(scanRoot, cur, reason);
104
+ } catch (e) {
105
+ policyBridge = { ok: false, reason: String(e && e.message || e) };
106
+ }
107
+ }
108
+ return { ok: true, policyBridge };
84
109
  }
85
110
 
86
111
  export function comment(scanRoot, id, author, body) {