@quantakrypto/core 0.1.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.
- package/README.md +337 -0
- package/dist/baseline.d.ts +37 -0
- package/dist/baseline.d.ts.map +1 -0
- package/dist/baseline.js +99 -0
- package/dist/baseline.js.map +1 -0
- package/dist/cbom.d.ts +25 -0
- package/dist/cbom.d.ts.map +1 -0
- package/dist/cbom.js +131 -0
- package/dist/cbom.js.map +1 -0
- package/dist/changed.d.ts +13 -0
- package/dist/changed.d.ts.map +1 -0
- package/dist/changed.js +67 -0
- package/dist/changed.js.map +1 -0
- package/dist/config.d.ts +55 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +232 -0
- package/dist/config.js.map +1 -0
- package/dist/cwe.d.ts +16 -0
- package/dist/cwe.d.ts.map +1 -0
- package/dist/cwe.js +16 -0
- package/dist/cwe.js.map +1 -0
- package/dist/dependencies.d.ts +25 -0
- package/dist/dependencies.d.ts.map +1 -0
- package/dist/dependencies.js +276 -0
- package/dist/dependencies.js.map +1 -0
- package/dist/detect-utils.d.ts +63 -0
- package/dist/detect-utils.d.ts.map +1 -0
- package/dist/detect-utils.js +113 -0
- package/dist/detect-utils.js.map +1 -0
- package/dist/detectors/pem.d.ts +9 -0
- package/dist/detectors/pem.d.ts.map +1 -0
- package/dist/detectors/pem.js +137 -0
- package/dist/detectors/pem.js.map +1 -0
- package/dist/detectors/source.d.ts +25 -0
- package/dist/detectors/source.d.ts.map +1 -0
- package/dist/detectors/source.js +575 -0
- package/dist/detectors/source.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/dist/inventory.d.ts +15 -0
- package/dist/inventory.d.ts.map +1 -0
- package/dist/inventory.js +74 -0
- package/dist/inventory.js.map +1 -0
- package/dist/parallel.d.ts +34 -0
- package/dist/parallel.d.ts.map +1 -0
- package/dist/parallel.js +237 -0
- package/dist/parallel.js.map +1 -0
- package/dist/registry.d.ts +42 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +51 -0
- package/dist/registry.js.map +1 -0
- package/dist/remediation.d.ts +42 -0
- package/dist/remediation.d.ts.map +1 -0
- package/dist/remediation.js +131 -0
- package/dist/remediation.js.map +1 -0
- package/dist/report.d.ts +24 -0
- package/dist/report.d.ts.map +1 -0
- package/dist/report.js +286 -0
- package/dist/report.js.map +1 -0
- package/dist/scan-worker.d.ts +2 -0
- package/dist/scan-worker.d.ts.map +1 -0
- package/dist/scan-worker.js +63 -0
- package/dist/scan-worker.js.map +1 -0
- package/dist/scan.d.ts +27 -0
- package/dist/scan.d.ts.map +1 -0
- package/dist/scan.js +168 -0
- package/dist/scan.js.map +1 -0
- package/dist/types.d.ts +190 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/dist/version.d.ts +7 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +7 -0
- package/dist/version.js.map +1 -0
- package/dist/walk.d.ts +37 -0
- package/dist/walk.d.ts.map +1 -0
- package/dist/walk.js +260 -0
- package/dist/walk.js.map +1 -0
- package/package.json +40 -0
- package/src/baseline.ts +111 -0
- package/src/cbom.ts +166 -0
- package/src/changed.ts +76 -0
- package/src/config.ts +295 -0
- package/src/cwe.ts +20 -0
- package/src/dependencies.ts +299 -0
- package/src/detect-utils.ts +157 -0
- package/src/detectors/pem.ts +162 -0
- package/src/detectors/source.ts +733 -0
- package/src/index.ts +79 -0
- package/src/inventory.ts +94 -0
- package/src/parallel.ts +288 -0
- package/src/registry.ts +71 -0
- package/src/remediation.ts +173 -0
- package/src/report.ts +340 -0
- package/src/scan-worker.ts +80 -0
- package/src/scan.ts +187 -0
- package/src/types.ts +224 -0
- package/src/version.ts +6 -0
- package/src/walk.ts +289 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @quantakrypto/core — public API (LOCKED CONTRACT).
|
|
3
|
+
*
|
|
4
|
+
* The exported NAMES and SIGNATURES below are the stable contract that
|
|
5
|
+
* @quantakrypto/qscan, @quantakrypto/mcp and the GitHub Action depend on — do not change
|
|
6
|
+
* them without updating all consumers. The implementations live in focused
|
|
7
|
+
* modules under src/ and are re-exported here; the public surface is identical
|
|
8
|
+
* to the original stub file.
|
|
9
|
+
*/
|
|
10
|
+
export * from "./types.js";
|
|
11
|
+
// Tool version, surfaced in reports. Keep in sync with package.json.
|
|
12
|
+
export { VERSION } from "./version.js";
|
|
13
|
+
// Core orchestration + built-in detector set.
|
|
14
|
+
export { scan, detectors, detectFile, compareFindings } from "./scan.js";
|
|
15
|
+
// Parallel scanning (worker_threads pool) + pure merge/chunk helpers.
|
|
16
|
+
export { scanParallel, mergeChunkResults, chunkByBytes } from "./parallel.js";
|
|
17
|
+
// Detector registry (plugin point) + helpers.
|
|
18
|
+
export { DetectorRegistry, defaultRegistry, detectorScope } from "./registry.js";
|
|
19
|
+
// Canonical baseline (shared by qScan + the Action).
|
|
20
|
+
export { fingerprintFinding, baselineFromFindings, applyBaseline, loadBaseline, saveBaseline, BASELINE_VERSION, } from "./baseline.js";
|
|
21
|
+
// Incremental scanning: changed-files helper (git-aware, tolerant).
|
|
22
|
+
export { changedFiles } from "./changed.js";
|
|
23
|
+
// Optional `quantakrypto.config.json` loader (P2-9; see docs/CONFIG.md).
|
|
24
|
+
export { loadConfig, ConfigError, CONFIG_FILENAME } from "./config.js";
|
|
25
|
+
// Filesystem walker (relative POSIX paths, default ignores, size/binary filters).
|
|
26
|
+
export { walkFiles, isBinaryPath, isGeneratedPath, looksMinified } from "./walk.js";
|
|
27
|
+
// Inventory + readiness score.
|
|
28
|
+
export { buildInventory } from "./inventory.js";
|
|
29
|
+
// Vulnerable-dependency database (the manifest scanner is used internally by scan()).
|
|
30
|
+
export { vulnerableDependencies } from "./dependencies.js";
|
|
31
|
+
// Reporters.
|
|
32
|
+
export { toSarif, toJson, formatSummary } from "./report.js";
|
|
33
|
+
// CycloneDX 1.6 cryptographic bill of materials (CBOM) export.
|
|
34
|
+
export { toCbom } from "./cbom.js";
|
|
35
|
+
// Remediation lookup (family + tier-aware) and stateful-HBS guidance.
|
|
36
|
+
export { remediationFor, remediationForTier, TIER_PARAMS, STATEFUL_HBS_NOTE, statefulHbsApplies, } from "./remediation.js";
|
|
37
|
+
// CWE identifier constants.
|
|
38
|
+
export { CWE_BROKEN_CRYPTO, CWE_WEAK_STRENGTH, CWE_CERT_VALIDATION, CWE_HARDCODED_KEY, CWE_RISKY_PRIMITIVE, } from "./cwe.js";
|
|
39
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,cAAc,YAAY,CAAC;AAE3B,qEAAqE;AACrE,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAKvC,8CAA8C;AAC9C,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAEzE,sEAAsE;AACtE,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAG9E,8CAA8C;AAC9C,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEjF,qDAAqD;AACrD,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,gBAAgB,GACjB,MAAM,eAAe,CAAC;AAGvB,oEAAoE;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,yEAAyE;AACzE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGvE,kFAAkF;AAClF,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAEpF,+BAA+B;AAC/B,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEhD,sFAAsF;AACtF,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAE3D,aAAa;AACb,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE7D,+DAA+D;AAC/D,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAGnC,sEAAsE;AACtE,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,WAAW,EACX,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,kBAAkB,CAAC;AAG1B,4BAA4B;AAC5B,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,mBAAmB,EACnB,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,UAAU,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aggregates findings into a {@link CryptoInventory}: per-algorithm,
|
|
3
|
+
* per-category and per-severity counts, the harvest-now-decrypt-later count,
|
|
4
|
+
* and a 0–100 readiness score.
|
|
5
|
+
*/
|
|
6
|
+
import type { CryptoInventory, Finding } from "./types.js";
|
|
7
|
+
/**
|
|
8
|
+
* Compute a 0–100 readiness score. Starts at 100 and subtracts severity-weighted
|
|
9
|
+
* penalties with diminishing returns per severity bucket. 100 means no classical
|
|
10
|
+
* asymmetric crypto was found; the score is clamped to [0, 100].
|
|
11
|
+
*/
|
|
12
|
+
export declare function readinessScore(findings: Finding[]): number;
|
|
13
|
+
/** Build the full inventory (counts + HNDL + score) from a set of findings. */
|
|
14
|
+
export declare function buildInventory(findings: Finding[]): CryptoInventory;
|
|
15
|
+
//# sourceMappingURL=inventory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inventory.d.ts","sourceRoot":"","sources":["../src/inventory.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAEV,eAAe,EACf,OAAO,EAGR,MAAM,YAAY,CAAC;AAwBpB;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,CAkB1D;AAED,+EAA+E;AAC/E,wBAAgB,cAAc,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,eAAe,CAgCnE"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/** All severities, most → least severe (used to seed the counts record). */
|
|
2
|
+
const SEVERITIES = ["critical", "high", "medium", "low", "info"];
|
|
3
|
+
/** Base penalty applied to the readiness score for the first finding of a severity. */
|
|
4
|
+
const SEVERITY_WEIGHT = {
|
|
5
|
+
critical: 30,
|
|
6
|
+
high: 18,
|
|
7
|
+
medium: 8,
|
|
8
|
+
low: 3,
|
|
9
|
+
info: 1,
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Apply diminishing returns to repeated findings of the same severity: the Nth
|
|
13
|
+
* finding contributes `weight / sqrt(N)`. This keeps a single critical hit very
|
|
14
|
+
* impactful while a codebase with hundreds of medium findings doesn't underflow
|
|
15
|
+
* past zero on the first few — the score still saturates toward 0 sensibly.
|
|
16
|
+
*/
|
|
17
|
+
function penaltyFor(weight, occurrence) {
|
|
18
|
+
return weight / Math.sqrt(occurrence);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Compute a 0–100 readiness score. Starts at 100 and subtracts severity-weighted
|
|
22
|
+
* penalties with diminishing returns per severity bucket. 100 means no classical
|
|
23
|
+
* asymmetric crypto was found; the score is clamped to [0, 100].
|
|
24
|
+
*/
|
|
25
|
+
export function readinessScore(findings) {
|
|
26
|
+
if (findings.length === 0)
|
|
27
|
+
return 100;
|
|
28
|
+
const seen = {
|
|
29
|
+
critical: 0,
|
|
30
|
+
high: 0,
|
|
31
|
+
medium: 0,
|
|
32
|
+
low: 0,
|
|
33
|
+
info: 0,
|
|
34
|
+
};
|
|
35
|
+
let score = 100;
|
|
36
|
+
for (const f of findings) {
|
|
37
|
+
seen[f.severity] += 1;
|
|
38
|
+
score -= penaltyFor(SEVERITY_WEIGHT[f.severity], seen[f.severity]);
|
|
39
|
+
}
|
|
40
|
+
return Math.max(0, Math.min(100, Math.round(score)));
|
|
41
|
+
}
|
|
42
|
+
/** Build the full inventory (counts + HNDL + score) from a set of findings. */
|
|
43
|
+
export function buildInventory(findings) {
|
|
44
|
+
const byAlgorithm = {};
|
|
45
|
+
const byCategory = {};
|
|
46
|
+
const bySeverity = {
|
|
47
|
+
critical: 0,
|
|
48
|
+
high: 0,
|
|
49
|
+
medium: 0,
|
|
50
|
+
low: 0,
|
|
51
|
+
info: 0,
|
|
52
|
+
};
|
|
53
|
+
let hndlCount = 0;
|
|
54
|
+
for (const f of findings) {
|
|
55
|
+
if (f.algorithm) {
|
|
56
|
+
byAlgorithm[f.algorithm] = (byAlgorithm[f.algorithm] ?? 0) + 1;
|
|
57
|
+
}
|
|
58
|
+
byCategory[f.category] = (byCategory[f.category] ?? 0) + 1;
|
|
59
|
+
bySeverity[f.severity] += 1;
|
|
60
|
+
if (f.hndl)
|
|
61
|
+
hndlCount += 1;
|
|
62
|
+
}
|
|
63
|
+
// Ensure all severities are present (seeded above to 0). SEVERITIES is the
|
|
64
|
+
// canonical ordering and guards against missing keys if the type changes.
|
|
65
|
+
void SEVERITIES;
|
|
66
|
+
return {
|
|
67
|
+
byAlgorithm,
|
|
68
|
+
byCategory,
|
|
69
|
+
bySeverity,
|
|
70
|
+
hndlCount,
|
|
71
|
+
readinessScore: readinessScore(findings),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=inventory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inventory.js","sourceRoot":"","sources":["../src/inventory.ts"],"names":[],"mappings":"AAaA,4EAA4E;AAC5E,MAAM,UAAU,GAAe,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;AAE7E,uFAAuF;AACvF,MAAM,eAAe,GAA6B;IAChD,QAAQ,EAAE,EAAE;IACZ,IAAI,EAAE,EAAE;IACR,MAAM,EAAE,CAAC;IACT,GAAG,EAAE,CAAC;IACN,IAAI,EAAE,CAAC;CACR,CAAC;AAEF;;;;;GAKG;AACH,SAAS,UAAU,CAAC,MAAc,EAAE,UAAkB;IACpD,OAAO,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;AACxC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,QAAmB;IAChD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,GAAG,CAAC;IAEtC,MAAM,IAAI,GAA6B;QACrC,QAAQ,EAAE,CAAC;QACX,IAAI,EAAE,CAAC;QACP,MAAM,EAAE,CAAC;QACT,GAAG,EAAE,CAAC;QACN,IAAI,EAAE,CAAC;KACR,CAAC;IAEF,IAAI,KAAK,GAAG,GAAG,CAAC;IAChB,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACtB,KAAK,IAAI,UAAU,CAAC,eAAe,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;IACrE,CAAC;IAED,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACvD,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,cAAc,CAAC,QAAmB;IAChD,MAAM,WAAW,GAA6C,EAAE,CAAC;IACjE,MAAM,UAAU,GAA6C,EAAE,CAAC;IAChE,MAAM,UAAU,GAA6B;QAC3C,QAAQ,EAAE,CAAC;QACX,IAAI,EAAE,CAAC;QACP,MAAM,EAAE,CAAC;QACT,GAAG,EAAE,CAAC;QACN,IAAI,EAAE,CAAC;KACR,CAAC;IACF,IAAI,SAAS,GAAG,CAAC,CAAC;IAElB,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,IAAI,CAAC,CAAC,SAAS,EAAE,CAAC;YAChB,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QACjE,CAAC;QACD,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QAC3D,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC5B,IAAI,CAAC,CAAC,IAAI;YAAE,SAAS,IAAI,CAAC,CAAC;IAC7B,CAAC;IAED,2EAA2E;IAC3E,0EAA0E;IAC1E,KAAK,UAAU,CAAC;IAEhB,OAAO;QACL,WAAW;QACX,UAAU;QACV,UAAU;QACV,SAAS;QACT,cAAc,EAAE,cAAc,CAAC,QAAQ,CAAC;KACzC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Finding, ParallelScanOptions, ScanResult } from "./types.js";
|
|
2
|
+
/** One unit of work dispatched to a worker. */
|
|
3
|
+
export interface ScanChunk {
|
|
4
|
+
files: string[];
|
|
5
|
+
}
|
|
6
|
+
/** What a worker returns for a chunk. */
|
|
7
|
+
export interface ChunkResult {
|
|
8
|
+
findings: Finding[];
|
|
9
|
+
filesScanned: number;
|
|
10
|
+
}
|
|
11
|
+
/** A file plus its byte size, used for byte-balanced chunking. */
|
|
12
|
+
export interface SizedFile {
|
|
13
|
+
rel: string;
|
|
14
|
+
size: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Bucket files into chunks of roughly `chunkBytes` total bytes each, so one
|
|
18
|
+
* large file doesn't starve a worker holding many tiny files. Pure +
|
|
19
|
+
* deterministic: input order is preserved within and across chunks.
|
|
20
|
+
*/
|
|
21
|
+
export declare function chunkByBytes(files: readonly SizedFile[], chunkBytes: number): ScanChunk[];
|
|
22
|
+
/**
|
|
23
|
+
* Merge per-chunk results into a single ordered finding set + total file count.
|
|
24
|
+
* Pure: applies the SAME comparator as the serial scan, so the result is
|
|
25
|
+
* byte-identical regardless of chunk completion order. Exported for unit tests.
|
|
26
|
+
*/
|
|
27
|
+
export declare function mergeChunkResults(results: readonly ChunkResult[]): ChunkResult;
|
|
28
|
+
/**
|
|
29
|
+
* Scan in parallel across a worker-thread pool, falling back to the in-process
|
|
30
|
+
* serial {@link scan} for small workloads or when workers can't be used. The
|
|
31
|
+
* result is deterministic and identical to the serial path.
|
|
32
|
+
*/
|
|
33
|
+
export declare function scanParallel(options: ParallelScanOptions): Promise<ScanResult>;
|
|
34
|
+
//# sourceMappingURL=parallel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parallel.d.ts","sourceRoot":"","sources":["../src/parallel.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAM3E,+CAA+C;AAC/C,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,yCAAyC;AACzC,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB;AAMD,kEAAkE;AAClE,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS,EAAE,CAgBzF;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,GAAG,WAAW,CAS9E;AA2DD;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,UAAU,CAAC,CAkEpF"}
|
package/dist/parallel.js
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parallel scanning over a worker-thread pool (P2-1). The scan is embarrassingly
|
|
3
|
+
* parallel: each file is read and regex-scanned independently, and the merge is
|
|
4
|
+
* a concatenation + the existing deterministic sort. `Finding` is plain
|
|
5
|
+
* structured-cloneable data, so it crosses the worker boundary cleanly.
|
|
6
|
+
*
|
|
7
|
+
* The merge and chunking logic are pure and exported for direct unit testing.
|
|
8
|
+
* `scanParallel` falls back to the in-process serial path below the crossover
|
|
9
|
+
* (small file counts / small total bytes) and whenever workers are unavailable.
|
|
10
|
+
*/
|
|
11
|
+
import { stat } from "node:fs/promises";
|
|
12
|
+
import * as os from "node:os";
|
|
13
|
+
import * as path from "node:path";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
import { walkFiles, toPosix } from "./walk.js";
|
|
16
|
+
import { buildInventory } from "./inventory.js";
|
|
17
|
+
import { compareFindings, scan } from "./scan.js";
|
|
18
|
+
import { VERSION } from "./version.js";
|
|
19
|
+
const DEFAULT_PARALLEL_THRESHOLD_BYTES = 2 * 1024 * 1024;
|
|
20
|
+
const DEFAULT_PARALLEL_FILE_THRESHOLD = 200;
|
|
21
|
+
const DEFAULT_CHUNK_BYTES = 4 * 1024 * 1024;
|
|
22
|
+
/**
|
|
23
|
+
* Bucket files into chunks of roughly `chunkBytes` total bytes each, so one
|
|
24
|
+
* large file doesn't starve a worker holding many tiny files. Pure +
|
|
25
|
+
* deterministic: input order is preserved within and across chunks.
|
|
26
|
+
*/
|
|
27
|
+
export function chunkByBytes(files, chunkBytes) {
|
|
28
|
+
const limit = Math.max(1, chunkBytes);
|
|
29
|
+
const chunks = [];
|
|
30
|
+
let current = [];
|
|
31
|
+
let currentBytes = 0;
|
|
32
|
+
for (const f of files) {
|
|
33
|
+
if (current.length > 0 && currentBytes + f.size > limit) {
|
|
34
|
+
chunks.push({ files: current });
|
|
35
|
+
current = [];
|
|
36
|
+
currentBytes = 0;
|
|
37
|
+
}
|
|
38
|
+
current.push(f.rel);
|
|
39
|
+
currentBytes += f.size;
|
|
40
|
+
}
|
|
41
|
+
if (current.length > 0)
|
|
42
|
+
chunks.push({ files: current });
|
|
43
|
+
return chunks;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Merge per-chunk results into a single ordered finding set + total file count.
|
|
47
|
+
* Pure: applies the SAME comparator as the serial scan, so the result is
|
|
48
|
+
* byte-identical regardless of chunk completion order. Exported for unit tests.
|
|
49
|
+
*/
|
|
50
|
+
export function mergeChunkResults(results) {
|
|
51
|
+
const findings = [];
|
|
52
|
+
let filesScanned = 0;
|
|
53
|
+
for (const r of results) {
|
|
54
|
+
for (const f of r.findings)
|
|
55
|
+
findings.push(f);
|
|
56
|
+
filesScanned += r.filesScanned;
|
|
57
|
+
}
|
|
58
|
+
findings.sort(compareFindings);
|
|
59
|
+
return { findings, filesScanned };
|
|
60
|
+
}
|
|
61
|
+
/** Resolve the worker count (>= 1). */
|
|
62
|
+
function resolveConcurrency(options) {
|
|
63
|
+
const raw = options.concurrency;
|
|
64
|
+
if (typeof raw === "number" && raw >= 1)
|
|
65
|
+
return Math.floor(raw);
|
|
66
|
+
const avail = typeof os.availableParallelism === "function" ? os.availableParallelism() : os.cpus().length;
|
|
67
|
+
return Math.max(1, avail);
|
|
68
|
+
}
|
|
69
|
+
/** Decide whether the workload is large enough to justify spawning workers. */
|
|
70
|
+
function shouldParallelize(options, files) {
|
|
71
|
+
const totalBytes = files.reduce((acc, f) => acc + f.size, 0);
|
|
72
|
+
const byteFloor = options.parallelThresholdBytes ?? DEFAULT_PARALLEL_THRESHOLD_BYTES;
|
|
73
|
+
const fileFloor = options.parallelFileThreshold ?? DEFAULT_PARALLEL_FILE_THRESHOLD;
|
|
74
|
+
if (resolveConcurrency(options) <= 1)
|
|
75
|
+
return false;
|
|
76
|
+
return totalBytes >= byteFloor && files.length >= fileFloor;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Enumerate the files to scan (relative POSIX paths + sizes), honouring an
|
|
80
|
+
* explicit `files` list or the walker. Sizes power byte-balanced chunking.
|
|
81
|
+
*/
|
|
82
|
+
async function enumerateFiles(options, baseDir) {
|
|
83
|
+
const rels = [];
|
|
84
|
+
if (options.files) {
|
|
85
|
+
for (const f of options.files)
|
|
86
|
+
rels.push(toPosix(f));
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
for await (const rel of walkFiles(options.root, {
|
|
90
|
+
include: options.include,
|
|
91
|
+
exclude: options.exclude,
|
|
92
|
+
noDefaultIgnores: options.noDefaultIgnores,
|
|
93
|
+
maxFileSize: options.maxFileSize,
|
|
94
|
+
})) {
|
|
95
|
+
rels.push(rel);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const sized = [];
|
|
99
|
+
for (const rel of rels) {
|
|
100
|
+
let size = 0;
|
|
101
|
+
try {
|
|
102
|
+
size = (await stat(path.join(baseDir, ...rel.split("/")))).size;
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
// Unreadable now; keep with size 0 — worker read will skip if it's gone.
|
|
106
|
+
}
|
|
107
|
+
sized.push({ rel, size });
|
|
108
|
+
}
|
|
109
|
+
return sized;
|
|
110
|
+
}
|
|
111
|
+
/** Resolve the built worker entry (dist/scan-worker.js) next to this module. */
|
|
112
|
+
function workerEntryPath() {
|
|
113
|
+
// After build this file is dist/parallel.js and the worker is dist/scan-worker.js.
|
|
114
|
+
const here = fileURLToPath(import.meta.url);
|
|
115
|
+
return path.join(path.dirname(here), "scan-worker.js");
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Scan in parallel across a worker-thread pool, falling back to the in-process
|
|
119
|
+
* serial {@link scan} for small workloads or when workers can't be used. The
|
|
120
|
+
* result is deterministic and identical to the serial path.
|
|
121
|
+
*/
|
|
122
|
+
export async function scanParallel(options) {
|
|
123
|
+
const startedAt = new Date();
|
|
124
|
+
const rootStat = await stat(options.root);
|
|
125
|
+
const baseDir = rootStat.isFile() ? path.dirname(options.root) : options.root;
|
|
126
|
+
// Single-file roots and the override-detectors path always run serially:
|
|
127
|
+
// detectors may not be structured-cloneable across the worker boundary.
|
|
128
|
+
if (rootStat.isFile() || options.detectors) {
|
|
129
|
+
return scan(options);
|
|
130
|
+
}
|
|
131
|
+
const files = await enumerateFiles(options, baseDir);
|
|
132
|
+
if (!shouldParallelize(options, files)) {
|
|
133
|
+
// In-process: reuse the exact serial path over the same file list.
|
|
134
|
+
return scan({ ...options, files: files.map((f) => f.rel) });
|
|
135
|
+
}
|
|
136
|
+
let WorkerCtor;
|
|
137
|
+
try {
|
|
138
|
+
({ Worker: WorkerCtor } = await import("node:worker_threads"));
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return scan({ ...options, files: files.map((f) => f.rel) });
|
|
142
|
+
}
|
|
143
|
+
const chunks = chunkByBytes(files, options.chunkBytes ?? DEFAULT_CHUNK_BYTES);
|
|
144
|
+
const concurrency = Math.min(resolveConcurrency(options), chunks.length);
|
|
145
|
+
const entry = workerEntryPath();
|
|
146
|
+
const toggles = {
|
|
147
|
+
source: options.source !== false,
|
|
148
|
+
config: options.config !== false,
|
|
149
|
+
deps: options.dependencies !== false,
|
|
150
|
+
scanMinified: options.scanMinified === true,
|
|
151
|
+
};
|
|
152
|
+
let results;
|
|
153
|
+
try {
|
|
154
|
+
results = await runPool(WorkerCtor, entry, baseDir, toggles, chunks, concurrency, options.onFile);
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
// Any worker failure → safe fallback to the serial path.
|
|
158
|
+
return scan({ ...options, files: files.map((f) => f.rel) });
|
|
159
|
+
}
|
|
160
|
+
const merged = mergeChunkResults(results);
|
|
161
|
+
const inventory = buildInventory(merged.findings);
|
|
162
|
+
const finishedAt = new Date();
|
|
163
|
+
return {
|
|
164
|
+
root: options.root,
|
|
165
|
+
findings: merged.findings,
|
|
166
|
+
filesScanned: merged.filesScanned,
|
|
167
|
+
inventory,
|
|
168
|
+
startedAt: startedAt.toISOString(),
|
|
169
|
+
finishedAt: finishedAt.toISOString(),
|
|
170
|
+
toolVersion: VERSION,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
/** Worker-pool driver: at most `concurrency` workers, each pulls chunks. */
|
|
174
|
+
function runPool(WorkerCtor, entry, baseDir, toggles, chunks, concurrency, onFile) {
|
|
175
|
+
return new Promise((resolve, reject) => {
|
|
176
|
+
const results = new Array(chunks.length);
|
|
177
|
+
let next = 0;
|
|
178
|
+
let done = 0;
|
|
179
|
+
let failed = false;
|
|
180
|
+
const workers = [];
|
|
181
|
+
const cleanup = () => {
|
|
182
|
+
for (const w of workers)
|
|
183
|
+
void w.terminate();
|
|
184
|
+
};
|
|
185
|
+
const dispatch = (w) => {
|
|
186
|
+
if (failed)
|
|
187
|
+
return;
|
|
188
|
+
if (next >= chunks.length) {
|
|
189
|
+
void w.terminate();
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const idx = next++;
|
|
193
|
+
w.postMessage({ index: idx, files: chunks[idx].files });
|
|
194
|
+
};
|
|
195
|
+
const spawn = () => {
|
|
196
|
+
const w = new WorkerCtor(entry, { workerData: { baseDir, toggles } });
|
|
197
|
+
w.on("message", (msg) => {
|
|
198
|
+
if (msg.error) {
|
|
199
|
+
if (!failed) {
|
|
200
|
+
failed = true;
|
|
201
|
+
cleanup();
|
|
202
|
+
reject(new Error(msg.error));
|
|
203
|
+
}
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (msg.files && onFile)
|
|
207
|
+
for (const f of msg.files)
|
|
208
|
+
onFile(f);
|
|
209
|
+
if (msg.result) {
|
|
210
|
+
results[msg.index] = msg.result;
|
|
211
|
+
done++;
|
|
212
|
+
if (done === chunks.length) {
|
|
213
|
+
cleanup();
|
|
214
|
+
resolve(results);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
dispatch(w);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
w.on("error", (err) => {
|
|
221
|
+
if (!failed) {
|
|
222
|
+
failed = true;
|
|
223
|
+
cleanup();
|
|
224
|
+
reject(err);
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
return w;
|
|
228
|
+
};
|
|
229
|
+
const n = Math.max(1, Math.min(concurrency, chunks.length));
|
|
230
|
+
for (let i = 0; i < n; i++) {
|
|
231
|
+
const w = spawn();
|
|
232
|
+
workers.push(w);
|
|
233
|
+
dispatch(w);
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
//# sourceMappingURL=parallel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parallel.js","sourceRoot":"","sources":["../src/parallel.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detector registry — the plugin point for source/config detectors.
|
|
3
|
+
*
|
|
4
|
+
* Instead of `scan()` closing over a hardcoded array and inferring scope from
|
|
5
|
+
* ruleId prefixes, detectors are registered with a declared `scope` and
|
|
6
|
+
* `language` (see {@link Detector}). `scan()` consults a registry (the
|
|
7
|
+
* {@link defaultRegistry} by default, or an explicit `detectors` override) and
|
|
8
|
+
* honours the source/config toggles by each detector's declared scope.
|
|
9
|
+
*
|
|
10
|
+
* To add a language or detector, see the "Adding a detector / language" section
|
|
11
|
+
* of the package README.
|
|
12
|
+
*/
|
|
13
|
+
import type { Detector, DetectorScope } from "./types.js";
|
|
14
|
+
/** Normalised scope of a detector (defaults to "source" when undeclared). */
|
|
15
|
+
export declare function detectorScope(d: Detector): DetectorScope;
|
|
16
|
+
/**
|
|
17
|
+
* An ordered, id-indexed collection of detectors. Registration order is
|
|
18
|
+
* preserved by {@link all} for deterministic scan output. Ids must be unique.
|
|
19
|
+
*/
|
|
20
|
+
export declare class DetectorRegistry {
|
|
21
|
+
private readonly byId;
|
|
22
|
+
private readonly order;
|
|
23
|
+
/** Construct a registry, optionally seeded with an initial detector set. */
|
|
24
|
+
constructor(initial?: readonly Detector[]);
|
|
25
|
+
/** Register a detector. Throws on a duplicate id. Returns `this` for chaining. */
|
|
26
|
+
register(d: Detector): this;
|
|
27
|
+
/** Look up a detector by its id (exact, not prefix). */
|
|
28
|
+
get(id: string): Detector | undefined;
|
|
29
|
+
/** True if a detector with this id is registered. */
|
|
30
|
+
has(id: string): boolean;
|
|
31
|
+
/** All registered detectors, in registration order. */
|
|
32
|
+
all(): Detector[];
|
|
33
|
+
/** A shallow copy of this registry (useful to extend the defaults). */
|
|
34
|
+
clone(): DetectorRegistry;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The default registry, preloaded with the built-in detectors in run order:
|
|
38
|
+
* the JS/TS source + config detectors, then the language-agnostic PEM detector.
|
|
39
|
+
* The manifest (dependency) scanner is handled separately by `scan()`.
|
|
40
|
+
*/
|
|
41
|
+
export declare const defaultRegistry: DetectorRegistry;
|
|
42
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAI1D,6EAA6E;AAC7E,wBAAgB,aAAa,CAAC,CAAC,EAAE,QAAQ,GAAG,aAAa,CAExD;AAED;;;GAGG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA+B;IACpD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgB;IAEtC,4EAA4E;gBAChE,OAAO,GAAE,SAAS,QAAQ,EAAO;IAI7C,kFAAkF;IAClF,QAAQ,CAAC,CAAC,EAAE,QAAQ,GAAG,IAAI;IAS3B,wDAAwD;IACxD,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS;IAIrC,qDAAqD;IACrD,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAIxB,uDAAuD;IACvD,GAAG,IAAI,QAAQ,EAAE;IAIjB,uEAAuE;IACvE,KAAK,IAAI,gBAAgB;CAG1B;AAED;;;;GAIG;AACH,eAAO,MAAM,eAAe,kBAA0D,CAAC"}
|
package/dist/registry.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { sourceDetectors } from "./detectors/source.js";
|
|
2
|
+
import { pemDetector } from "./detectors/pem.js";
|
|
3
|
+
/** Normalised scope of a detector (defaults to "source" when undeclared). */
|
|
4
|
+
export function detectorScope(d) {
|
|
5
|
+
return d.scope ?? "source";
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* An ordered, id-indexed collection of detectors. Registration order is
|
|
9
|
+
* preserved by {@link all} for deterministic scan output. Ids must be unique.
|
|
10
|
+
*/
|
|
11
|
+
export class DetectorRegistry {
|
|
12
|
+
byId = new Map();
|
|
13
|
+
order = [];
|
|
14
|
+
/** Construct a registry, optionally seeded with an initial detector set. */
|
|
15
|
+
constructor(initial = []) {
|
|
16
|
+
for (const d of initial)
|
|
17
|
+
this.register(d);
|
|
18
|
+
}
|
|
19
|
+
/** Register a detector. Throws on a duplicate id. Returns `this` for chaining. */
|
|
20
|
+
register(d) {
|
|
21
|
+
if (this.byId.has(d.id)) {
|
|
22
|
+
throw new Error(`duplicate detector id: ${d.id}`);
|
|
23
|
+
}
|
|
24
|
+
this.byId.set(d.id, d);
|
|
25
|
+
this.order.push(d.id);
|
|
26
|
+
return this;
|
|
27
|
+
}
|
|
28
|
+
/** Look up a detector by its id (exact, not prefix). */
|
|
29
|
+
get(id) {
|
|
30
|
+
return this.byId.get(id);
|
|
31
|
+
}
|
|
32
|
+
/** True if a detector with this id is registered. */
|
|
33
|
+
has(id) {
|
|
34
|
+
return this.byId.has(id);
|
|
35
|
+
}
|
|
36
|
+
/** All registered detectors, in registration order. */
|
|
37
|
+
all() {
|
|
38
|
+
return this.order.map((id) => this.byId.get(id));
|
|
39
|
+
}
|
|
40
|
+
/** A shallow copy of this registry (useful to extend the defaults). */
|
|
41
|
+
clone() {
|
|
42
|
+
return new DetectorRegistry(this.all());
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The default registry, preloaded with the built-in detectors in run order:
|
|
47
|
+
* the JS/TS source + config detectors, then the language-agnostic PEM detector.
|
|
48
|
+
* The manifest (dependency) scanner is handled separately by `scan()`.
|
|
49
|
+
*/
|
|
50
|
+
export const defaultRegistry = new DetectorRegistry([...sourceDetectors, pemDetector]);
|
|
51
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD,6EAA6E;AAC7E,MAAM,UAAU,aAAa,CAAC,CAAW;IACvC,OAAO,CAAC,CAAC,KAAK,IAAI,QAAQ,CAAC;AAC7B,CAAC;AAED;;;GAGG;AACH,MAAM,OAAO,gBAAgB;IACV,IAAI,GAAG,IAAI,GAAG,EAAoB,CAAC;IACnC,KAAK,GAAa,EAAE,CAAC;IAEtC,4EAA4E;IAC5E,YAAY,UAA+B,EAAE;QAC3C,KAAK,MAAM,CAAC,IAAI,OAAO;YAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC5C,CAAC;IAED,kFAAkF;IAClF,QAAQ,CAAC,CAAW;QAClB,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACpD,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;QACvB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACtB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,wDAAwD;IACxD,GAAG,CAAC,EAAU;QACZ,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC3B,CAAC;IAED,qDAAqD;IACrD,GAAG,CAAC,EAAU;QACZ,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC3B,CAAC;IAED,uDAAuD;IACvD,GAAG;QACD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAE,CAAC,CAAC;IACpD,CAAC;IAED,uEAAuE;IACvE,KAAK;QACH,OAAO,IAAI,gBAAgB,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IAC1C,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,gBAAgB,CAAC,CAAC,GAAG,eAAe,EAAE,WAAW,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
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).
|
|
6
|
+
*/
|
|
7
|
+
import type { AlgorithmFamily, Remediation } from "./types.js";
|
|
8
|
+
/** Look up the recommended post-quantum remediation for a classical algorithm. */
|
|
9
|
+
export declare function remediationFor(algorithm: AlgorithmFamily): Remediation | undefined;
|
|
10
|
+
/** Convenience: just the short recommendation string for a family (always defined). */
|
|
11
|
+
export declare function remediationText(algorithm: AlgorithmFamily): string;
|
|
12
|
+
/**
|
|
13
|
+
* Security tier for remediation guidance.
|
|
14
|
+
* - `"category-3"` (default): commercial use — ML-KEM-768 / ML-DSA-65.
|
|
15
|
+
* - `"category-5"`: CNSA 2.0 national-security-systems / long-lived secrets —
|
|
16
|
+
* ML-KEM-1024 / ML-DSA-87.
|
|
17
|
+
*/
|
|
18
|
+
export type SecurityTier = "category-3" | "category-5";
|
|
19
|
+
/** Per-tier KEM / signature parameter sets. */
|
|
20
|
+
export declare const TIER_PARAMS: Record<SecurityTier, {
|
|
21
|
+
kem: string;
|
|
22
|
+
signature: string;
|
|
23
|
+
note: string;
|
|
24
|
+
}>;
|
|
25
|
+
/**
|
|
26
|
+
* Tier-aware remediation. Returns the base family remediation plus the
|
|
27
|
+
* parameter sets for the requested CNSA tier. Category 5 surfaces the
|
|
28
|
+
* ML-KEM-1024 / ML-DSA-87 sets mandated by CNSA 2.0; category 3 is the
|
|
29
|
+
* commercial default.
|
|
30
|
+
*/
|
|
31
|
+
export declare function remediationForTier(algorithm: AlgorithmFamily, tier?: SecurityTier): Remediation;
|
|
32
|
+
/**
|
|
33
|
+
* Guidance for stateful hash-based signatures (SP 800-208: LMS / XMSS / HSS).
|
|
34
|
+
* These are NIST-approved for firmware / boot signing but are STATEFUL — each
|
|
35
|
+
* private key may sign a bounded number of messages and the state MUST be
|
|
36
|
+
* managed to avoid catastrophic key reuse. Surfaced where a long-lived,
|
|
37
|
+
* low-volume signing root is appropriate.
|
|
38
|
+
*/
|
|
39
|
+
export declare const STATEFUL_HBS_NOTE: string;
|
|
40
|
+
/** True when stateful HBS (SP 800-208) is a reasonable alternative for a family. */
|
|
41
|
+
export declare function statefulHbsApplies(algorithm: AlgorithmFamily): boolean;
|
|
42
|
+
//# sourceMappingURL=remediation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remediation.d.ts","sourceRoot":"","sources":["../src/remediation.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAkF/D,kFAAkF;AAClF,wBAAgB,cAAc,CAAC,SAAS,EAAE,eAAe,GAAG,WAAW,GAAG,SAAS,CAElF;AAED,uFAAuF;AACvF,wBAAgB,eAAe,CAAC,SAAS,EAAE,eAAe,GAAG,MAAM,CAElE;AAMD;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,YAAY,GAAG,YAAY,CAAC;AAEvD,+CAA+C;AAC/C,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,YAAY,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAW9F,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,eAAe,EAC1B,IAAI,GAAE,YAA2B,GAChC,WAAW,CAiBb;AAED;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,QAKO,CAAC;AAEtC,oFAAoF;AACpF,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,eAAe,GAAG,OAAO,CAKtE"}
|