@clear-capabilities/agentic-security-scanner 0.86.0 → 0.118.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.
Files changed (120) hide show
  1. package/CHANGELOG.md +404 -0
  2. package/dist/178.index.js +1 -1
  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/agentic-security.mjs +84 -84
  7. package/dist/agentic-security.mjs.sha256 +1 -1
  8. package/package.json +12 -6
  9. package/src/.agentic-security/dpia.md +26 -0
  10. package/src/.agentic-security/pqc-migration-plan.json +80 -0
  11. package/src/.agentic-security/pqc-migration-plan.md +32 -0
  12. package/src/.agentic-security/sbom-history/0775d96ba4cf814520d02760cfb3010760c7684f.json +6 -0
  13. package/src/.agentic-security/sbom-history/29300441d3460832137a8e51a92b74068cad2f15.json +6 -0
  14. package/src/.agentic-security/sbom-history/3852ad2aa0e50951bed0c930c29d1861aa13d048.json +6 -0
  15. package/src/.agentic-security/sbom-history/3d96e2daec97e3226c9679bac6e64a89ca10bfe9.json +6 -0
  16. package/src/.agentic-security/sbom-history/5f0b136622fce09b469a1a6137c04d5b18652ea4.json +6 -0
  17. package/src/.agentic-security/sbom-history/64e30595d447b62344d0d9200760269df2095332.json +6 -0
  18. package/src/.agentic-security/sbom-history/71f3241902c9e7437764e3caa046ba8a3bcdd32a.json +6 -0
  19. package/src/.agentic-security/sbom-history/99c372cdc33ecc9dde92960b4ffadf7775860e61.json +6 -0
  20. package/src/.agentic-security/sbom-history/9c93b15e8cf3ff2ddb8cdae92371b82e178b463c.json +6 -0
  21. package/src/.agentic-security/sbom-history/a3544abf1863d71776b61302ca74d9ead2b26a9f.json +6 -0
  22. package/src/.agentic-security/sbom-history/dca15abdec43c508aa342db86dc5db8d7f01fbbe.json +6 -0
  23. package/src/.agentic-security/threat-model.json +3702 -0
  24. package/src/.agentic-security/threat-model.md +118 -0
  25. package/src/dataflow/.agentic-security/dpia.md +26 -0
  26. package/src/dataflow/.agentic-security/sbom-history/15d7c4d20f9d968ebec29445c9e9b3fc6f74e6a1.json +6 -0
  27. package/src/dataflow/.agentic-security/sbom-history/3852ad2aa0e50951bed0c930c29d1861aa13d048.json +6 -0
  28. package/src/dataflow/.agentic-security/sbom-history/73166872888d9ddcbe98645af818ecc7d8917b07.json +6 -0
  29. package/src/dataflow/.agentic-security/sbom-history/8869fe64035bec0152d92ece961e9369d8637076.json +6 -0
  30. package/src/dataflow/.agentic-security/sbom-history/9c93b15e8cf3ff2ddb8cdae92371b82e178b463c.json +6 -0
  31. package/src/dataflow/.agentic-security/sbom-history/b7ac2dada4a9216ccfe8969fc57f988c83f46975.json +6 -0
  32. package/src/dataflow/.agentic-security/threat-model.json +161 -0
  33. package/src/dataflow/.agentic-security/threat-model.md +43 -0
  34. package/src/dataflow/CLAUDE.md +17 -5
  35. package/src/dataflow/catalog.js +9 -0
  36. package/src/dataflow/engine.js +30 -4
  37. package/src/dataflow/index.js +8 -0
  38. package/src/dataflow/proof-gate.js +95 -0
  39. package/src/dataflow/summaries.js +44 -5
  40. package/src/engine.js +199 -5
  41. package/src/ir/.agentic-security/dpia.md +26 -0
  42. package/src/ir/.agentic-security/exploit-bundles.json +77 -0
  43. package/src/ir/.agentic-security/sbom-history/73166872888d9ddcbe98645af818ecc7d8917b07.json +6 -0
  44. package/src/ir/.agentic-security/sbom-history/b5f468a158f6a145aedd8680841c106b9809f16b.json +6 -0
  45. package/src/ir/.agentic-security/threat-model.json +143 -0
  46. package/src/ir/.agentic-security/threat-model.md +39 -0
  47. package/src/ir/CLAUDE.md +1 -0
  48. package/src/ir/callgraph.js +12 -1
  49. package/src/ir/tree-sitter-loader.js +108 -0
  50. package/src/posture/.agentic-security/dpia.md +1 -1
  51. package/src/posture/.agentic-security/pqc-migration-plan.json +1 -1
  52. package/src/posture/.agentic-security/pqc-migration-plan.md +1 -1
  53. package/src/posture/.agentic-security/sbom-history/0724c133d2943c87555695b5b2adcfcfc3373a2d.json +6 -0
  54. package/src/posture/.agentic-security/sbom-history/99c372cdc33ecc9dde92960b4ffadf7775860e61.json +6 -0
  55. package/src/posture/.agentic-security/sbom-history/b7ac2dada4a9216ccfe8969fc57f988c83f46975.json +6 -0
  56. package/src/posture/.agentic-security/threat-model.json +3 -3
  57. package/src/posture/.agentic-security/threat-model.md +1 -1
  58. package/src/posture/corpus-status.js +74 -0
  59. package/src/posture/coverage-report.js +113 -0
  60. package/src/posture/holdout-eval.js +57 -0
  61. package/src/posture/router.js +33 -3
  62. package/src/report/.agentic-security/dpia.md +26 -0
  63. package/src/report/.agentic-security/sbom-history/f94be839aa6a1011f53da016416a561698b8f338.json +6 -0
  64. package/src/report/.agentic-security/threat-model.md +1 -1
  65. package/src/report/index.js +17 -0
  66. package/src/sast/.agentic-security/dpia.md +26 -0
  67. package/src/sast/.agentic-security/pqc-migration-plan.json +65 -0
  68. package/src/sast/.agentic-security/pqc-migration-plan.md +30 -0
  69. package/src/sast/.agentic-security/sbom-history/043a7b401c68f5a298c83d4c42e94f118ff2a89a.json +6 -0
  70. package/src/sast/.agentic-security/sbom-history/0775d96ba4cf814520d02760cfb3010760c7684f.json +6 -0
  71. package/src/sast/.agentic-security/sbom-history/20d77ad7363a82777d506dedbbb1ecfed52f094a.json +6 -0
  72. package/src/sast/.agentic-security/sbom-history/29300441d3460832137a8e51a92b74068cad2f15.json +6 -0
  73. package/src/sast/.agentic-security/sbom-history/3852ad2aa0e50951bed0c930c29d1861aa13d048.json +6 -0
  74. package/src/sast/.agentic-security/sbom-history/3c6b3205f04e26f7654943d465281d00e0fa395e.json +6 -0
  75. package/src/sast/.agentic-security/sbom-history/3d96e2daec97e3226c9679bac6e64a89ca10bfe9.json +6 -0
  76. package/src/sast/.agentic-security/sbom-history/5282502e090013219eb006f7e02d8c983bc29c8e.json +6 -0
  77. package/src/sast/.agentic-security/sbom-history/5f0b136622fce09b469a1a6137c04d5b18652ea4.json +6 -0
  78. package/src/sast/.agentic-security/sbom-history/64e30595d447b62344d0d9200760269df2095332.json +6 -0
  79. package/src/sast/.agentic-security/sbom-history/71f3241902c9e7437764e3caa046ba8a3bcdd32a.json +6 -0
  80. package/src/sast/.agentic-security/sbom-history/73514b485d4770f3c5ee57d87338bccd1c61d8b7.json +6 -0
  81. package/src/sast/.agentic-security/sbom-history/96fa584fc3134512237d58aac63d6f3778b0cbcb.json +6 -0
  82. package/src/sast/.agentic-security/sbom-history/99c372cdc33ecc9dde92960b4ffadf7775860e61.json +6 -0
  83. package/src/sast/.agentic-security/sbom-history/a3544abf1863d71776b61302ca74d9ead2b26a9f.json +6 -0
  84. package/src/sast/.agentic-security/sbom-history/a4a34ab8b3d758de2adc8c3dc3e42d4cb0db00a3.json +6 -0
  85. package/src/sast/.agentic-security/sbom-history/ac236ec3602cfc8fb133f389ec3687e409084673.json +6 -0
  86. package/src/sast/.agentic-security/sbom-history/b57b3e1292f1f9f0de6c5a34a436e330c78008a3.json +6 -0
  87. package/src/sast/.agentic-security/sbom-history/dca15abdec43c508aa342db86dc5db8d7f01fbbe.json +6 -0
  88. package/src/sast/.agentic-security/sbom-history/dcbfd4935922dd165093c08a1003c338eed937c5.json +6 -0
  89. package/src/sast/.agentic-security/sbom-history/fa017f725615b0293814344c8ef7dfeb06775ca0.json +6 -0
  90. package/src/sast/.agentic-security/threat-model.json +816 -0
  91. package/src/sast/.agentic-security/threat-model.md +91 -0
  92. package/src/sast/CLAUDE.md +9 -3
  93. package/src/sast/code-injection-multilang.js +117 -0
  94. package/src/sast/crypto-protocol.js +43 -1
  95. package/src/sast/csharp-structural.js +65 -0
  96. package/src/sast/csharp.js +3 -0
  97. package/src/sast/csrf.js +24 -3
  98. package/src/sast/csv-injection.js +84 -0
  99. package/src/sast/frontend-hygiene.js +106 -0
  100. package/src/sast/go-structural.js +60 -0
  101. package/src/sast/java-structural.js +81 -0
  102. package/src/sast/js-framework-structural.js +115 -0
  103. package/src/sast/kotlin.js +72 -0
  104. package/src/sast/ldap-injection.js +73 -17
  105. package/src/sast/open-redirect.js +47 -5
  106. package/src/sast/php.js +35 -0
  107. package/src/sast/python-structural.js +83 -0
  108. package/src/sast/redos-nfa.js +75 -0
  109. package/src/sast/response-splitting.js +48 -19
  110. package/src/sast/ruby.js +38 -0
  111. package/src/sast/secret-concat.js +60 -0
  112. package/src/sast/ssrf-cloud-metadata.js +13 -0
  113. package/src/sast/stored-taint.js +88 -0
  114. package/src/sast/tree-sitter-sinks.js +62 -0
  115. package/src/sast/weak-password-hash.js +28 -0
  116. package/src/sast/weak-randomness.js +46 -5
  117. package/src/sast/wrong-context-sanitizer.js +94 -0
  118. package/src/sast/xpath-injection.js +30 -6
  119. package/src/sast/xss-reflected-multilang.js +116 -0
  120. package/src/sast/xxe.js +67 -0
@@ -23,6 +23,25 @@ import { brierScore, computeBrierOnHeldOut, wilsonInterval } from './calibration
23
23
 
24
24
  const ECE_BINS_DEFAULT = 10;
25
25
 
26
+ // Map a labeled sample to a source language. Prefer an explicit `language`
27
+ // field; otherwise derive it from the `file` extension. Keeps per-language
28
+ // metrics meaningful even for older corpora that only carry a file path.
29
+ const EXT_TO_LANG = {
30
+ js: 'js', jsx: 'js', mjs: 'js', cjs: 'js', ts: 'ts', tsx: 'ts',
31
+ py: 'py', java: 'java', go: 'go', rb: 'rb', php: 'php',
32
+ cs: 'cs', kt: 'kt', kts: 'kt', swift: 'swift', rs: 'rs',
33
+ c: 'c', h: 'c', cpp: 'cpp', cc: 'cpp', cxx: 'cpp', hpp: 'cpp',
34
+ sol: 'sol', dart: 'dart',
35
+ };
36
+ export function languageOf(o) {
37
+ if (o && typeof o.language === 'string' && o.language) return o.language;
38
+ if (o && typeof o.file === 'string') {
39
+ const ext = o.file.split('.').pop().toLowerCase();
40
+ if (EXT_TO_LANG[ext]) return EXT_TO_LANG[ext];
41
+ }
42
+ return 'unknown';
43
+ }
44
+
26
45
  export function parseLabeledJsonl(text) {
27
46
  if (typeof text !== 'string' || !text.length) return [];
28
47
  const out = [];
@@ -39,6 +58,7 @@ export function parseLabeledJsonl(text) {
39
58
  if (p === null || a === null) continue;
40
59
  out.push({
41
60
  family: typeof o.family === 'string' ? o.family : 'unknown',
61
+ language: languageOf(o),
42
62
  stableId: typeof o.stableId === 'string' ? o.stableId : null,
43
63
  predicted: Math.max(0, Math.min(1, p)),
44
64
  actual: a,
@@ -98,6 +118,27 @@ export function perFamily(samples) {
98
118
  return fams;
99
119
  }
100
120
 
121
+ // Per-language precision/recall breakdown (roadmap #9). The "perfect
122
+ // multi-language SAST" claim is only defensible with per-language ground
123
+ // truth — a corpus that's 90% JS can hide poor Ruby precision behind a
124
+ // healthy aggregate. Mirrors perFamily, keyed by sample.language, and adds
125
+ // a per-language precision so a regression in one language is visible.
126
+ export function perLanguage(samples) {
127
+ const langs = {};
128
+ for (const s of samples) {
129
+ const l = s.language || 'unknown';
130
+ if (!langs[l]) langs[l] = { tp: 0, fp: 0, n: 0, precision: 0 };
131
+ langs[l].n++;
132
+ if (s.actual === 1) langs[l].tp++;
133
+ else if (s.actual === 0) langs[l].fp++;
134
+ }
135
+ for (const l of Object.keys(langs)) {
136
+ const { tp, fp } = langs[l];
137
+ langs[l].precision = (tp + fp) > 0 ? Number((tp / (tp + fp)).toFixed(4)) : 0;
138
+ }
139
+ return langs;
140
+ }
141
+
101
142
  // One-shot evaluation: Brier + ECE + per-family TP/FP + overall precision.
102
143
  // Returns null only when there's truly no data; never returns a tautological
103
144
  // zero.
@@ -124,14 +165,30 @@ export function evaluateHeldOut(samples) {
124
165
  precision,
125
166
  precisionCi95: ci,
126
167
  perFamily: fams,
168
+ perLanguage: perLanguage(samples),
127
169
  notes: [
128
170
  ...(samples.length < 100 ? ['n<100: Brier and ECE have wide confidence; treat as directional, not decision-grade.'] : []),
129
171
  ...(brierR.brier !== null && brierR.brier > 0.10 ? [`brier=${brierR.brier.toFixed(3)} exceeds PRD target 0.10`] : []),
130
172
  ...(ece && ece.ece > 0.05 ? [`ece=${ece.ece.toFixed(3)} exceeds 0.05 calibration target`] : []),
173
+ // Per-language regression surfacing: a language with enough samples
174
+ // whose precision trails the aggregate by >0.15 is the failure the
175
+ // aggregate would otherwise mask.
176
+ ...Object.entries(perLanguage(samples))
177
+ .filter(([, m]) => m.n >= 20 && precision - m.precision > 0.15)
178
+ .map(([lang, m]) => `language '${lang}' precision=${m.precision.toFixed(3)} (n=${m.n}) trails aggregate ${precision.toFixed(3)}`),
131
179
  ],
132
180
  };
133
181
  }
134
182
 
183
+ // Per-language dashboard line: one row per language, sorted by sample count.
184
+ export function summarizePerLanguage(result) {
185
+ if (!result || !result.ok || !result.perLanguage) return 'per-language: no data';
186
+ return Object.entries(result.perLanguage)
187
+ .sort((a, b) => b[1].n - a[1].n)
188
+ .map(([lang, m]) => `${lang}: precision=${m.precision.toFixed(3)} (tp=${m.tp} fp=${m.fp} n=${m.n})`)
189
+ .join('\n');
190
+ }
191
+
135
192
  // CLI-friendly summary line.
136
193
  export function summarize(result) {
137
194
  if (!result || !result.ok) return `held-out: ${result?.reason || 'unknown error'}`;
@@ -27,7 +27,34 @@ function ageHours(fp) {
27
27
  return (Date.now() - fs.statSync(fp).mtimeMs) / 3_600_000;
28
28
  }
29
29
 
30
- export function decide({ scanRoot, intent }) {
30
+ // Compare the two most-recent scan-history entries to tell the user whether
31
+ // they're trending safer or more exposed. Returns {} when there isn't enough
32
+ // history to say anything honest. (premortem: never invent a trend from one
33
+ // data point.)
34
+ export function computeScanTrend(scanRoot) {
35
+ const hist = readJson(path.join(scanRoot, '.agentic-security', 'scan-history.json'));
36
+ if (!Array.isArray(hist) || hist.length < 2) return {};
37
+ const cur = hist[hist.length - 1];
38
+ const prev = hist[hist.length - 2];
39
+ const sevOf = (e) => (e?.critical || 0) + (e?.high || 0);
40
+ const delta = sevOf(cur) - sevOf(prev);
41
+ let trend = 'flat';
42
+ if (delta < 0) trend = 'improving';
43
+ else if (delta > 0) trend = 'regressing';
44
+ const n = Math.abs(delta);
45
+ const whatChanged =
46
+ trend === 'improving' ? `${n} fewer critical+high than last scan — keep going.`
47
+ : trend === 'regressing' ? `${n} more critical+high than last scan — new risk crept in.`
48
+ : 'No change in critical+high since last scan.';
49
+ return { trend, whatChanged };
50
+ }
51
+
52
+ export function decide(opts) {
53
+ const base = baseDecision(opts);
54
+ return { ...base, ...computeScanTrend(opts.scanRoot) };
55
+ }
56
+
57
+ function baseDecision({ scanRoot, intent }) {
31
58
  const stateDir = path.join(scanRoot, '.agentic-security');
32
59
  const lastScan = readJson(path.join(stateDir, 'last-scan.json'));
33
60
  const scanAge = ageHours(path.join(stateDir, 'last-scan.json'));
@@ -102,8 +129,11 @@ export function explain(decision) {
102
129
  ``,
103
130
  ` Action: ${decision.action}`,
104
131
  ` Why: ${decision.reason}`,
105
- ` Run: ${decision.command}`,
106
- ``,
107
132
  ];
133
+ if (decision.trend) {
134
+ const arrow = decision.trend === 'improving' ? '↓' : decision.trend === 'regressing' ? '↑' : '→';
135
+ lines.push(` Trend: ${arrow} ${decision.whatChanged}`);
136
+ }
137
+ lines.push(` Run: ${decision.command}`, ``);
108
138
  return lines.join('\n');
109
139
  }
@@ -0,0 +1,26 @@
1
+ # Data Protection Impact Assessment (DPIA)
2
+
3
+ Generated by agentic-security scanner on 2026-05-31.
4
+
5
+ This is an automated DPIA scaffold derived from static analysis.
6
+ It must be reviewed and completed by a privacy officer before use.
7
+
8
+ ## Data classes identified
9
+
10
+ ## Privacy-related findings
11
+
12
+ | Severity | File:Line | Class → Sink | Description |
13
+ |---|---|---|---|
14
+
15
+ ## Regulatory framework mapping
16
+
17
+ - **GDPR Art. 35** — DPIA required when processing is likely to result in high risk to data subjects.
18
+ - **CCPA §1798.130** — Notice + access rights for collected personal information.
19
+
20
+ ## Reviewer checklist
21
+
22
+ - [ ] Confirm each PII field's collection has a documented lawful basis
23
+ - [ ] Confirm retention period for each class is documented
24
+ - [ ] Confirm DSAR (data subject access request) workflow exists
25
+ - [ ] Confirm encryption at rest + in transit for each class
26
+ - [ ] Confirm logging of PII access for audit (where applicable)
@@ -0,0 +1,6 @@
1
+ {
2
+ "sha": "f94be839aa6a1011f53da016416a561698b8f338",
3
+ "ts": "2026-05-31T16:06:10.902Z",
4
+ "componentCount": 0,
5
+ "components": []
6
+ }
@@ -1,6 +1,6 @@
1
1
  # Threat Model (auto-generated)
2
2
 
3
- Generated by agentic-security on 2026-05-30.
3
+ Generated by agentic-security on 2026-05-31.
4
4
 
5
5
  This threat model is derived from static analysis of the current codebase and is regenerated on every scan. It is intended as a working artifact, not a finished compliance document.
6
6
 
@@ -1001,6 +1001,23 @@ export function toShipVerdict(scan, options = {}) {
1001
1001
  } else if (advisoryCount > 0) {
1002
1002
  lines.push(c(` ${advisoryCount} advisory item${advisoryCount === 1 ? '' : 's'} — run /security-scan-all --firehose to see them.`, DIM));
1003
1003
  }
1004
+ // Coverage-honesty line (#5/#6): show the scan's blind spots — which
1005
+ // languages got flow analysis vs pattern-only, what was skipped, and how
1006
+ // many dangerous-looking calls had no finding. One concise line, not bloat.
1007
+ const _covMeta = scan._scanMeta;
1008
+ if (_covMeta && _covMeta.analysisTier) {
1009
+ const t = _covMeta.analysisTier;
1010
+ const pat = Object.keys(t.patternOnly || {});
1011
+ const segs = [`${_covMeta.filesScanned ?? '?'} files`];
1012
+ const ir = Object.keys(t.irTaint || {});
1013
+ if (ir.length) segs.push(`flow=[${ir.join(',')}]`);
1014
+ if (pat.length) segs.push(`pattern-only=[${pat.join(',')}]`);
1015
+ const skipped = (_covMeta.filesSkipped || 0) + (_covMeta.filesDenseSkipped || 0);
1016
+ if (skipped) segs.push(`${skipped} skipped`);
1017
+ if (_covMeta.unmodeledSinkCandidates?.count) segs.push(`${_covMeta.unmodeledSinkCandidates.count} unmodeled-sink candidate${_covMeta.unmodeledSinkCandidates.count === 1 ? '' : 's'}`);
1018
+ lines.push('');
1019
+ lines.push(c(' Coverage: ' + segs.join(' · '), DIM));
1020
+ }
1004
1021
  lines.push('');
1005
1022
  lines.push(c(' 🛡 agentic-security · created by ClearCapabilities.Com', DIM));
1006
1023
  return lines.join('\n');
@@ -0,0 +1,26 @@
1
+ # Data Protection Impact Assessment (DPIA)
2
+
3
+ Generated by agentic-security scanner on 2026-06-01.
4
+
5
+ This is an automated DPIA scaffold derived from static analysis.
6
+ It must be reviewed and completed by a privacy officer before use.
7
+
8
+ ## Data classes identified
9
+
10
+ ## Privacy-related findings
11
+
12
+ | Severity | File:Line | Class → Sink | Description |
13
+ |---|---|---|---|
14
+
15
+ ## Regulatory framework mapping
16
+
17
+ - **GDPR Art. 35** — DPIA required when processing is likely to result in high risk to data subjects.
18
+ - **CCPA §1798.130** — Notice + access rights for collected personal information.
19
+
20
+ ## Reviewer checklist
21
+
22
+ - [ ] Confirm each PII field's collection has a documented lawful basis
23
+ - [ ] Confirm retention period for each class is documented
24
+ - [ ] Confirm DSAR (data subject access request) workflow exists
25
+ - [ ] Confirm encryption at rest + in transit for each class
26
+ - [ ] Confirm logging of PII access for audit (where applicable)
@@ -0,0 +1,65 @@
1
+ {
2
+ "generatedAt": "2026-06-01T03:15:31.819Z",
3
+ "summary": {
4
+ "total": 1,
5
+ "hndlCritical": 1,
6
+ "standard": 0,
7
+ "filesAffected": 1,
8
+ "primitivesNeeded": [
9
+ "ML-KEM-768"
10
+ ]
11
+ },
12
+ "milestones": [
13
+ {
14
+ "id": "M1",
15
+ "title": "Inventory & policy",
16
+ "target": "90 days",
17
+ "owner": "security",
18
+ "items": [
19
+ "Confirm scanner findings against design docs",
20
+ "Adopt PQC migration policy (CNSA 2.0 / NIST IR 8547 alignment)",
21
+ "Establish KMS support for hybrid keys"
22
+ ]
23
+ },
24
+ {
25
+ "id": "M2",
26
+ "title": "HNDL-critical paths to PQ-hybrid",
27
+ "target": "180 days",
28
+ "owner": "platform",
29
+ "items": [
30
+ {
31
+ "finding": "pqc-rsa-encrypt:post-quantum-crypto.js:136",
32
+ "file": "post-quantum-crypto.js",
33
+ "line": 136,
34
+ "replacement": "X25519+ML-KEM-768 (CNSA 2.0 transitional)"
35
+ }
36
+ ]
37
+ },
38
+ {
39
+ "id": "M3",
40
+ "title": "Standard signing/KEX migration",
41
+ "target": "12 months",
42
+ "owner": "platform",
43
+ "items": []
44
+ },
45
+ {
46
+ "id": "M4",
47
+ "title": "Deprecate classical primitives",
48
+ "target": "24 months",
49
+ "owner": "security",
50
+ "items": [
51
+ "Remove dual-stack libraries once peers are PQ-capable",
52
+ "Rotate root CA / long-lived signing keys to ML-DSA"
53
+ ]
54
+ }
55
+ ],
56
+ "perFile": {
57
+ "post-quantum-crypto.js": {
58
+ "count": 1,
59
+ "subfamilies": [
60
+ "pqc-rsa-encrypt"
61
+ ],
62
+ "hndlCritical": true
63
+ }
64
+ }
65
+ }
@@ -0,0 +1,30 @@
1
+ # Post-quantum cryptography migration plan
2
+
3
+ Generated 2026-06-01.
4
+
5
+ **1** pre-quantum primitive sites across **1** files.
6
+ HNDL-critical: **1** | Standard: **0**
7
+
8
+ ## Recommended PQ primitives
9
+ - ML-KEM-768
10
+
11
+ ## M1 — Inventory & policy (target 90 days, owner security)
12
+ - Confirm scanner findings against design docs
13
+ - Adopt PQC migration policy (CNSA 2.0 / NIST IR 8547 alignment)
14
+ - Establish KMS support for hybrid keys
15
+
16
+ ## M2 — HNDL-critical paths to PQ-hybrid (target 180 days, owner platform)
17
+ - `post-quantum-crypto.js:136` → X25519+ML-KEM-768 (CNSA 2.0 transitional)
18
+
19
+ ## M3 — Standard signing/KEX migration (target 12 months, owner platform)
20
+
21
+ ## M4 — Deprecate classical primitives (target 24 months, owner security)
22
+ - Remove dual-stack libraries once peers are PQ-capable
23
+ - Rotate root CA / long-lived signing keys to ML-DSA
24
+
25
+ ## References
26
+ - NIST FIPS 203 (ML-KEM), FIPS 204 (ML-DSA), FIPS 205 (SLH-DSA)
27
+ - NIST IR 8547 — Transition to Post-Quantum Cryptographic Standards
28
+ - CNSA 2.0 — Commercial National Security Algorithm Suite, Sept 2022
29
+ - RFC 9794 — X25519MLKEM768 hybrid key exchange for TLS 1.3
30
+ - Open Quantum Safe project (liboqs, oqs-provider for OpenSSL 3)
@@ -0,0 +1,6 @@
1
+ {
2
+ "sha": "043a7b401c68f5a298c83d4c42e94f118ff2a89a",
3
+ "ts": "2026-05-31T23:19:07.127Z",
4
+ "componentCount": 0,
5
+ "components": []
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "sha": "0775d96ba4cf814520d02760cfb3010760c7684f",
3
+ "ts": "2026-06-01T03:15:31.817Z",
4
+ "componentCount": 0,
5
+ "components": []
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "sha": "20d77ad7363a82777d506dedbbb1ecfed52f094a",
3
+ "ts": "2026-06-01T01:23:04.006Z",
4
+ "componentCount": 0,
5
+ "components": []
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "sha": "29300441d3460832137a8e51a92b74068cad2f15",
3
+ "ts": "2026-05-31T15:11:55.419Z",
4
+ "componentCount": 0,
5
+ "components": []
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "sha": "3852ad2aa0e50951bed0c930c29d1861aa13d048",
3
+ "ts": "2026-05-31T18:27:58.109Z",
4
+ "componentCount": 0,
5
+ "components": []
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "sha": "3c6b3205f04e26f7654943d465281d00e0fa395e",
3
+ "ts": "2026-06-01T02:32:18.105Z",
4
+ "componentCount": 0,
5
+ "components": []
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "sha": "3d96e2daec97e3226c9679bac6e64a89ca10bfe9",
3
+ "ts": "2026-06-01T01:30:24.496Z",
4
+ "componentCount": 0,
5
+ "components": []
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "sha": "5282502e090013219eb006f7e02d8c983bc29c8e",
3
+ "ts": "2026-06-01T01:37:31.644Z",
4
+ "componentCount": 0,
5
+ "components": []
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "sha": "5f0b136622fce09b469a1a6137c04d5b18652ea4",
3
+ "ts": "2026-05-31T17:45:32.183Z",
4
+ "componentCount": 0,
5
+ "components": []
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "sha": "64e30595d447b62344d0d9200760269df2095332",
3
+ "ts": "2026-05-31T23:04:22.450Z",
4
+ "componentCount": 0,
5
+ "components": []
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "sha": "71f3241902c9e7437764e3caa046ba8a3bcdd32a",
3
+ "ts": "2026-06-01T02:53:45.965Z",
4
+ "componentCount": 0,
5
+ "components": []
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "sha": "73514b485d4770f3c5ee57d87338bccd1c61d8b7",
3
+ "ts": "2026-05-31T17:03:16.200Z",
4
+ "componentCount": 0,
5
+ "components": []
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "sha": "96fa584fc3134512237d58aac63d6f3778b0cbcb",
3
+ "ts": "2026-06-01T01:53:20.629Z",
4
+ "componentCount": 0,
5
+ "components": []
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "sha": "99c372cdc33ecc9dde92960b4ffadf7775860e61",
3
+ "ts": "2026-05-31T14:19:38.559Z",
4
+ "componentCount": 0,
5
+ "components": []
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "sha": "a3544abf1863d71776b61302ca74d9ead2b26a9f",
3
+ "ts": "2026-05-31T21:58:18.964Z",
4
+ "componentCount": 0,
5
+ "components": []
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "sha": "a4a34ab8b3d758de2adc8c3dc3e42d4cb0db00a3",
3
+ "ts": "2026-06-01T02:13:24.420Z",
4
+ "componentCount": 0,
5
+ "components": []
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "sha": "ac236ec3602cfc8fb133f389ec3687e409084673",
3
+ "ts": "2026-05-31T17:31:27.530Z",
4
+ "componentCount": 0,
5
+ "components": []
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "sha": "b57b3e1292f1f9f0de6c5a34a436e330c78008a3",
3
+ "ts": "2026-06-01T01:47:59.160Z",
4
+ "componentCount": 0,
5
+ "components": []
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "sha": "dca15abdec43c508aa342db86dc5db8d7f01fbbe",
3
+ "ts": "2026-05-31T22:05:36.857Z",
4
+ "componentCount": 0,
5
+ "components": []
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "sha": "dcbfd4935922dd165093c08a1003c338eed937c5",
3
+ "ts": "2026-06-01T02:02:21.209Z",
4
+ "componentCount": 0,
5
+ "components": []
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "sha": "fa017f725615b0293814344c8ef7dfeb06775ca0",
3
+ "ts": "2026-06-01T02:21:07.710Z",
4
+ "componentCount": 0,
5
+ "components": []
6
+ }