@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/README.md ADDED
@@ -0,0 +1,251 @@
1
+ # @quantakrypto/qscan
2
+
3
+ **Find quantum-vulnerable cryptography in any codebase.**
4
+
5
+ `qscan` walks a project and reports where it relies on classical asymmetric
6
+ cryptography (RSA, (EC)DH, ECDSA, EdDSA, DSA, …) — the algorithms broken by a
7
+ sufficiently large quantum computer, and the ones exposed to *harvest-now,
8
+ decrypt-later* (HNDL) attacks today. It scans source files, dependency
9
+ manifests, and configuration, then prints a readiness score and a concrete next
10
+ step.
11
+
12
+ - **Zero runtime dependencies.** Node built-ins only; the engine is
13
+ [`@quantakrypto/core`](../core).
14
+ - **CI-friendly.** Severity thresholds drive the exit code; baselines suppress
15
+ known findings so the build only fails on *new* problems.
16
+ - **Multiple formats.** `human` (default), `json`, SARIF 2.1.0 for code-scanning
17
+ dashboards, and a CycloneDX 1.6 **CBOM** for compliance tooling.
18
+ - **Fast on big repos.** Optional worker-thread parallelism (`--parallel`) and
19
+ git-aware incremental scanning (`--changed`).
20
+
21
+ ## Install
22
+
23
+ ```bash
24
+ npm install -g @quantakrypto/qscan
25
+ # or run without installing
26
+ npx @quantakrypto/qscan .
27
+ ```
28
+
29
+ Requires Node ≥ 20.
30
+
31
+ ## Usage
32
+
33
+ ```bash
34
+ qscan [path] [options]
35
+ ```
36
+
37
+ `path` defaults to the current directory (`.`).
38
+
39
+ ### Options
40
+
41
+ | Flag | Description | Default |
42
+ | --- | --- | --- |
43
+ | `--format <human\|json\|sarif\|cbom>` | Output format. | `human` |
44
+ | `--cbom` | Alias for `--format cbom` (CycloneDX 1.6 CBOM). | — |
45
+ | `-o, --output <file>` | Write the report to a file instead of stdout. | stdout |
46
+ | `--severity-threshold <level>` | Exit 1 if any finding is at/above this level. One of `critical`, `high`, `medium`, `low`, `info`. | `high` |
47
+ | `--no-source` | Skip scanning source files for inline crypto. | scan on |
48
+ | `--no-deps` | Skip scanning dependency manifests. | scan on |
49
+ | `--no-config` | Skip scanning config files (TLS/certificates). Toggles config *detectors* — not the config file below. | scan on |
50
+ | `--config <path>` | Use this `quantakrypto.config.json` instead of auto-discovering one at the scan root. | auto |
51
+ | `--no-config-file` | Disable `quantakrypto.config.json` auto-discovery. | discovery on |
52
+ | `--ignore <pattern>` | Exclude paths matching `<pattern>`. Repeatable. | — |
53
+ | `--include <pattern>` | Restrict the scan to paths matching `<pattern>`. Repeatable. | all files |
54
+ | `--max-file-size <bytes>` | Skip files larger than `<bytes>`. | 2 MiB |
55
+ | `--no-default-ignores` | Don't skip `node_modules`/`.git`/`dist` by default. | ignores on |
56
+ | `--scan-minified` | Scan minified/generated/bundled files too. | skipped |
57
+ | `--changed` | Incremental: scan only files git reports as changed. | off |
58
+ | `--since <git-ref>` | With `--changed`, diff against `<git-ref>` (implies `--changed`). | working tree |
59
+ | `--parallel` | Scan using a worker-thread pool when the workload is large enough. | off |
60
+ | `--concurrency <n>` | Worker count for `--parallel` (implies `--parallel`). `0`/`1` forces serial. | CPU count |
61
+ | `--baseline <file>` | Suppress findings whose fingerprint is in the baseline file. | — |
62
+ | `--write-baseline <file>` | Write current findings as a baseline, then exit 0. | — |
63
+ | `--quiet` | Suppress the human summary banner. | off |
64
+ | `-v, --version` | Print version and exit. | — |
65
+ | `-h, --help` | Print help and exit. | — |
66
+
67
+ ### Exit codes
68
+
69
+ | Code | Meaning |
70
+ | --- | --- |
71
+ | `0` | No findings at/above the threshold — or a baseline was written. |
72
+ | `1` | One or more findings at/above the severity threshold. |
73
+ | `2` | Usage error or I/O failure. |
74
+
75
+ ### Config file (`quantakrypto.config.json`)
76
+
77
+ qScan auto-discovers a `quantakrypto.config.json` at the scan root and applies it
78
+ under a strict precedence: **CLI flags > config file > built-in defaults**
79
+ (per-key). It encodes `include` / `exclude` / `maxFileSize` / `noDefaultIgnores`
80
+ / `scanMinified`, the detector-family toggles, `severityThreshold`, and a
81
+ `baseline` path so CI and local runs share one policy. Point at a file elsewhere
82
+ with `--config <path>`, or disable discovery with `--no-config-file`. See
83
+ [docs/CONFIG.md](../../docs/CONFIG.md) for the full schema.
84
+
85
+ ## Example output
86
+
87
+ ```
88
+ qScan — quantum-vulnerable cryptography report
89
+ root: ./examples/vulnerable-app • files scanned: 2 • qscan v0.1.0
90
+
91
+ 3 findings (2 high, 1 medium)
92
+ 2 exposed to harvest-now-decrypt-later (HNDL).
93
+ Readiness score: 70/100
94
+
95
+ Top findings
96
+ high rsa-keygen src/crypto.js:5
97
+ RSA is not quantum-safe.
98
+ → Use ML-KEM-768 (hybrid X25519MLKEM768).
99
+ high dep-vulnerable package.json:7
100
+ Dependency "node-forge" provides classical asymmetric crypto.
101
+ medium ecdh-usage src/crypto.js:13
102
+ ECDH is not quantum-safe.
103
+ → Use a hybrid KEM (X25519MLKEM768).
104
+
105
+ Next step: migrate src/crypto.js — Use ML-KEM-768 (hybrid X25519MLKEM768).
106
+ ```
107
+
108
+ Color is emitted only when writing the human format to an interactive terminal
109
+ (and is suppressed by the `NO_COLOR` environment variable). Reports written to a
110
+ file or piped are always plain text.
111
+
112
+ ## Baselines
113
+
114
+ A baseline records the **fingerprints** of findings you have already triaged.
115
+ qScan uses the **canonical baseline** shared across the whole monorepo
116
+ (`@quantakrypto/core`, the GitHub Action, and this CLI): a single on-disk format and a
117
+ single fingerprint algorithm, so a baseline written by one tool is understood by
118
+ the others.
119
+
120
+ A fingerprint is a full SHA-256 of `ruleId|file|normalizedSnippet`. It is
121
+ deliberately **line-insensitive** — unrelated edits that shift line numbers no
122
+ longer invalidate it — and the snippet's whitespace is normalized so
123
+ reformatting doesn't either. It ignores volatile fields like severity wording or
124
+ timestamps.
125
+
126
+ ```bash
127
+ # 1. Accept the current state of the world.
128
+ qscan . --write-baseline qscan-baseline.json
129
+
130
+ # 2. From now on, fail only on findings that are NOT in the baseline.
131
+ qscan . --baseline qscan-baseline.json
132
+ ```
133
+
134
+ The baseline file is plain JSON:
135
+
136
+ ```json
137
+ {
138
+ "version": 1,
139
+ "fingerprints": [
140
+ "0f1e2d3c4b5a...full-sha256...",
141
+ "a1b2c3d4e5f6...full-sha256..."
142
+ ]
143
+ }
144
+ ```
145
+
146
+ ## Incremental scans
147
+
148
+ In CI you usually only care about what a change introduced. `--changed` restricts
149
+ the scan to the files git reports as modified, which is a large win on big repos:
150
+
151
+ ```bash
152
+ # Files modified in the working tree (staged, unstaged, and untracked).
153
+ qscan . --changed
154
+
155
+ # Everything that changed since a base ref (e.g. a PR base).
156
+ qscan . --changed --since origin/main
157
+ ```
158
+
159
+ Outside a git work tree the changed-file list is empty (nothing is scanned),
160
+ rather than an error.
161
+
162
+ ## Parallel scans
163
+
164
+ For large trees, route the scan through a worker-thread pool. qScan automatically
165
+ stays serial for small inputs, so `--parallel` is safe to leave on:
166
+
167
+ ```bash
168
+ qscan . --parallel # auto worker count (CPU cores)
169
+ qscan . --concurrency 4 # pin the worker count (implies --parallel)
170
+ ```
171
+
172
+ ## CBOM (CycloneDX)
173
+
174
+ Emit a CycloneDX 1.6 **cryptographic bill of materials** — one
175
+ `cryptographic-asset` component per distinct (algorithm, primitive) pair, with
176
+ file:line occurrence evidence — for compliance and supply-chain tooling:
177
+
178
+ ```bash
179
+ qscan . --cbom -o qscan-cbom.json
180
+ # equivalently:
181
+ qscan . --format cbom -o qscan-cbom.json
182
+ ```
183
+
184
+ The output is deterministic (sorted components and occurrences, stable serial
185
+ number), so re-running on an unchanged tree produces byte-identical CBOMs.
186
+
187
+ ## CI
188
+
189
+ Fail the build on any high-or-worse finding, while tolerating an accepted
190
+ baseline:
191
+
192
+ ```yaml
193
+ # .github/workflows/qscan.yml
194
+ name: qscan
195
+ on: [push, pull_request]
196
+
197
+ jobs:
198
+ quantum-readiness:
199
+ runs-on: ubuntu-latest
200
+ steps:
201
+ - uses: actions/checkout@v4
202
+ - uses: actions/setup-node@v4
203
+ with:
204
+ node-version: 20
205
+ - name: Scan for quantum-vulnerable crypto
206
+ run: npx @quantakrypto/qscan . --severity-threshold high --baseline qscan-baseline.json
207
+
208
+ # Optional: upload SARIF to GitHub code scanning.
209
+ - name: Generate SARIF
210
+ if: always()
211
+ run: npx @quantakrypto/qscan . --format sarif -o qscan.sarif
212
+ - uses: github/codeql-action/upload-sarif@v3
213
+ if: always()
214
+ with:
215
+ sarif_file: qscan.sarif
216
+ ```
217
+
218
+ ## Programmatic API
219
+
220
+ The CLI is a thin shell over `runQscan`, which `@quantakrypto/action` reuses:
221
+
222
+ ```ts
223
+ import { runQscan, EXIT } from "@quantakrypto/qscan";
224
+
225
+ const { result, exitCode, suppressed } = await runQscan({
226
+ path: "src",
227
+ format: "json",
228
+ severityThreshold: "high",
229
+ baseline: "qscan-baseline.json",
230
+ });
231
+
232
+ console.log(`${result.findings.length} findings, exit ${exitCode}`);
233
+ if (exitCode === EXIT.FINDINGS) process.exitCode = 1;
234
+ ```
235
+
236
+ `runQscan` never touches `process` or stdout — it returns the rendered `report`
237
+ string and a suggested `exitCode`, leaving I/O to the caller. The package also
238
+ re-exports the argument parser (`parseArgs`, `defaultOptions`), severity helpers
239
+ (`severityRank`, `meetsThreshold`), and the **canonical** baseline utilities from
240
+ `@quantakrypto/core` (`fingerprintFinding`, `baselineFromFindings`, `applyBaseline`,
241
+ `loadBaseline`, `saveBaseline`) plus their legacy aliases (`fingerprint`,
242
+ `buildBaseline`, `readBaseline`, `writeBaseline`) for source compatibility.
243
+
244
+ ## Examples
245
+
246
+ See [`examples/`](./examples) for a sample vulnerable project and the commands
247
+ to scan it.
248
+
249
+ ## License
250
+
251
+ Apache-2.0
package/dist/args.d.ts ADDED
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Zero-dependency command-line argument parsing for qScan.
3
+ *
4
+ * Hand-rolled rather than pulled from a library to keep the package free of
5
+ * runtime dependencies. The grammar is deliberately small: long flags
6
+ * (`--flag`, `--flag value`, `--flag=value`), a couple of short aliases
7
+ * (`-o`, `-v`, `-h`), repeatable `--ignore`, and a single optional positional
8
+ * path. Unknown flags are a usage error.
9
+ */
10
+ import type { ReportFormat, Severity } from "@quantakrypto/core";
11
+ /** Severities ordered most → least severe; index 0 is the most severe. */
12
+ export declare const SEVERITY_ORDER: readonly Severity[];
13
+ /**
14
+ * Output formats qScan accepts on the command line. Extends core's
15
+ * {@link ReportFormat} with `"cbom"` (a CycloneDX cryptographic bill of
16
+ * materials), which qScan renders locally via core's `toCbom`.
17
+ */
18
+ export type QscanFormat = ReportFormat | "cbom";
19
+ /** Fully-resolved options the CLI/programmatic runner operates on. */
20
+ export interface QscanOptions {
21
+ /** Directory or file to scan. */
22
+ path: string;
23
+ /** Report format. */
24
+ format: QscanFormat;
25
+ /** Write the report to this file instead of stdout, when set. */
26
+ output?: string;
27
+ /** Findings at or above this severity cause a non-zero exit. */
28
+ severityThreshold: Severity;
29
+ /** Scan source files for inline crypto usage. */
30
+ source: boolean;
31
+ /** Scan dependency manifests for vulnerable libraries. */
32
+ dependencies: boolean;
33
+ /** Scan config files (TLS/certificates). */
34
+ config: boolean;
35
+ /** Extra exclude patterns (repeatable `--ignore`). */
36
+ ignore: string[];
37
+ /**
38
+ * Restrict the walk to paths matching one of these include patterns
39
+ * (repeatable `--include`). When empty, every non-excluded file is scanned.
40
+ */
41
+ include: string[];
42
+ /** Max file size to read, in bytes (`--max-file-size`). */
43
+ maxFileSize?: number;
44
+ /** Disable the built-in ignore list (`--no-default-ignores`). */
45
+ noDefaultIgnores: boolean;
46
+ /** Scan minified/generated/bundled files instead of skipping them. */
47
+ scanMinified: boolean;
48
+ /**
49
+ * Incremental mode: scan only the files git reports as changed
50
+ * (`--changed`), optionally relative to {@link since}.
51
+ */
52
+ changed: boolean;
53
+ /** Git ref/range the `--changed` diff is taken against (`--since`). */
54
+ since?: string;
55
+ /** Route the scan through core's worker-thread pool (`--parallel`). */
56
+ parallel: boolean;
57
+ /**
58
+ * Worker count for parallel scanning (`--concurrency`). Implies parallel.
59
+ * A value of 0 or 1 forces the in-process serial path.
60
+ */
61
+ concurrency?: number;
62
+ /** Suppress findings whose fingerprint is in this baseline file. */
63
+ baseline?: string;
64
+ /** Write current findings as a baseline to this file, then exit 0. */
65
+ writeBaseline?: string;
66
+ /** Suppress the human summary banner (still writes reports/output files). */
67
+ quiet: boolean;
68
+ /**
69
+ * Explicit path to a `quantakrypto.config.json` (`--config <path>`). Overrides
70
+ * auto-discovery at the scan root. Distinct from `--no-config`, which toggles
71
+ * config/TLS *detector* scanning — this names the config FILE.
72
+ */
73
+ configFile?: string;
74
+ /**
75
+ * Disable `quantakrypto.config.json` auto-discovery (`--no-config-file`). Distinct
76
+ * from `--no-config` (which skips config-file *detectors*).
77
+ */
78
+ noConfigFile: boolean;
79
+ }
80
+ /**
81
+ * Option keys that a `quantakrypto.config.json` may also set. When such a key was set
82
+ * by a CLI flag, the flag wins (precedence: flags > config > defaults); when it
83
+ * was left at its default, config may fill it. {@link parseArgs} records which
84
+ * of these keys came from an explicit flag in {@link ParsedRun.explicit}.
85
+ */
86
+ export type ConfigurableKey = "severityThreshold" | "source" | "dependencies" | "config" | "include" | "ignore" | "maxFileSize" | "noDefaultIgnores" | "scanMinified" | "baseline";
87
+ /** A successful parse: resolved options plus which configurable keys were explicit. */
88
+ export interface ParsedRun {
89
+ kind: "run";
90
+ options: QscanOptions;
91
+ /** The set of {@link ConfigurableKey}s the user set via a flag. */
92
+ explicit: Set<ConfigurableKey>;
93
+ }
94
+ /** Result of {@link parseArgs}: either resolved options or a meta action. */
95
+ export type ParsedArgs = ParsedRun | {
96
+ kind: "help";
97
+ } | {
98
+ kind: "version";
99
+ };
100
+ /** Thrown on malformed input; the CLI maps this to exit code 2. */
101
+ export declare class ArgError extends Error {
102
+ readonly name = "ArgError";
103
+ }
104
+ /** Default options, before any flags are applied. */
105
+ export declare function defaultOptions(): QscanOptions;
106
+ /**
107
+ * Parse a raw argv slice (i.e. without `node` and the script path).
108
+ *
109
+ * @throws {ArgError} On unknown flags, missing values, or invalid enum values.
110
+ */
111
+ export declare function parseArgs(argv: readonly string[]): ParsedArgs;
112
+ /** Validate/normalize a `--format` value. */
113
+ export declare function asFormat(value: string): QscanFormat;
114
+ /** Validate/normalize a non-negative integer flag value. */
115
+ export declare function asInt(value: string, flag: string): number;
116
+ /** Validate/normalize a severity value. */
117
+ export declare function asSeverity(value: string): Severity;
118
+ /**
119
+ * Rank a severity: lower number = more severe.
120
+ * `critical` → 0, `info` → 4.
121
+ */
122
+ export declare function severityRank(severity: Severity): number;
123
+ /**
124
+ * Whether `severity` is at or above `threshold` in seriousness.
125
+ * E.g. `high` meets a `medium` threshold; `low` does not.
126
+ */
127
+ export declare function meetsThreshold(severity: Severity, threshold: Severity): boolean;
128
+ //# sourceMappingURL=args.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"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"}
package/dist/args.js ADDED
@@ -0,0 +1,200 @@
1
+ /**
2
+ * Zero-dependency command-line argument parsing for qScan.
3
+ *
4
+ * Hand-rolled rather than pulled from a library to keep the package free of
5
+ * runtime dependencies. The grammar is deliberately small: long flags
6
+ * (`--flag`, `--flag value`, `--flag=value`), a couple of short aliases
7
+ * (`-o`, `-v`, `-h`), repeatable `--ignore`, and a single optional positional
8
+ * path. Unknown flags are a usage error.
9
+ */
10
+ /** Severities ordered most → least severe; index 0 is the most severe. */
11
+ export const SEVERITY_ORDER = ["critical", "high", "medium", "low", "info"];
12
+ const FORMATS = ["human", "json", "sarif", "cbom"];
13
+ /** Thrown on malformed input; the CLI maps this to exit code 2. */
14
+ export class ArgError extends Error {
15
+ name = "ArgError";
16
+ }
17
+ /** Default options, before any flags are applied. */
18
+ export function defaultOptions() {
19
+ return {
20
+ path: ".",
21
+ format: "human",
22
+ severityThreshold: "high",
23
+ source: true,
24
+ dependencies: true,
25
+ config: true,
26
+ ignore: [],
27
+ include: [],
28
+ noDefaultIgnores: false,
29
+ scanMinified: false,
30
+ changed: false,
31
+ parallel: false,
32
+ quiet: false,
33
+ noConfigFile: false,
34
+ };
35
+ }
36
+ /**
37
+ * Parse a raw argv slice (i.e. without `node` and the script path).
38
+ *
39
+ * @throws {ArgError} On unknown flags, missing values, or invalid enum values.
40
+ */
41
+ export function parseArgs(argv) {
42
+ const options = defaultOptions();
43
+ const explicit = new Set();
44
+ let positional;
45
+ // Manual index walk so flags can consume the following token as a value.
46
+ for (let i = 0; i < argv.length; i++) {
47
+ const arg = argv[i];
48
+ // `--flag=value` → split into flag + inline value.
49
+ let inlineValue;
50
+ let flag = arg;
51
+ if (arg.startsWith("--") && arg.includes("=")) {
52
+ const eq = arg.indexOf("=");
53
+ flag = arg.slice(0, eq);
54
+ inlineValue = arg.slice(eq + 1);
55
+ }
56
+ /** Consume a value for `flag`: prefer the inline `=value`, else the next token. */
57
+ const takeValue = () => {
58
+ if (inlineValue !== undefined)
59
+ return inlineValue;
60
+ const next = argv[i + 1];
61
+ if (next === undefined || (next.startsWith("-") && next !== "-")) {
62
+ throw new ArgError(`option "${flag}" requires a value`);
63
+ }
64
+ i++;
65
+ return next;
66
+ };
67
+ switch (flag) {
68
+ case "-h":
69
+ case "--help":
70
+ return { kind: "help" };
71
+ case "-v":
72
+ case "--version":
73
+ return { kind: "version" };
74
+ case "--format":
75
+ options.format = asFormat(takeValue());
76
+ break;
77
+ case "--cbom":
78
+ options.format = "cbom";
79
+ break;
80
+ case "-o":
81
+ case "--output":
82
+ options.output = takeValue();
83
+ break;
84
+ case "--severity-threshold":
85
+ options.severityThreshold = asSeverity(takeValue());
86
+ explicit.add("severityThreshold");
87
+ break;
88
+ case "--no-source":
89
+ options.source = false;
90
+ explicit.add("source");
91
+ break;
92
+ case "--no-deps":
93
+ options.dependencies = false;
94
+ explicit.add("dependencies");
95
+ break;
96
+ case "--no-config":
97
+ options.config = false;
98
+ explicit.add("config");
99
+ break;
100
+ case "--ignore":
101
+ options.ignore.push(takeValue());
102
+ explicit.add("ignore");
103
+ break;
104
+ case "--include":
105
+ options.include.push(takeValue());
106
+ explicit.add("include");
107
+ break;
108
+ case "--max-file-size":
109
+ options.maxFileSize = asInt(takeValue(), "--max-file-size");
110
+ explicit.add("maxFileSize");
111
+ break;
112
+ case "--no-default-ignores":
113
+ options.noDefaultIgnores = true;
114
+ explicit.add("noDefaultIgnores");
115
+ break;
116
+ case "--scan-minified":
117
+ options.scanMinified = true;
118
+ explicit.add("scanMinified");
119
+ break;
120
+ // `quantakrypto.config.json` FILE controls (distinct from `--no-config`, which
121
+ // toggles config/TLS *detector* scanning above).
122
+ case "--config":
123
+ options.configFile = takeValue();
124
+ break;
125
+ case "--no-config-file":
126
+ options.noConfigFile = true;
127
+ break;
128
+ case "--changed":
129
+ options.changed = true;
130
+ break;
131
+ case "--since":
132
+ options.since = takeValue();
133
+ options.changed = true; // --since implies incremental mode
134
+ break;
135
+ case "--parallel":
136
+ options.parallel = true;
137
+ break;
138
+ case "--concurrency":
139
+ options.concurrency = asInt(takeValue(), "--concurrency");
140
+ options.parallel = true; // an explicit worker count implies parallel
141
+ break;
142
+ case "--baseline":
143
+ options.baseline = takeValue();
144
+ explicit.add("baseline");
145
+ break;
146
+ case "--write-baseline":
147
+ options.writeBaseline = takeValue();
148
+ break;
149
+ case "--quiet":
150
+ options.quiet = true;
151
+ break;
152
+ default:
153
+ if (flag.startsWith("-") && flag !== "-") {
154
+ throw new ArgError(`unknown option "${flag}"`);
155
+ }
156
+ if (positional !== undefined) {
157
+ throw new ArgError(`unexpected extra argument "${arg}" (path already set to "${positional}")`);
158
+ }
159
+ positional = arg;
160
+ break;
161
+ }
162
+ }
163
+ if (positional !== undefined)
164
+ options.path = positional;
165
+ return { kind: "run", options, explicit };
166
+ }
167
+ /** Validate/normalize a `--format` value. */
168
+ export function asFormat(value) {
169
+ if (FORMATS.includes(value))
170
+ return value;
171
+ throw new ArgError(`invalid --format "${value}" (expected one of: ${FORMATS.join(", ")})`);
172
+ }
173
+ /** Validate/normalize a non-negative integer flag value. */
174
+ export function asInt(value, flag) {
175
+ if (!/^\d+$/.test(value)) {
176
+ throw new ArgError(`invalid ${flag} "${value}" (expected a non-negative integer)`);
177
+ }
178
+ return Number.parseInt(value, 10);
179
+ }
180
+ /** Validate/normalize a severity value. */
181
+ export function asSeverity(value) {
182
+ if (SEVERITY_ORDER.includes(value))
183
+ return value;
184
+ throw new ArgError(`invalid severity "${value}" (expected one of: ${SEVERITY_ORDER.join(", ")})`);
185
+ }
186
+ /**
187
+ * Rank a severity: lower number = more severe.
188
+ * `critical` → 0, `info` → 4.
189
+ */
190
+ export function severityRank(severity) {
191
+ return SEVERITY_ORDER.indexOf(severity);
192
+ }
193
+ /**
194
+ * Whether `severity` is at or above `threshold` in seriousness.
195
+ * E.g. `high` meets a `medium` threshold; `low` does not.
196
+ */
197
+ export function meetsThreshold(severity, threshold) {
198
+ return severityRank(severity) <= severityRank(threshold);
199
+ }
200
+ //# sourceMappingURL=args.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"args.js","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,0EAA0E;AAC1E,MAAM,CAAC,MAAM,cAAc,GAAwB,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;AASjG,MAAM,OAAO,GAA2B,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;AA6F3E,mEAAmE;AACnE,MAAM,OAAO,QAAS,SAAQ,KAAK;IACf,IAAI,GAAG,UAAU,CAAC;CACrC;AAED,qDAAqD;AACrD,MAAM,UAAU,cAAc;IAC5B,OAAO;QACL,IAAI,EAAE,GAAG;QACT,MAAM,EAAE,OAAO;QACf,iBAAiB,EAAE,MAAM;QACzB,MAAM,EAAE,IAAI;QACZ,YAAY,EAAE,IAAI;QAClB,MAAM,EAAE,IAAI;QACZ,MAAM,EAAE,EAAE;QACV,OAAO,EAAE,EAAE;QACX,gBAAgB,EAAE,KAAK;QACvB,YAAY,EAAE,KAAK;QACnB,OAAO,EAAE,KAAK;QACd,QAAQ,EAAE,KAAK;QACf,KAAK,EAAE,KAAK;QACZ,YAAY,EAAE,KAAK;KACpB,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,IAAuB;IAC/C,MAAM,OAAO,GAAG,cAAc,EAAE,CAAC;IACjC,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAmB,CAAC;IAC5C,IAAI,UAA8B,CAAC;IAEnC,yEAAyE;IACzE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAW,CAAC;QAE9B,mDAAmD;QACnD,IAAI,WAA+B,CAAC;QACpC,IAAI,IAAI,GAAG,GAAG,CAAC;QACf,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YAC9C,MAAM,EAAE,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YAC5B,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACxB,WAAW,GAAG,GAAG,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;QAClC,CAAC;QAED,mFAAmF;QACnF,MAAM,SAAS,GAAG,GAAW,EAAE;YAC7B,IAAI,WAAW,KAAK,SAAS;gBAAE,OAAO,WAAW,CAAC;YAClD,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YACzB,IAAI,IAAI,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;gBACjE,MAAM,IAAI,QAAQ,CAAC,WAAW,IAAI,oBAAoB,CAAC,CAAC;YAC1D,CAAC;YACD,CAAC,EAAE,CAAC;YACJ,OAAO,IAAI,CAAC;QACd,CAAC,CAAC;QAEF,QAAQ,IAAI,EAAE,CAAC;YACb,KAAK,IAAI,CAAC;YACV,KAAK,QAAQ;gBACX,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;YAC1B,KAAK,IAAI,CAAC;YACV,KAAK,WAAW;gBACd,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;YAE7B,KAAK,UAAU;gBACb,OAAO,CAAC,MAAM,GAAG,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC;gBACvC,MAAM;YACR,KAAK,QAAQ;gBACX,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC;gBACxB,MAAM;YACR,KAAK,IAAI,CAAC;YACV,KAAK,UAAU;gBACb,OAAO,CAAC,MAAM,GAAG,SAAS,EAAE,CAAC;gBAC7B,MAAM;YACR,KAAK,sBAAsB;gBACzB,OAAO,CAAC,iBAAiB,GAAG,UAAU,CAAC,SAAS,EAAE,CAAC,CAAC;gBACpD,QAAQ,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;gBAClC,MAAM;YAER,KAAK,aAAa;gBAChB,OAAO,CAAC,MAAM,GAAG,KAAK,CAAC;gBACvB,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;gBACvB,MAAM;YACR,KAAK,WAAW;gBACd,OAAO,CAAC,YAAY,GAAG,KAAK,CAAC;gBAC7B,QAAQ,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;gBAC7B,MAAM;YACR,KAAK,aAAa;gBAChB,OAAO,CAAC,MAAM,GAAG,KAAK,CAAC;gBACvB,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;gBACvB,MAAM;YAER,KAAK,UAAU;gBACb,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;gBACjC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;gBACvB,MAAM;YACR,KAAK,WAAW;gBACd,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;gBAClC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;gBACxB,MAAM;YACR,KAAK,iBAAiB;gBACpB,OAAO,CAAC,WAAW,GAAG,KAAK,CAAC,SAAS,EAAE,EAAE,iBAAiB,CAAC,CAAC;gBAC5D,QAAQ,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;gBAC5B,MAAM;YACR,KAAK,sBAAsB;gBACzB,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;gBAChC,QAAQ,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;gBACjC,MAAM;YACR,KAAK,iBAAiB;gBACpB,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;gBAC5B,QAAQ,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;gBAC7B,MAAM;YAER,+EAA+E;YAC/E,iDAAiD;YACjD,KAAK,UAAU;gBACb,OAAO,CAAC,UAAU,GAAG,SAAS,EAAE,CAAC;gBACjC,MAAM;YACR,KAAK,kBAAkB;gBACrB,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;gBAC5B,MAAM;YAER,KAAK,WAAW;gBACd,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;gBACvB,MAAM;YACR,KAAK,SAAS;gBACZ,OAAO,CAAC,KAAK,GAAG,SAAS,EAAE,CAAC;gBAC5B,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC,mCAAmC;gBAC3D,MAAM;YAER,KAAK,YAAY;gBACf,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;gBACxB,MAAM;YACR,KAAK,eAAe;gBAClB,OAAO,CAAC,WAAW,GAAG,KAAK,CAAC,SAAS,EAAE,EAAE,eAAe,CAAC,CAAC;gBAC1D,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC,4CAA4C;gBACrE,MAAM;YAER,KAAK,YAAY;gBACf,OAAO,CAAC,QAAQ,GAAG,SAAS,EAAE,CAAC;gBAC/B,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;gBACzB,MAAM;YACR,KAAK,kBAAkB;gBACrB,OAAO,CAAC,aAAa,GAAG,SAAS,EAAE,CAAC;gBACpC,MAAM;YACR,KAAK,SAAS;gBACZ,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC;gBACrB,MAAM;YAER;gBACE,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;oBACzC,MAAM,IAAI,QAAQ,CAAC,mBAAmB,IAAI,GAAG,CAAC,CAAC;gBACjD,CAAC;gBACD,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;oBAC7B,MAAM,IAAI,QAAQ,CAChB,8BAA8B,GAAG,2BAA2B,UAAU,IAAI,CAC3E,CAAC;gBACJ,CAAC;gBACD,UAAU,GAAG,GAAG,CAAC;gBACjB,MAAM;QACV,CAAC;IACH,CAAC;IAED,IAAI,UAAU,KAAK,SAAS;QAAE,OAAO,CAAC,IAAI,GAAG,UAAU,CAAC;IACxD,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;AAC5C,CAAC;AAED,6CAA6C;AAC7C,MAAM,UAAU,QAAQ,CAAC,KAAa;IACpC,IAAK,OAA6B,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,KAAoB,CAAC;IAChF,MAAM,IAAI,QAAQ,CAAC,qBAAqB,KAAK,uBAAuB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC7F,CAAC;AAED,4DAA4D;AAC5D,MAAM,UAAU,KAAK,CAAC,KAAa,EAAE,IAAY;IAC/C,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,QAAQ,CAAC,WAAW,IAAI,KAAK,KAAK,qCAAqC,CAAC,CAAC;IACrF,CAAC;IACD,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AACpC,CAAC;AAED,2CAA2C;AAC3C,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,IAAK,cAAoC,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,KAAiB,CAAC;IACpF,MAAM,IAAI,QAAQ,CAAC,qBAAqB,KAAK,uBAAuB,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACpG,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,QAAkB;IAC7C,OAAO,cAAc,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;AAC1C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,QAAkB,EAAE,SAAmB;IACpE,OAAO,YAAY,CAAC,QAAQ,CAAC,IAAI,YAAY,CAAC,SAAS,CAAC,CAAC;AAC3D,CAAC"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Baseline support for qScan — a thin adapter over the **canonical** baseline
3
+ * implementation in `@quantakrypto/core` (P1-1).
4
+ *
5
+ * Historically qScan carried its own baseline scheme (12-char hash of
6
+ * `ruleId|file|snippet|line`) that was incompatible with the GitHub Action's.
7
+ * Both have been unified onto core's single source of truth, so this module no
8
+ * longer defines its own format or hashing — it re-exports core's primitives
9
+ * and adds only the small filename-resolution / API-shape conveniences the CLI
10
+ * and `@quantakrypto/action` rely on.
11
+ *
12
+ * The on-disk format is core's {@link Baseline}: `{ version, fingerprints }`,
13
+ * where each fingerprint is a full, line-INSENSITIVE SHA-256 (so unrelated edits
14
+ * that shift line numbers no longer invalidate a baseline).
15
+ */
16
+ import { baselineFromFindings, BASELINE_VERSION, fingerprintFinding, loadBaseline, saveBaseline } from "@quantakrypto/core";
17
+ import type { Baseline, Finding } from "@quantakrypto/core";
18
+ export { baselineFromFindings, BASELINE_VERSION, fingerprintFinding, loadBaseline, saveBaseline };
19
+ export type { Baseline };
20
+ /**
21
+ * Compute a stable fingerprint for a finding. Alias for core's
22
+ * {@link fingerprintFinding}; kept under the historical name `fingerprint` for
23
+ * source compatibility with existing call sites and tests.
24
+ */
25
+ export declare const fingerprint: typeof fingerprintFinding;
26
+ /**
27
+ * Partition findings into those kept and those suppressed by a baseline.
28
+ *
29
+ * Wraps core's {@link coreApplyBaseline} (which takes a {@link Baseline} and
30
+ * returns `{ newFindings, suppressed }`) but preserves qScan's historical
31
+ * `{ kept, suppressed }` shape and its lenient `ReadonlySet<string>` overload so
32
+ * existing callers keep working.
33
+ *
34
+ * @param findings All findings produced by a scan.
35
+ * @param baseline Either a {@link Baseline} object or a set of accepted
36
+ * fingerprints.
37
+ */
38
+ export declare function applyBaseline(findings: readonly Finding[], baseline: Baseline | ReadonlySet<string>): {
39
+ kept: Finding[];
40
+ suppressed: Finding[];
41
+ };
42
+ /**
43
+ * Build a {@link Baseline} from a set of findings (deduped + sorted). Alias for
44
+ * core's {@link baselineFromFindings} under qScan's historical name.
45
+ */
46
+ export declare function buildBaseline(findings: readonly Finding[]): Baseline;
47
+ /**
48
+ * Read a baseline file from disk and return its accepted fingerprints as a set.
49
+ *
50
+ * Unlike core's tolerant {@link loadBaseline} (which returns an empty baseline
51
+ * for a missing/unparseable file), this preserves qScan's historical *strict*
52
+ * contract: a missing or malformed file is an error, surfaced to the CLI as an
53
+ * I/O failure (exit 2).
54
+ *
55
+ * @throws {Error} If the file cannot be read or is not valid baseline JSON.
56
+ */
57
+ export declare function readBaseline(path: string): Promise<Set<string>>;
58
+ /** Serialize and write a baseline to disk (pretty-printed, trailing newline). */
59
+ export declare function writeBaseline(path: string, baseline: Baseline): Promise<void>;
60
+ //# sourceMappingURL=baseline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"baseline.d.ts","sourceRoot":"","sources":["../src/baseline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAEL,oBAAoB,EACpB,gBAAgB,EAChB,kBAAkB,EAClB,YAAY,EACZ,YAAY,EACb,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAI5D,OAAO,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,YAAY,EAAE,YAAY,EAAE,CAAC;AAClG,YAAY,EAAE,QAAQ,EAAE,CAAC;AAEzB;;;;GAIG;AACH,eAAO,MAAM,WAAW,2BAAqB,CAAC;AAE9C;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,SAAS,OAAO,EAAE,EAC5B,QAAQ,EAAE,QAAQ,GAAG,WAAW,CAAC,MAAM,CAAC,GACvC;IAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IAAC,UAAU,EAAE,OAAO,EAAE,CAAA;CAAE,CAO5C;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,GAAG,QAAQ,CAEpE;AAED;;;;;;;;;GASG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAoBrE;AAED,iFAAiF;AACjF,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAQnF"}