@quantakrypto/qscan 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/dist/index.js ADDED
@@ -0,0 +1,133 @@
1
+ /**
2
+ * @quantakrypto/qscan — programmatic API.
3
+ *
4
+ * `runQscan` is the single entry point shared by the CLI (`src/cli.ts`) and by
5
+ * `@quantakrypto/action`. It runs a scan via `@quantakrypto/core`, applies an optional
6
+ * baseline, decides an exit code from the severity threshold, and (optionally)
7
+ * renders a report. The CLI is a thin shell around it.
8
+ *
9
+ * The module also re-exports the argument-parsing and baseline helpers so
10
+ * downstream tools can reuse them without reaching into internal paths.
11
+ */
12
+ import { changedFiles, scan, scanParallel } from "@quantakrypto/core";
13
+ import { applyBaseline, loadBaseline, saveBaseline } from "./baseline.js";
14
+ import { defaultOptions, meetsThreshold } from "./args.js";
15
+ import { renderCbom, renderHuman, renderJson, renderSarif } from "./report.js";
16
+ export { ArgError, asFormat, asInt, asSeverity, defaultOptions, meetsThreshold, parseArgs, severityRank, SEVERITY_ORDER, } from "./args.js";
17
+ export { applyBaseline, baselineFromFindings, BASELINE_VERSION, buildBaseline, fingerprint, fingerprintFinding, loadBaseline, readBaseline, saveBaseline, writeBaseline, } from "./baseline.js";
18
+ export { renderCbom, renderHuman, renderJson, renderSarif } from "./report.js";
19
+ export { HELP_TEXT, versionLine } from "./help.js";
20
+ export { applyConfig, resolveConfig } from "./config.js";
21
+ /** Process-style exit codes qScan uses. */
22
+ export const EXIT = {
23
+ /** No findings at/above threshold, or a baseline was written. */
24
+ OK: 0,
25
+ /** One or more findings at/above the severity threshold. */
26
+ FINDINGS: 1,
27
+ /** Usage error or I/O failure. */
28
+ ERROR: 2,
29
+ };
30
+ /**
31
+ * Translate resolved {@link QscanOptions} into core {@link ParallelScanOptions}.
32
+ * `files` (the incremental file list) is layered on by {@link runQscan}.
33
+ */
34
+ function toScanOptions(options) {
35
+ const scanOptions = {
36
+ root: options.path,
37
+ source: options.source,
38
+ dependencies: options.dependencies,
39
+ config: options.config,
40
+ noDefaultIgnores: options.noDefaultIgnores,
41
+ scanMinified: options.scanMinified,
42
+ };
43
+ if (options.ignore.length > 0)
44
+ scanOptions.exclude = options.ignore;
45
+ if (options.include.length > 0)
46
+ scanOptions.include = options.include;
47
+ if (options.maxFileSize !== undefined)
48
+ scanOptions.maxFileSize = options.maxFileSize;
49
+ if (options.concurrency !== undefined)
50
+ scanOptions.concurrency = options.concurrency;
51
+ return scanOptions;
52
+ }
53
+ /**
54
+ * Run a complete qScan pass: scan → baseline → threshold → render.
55
+ *
56
+ * This never touches `process` or stdout; the CLI is responsible for printing
57
+ * `report`/writing `output` and calling `process.exit(exitCode)`. That keeps
58
+ * the function pure enough to unit-test and to embed in the GitHub Action.
59
+ *
60
+ * Behavior:
61
+ * - The walk is configured by `include` / `ignore` / `maxFileSize` /
62
+ * `noDefaultIgnores` / `scanMinified`.
63
+ * - With `changed` set, only the files git reports as changed (relative to
64
+ * `since`, if given) are scanned via `ScanOptions.files`. A non-git tree
65
+ * yields an empty list, so nothing is scanned.
66
+ * - With `parallel` (or `concurrency`) set, the scan is routed through core's
67
+ * `scanParallel`, which itself falls back to the serial path for small
68
+ * inputs.
69
+ * - When `opts.writeBaseline` is set, the scan runs, a baseline is built from
70
+ * *all* findings, written to disk, and `exitCode` is {@link EXIT.OK}. No
71
+ * report is rendered.
72
+ * - When `opts.baseline` is set, its fingerprints are loaded and matching
73
+ * findings are moved to `suppressed` (and removed from `result.findings`).
74
+ * - `exitCode` is {@link EXIT.FINDINGS} when any *kept* finding meets the
75
+ * severity threshold, else {@link EXIT.OK}.
76
+ *
77
+ * @throws {Error} Propagates scan / baseline I/O errors; the CLI maps these to
78
+ * {@link EXIT.ERROR}.
79
+ */
80
+ export async function runQscan(opts, hooks = {}) {
81
+ const options = { ...defaultOptions(), ...opts };
82
+ // Route to the parallel pool when requested; both share the ScanOptions shape.
83
+ const scanFn = hooks.scanFn ?? (options.parallel ? scanParallel : scan);
84
+ const resolveChanged = hooks.changedFilesFn ?? changedFiles;
85
+ const scanOptions = toScanOptions(options);
86
+ // Incremental mode: restrict the scan to git-changed files.
87
+ if (options.changed) {
88
+ scanOptions.files = await resolveChanged(options.path, options.since);
89
+ }
90
+ const result = await scanFn(scanOptions);
91
+ // --write-baseline: snapshot every finding, persist, and exit cleanly.
92
+ if (options.writeBaseline) {
93
+ const baseline = await saveBaseline(options.writeBaseline, result.findings);
94
+ return {
95
+ result,
96
+ suppressed: [],
97
+ baselineWritten: baseline,
98
+ exitCode: EXIT.OK,
99
+ };
100
+ }
101
+ // --baseline: suppress previously-accepted findings.
102
+ let suppressed = [];
103
+ if (options.baseline) {
104
+ const baseline = await loadBaseline(options.baseline);
105
+ const split = applyBaseline(result.findings, baseline);
106
+ result.findings = split.kept;
107
+ suppressed = split.suppressed;
108
+ }
109
+ const exitCode = result.findings.some((f) => meetsThreshold(f.severity, options.severityThreshold))
110
+ ? EXIT.FINDINGS
111
+ : EXIT.OK;
112
+ return {
113
+ result,
114
+ suppressed,
115
+ report: renderReport(result, options.format, hooks.color ?? false),
116
+ exitCode,
117
+ };
118
+ }
119
+ /** Render a scan result in the requested format. */
120
+ export function renderReport(result, format, color = false) {
121
+ switch (format) {
122
+ case "json":
123
+ return renderJson(result);
124
+ case "sarif":
125
+ return renderSarif(result);
126
+ case "cbom":
127
+ return renderCbom(result);
128
+ case "human":
129
+ default:
130
+ return renderHuman(result, { color });
131
+ }
132
+ }
133
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * qScan report rendering.
3
+ *
4
+ * Produces the three output formats the CLI supports:
5
+ * - `human` — a tasteful plain-text banner (counts, top findings, readiness
6
+ * score, and a one-line next step). Optional raw ANSI color.
7
+ * - `json` — the structured scan result via core's `toJson`.
8
+ * - `sarif` — SARIF 2.1.0 via core's `toSarif`.
9
+ *
10
+ * Only `human` lives here; `json`/`sarif` delegate to `@quantakrypto/core` so the
11
+ * serialized shape stays consistent across every tool in the monorepo.
12
+ */
13
+ import type { ScanResult } from "@quantakrypto/core";
14
+ /**
15
+ * Render the JSON report (pretty-printed, no trailing newline).
16
+ *
17
+ * Delegates to core's `toJson` for a monorepo-consistent shape. If the running
18
+ * core build has not implemented the serializer yet, falls back to emitting the
19
+ * `ScanResult` directly (which is already JSON-friendly) so reports never break.
20
+ */
21
+ export declare function renderJson(result: ScanResult): string;
22
+ /**
23
+ * Render the SARIF 2.1.0 report (pretty-printed, no trailing newline).
24
+ *
25
+ * Delegates to core's `toSarif`, with a minimal SARIF 2.1.0 fallback so the
26
+ * format always produces valid, ingestible output.
27
+ */
28
+ export declare function renderSarif(result: ScanResult): string;
29
+ /**
30
+ * Render a CycloneDX 1.6 CBOM (cryptographic bill of materials) for the scan,
31
+ * pretty-printed with no trailing newline. Delegates to core's `toCbom` so the
32
+ * serialized shape stays consistent across every tool in the monorepo.
33
+ */
34
+ export declare function renderCbom(result: ScanResult): string;
35
+ /**
36
+ * Render the human-readable banner.
37
+ *
38
+ * @param result The scan result.
39
+ * @param opts.color Emit raw ANSI escapes (default: false / plain text).
40
+ * @param opts.topN How many findings to list (default: 5).
41
+ */
42
+ export declare function renderHuman(result: ScanResult, opts?: {
43
+ color?: boolean;
44
+ topN?: number;
45
+ }): string;
46
+ //# sourceMappingURL=report.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAW,UAAU,EAAY,MAAM,oBAAoB,CAAC;AAyBxE;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAMrD;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAMtD;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAErD;AAmED;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,UAAU,EAClB,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,GAC5C,MAAM,CAyDR"}
package/dist/report.js ADDED
@@ -0,0 +1,204 @@
1
+ /**
2
+ * qScan report rendering.
3
+ *
4
+ * Produces the three output formats the CLI supports:
5
+ * - `human` — a tasteful plain-text banner (counts, top findings, readiness
6
+ * score, and a one-line next step). Optional raw ANSI color.
7
+ * - `json` — the structured scan result via core's `toJson`.
8
+ * - `sarif` — SARIF 2.1.0 via core's `toSarif`.
9
+ *
10
+ * Only `human` lives here; `json`/`sarif` delegate to `@quantakrypto/core` so the
11
+ * serialized shape stays consistent across every tool in the monorepo.
12
+ */
13
+ import { toCbom, toJson, toSarif } from "@quantakrypto/core";
14
+ import { SEVERITY_ORDER, severityRank } from "./args.js";
15
+ const PLAIN = { reset: "", bold: "", dim: "", red: "", yellow: "", green: "", cyan: "" };
16
+ const COLOR = {
17
+ reset: "\x1b[0m",
18
+ bold: "\x1b[1m",
19
+ dim: "\x1b[2m",
20
+ red: "\x1b[31m",
21
+ yellow: "\x1b[33m",
22
+ green: "\x1b[32m",
23
+ cyan: "\x1b[36m",
24
+ };
25
+ /**
26
+ * Render the JSON report (pretty-printed, no trailing newline).
27
+ *
28
+ * Delegates to core's `toJson` for a monorepo-consistent shape. If the running
29
+ * core build has not implemented the serializer yet, falls back to emitting the
30
+ * `ScanResult` directly (which is already JSON-friendly) so reports never break.
31
+ */
32
+ export function renderJson(result) {
33
+ return JSON.stringify(serialize(() => toJson(result), result), null, 2);
34
+ }
35
+ /**
36
+ * Render the SARIF 2.1.0 report (pretty-printed, no trailing newline).
37
+ *
38
+ * Delegates to core's `toSarif`, with a minimal SARIF 2.1.0 fallback so the
39
+ * format always produces valid, ingestible output.
40
+ */
41
+ export function renderSarif(result) {
42
+ return JSON.stringify(serialize(() => toSarif(result), fallbackSarif(result)), null, 2);
43
+ }
44
+ /**
45
+ * Render a CycloneDX 1.6 CBOM (cryptographic bill of materials) for the scan,
46
+ * pretty-printed with no trailing newline. Delegates to core's `toCbom` so the
47
+ * serialized shape stays consistent across every tool in the monorepo.
48
+ */
49
+ export function renderCbom(result) {
50
+ return JSON.stringify(toCbom(result), null, 2);
51
+ }
52
+ /**
53
+ * Call a core serializer, falling back when it is an unimplemented stub.
54
+ * Only the documented "not implemented" stub error is swallowed; real errors
55
+ * (e.g. a bug in a future core) propagate. The two branches may have different
56
+ * shapes — both are ultimately handed to `JSON.stringify`.
57
+ */
58
+ function serialize(fn, fallback) {
59
+ try {
60
+ return fn();
61
+ }
62
+ catch (err) {
63
+ const message = err instanceof Error ? err.message : String(err);
64
+ if (message.includes("not implemented"))
65
+ return fallback;
66
+ throw err;
67
+ }
68
+ }
69
+ /** A minimal, valid SARIF 2.1.0 log used when core's `toSarif` is unavailable. */
70
+ function fallbackSarif(result) {
71
+ return {
72
+ $schema: "https://json.schemastore.org/sarif-2.1.0.json",
73
+ version: "2.1.0",
74
+ runs: [
75
+ {
76
+ tool: {
77
+ driver: {
78
+ name: "qscan",
79
+ informationUri: "https://github.com/qproof/qproof-tools",
80
+ version: result.toolVersion,
81
+ rules: [],
82
+ },
83
+ },
84
+ results: result.findings.map((f) => ({
85
+ ruleId: f.ruleId,
86
+ level: sarifLevel(f.severity),
87
+ message: { text: f.message },
88
+ locations: [
89
+ {
90
+ physicalLocation: {
91
+ artifactLocation: { uri: f.location.file },
92
+ region: {
93
+ startLine: f.location.line,
94
+ ...(f.location.column ? { startColumn: f.location.column } : {}),
95
+ },
96
+ },
97
+ },
98
+ ],
99
+ })),
100
+ },
101
+ ],
102
+ };
103
+ }
104
+ /** Map a qScan severity to a SARIF level. */
105
+ function sarifLevel(severity) {
106
+ switch (severity) {
107
+ case "critical":
108
+ case "high":
109
+ return "error";
110
+ case "medium":
111
+ return "warning";
112
+ default:
113
+ return "note";
114
+ }
115
+ }
116
+ /**
117
+ * Render the human-readable banner.
118
+ *
119
+ * @param result The scan result.
120
+ * @param opts.color Emit raw ANSI escapes (default: false / plain text).
121
+ * @param opts.topN How many findings to list (default: 5).
122
+ */
123
+ export function renderHuman(result, opts = {}) {
124
+ const c = opts.color ? COLOR : PLAIN;
125
+ const topN = opts.topN ?? 5;
126
+ const { findings, inventory, filesScanned } = result;
127
+ const lines = [];
128
+ lines.push(`${c.bold}qScan — quantum-vulnerable cryptography report${c.reset}`);
129
+ lines.push(`${c.dim}root: ${result.root} • files scanned: ${filesScanned} • qscan v${result.toolVersion}${c.reset}`);
130
+ lines.push("");
131
+ if (findings.length === 0) {
132
+ lines.push(`${c.green}No quantum-vulnerable cryptography detected.${c.reset}`);
133
+ lines.push(`${c.bold}Readiness score: ${readiness(inventory.readinessScore, c)}/100${c.reset}`);
134
+ lines.push("");
135
+ lines.push(`${c.dim}Next step:${c.reset} keep scanning in CI to catch regressions.`);
136
+ return lines.join("\n");
137
+ }
138
+ // Severity counts, most-severe first.
139
+ const counts = SEVERITY_ORDER.map((sev) => {
140
+ const n = inventory.bySeverity[sev] ?? 0;
141
+ return n > 0 ? `${severityColor(sev, c)}${n} ${sev}${c.reset}` : null;
142
+ }).filter((s) => s !== null);
143
+ lines.push(`${c.bold}${findings.length} finding${findings.length === 1 ? "" : "s"}${c.reset} (${counts.join(", ")})`);
144
+ if (inventory.hndlCount > 0) {
145
+ lines.push(`${c.yellow}${inventory.hndlCount}${c.reset} exposed to harvest-now-decrypt-later (HNDL).`);
146
+ }
147
+ lines.push(`${c.bold}Readiness score: ${readiness(inventory.readinessScore, c)}/100${c.reset}`);
148
+ lines.push("");
149
+ // Top findings, sorted by severity then file/line for determinism.
150
+ const top = [...findings].sort(compareFindings).slice(0, topN);
151
+ lines.push(`${c.bold}Top findings${c.reset}`);
152
+ for (const f of top) {
153
+ const loc = `${f.location.file}:${f.location.line}`;
154
+ lines.push(` ${severityColor(f.severity, c)}${f.severity.padEnd(8)}${c.reset} ${c.cyan}${f.ruleId}${c.reset} ${loc}`);
155
+ lines.push(` ${f.message}`);
156
+ if (f.remediation) {
157
+ lines.push(` ${c.dim}→ ${f.remediation}${c.reset}`);
158
+ }
159
+ }
160
+ if (findings.length > top.length) {
161
+ lines.push(` ${c.dim}…and ${findings.length - top.length} more${c.reset}`);
162
+ }
163
+ lines.push("");
164
+ lines.push(`${c.dim}Next step:${c.reset} ${nextStep(findings)}`);
165
+ return lines.join("\n");
166
+ }
167
+ /** Suggest a single concrete next action based on the worst finding. */
168
+ function nextStep(findings) {
169
+ const worst = [...findings].sort(compareFindings)[0];
170
+ if (!worst)
171
+ return "review the findings above.";
172
+ if (worst.remediation) {
173
+ return `migrate ${worst.location.file} — ${worst.remediation}`;
174
+ }
175
+ return `triage ${worst.ruleId} in ${worst.location.file}:${worst.location.line}.`;
176
+ }
177
+ /** Deterministic ordering: most severe first, then file, then line. */
178
+ function compareFindings(a, b) {
179
+ const bySev = severityRank(a.severity) - severityRank(b.severity);
180
+ if (bySev !== 0)
181
+ return bySev;
182
+ const byFile = a.location.file.localeCompare(b.location.file);
183
+ if (byFile !== 0)
184
+ return byFile;
185
+ return a.location.line - b.location.line;
186
+ }
187
+ /** Color the readiness score green/yellow/red by band. */
188
+ function readiness(score, c) {
189
+ const color = score >= 80 ? c.green : score >= 50 ? c.yellow : c.red;
190
+ return `${color}${score}${c.reset}`;
191
+ }
192
+ /** Map a severity to its palette color. */
193
+ function severityColor(severity, c) {
194
+ switch (severity) {
195
+ case "critical":
196
+ case "high":
197
+ return c.red;
198
+ case "medium":
199
+ return c.yellow;
200
+ default:
201
+ return c.dim;
202
+ }
203
+ }
204
+ //# sourceMappingURL=report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.js","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@quantakrypto/qscan",
3
+ "version": "0.1.0",
4
+ "description": "qScan — find quantum-vulnerable cryptography in any codebase (CLI). Zero runtime dependencies.",
5
+ "license": "Apache-2.0",
6
+ "author": "Dandelion Labs <hello@dandelionlabs.io> (https://dandelionlabs.io)",
7
+ "homepage": "https://github.com/dandelionlabs-io/qproof-tools/tree/main/packages/qscan#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/dandelionlabs-io/qproof-tools.git",
11
+ "directory": "packages/qscan"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/dandelionlabs-io/qproof-tools/issues"
15
+ },
16
+ "type": "module",
17
+ "bin": {
18
+ "qscan": "./dist/cli.js"
19
+ },
20
+ "main": "./dist/index.js",
21
+ "types": "./dist/index.d.ts",
22
+ "exports": {
23
+ ".": {
24
+ "types": "./dist/index.d.ts",
25
+ "default": "./dist/index.js"
26
+ }
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "README.md"
31
+ ],
32
+ "engines": {
33
+ "node": ">=20"
34
+ },
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
38
+ "dependencies": {
39
+ "@quantakrypto/core": "0.1.0"
40
+ },
41
+ "scripts": {
42
+ "build": "tsc -b",
43
+ "test": "node --import tsx --test test/*.test.ts"
44
+ }
45
+ }