@quantakrypto/core 0.4.1 → 0.4.3

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 (140) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +38 -10
  3. package/dist/agent-types.js.map +1 -1
  4. package/dist/baseline.js.map +1 -1
  5. package/dist/cache.js.map +1 -1
  6. package/dist/cbom.d.ts.map +1 -1
  7. package/dist/cbom.js +13 -4
  8. package/dist/cbom.js.map +1 -1
  9. package/dist/changed.js.map +1 -1
  10. package/dist/codemods/config-toggle.js.map +1 -1
  11. package/dist/codemods/registry.js.map +1 -1
  12. package/dist/comments.d.ts +26 -0
  13. package/dist/comments.d.ts.map +1 -1
  14. package/dist/comments.js +141 -1
  15. package/dist/comments.js.map +1 -1
  16. package/dist/config.js.map +1 -1
  17. package/dist/cwe.js.map +1 -1
  18. package/dist/dependencies.d.ts +3 -2
  19. package/dist/dependencies.d.ts.map +1 -1
  20. package/dist/dependencies.js +151 -4
  21. package/dist/dependencies.js.map +1 -1
  22. package/dist/detect-utils.d.ts +14 -3
  23. package/dist/detect-utils.d.ts.map +1 -1
  24. package/dist/detect-utils.js +32 -4
  25. package/dist/detect-utils.js.map +1 -1
  26. package/dist/detectors/c.d.ts.map +1 -1
  27. package/dist/detectors/c.js +190 -2
  28. package/dist/detectors/c.js.map +1 -1
  29. package/dist/detectors/csharp.d.ts.map +1 -1
  30. package/dist/detectors/csharp.js +141 -3
  31. package/dist/detectors/csharp.js.map +1 -1
  32. package/dist/detectors/go.d.ts +5 -3
  33. package/dist/detectors/go.d.ts.map +1 -1
  34. package/dist/detectors/go.js +187 -3
  35. package/dist/detectors/go.js.map +1 -1
  36. package/dist/detectors/java.d.ts.map +1 -1
  37. package/dist/detectors/java.js +168 -3
  38. package/dist/detectors/java.js.map +1 -1
  39. package/dist/detectors/pem.d.ts.map +1 -1
  40. package/dist/detectors/pem.js +49 -1
  41. package/dist/detectors/pem.js.map +1 -1
  42. package/dist/detectors/python.d.ts.map +1 -1
  43. package/dist/detectors/python.js +74 -3
  44. package/dist/detectors/python.js.map +1 -1
  45. package/dist/detectors/ruby.d.ts +9 -5
  46. package/dist/detectors/ruby.d.ts.map +1 -1
  47. package/dist/detectors/ruby.js +122 -2
  48. package/dist/detectors/ruby.js.map +1 -1
  49. package/dist/detectors/rust.d.ts +8 -3
  50. package/dist/detectors/rust.d.ts.map +1 -1
  51. package/dist/detectors/rust.js +185 -3
  52. package/dist/detectors/rust.js.map +1 -1
  53. package/dist/detectors/source.d.ts.map +1 -1
  54. package/dist/detectors/source.js +131 -13
  55. package/dist/detectors/source.js.map +1 -1
  56. package/dist/detectors/stateful-hbs.d.ts +22 -0
  57. package/dist/detectors/stateful-hbs.d.ts.map +1 -0
  58. package/dist/detectors/stateful-hbs.js +139 -0
  59. package/dist/detectors/stateful-hbs.js.map +1 -0
  60. package/dist/errors.js.map +1 -1
  61. package/dist/evidence.d.ts +48 -0
  62. package/dist/evidence.d.ts.map +1 -0
  63. package/dist/evidence.js +76 -0
  64. package/dist/evidence.js.map +1 -0
  65. package/dist/index.d.ts +4 -2
  66. package/dist/index.d.ts.map +1 -1
  67. package/dist/index.js +4 -2
  68. package/dist/index.js.map +1 -1
  69. package/dist/inventory.d.ts +2 -0
  70. package/dist/inventory.d.ts.map +1 -1
  71. package/dist/inventory.js +27 -1
  72. package/dist/inventory.js.map +1 -1
  73. package/dist/parallel.js +15 -0
  74. package/dist/parallel.js.map +1 -1
  75. package/dist/patch-policy.js.map +1 -1
  76. package/dist/redact.d.ts.map +1 -1
  77. package/dist/redact.js +122 -7
  78. package/dist/redact.js.map +1 -1
  79. package/dist/registry.d.ts +2 -2
  80. package/dist/registry.d.ts.map +1 -1
  81. package/dist/registry.js +4 -2
  82. package/dist/registry.js.map +1 -1
  83. package/dist/remediate-pipeline.d.ts +3 -0
  84. package/dist/remediate-pipeline.d.ts.map +1 -1
  85. package/dist/remediate-pipeline.js +50 -0
  86. package/dist/remediate-pipeline.js.map +1 -1
  87. package/dist/remediate-request.js.map +1 -1
  88. package/dist/remediation.d.ts +9 -3
  89. package/dist/remediation.d.ts.map +1 -1
  90. package/dist/remediation.js +33 -6
  91. package/dist/remediation.js.map +1 -1
  92. package/dist/report.d.ts +9 -0
  93. package/dist/report.d.ts.map +1 -1
  94. package/dist/report.js +31 -0
  95. package/dist/report.js.map +1 -1
  96. package/dist/scan-worker.js.map +1 -1
  97. package/dist/scan.d.ts.map +1 -1
  98. package/dist/scan.js +16 -2
  99. package/dist/scan.js.map +1 -1
  100. package/dist/severity.js.map +1 -1
  101. package/dist/triage.js.map +1 -1
  102. package/dist/types.d.ts +8 -1
  103. package/dist/types.d.ts.map +1 -1
  104. package/dist/types.js.map +1 -1
  105. package/dist/verify.js.map +1 -1
  106. package/dist/version.d.ts +1 -1
  107. package/dist/version.js +1 -1
  108. package/dist/version.js.map +1 -1
  109. package/dist/walk.js.map +1 -1
  110. package/dist/worktree.d.ts.map +1 -1
  111. package/dist/worktree.js +10 -1
  112. package/dist/worktree.js.map +1 -1
  113. package/package.json +1 -1
  114. package/src/cbom.ts +13 -4
  115. package/src/comments.ts +140 -1
  116. package/src/dependencies.ts +156 -6
  117. package/src/detect-utils.ts +33 -4
  118. package/src/detectors/c.ts +202 -2
  119. package/src/detectors/csharp.ts +154 -3
  120. package/src/detectors/go.ts +209 -6
  121. package/src/detectors/java.ts +189 -3
  122. package/src/detectors/pem.ts +53 -1
  123. package/src/detectors/python.ts +82 -3
  124. package/src/detectors/ruby.ts +138 -7
  125. package/src/detectors/rust.ts +211 -7
  126. package/src/detectors/source.ts +163 -12
  127. package/src/detectors/stateful-hbs.ts +177 -0
  128. package/src/evidence.ts +124 -0
  129. package/src/index.ts +5 -1
  130. package/src/inventory.ts +31 -1
  131. package/src/parallel.ts +15 -0
  132. package/src/redact.ts +125 -8
  133. package/src/registry.ts +4 -2
  134. package/src/remediate-pipeline.ts +55 -0
  135. package/src/remediation.ts +41 -9
  136. package/src/report.ts +41 -2
  137. package/src/scan.ts +21 -2
  138. package/src/types.ts +8 -1
  139. package/src/version.ts +1 -1
  140. package/src/worktree.ts +9 -1
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * Post-quantum remediation guidance for each classical asymmetric algorithm
3
- * family. The recommendations follow NIST's standardized PQC algorithms
4
- * (ML-KEM / FIPS 203, ML-DSA / FIPS 204, SLH-DSA / FIPS 205) and the IETF
5
- * hybrid key-exchange drafts (X25519MLKEM768).
3
+ * family. The recommendations follow NIST's finalized PQC algorithms — ML-KEM
4
+ * (FIPS 203), ML-DSA (FIPS 204), SLH-DSA (FIPS 205) and the IETF hybrid
5
+ * key-exchange groups (X25519MLKEM768, SecP384r1MLKEM1024). Forward-looking
6
+ * standards to track are captured in {@link PQC_TRANSITION_NOTE}.
6
7
  */
7
8
  import type { AlgorithmFamily, Remediation } from "./types.js";
8
9
 
@@ -63,19 +64,21 @@ const REMEDIATIONS: Record<AlgorithmFamily, Remediation> = {
63
64
  },
64
65
  X448: {
65
66
  algorithm: "X448",
66
- recommendation: "hybrid X25519MLKEM768 (ML-KEM-768)",
67
+ recommendation: "hybrid SecP384r1MLKEM1024 (or X25519MLKEM768)",
67
68
  detail:
68
69
  "X448 (Goldilocks curve) is a modern classical key-agreement primitive at a " +
69
70
  "higher classical security level, but it is still broken by Shor's algorithm. " +
70
- "Adopt a hybrid PQC KEM (X25519MLKEM768 / ML-KEM-768) during the transition.",
71
+ "To preserve that assurance level, prefer the SecP384r1MLKEM1024 hybrid " +
72
+ "(ML-KEM-1024); X25519MLKEM768 is acceptable at the commercial tier.",
71
73
  },
72
74
  ECIES: {
73
75
  algorithm: "ECIES",
74
- recommendation: "ML-KEM-768 hybrid encryption",
76
+ recommendation: "ML-KEM-768 hybrid encryption (X-Wing for HPKE)",
75
77
  detail:
76
78
  "ECIES relies on classical ECDH for its key encapsulation and is exposed to " +
77
- "harvest-now-decrypt-later. Replace the KEM step with ML-KEM-768 (FIPS 203), " +
78
- "preferably in a hybrid construction.",
79
+ "harvest-now-decrypt-later. Replace the KEM step with ML-KEM-768 (FIPS 203) in " +
80
+ "a hybrid construction — for HPKE-style application-layer encryption, X-Wing " +
81
+ "(X25519 + ML-KEM-768) is the emerging hybrid KEM target.",
79
82
  },
80
83
  unknown: {
81
84
  algorithm: "unknown",
@@ -160,7 +163,24 @@ export function remediationForTier(
160
163
  const base = REMEDIATIONS[algorithm];
161
164
  const params = TIER_PARAMS[tier];
162
165
  // Confidentiality families lean on the KEM; signature families on the signer.
163
- const primary = isConfidentialityFamily(algorithm) ? params.kem : params.signature;
166
+ const isConf = isConfidentialityFamily(algorithm);
167
+ const primary = isConf ? params.kem : params.signature;
168
+
169
+ // Category-5 (CNSA 2.0 / NSS) mandates the ML-KEM-1024 / ML-DSA-87 parameter
170
+ // sets and must NOT surface the category-3 X25519MLKEM768 hybrid (its PQC
171
+ // component is ML-KEM-768 — sub-CNSA). Lead with the mandated set; only point
172
+ // at a hybrid TLS group at the 1024 level (audit: quantum #1, crypto S5).
173
+ if (tier === "category-5") {
174
+ const hybridNote = isConf
175
+ ? " If a hybrid TLS group is required, use SecP384r1MLKEM1024 (draft-ietf-tls-ecdhe-mlkem) — not X25519MLKEM768, whose ML-KEM-768 component does not meet CNSA 2.0."
176
+ : "";
177
+ return {
178
+ algorithm,
179
+ recommendation: `${primary} — CNSA 2.0 mandates this parameter set (hybrids optional)`,
180
+ detail: `${base.detail} ${params.note} For CNSA 2.0 / national-security systems use ${params.kem} (KEM) and ${params.signature} (signatures).${hybridNote}`,
181
+ };
182
+ }
183
+
164
184
  return {
165
185
  algorithm,
166
186
  recommendation: `${base.recommendation} — ${tier}: ${primary}`,
@@ -182,6 +202,18 @@ export const STATEFUL_HBS_NOTE =
182
202
  "Use only with rigorous state management; otherwise prefer stateless ML-DSA " +
183
203
  "(FIPS 204) or SLH-DSA (FIPS 205).";
184
204
 
205
+ /**
206
+ * Forward-looking PQC standards worth tracking beyond the current FIPS 203/204/205
207
+ * targets. Surfaced for operators planning multi-year migrations.
208
+ */
209
+ export const PQC_TRANSITION_NOTE =
210
+ "Migration urgency: NIST IR 8547 deprecates classical public-key crypto after " +
211
+ "2030 and disallows it after 2035 — long-lived (harvest-now-decrypt-later) data " +
212
+ "must move sooner. Standards to track: HQC (NIST's code-based backup KEM, " +
213
+ "selected March 2025; draft FIPS expected ~2026) as a diversity hedge against " +
214
+ "ML-KEM; FN-DSA / Falcon (draft FIPS 206) for compact lattice signatures; and " +
215
+ "X-Wing (X25519 + ML-KEM-768) for HPKE-style hybrid encryption.";
216
+
185
217
  /** True when stateful HBS (SP 800-208) is a reasonable alternative for a family. */
186
218
  export function statefulHbsApplies(algorithm: AlgorithmFamily): boolean {
187
219
  // Signature families only — LMS/XMSS are signatures, not KEMs.
package/src/report.ts CHANGED
@@ -3,10 +3,12 @@
3
3
  * or a human-readable text summary. No third-party dependencies — ANSI colour
4
4
  * is emitted with raw escape codes and is off by default.
5
5
  */
6
- import type { Finding, RuleMeta, ScanResult, Severity } from "./types.js";
6
+ import type { AlgorithmFamily, Finding, RuleMeta, ScanResult, Severity } from "./types.js";
7
7
  import { VERSION } from "./version.js";
8
8
  import { SEVERITY_ORDER, sarifLevel } from "./severity.js";
9
9
  import { ANALYZABLE_LANGUAGES_LABEL } from "./detect-utils.js";
10
+ import { remediationFor, remediationForTier } from "./remediation.js";
11
+ import type { SecurityTier } from "./remediation.js";
10
12
 
11
13
  /** Minimal SARIF 2.1.0 log shape (kept permissive on purpose). */
12
14
  export interface SarifLog {
@@ -327,7 +329,10 @@ function scoreColor(score: number): string {
327
329
  * Render a human-readable summary of a scan result. Colour is off by default;
328
330
  * pass `{ color: true }` to emit ANSI escape codes.
329
331
  */
330
- export function formatSummary(result: ScanResult, options?: { color?: boolean }): string {
332
+ export function formatSummary(
333
+ result: ScanResult,
334
+ options?: { color?: boolean; tier?: SecurityTier },
335
+ ): string {
331
336
  const color = options?.color ?? false;
332
337
  const c = (code: string, text: string): string => (color ? `${code}${text}${ANSI.reset}` : text);
333
338
 
@@ -427,5 +432,39 @@ export function formatSummary(result: ScanResult, options?: { color?: boolean })
427
432
  );
428
433
  }
429
434
 
435
+ if (options?.tier) {
436
+ lines.push("", ...formatTierGuidance(inv.byAlgorithm, options.tier));
437
+ }
438
+
430
439
  return lines.join("\n");
431
440
  }
441
+
442
+ /**
443
+ * Per-family migration targets for a CNSA security tier — surfaces the otherwise
444
+ * library-only {@link remediationForTier} in human reports. Category 5 shows the
445
+ * ML-KEM-1024 / ML-DSA-87 sets CNSA 2.0 mandates for national-security systems and
446
+ * long-lived secrets. Returns plain (un-coloured) lines; the caller styles them.
447
+ */
448
+ export function formatTierGuidance(
449
+ byAlgorithm: Record<string, number>,
450
+ tier: SecurityTier,
451
+ ): string[] {
452
+ const label = tier === "category-5" ? "CNSA 2.0 (Category 5)" : "Category 3 (commercial)";
453
+ const out: string[] = [`${label} migration targets:`];
454
+ const seen = new Set<string>();
455
+ for (const [k, n] of Object.entries(byAlgorithm)) {
456
+ if (n <= 0) continue;
457
+ const fam = k as AlgorithmFamily;
458
+ if (fam === "unknown" || !remediationFor(fam)) continue; // skip unmapped families
459
+ const rem = remediationForTier(fam, tier);
460
+ if (seen.has(rem.recommendation)) continue;
461
+ seen.add(rem.recommendation);
462
+ out.push(` ${fam} → ${rem.recommendation}`);
463
+ }
464
+ if (tier === "category-5") {
465
+ out.push(
466
+ " CNSA 2.0 mandates ML-KEM-1024 / ML-DSA-87 for national-security systems and long-lived secrets (2030/2033 milestones).",
467
+ );
468
+ }
469
+ return out;
470
+ }
package/src/scan.ts CHANGED
@@ -15,7 +15,18 @@ import * as path from "node:path";
15
15
  import type { Detector, Finding, ScanOptions, ScanResult } from "./types.js";
16
16
  import { walkFiles, toPosix, isBinaryPath, looksMinified, matchesAny } from "./walk.js";
17
17
  import { isAnalyzableSource } from "./detect-utils.js";
18
- import { stripCommentFindings, stripIgnoredFindings } from "./comments.js";
18
+ import {
19
+ stripCommentFindings,
20
+ stripIgnoredFindings,
21
+ stripStringLiteralFindings,
22
+ } from "./comments.js";
23
+
24
+ /**
25
+ * Rules whose token is only meaningful as code, never inside a string literal.
26
+ * A match inside a string (e.g. `t.Error("SigningMethodPS256 …")`) is prose and
27
+ * is dropped. Rules that legitimately match quoted tokens are NOT listed here.
28
+ */
29
+ const CODE_ONLY_RULES: ReadonlySet<string> = new Set(["go-jwt-signingmethod"]);
19
30
  import { hashContent, loadCache, rulesetFingerprint, saveCache } from "./cache.js";
20
31
  import type { CacheEntry } from "./cache.js";
21
32
  import { builtinDetectors, defaultRegistry, detectorScope } from "./registry.js";
@@ -71,6 +82,7 @@ export function detectFile(
71
82
  // `qscan-ignore-next-line` directives. Both run before the dependency scan
72
83
  // appends its findings (manifests carry neither).
73
84
  out = stripCommentFindings(out, content, file);
85
+ out = stripStringLiteralFindings(out, content, file, CODE_ONLY_RULES);
74
86
  out = stripIgnoredFindings(out, content);
75
87
 
76
88
  if (toggles.deps && isManifestFile(file)) {
@@ -118,7 +130,14 @@ export async function scan(options: ScanOptions): Promise<ScanResult> {
118
130
  const cacheFile = options.cacheFile;
119
131
  const ruleset = cacheFile ? rulesetFingerprint(dets, options.disabledRules) : "";
120
132
  const cache = cacheFile ? await loadCache(cacheFile, ruleset) : null;
121
- const nextEntries: Map<string, CacheEntry> | null = cacheFile ? new Map() : null;
133
+ // A full scan starts with an empty next-cache so files that vanished are
134
+ // evicted. An INCREMENTAL scan (explicit `files` list) only visits a subset,
135
+ // so seed the next-cache with the existing entries — otherwise saving would
136
+ // drop every file we didn't happen to scan this run (audit: arch #4).
137
+ const incremental = Array.isArray(options.files);
138
+ const nextEntries: Map<string, CacheEntry> | null = cacheFile
139
+ ? new Map(incremental && cache ? cache : [])
140
+ : null;
122
141
 
123
142
  // Work-budget / cancellation controls (all optional, unlimited when omitted).
124
143
  const signal = options.signal;
package/src/types.ts CHANGED
@@ -95,7 +95,7 @@ export interface Finding {
95
95
  }
96
96
 
97
97
  /** Package ecosystems the dependency scanner understands. */
98
- export type DependencyEcosystem = "npm" | "pypi" | "cargo" | "go" | "maven" | "rubygems";
98
+ export type DependencyEcosystem = "npm" | "pypi" | "cargo" | "go" | "maven" | "rubygems" | "nuget";
99
99
 
100
100
  /** A known quantum-vulnerable dependency entry. */
101
101
  export interface VulnerableDependency {
@@ -107,6 +107,13 @@ export interface VulnerableDependency {
107
107
  /** Algorithm families the package primarily exposes. */
108
108
  algorithms: AlgorithmFamily[];
109
109
  severity: Severity;
110
+ /**
111
+ * Explicit harvest-now-decrypt-later override. When omitted, HNDL is inferred
112
+ * from whether any listed family is a confidentiality family. Set `false` for
113
+ * signing-only packages (e.g. JWS/JWT libraries) that list RSA/EC as families
114
+ * but never do key transport or key agreement — signatures are not HNDL-exposed.
115
+ */
116
+ hndl?: boolean;
110
117
  }
111
118
 
112
119
  /**
package/src/version.ts CHANGED
@@ -3,4 +3,4 @@
3
3
  * the scan orchestrator can import it without creating a cycle through index.ts.
4
4
  * Keep in sync with packages/core/package.json.
5
5
  */
6
- export const VERSION = "0.4.1";
6
+ export const VERSION = "0.4.3";
package/src/worktree.ts CHANGED
@@ -31,8 +31,10 @@ export async function withWorktree<T>(
31
31
  }
32
32
  const base = await mkdtemp(join(tmpdir(), "quantakrypto-wt-"));
33
33
  const dir = join(base, "wt");
34
- await git(["worktree", "add", "--detach", dir], repoRoot);
34
+ // `worktree add` is INSIDE the try so a failure (locked index, disk full)
35
+ // still cleans up the temp dir in `finally` (audit: arch #6).
35
36
  try {
37
+ await git(["worktree", "add", "--detach", dir], repoRoot);
36
38
  return await fn(dir);
37
39
  } finally {
38
40
  try {
@@ -40,6 +42,12 @@ export async function withWorktree<T>(
40
42
  } catch {
41
43
  // best effort — the temp dir removal below still reclaims the disk.
42
44
  }
45
+ // Prune stale `.git/worktrees/<name>` metadata a failed add/remove can leave.
46
+ try {
47
+ await git(["worktree", "prune"], repoRoot);
48
+ } catch {
49
+ // best effort
50
+ }
43
51
  await rm(base, { recursive: true, force: true });
44
52
  }
45
53
  }