@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 +251 -0
- package/dist/args.d.ts +128 -0
- package/dist/args.d.ts.map +1 -0
- package/dist/args.js +200 -0
- package/dist/args.js.map +1 -0
- package/dist/baseline.d.ts +60 -0
- package/dist/baseline.d.ts.map +1 -0
- package/dist/baseline.js +105 -0
- package/dist/baseline.js.map +1 -0
- package/dist/cli.d.ts +13 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +121 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +42 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +78 -0
- package/dist/config.js.map +1 -0
- package/dist/help.d.ts +11 -0
- package/dist/help.d.ts.map +1 -0
- package/dist/help.js +67 -0
- package/dist/help.js.map +1 -0
- package/dist/index.d.ts +99 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +133 -0
- package/dist/index.js.map +1 -0
- package/dist/report.d.ts +46 -0
- package/dist/report.d.ts.map +1 -0
- package/dist/report.js +204 -0
- package/dist/report.js.map +1 -0
- package/package.json +45 -0
package/dist/baseline.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
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 { applyBaseline as coreApplyBaseline, baselineFromFindings, BASELINE_VERSION, fingerprintFinding, loadBaseline, saveBaseline, } from "@quantakrypto/core";
|
|
17
|
+
// Re-export core's canonical primitives so downstream tools (and tests) can use
|
|
18
|
+
// them through `@quantakrypto/qscan` without reaching into `@quantakrypto/core` internals.
|
|
19
|
+
export { baselineFromFindings, BASELINE_VERSION, fingerprintFinding, loadBaseline, saveBaseline };
|
|
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 const fingerprint = 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 function applyBaseline(findings, baseline) {
|
|
39
|
+
const resolved = baseline instanceof Set
|
|
40
|
+
? { version: BASELINE_VERSION, fingerprints: [...baseline] }
|
|
41
|
+
: baseline;
|
|
42
|
+
const { newFindings, suppressed } = coreApplyBaseline(findings, resolved);
|
|
43
|
+
return { kept: newFindings, suppressed };
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Build a {@link Baseline} from a set of findings (deduped + sorted). Alias for
|
|
47
|
+
* core's {@link baselineFromFindings} under qScan's historical name.
|
|
48
|
+
*/
|
|
49
|
+
export function buildBaseline(findings) {
|
|
50
|
+
return baselineFromFindings(findings);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Read a baseline file from disk and return its accepted fingerprints as a set.
|
|
54
|
+
*
|
|
55
|
+
* Unlike core's tolerant {@link loadBaseline} (which returns an empty baseline
|
|
56
|
+
* for a missing/unparseable file), this preserves qScan's historical *strict*
|
|
57
|
+
* contract: a missing or malformed file is an error, surfaced to the CLI as an
|
|
58
|
+
* I/O failure (exit 2).
|
|
59
|
+
*
|
|
60
|
+
* @throws {Error} If the file cannot be read or is not valid baseline JSON.
|
|
61
|
+
*/
|
|
62
|
+
export async function readBaseline(path) {
|
|
63
|
+
const { readFile } = await import("node:fs/promises");
|
|
64
|
+
let raw;
|
|
65
|
+
try {
|
|
66
|
+
raw = await readFile(path, "utf8");
|
|
67
|
+
}
|
|
68
|
+
catch (cause) {
|
|
69
|
+
throw new Error(`could not read baseline file "${path}": ${errMessage(cause)}`);
|
|
70
|
+
}
|
|
71
|
+
let parsed;
|
|
72
|
+
try {
|
|
73
|
+
parsed = JSON.parse(raw);
|
|
74
|
+
}
|
|
75
|
+
catch (cause) {
|
|
76
|
+
throw new Error(`baseline file "${path}" is not valid JSON: ${errMessage(cause)}`);
|
|
77
|
+
}
|
|
78
|
+
if (!isBaselineFile(parsed)) {
|
|
79
|
+
throw new Error(`baseline file "${path}" is missing a string "fingerprints" array`);
|
|
80
|
+
}
|
|
81
|
+
return new Set(parsed.fingerprints);
|
|
82
|
+
}
|
|
83
|
+
/** Serialize and write a baseline to disk (pretty-printed, trailing newline). */
|
|
84
|
+
export async function writeBaseline(path, baseline) {
|
|
85
|
+
const { writeFile } = await import("node:fs/promises");
|
|
86
|
+
const json = `${JSON.stringify(baseline, null, 2)}\n`;
|
|
87
|
+
try {
|
|
88
|
+
await writeFile(path, json, "utf8");
|
|
89
|
+
}
|
|
90
|
+
catch (cause) {
|
|
91
|
+
throw new Error(`could not write baseline file "${path}": ${errMessage(cause)}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/** Narrowing type guard for parsed baseline JSON. */
|
|
95
|
+
function isBaselineFile(value) {
|
|
96
|
+
if (typeof value !== "object" || value === null)
|
|
97
|
+
return false;
|
|
98
|
+
const obj = value;
|
|
99
|
+
return Array.isArray(obj.fingerprints) && obj.fingerprints.every((f) => typeof f === "string");
|
|
100
|
+
}
|
|
101
|
+
/** Extract a human message from an unknown thrown value. */
|
|
102
|
+
function errMessage(cause) {
|
|
103
|
+
return cause instanceof Error ? cause.message : String(cause);
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=baseline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"baseline.js","sourceRoot":"","sources":["../src/baseline.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* qScan command-line entry point.
|
|
4
|
+
*
|
|
5
|
+
* Thin shell over the programmatic API in `./index.ts`:
|
|
6
|
+
* parse argv → runQscan → print/write report → process.exit(code).
|
|
7
|
+
*
|
|
8
|
+
* All policy (scanning, baseline, thresholds, rendering) lives in `index.ts`;
|
|
9
|
+
* this file only deals with argv, stdout/stderr, files, and exit codes.
|
|
10
|
+
*/
|
|
11
|
+
/** Run the CLI and return the desired process exit code (never throws). */
|
|
12
|
+
export declare function main(argv: readonly string[]): Promise<number>;
|
|
13
|
+
//# sourceMappingURL=cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;GAQG;AAeH,2EAA2E;AAC3E,wBAAsB,IAAI,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA+FnE"}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* qScan command-line entry point.
|
|
4
|
+
*
|
|
5
|
+
* Thin shell over the programmatic API in `./index.ts`:
|
|
6
|
+
* parse argv → runQscan → print/write report → process.exit(code).
|
|
7
|
+
*
|
|
8
|
+
* All policy (scanning, baseline, thresholds, rendering) lives in `index.ts`;
|
|
9
|
+
* this file only deals with argv, stdout/stderr, files, and exit codes.
|
|
10
|
+
*/
|
|
11
|
+
import { writeFile } from "node:fs/promises";
|
|
12
|
+
import process from "node:process";
|
|
13
|
+
import { pathToFileURL } from "node:url";
|
|
14
|
+
import { ConfigError } from "@quantakrypto/core";
|
|
15
|
+
import { ArgError, parseArgs } from "./args.js";
|
|
16
|
+
import { resolveConfig } from "./config.js";
|
|
17
|
+
import { HELP_TEXT, versionLine } from "./help.js";
|
|
18
|
+
import { EXIT, runQscan } from "./index.js";
|
|
19
|
+
/** Run the CLI and return the desired process exit code (never throws). */
|
|
20
|
+
export async function main(argv) {
|
|
21
|
+
let parsed;
|
|
22
|
+
try {
|
|
23
|
+
parsed = parseArgs(argv);
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
if (err instanceof ArgError) {
|
|
27
|
+
process.stderr.write(`qscan: ${err.message}\n`);
|
|
28
|
+
process.stderr.write(`Run "qscan --help" for usage.\n`);
|
|
29
|
+
return EXIT.ERROR;
|
|
30
|
+
}
|
|
31
|
+
throw err;
|
|
32
|
+
}
|
|
33
|
+
if (parsed.kind === "help") {
|
|
34
|
+
process.stdout.write(HELP_TEXT);
|
|
35
|
+
return EXIT.OK;
|
|
36
|
+
}
|
|
37
|
+
if (parsed.kind === "version") {
|
|
38
|
+
process.stdout.write(`${versionLine()}\n`);
|
|
39
|
+
return EXIT.OK;
|
|
40
|
+
}
|
|
41
|
+
// Resolve `quantakrypto.config.json` (flags > config > defaults) before scanning.
|
|
42
|
+
let options;
|
|
43
|
+
try {
|
|
44
|
+
const resolved = await resolveConfig(parsed.options, parsed.explicit);
|
|
45
|
+
options = resolved.options;
|
|
46
|
+
if (!options.quiet) {
|
|
47
|
+
for (const w of resolved.warnings) {
|
|
48
|
+
process.stderr.write(`qscan: config warning: ${w}\n`);
|
|
49
|
+
}
|
|
50
|
+
if (resolved.configPath) {
|
|
51
|
+
process.stderr.write(`qscan: using config ${resolved.configPath}\n`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
if (err instanceof ConfigError) {
|
|
57
|
+
process.stderr.write(`qscan: ${err.message}\n`);
|
|
58
|
+
return EXIT.ERROR;
|
|
59
|
+
}
|
|
60
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
61
|
+
process.stderr.write(`qscan: ${message}\n`);
|
|
62
|
+
return EXIT.ERROR;
|
|
63
|
+
}
|
|
64
|
+
// Color only when writing the human report to an interactive stdout.
|
|
65
|
+
const color = options.format === "human" &&
|
|
66
|
+
!options.output &&
|
|
67
|
+
Boolean(process.stdout.isTTY) &&
|
|
68
|
+
process.env.NO_COLOR === undefined;
|
|
69
|
+
let run;
|
|
70
|
+
try {
|
|
71
|
+
run = await runQscan(options, { color });
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
75
|
+
process.stderr.write(`qscan: ${message}\n`);
|
|
76
|
+
return EXIT.ERROR;
|
|
77
|
+
}
|
|
78
|
+
// --write-baseline: report what was written and stop.
|
|
79
|
+
if (run.baselineWritten) {
|
|
80
|
+
if (!options.quiet) {
|
|
81
|
+
const n = run.baselineWritten.fingerprints.length;
|
|
82
|
+
process.stderr.write(`qscan: wrote baseline with ${n} fingerprint${n === 1 ? "" : "s"} to ${options.writeBaseline}\n`);
|
|
83
|
+
}
|
|
84
|
+
return run.exitCode;
|
|
85
|
+
}
|
|
86
|
+
const report = run.report ?? "";
|
|
87
|
+
try {
|
|
88
|
+
if (options.output) {
|
|
89
|
+
await writeFile(options.output, report.endsWith("\n") ? report : `${report}\n`, "utf8");
|
|
90
|
+
if (!options.quiet) {
|
|
91
|
+
process.stderr.write(`qscan: wrote ${options.format} report to ${options.output}\n`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
else if (!options.quiet || options.format !== "human") {
|
|
95
|
+
// In quiet mode we still emit machine formats to stdout (the point of a
|
|
96
|
+
// pipe), but suppress the human banner.
|
|
97
|
+
process.stdout.write(report.endsWith("\n") ? report : `${report}\n`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
102
|
+
process.stderr.write(`qscan: ${message}\n`);
|
|
103
|
+
return EXIT.ERROR;
|
|
104
|
+
}
|
|
105
|
+
if (!options.quiet && run.suppressed.length > 0) {
|
|
106
|
+
process.stderr.write(`qscan: suppressed ${run.suppressed.length} finding(s) via baseline\n`);
|
|
107
|
+
}
|
|
108
|
+
return run.exitCode;
|
|
109
|
+
}
|
|
110
|
+
// Only auto-run when invoked as a script (not when imported by a test).
|
|
111
|
+
const invokedDirectly = process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
112
|
+
if (invokedDirectly) {
|
|
113
|
+
main(process.argv.slice(2))
|
|
114
|
+
.then((code) => process.exit(code))
|
|
115
|
+
.catch((err) => {
|
|
116
|
+
const message = err instanceof Error ? (err.stack ?? err.message) : String(err);
|
|
117
|
+
process.stderr.write(`qscan: fatal: ${message}\n`);
|
|
118
|
+
process.exit(EXIT.ERROR);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* qScan-side resolution of `quantakrypto.config.json` (ROADMAP P2-9, docs/CONFIG.md).
|
|
3
|
+
*
|
|
4
|
+
* core's {@link loadConfig} does the reading + type-validation; this module
|
|
5
|
+
* applies the file's values onto parsed CLI options with the documented
|
|
6
|
+
* precedence:
|
|
7
|
+
*
|
|
8
|
+
* CLI flags > quantakrypto.config.json > built-in defaults
|
|
9
|
+
*
|
|
10
|
+
* Resolution is **per-key**: a key set by a flag (tracked in the `explicit` set
|
|
11
|
+
* from `parseArgs`) is left alone; otherwise the config value fills it. The
|
|
12
|
+
* list-valued keys (`include` / `ignore`→`exclude`) *append* — the config
|
|
13
|
+
* provides a base set and any CLI flags add to it (docs/CONFIG.md §4.2).
|
|
14
|
+
*/
|
|
15
|
+
import type { QuantakryptoFileConfig } from "@quantakrypto/core";
|
|
16
|
+
import type { ConfigurableKey, QscanOptions } from "./args.js";
|
|
17
|
+
/** What {@link resolveConfig} returns: the merged options + provenance. */
|
|
18
|
+
export interface ResolvedConfig {
|
|
19
|
+
/** Options with config applied under the flags > config > defaults rule. */
|
|
20
|
+
options: QscanOptions;
|
|
21
|
+
/** Absolute path of the config file that was applied, when one was. */
|
|
22
|
+
configPath?: string;
|
|
23
|
+
/** Non-fatal warnings from parsing (unknown keys, future version, …). */
|
|
24
|
+
warnings: string[];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Load and merge `quantakrypto.config.json` into the parsed CLI options.
|
|
28
|
+
*
|
|
29
|
+
* @param options Fully-resolved options from {@link parseArgs} (defaults filled).
|
|
30
|
+
* @param explicit The set of configurable keys the user set via a flag.
|
|
31
|
+
* @returns The merged options plus the applied config path + any warnings.
|
|
32
|
+
* @throws {ConfigError} (from core) on a malformed config or a missing
|
|
33
|
+
* explicitly-named `--config` file. The CLI maps this to exit 2.
|
|
34
|
+
*/
|
|
35
|
+
export declare function resolveConfig(options: QscanOptions, explicit: ReadonlySet<ConfigurableKey>): Promise<ResolvedConfig>;
|
|
36
|
+
/**
|
|
37
|
+
* Apply a parsed config onto options under the precedence rule. Pure; returns a
|
|
38
|
+
* new options object. Scalars: config fills only keys NOT set by a flag. Lists:
|
|
39
|
+
* config provides the base and the CLI flag values are appended.
|
|
40
|
+
*/
|
|
41
|
+
export declare function applyConfig(options: QscanOptions, config: QuantakryptoFileConfig, explicit: ReadonlySet<ConfigurableKey>): QscanOptions;
|
|
42
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAEjE,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAE/D,2EAA2E;AAC3E,MAAM,WAAW,cAAc;IAC7B,4EAA4E;IAC5E,OAAO,EAAE,YAAY,CAAC;IACtB,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yEAAyE;IACzE,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,YAAY,EACrB,QAAQ,EAAE,WAAW,CAAC,eAAe,CAAC,GACrC,OAAO,CAAC,cAAc,CAAC,CAkBzB;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,YAAY,EACrB,MAAM,EAAE,sBAAsB,EAC9B,QAAQ,EAAE,WAAW,CAAC,eAAe,CAAC,GACrC,YAAY,CAoCd"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* qScan-side resolution of `quantakrypto.config.json` (ROADMAP P2-9, docs/CONFIG.md).
|
|
3
|
+
*
|
|
4
|
+
* core's {@link loadConfig} does the reading + type-validation; this module
|
|
5
|
+
* applies the file's values onto parsed CLI options with the documented
|
|
6
|
+
* precedence:
|
|
7
|
+
*
|
|
8
|
+
* CLI flags > quantakrypto.config.json > built-in defaults
|
|
9
|
+
*
|
|
10
|
+
* Resolution is **per-key**: a key set by a flag (tracked in the `explicit` set
|
|
11
|
+
* from `parseArgs`) is left alone; otherwise the config value fills it. The
|
|
12
|
+
* list-valued keys (`include` / `ignore`→`exclude`) *append* — the config
|
|
13
|
+
* provides a base set and any CLI flags add to it (docs/CONFIG.md §4.2).
|
|
14
|
+
*/
|
|
15
|
+
import { loadConfig } from "@quantakrypto/core";
|
|
16
|
+
/**
|
|
17
|
+
* Load and merge `quantakrypto.config.json` into the parsed CLI options.
|
|
18
|
+
*
|
|
19
|
+
* @param options Fully-resolved options from {@link parseArgs} (defaults filled).
|
|
20
|
+
* @param explicit The set of configurable keys the user set via a flag.
|
|
21
|
+
* @returns The merged options plus the applied config path + any warnings.
|
|
22
|
+
* @throws {ConfigError} (from core) on a malformed config or a missing
|
|
23
|
+
* explicitly-named `--config` file. The CLI maps this to exit 2.
|
|
24
|
+
*/
|
|
25
|
+
export async function resolveConfig(options, explicit) {
|
|
26
|
+
// `--no-config-file` disables discovery entirely; nothing to merge.
|
|
27
|
+
if (options.noConfigFile && options.configFile === undefined) {
|
|
28
|
+
return { options, warnings: [] };
|
|
29
|
+
}
|
|
30
|
+
// `--config <path>` names the file explicitly (a missing file is then fatal);
|
|
31
|
+
// otherwise auto-discover at the scan root.
|
|
32
|
+
const target = options.configFile ?? options.path;
|
|
33
|
+
const loaded = await loadConfig(target, { explicit: options.configFile !== undefined });
|
|
34
|
+
if (loaded.path === undefined) {
|
|
35
|
+
// No file found (auto-discovery, tolerant): options unchanged.
|
|
36
|
+
return { options, warnings: loaded.warnings };
|
|
37
|
+
}
|
|
38
|
+
const merged = applyConfig(options, loaded.config, explicit);
|
|
39
|
+
return { options: merged, configPath: loaded.path, warnings: loaded.warnings };
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Apply a parsed config onto options under the precedence rule. Pure; returns a
|
|
43
|
+
* new options object. Scalars: config fills only keys NOT set by a flag. Lists:
|
|
44
|
+
* config provides the base and the CLI flag values are appended.
|
|
45
|
+
*/
|
|
46
|
+
export function applyConfig(options, config, explicit) {
|
|
47
|
+
const out = {
|
|
48
|
+
...options,
|
|
49
|
+
ignore: [...options.ignore],
|
|
50
|
+
include: [...options.include],
|
|
51
|
+
};
|
|
52
|
+
/** Set a scalar key from config only when the user didn't set it via a flag. */
|
|
53
|
+
const fillScalar = (key, value) => {
|
|
54
|
+
if (value === undefined)
|
|
55
|
+
return;
|
|
56
|
+
if (explicit.has(key))
|
|
57
|
+
return; // flag wins.
|
|
58
|
+
out[key] = value;
|
|
59
|
+
};
|
|
60
|
+
fillScalar("severityThreshold", config.severityThreshold);
|
|
61
|
+
fillScalar("source", config.source);
|
|
62
|
+
fillScalar("dependencies", config.dependencies);
|
|
63
|
+
fillScalar("config", config.config);
|
|
64
|
+
fillScalar("noDefaultIgnores", config.noDefaultIgnores);
|
|
65
|
+
fillScalar("scanMinified", config.scanMinified);
|
|
66
|
+
fillScalar("maxFileSize", config.maxFileSize);
|
|
67
|
+
fillScalar("baseline", config.baseline);
|
|
68
|
+
// List-valued keys: config is the base, CLI flags append (config first so the
|
|
69
|
+
// committed policy reads as the baseline, ad-hoc CLI excludes/includes after).
|
|
70
|
+
if (config.exclude && config.exclude.length > 0) {
|
|
71
|
+
out.ignore = [...config.exclude, ...options.ignore];
|
|
72
|
+
}
|
|
73
|
+
if (config.include && config.include.length > 0) {
|
|
74
|
+
out.include = [...config.include, ...options.include];
|
|
75
|
+
}
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/dist/help.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static help / usage text for the qScan CLI.
|
|
3
|
+
*
|
|
4
|
+
* Kept in its own module so it can be unit-tested and reused without pulling in
|
|
5
|
+
* filesystem or process side effects.
|
|
6
|
+
*/
|
|
7
|
+
/** The full `--help` screen. */
|
|
8
|
+
export declare const HELP_TEXT = "qscan \u2014 find quantum-vulnerable cryptography in any codebase\n\nUSAGE\n qscan [path] [options]\n\nARGUMENTS\n path Directory or file to scan (default: \".\")\n\nOPTIONS\n --format <human|json|sarif|cbom>\n Output format (default: human)\n --cbom Alias for --format cbom (CycloneDX CBOM)\n -o, --output <file> Write the report to a file instead of stdout\n --severity-threshold <level> Fail (exit 1) on findings at/above this level;\n one of critical|high|medium|low|info\n (default: high)\n --no-source Skip scanning source files for inline crypto\n --no-deps Skip scanning dependency manifests\n --no-config Skip scanning config files (TLS/certificates)\n --config <path> Use this quantakrypto.config.json instead of\n auto-discovering one at the scan root\n --no-config-file Disable quantakrypto.config.json auto-discovery\n --ignore <pattern> Exclude paths matching <pattern> (repeatable)\n --include <pattern> Restrict the scan to matching paths (repeatable)\n --max-file-size <bytes> Skip files larger than <bytes> (default: 2 MiB)\n --no-default-ignores Don't skip node_modules/.git/dist by default\n --scan-minified Scan minified/generated/bundled files too\n --changed Scan only files changed in the git work tree\n --since <git-ref> With --changed, diff against <git-ref>\n --parallel Scan using a worker-thread pool when worthwhile\n --concurrency <n> Worker count for --parallel (implies --parallel)\n --baseline <file> Suppress findings listed in a baseline file\n --write-baseline <file> Write current findings as a baseline, then exit 0\n --quiet Suppress the human summary banner\n -v, --version Print version and exit\n -h, --help Print this help and exit\n\nEXIT CODES\n 0 No findings at/above the threshold (or a baseline was written)\n 1 One or more findings at/above the severity threshold\n 2 Usage error or I/O failure\n\nEXAMPLES\n qscan . Scan the current directory\n qscan src --format sarif -o qscan.sarif\n qscan . --severity-threshold critical\n qscan . --write-baseline qscan-baseline.json\n qscan . --baseline qscan-baseline.json\n qscan . --include src --include lib\n qscan . --config ./ci/quantakrypto.config.json\n qscan . --changed --since origin/main\n qscan . --parallel --concurrency 4\n qscan . --cbom -o qscan-cbom.json\n";
|
|
9
|
+
/** The `--version` line. */
|
|
10
|
+
export declare function versionLine(): string;
|
|
11
|
+
//# sourceMappingURL=help.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../src/help.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,gCAAgC;AAChC,eAAO,MAAM,SAAS,uuFAqDrB,CAAC;AAEF,4BAA4B;AAC5B,wBAAgB,WAAW,IAAI,MAAM,CAEpC"}
|
package/dist/help.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static help / usage text for the qScan CLI.
|
|
3
|
+
*
|
|
4
|
+
* Kept in its own module so it can be unit-tested and reused without pulling in
|
|
5
|
+
* filesystem or process side effects.
|
|
6
|
+
*/
|
|
7
|
+
import { VERSION } from "@quantakrypto/core";
|
|
8
|
+
/** The full `--help` screen. */
|
|
9
|
+
export const HELP_TEXT = `qscan — find quantum-vulnerable cryptography in any codebase
|
|
10
|
+
|
|
11
|
+
USAGE
|
|
12
|
+
qscan [path] [options]
|
|
13
|
+
|
|
14
|
+
ARGUMENTS
|
|
15
|
+
path Directory or file to scan (default: ".")
|
|
16
|
+
|
|
17
|
+
OPTIONS
|
|
18
|
+
--format <human|json|sarif|cbom>
|
|
19
|
+
Output format (default: human)
|
|
20
|
+
--cbom Alias for --format cbom (CycloneDX CBOM)
|
|
21
|
+
-o, --output <file> Write the report to a file instead of stdout
|
|
22
|
+
--severity-threshold <level> Fail (exit 1) on findings at/above this level;
|
|
23
|
+
one of critical|high|medium|low|info
|
|
24
|
+
(default: high)
|
|
25
|
+
--no-source Skip scanning source files for inline crypto
|
|
26
|
+
--no-deps Skip scanning dependency manifests
|
|
27
|
+
--no-config Skip scanning config files (TLS/certificates)
|
|
28
|
+
--config <path> Use this quantakrypto.config.json instead of
|
|
29
|
+
auto-discovering one at the scan root
|
|
30
|
+
--no-config-file Disable quantakrypto.config.json auto-discovery
|
|
31
|
+
--ignore <pattern> Exclude paths matching <pattern> (repeatable)
|
|
32
|
+
--include <pattern> Restrict the scan to matching paths (repeatable)
|
|
33
|
+
--max-file-size <bytes> Skip files larger than <bytes> (default: 2 MiB)
|
|
34
|
+
--no-default-ignores Don't skip node_modules/.git/dist by default
|
|
35
|
+
--scan-minified Scan minified/generated/bundled files too
|
|
36
|
+
--changed Scan only files changed in the git work tree
|
|
37
|
+
--since <git-ref> With --changed, diff against <git-ref>
|
|
38
|
+
--parallel Scan using a worker-thread pool when worthwhile
|
|
39
|
+
--concurrency <n> Worker count for --parallel (implies --parallel)
|
|
40
|
+
--baseline <file> Suppress findings listed in a baseline file
|
|
41
|
+
--write-baseline <file> Write current findings as a baseline, then exit 0
|
|
42
|
+
--quiet Suppress the human summary banner
|
|
43
|
+
-v, --version Print version and exit
|
|
44
|
+
-h, --help Print this help and exit
|
|
45
|
+
|
|
46
|
+
EXIT CODES
|
|
47
|
+
0 No findings at/above the threshold (or a baseline was written)
|
|
48
|
+
1 One or more findings at/above the severity threshold
|
|
49
|
+
2 Usage error or I/O failure
|
|
50
|
+
|
|
51
|
+
EXAMPLES
|
|
52
|
+
qscan . Scan the current directory
|
|
53
|
+
qscan src --format sarif -o qscan.sarif
|
|
54
|
+
qscan . --severity-threshold critical
|
|
55
|
+
qscan . --write-baseline qscan-baseline.json
|
|
56
|
+
qscan . --baseline qscan-baseline.json
|
|
57
|
+
qscan . --include src --include lib
|
|
58
|
+
qscan . --config ./ci/quantakrypto.config.json
|
|
59
|
+
qscan . --changed --since origin/main
|
|
60
|
+
qscan . --parallel --concurrency 4
|
|
61
|
+
qscan . --cbom -o qscan-cbom.json
|
|
62
|
+
`;
|
|
63
|
+
/** The `--version` line. */
|
|
64
|
+
export function versionLine() {
|
|
65
|
+
return `qscan ${VERSION}`;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=help.js.map
|
package/dist/help.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"help.js","sourceRoot":"","sources":["../src/help.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAE7C,gCAAgC;AAChC,MAAM,CAAC,MAAM,SAAS,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqDxB,CAAC;AAEF,4BAA4B;AAC5B,MAAM,UAAU,WAAW;IACzB,OAAO,SAAS,OAAO,EAAE,CAAC;AAC5B,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
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 type { Baseline, Finding, ParallelScanOptions, ScanResult } from "@quantakrypto/core";
|
|
13
|
+
import type { QscanOptions } from "./args.js";
|
|
14
|
+
export type { QscanOptions, ParsedArgs, ParsedRun, QscanFormat } from "./args.js";
|
|
15
|
+
export type { Baseline } from "./baseline.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
|
+
export type { ResolvedConfig } from "./config.js";
|
|
22
|
+
export type { ConfigurableKey } from "./args.js";
|
|
23
|
+
/** Process-style exit codes qScan uses. */
|
|
24
|
+
export declare const EXIT: {
|
|
25
|
+
/** No findings at/above threshold, or a baseline was written. */
|
|
26
|
+
readonly OK: 0;
|
|
27
|
+
/** One or more findings at/above the severity threshold. */
|
|
28
|
+
readonly FINDINGS: 1;
|
|
29
|
+
/** Usage error or I/O failure. */
|
|
30
|
+
readonly ERROR: 2;
|
|
31
|
+
};
|
|
32
|
+
/** Outcome of {@link runQscan}. */
|
|
33
|
+
export interface QscanRun {
|
|
34
|
+
/** The scan result, with the baseline already applied to `findings`. */
|
|
35
|
+
result: ScanResult;
|
|
36
|
+
/** Findings suppressed because their fingerprint was in the baseline. */
|
|
37
|
+
suppressed: Finding[];
|
|
38
|
+
/** Rendered report in the requested format (`undefined` for a baseline write). */
|
|
39
|
+
report?: string;
|
|
40
|
+
/** The baseline that was written, when `writeBaseline` was requested. */
|
|
41
|
+
baselineWritten?: Baseline;
|
|
42
|
+
/** Suggested process exit code. */
|
|
43
|
+
exitCode: number;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The scan implementation `runQscan` calls. Matches `@quantakrypto/core`'s `scan` /
|
|
47
|
+
* `scanParallel` (parallel options are a superset of `ScanOptions`).
|
|
48
|
+
* Injectable so the GitHub Action and tests can supply a custom scanner.
|
|
49
|
+
*/
|
|
50
|
+
export type ScanFn = (options: ParallelScanOptions) => Promise<ScanResult>;
|
|
51
|
+
/**
|
|
52
|
+
* Resolve the changed-file list for incremental scans. Injectable for testing;
|
|
53
|
+
* defaults to core's git-aware {@link changedFiles}.
|
|
54
|
+
*/
|
|
55
|
+
export type ChangedFilesFn = (root: string, since?: string) => Promise<string[]>;
|
|
56
|
+
/** Behavioral hooks for {@link runQscan}, mainly for testing. */
|
|
57
|
+
export interface RunQscanHooks {
|
|
58
|
+
/** Emit raw ANSI color in the human report. Default: false. */
|
|
59
|
+
color?: boolean;
|
|
60
|
+
/** Override the scanner. Default: `scan` / `scanParallel` from `@quantakrypto/core`. */
|
|
61
|
+
scanFn?: ScanFn;
|
|
62
|
+
/** Override changed-file resolution. Default: `changedFiles` from `@quantakrypto/core`. */
|
|
63
|
+
changedFilesFn?: ChangedFilesFn;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Run a complete qScan pass: scan → baseline → threshold → render.
|
|
67
|
+
*
|
|
68
|
+
* This never touches `process` or stdout; the CLI is responsible for printing
|
|
69
|
+
* `report`/writing `output` and calling `process.exit(exitCode)`. That keeps
|
|
70
|
+
* the function pure enough to unit-test and to embed in the GitHub Action.
|
|
71
|
+
*
|
|
72
|
+
* Behavior:
|
|
73
|
+
* - The walk is configured by `include` / `ignore` / `maxFileSize` /
|
|
74
|
+
* `noDefaultIgnores` / `scanMinified`.
|
|
75
|
+
* - With `changed` set, only the files git reports as changed (relative to
|
|
76
|
+
* `since`, if given) are scanned via `ScanOptions.files`. A non-git tree
|
|
77
|
+
* yields an empty list, so nothing is scanned.
|
|
78
|
+
* - With `parallel` (or `concurrency`) set, the scan is routed through core's
|
|
79
|
+
* `scanParallel`, which itself falls back to the serial path for small
|
|
80
|
+
* inputs.
|
|
81
|
+
* - When `opts.writeBaseline` is set, the scan runs, a baseline is built from
|
|
82
|
+
* *all* findings, written to disk, and `exitCode` is {@link EXIT.OK}. No
|
|
83
|
+
* report is rendered.
|
|
84
|
+
* - When `opts.baseline` is set, its fingerprints are loaded and matching
|
|
85
|
+
* findings are moved to `suppressed` (and removed from `result.findings`).
|
|
86
|
+
* - `exitCode` is {@link EXIT.FINDINGS} when any *kept* finding meets the
|
|
87
|
+
* severity threshold, else {@link EXIT.OK}.
|
|
88
|
+
*
|
|
89
|
+
* @throws {Error} Propagates scan / baseline I/O errors; the CLI maps these to
|
|
90
|
+
* {@link EXIT.ERROR}.
|
|
91
|
+
*/
|
|
92
|
+
export declare function runQscan(opts: Partial<QscanOptions> & {
|
|
93
|
+
path: string;
|
|
94
|
+
}, hooks?: RunQscanHooks): Promise<QscanRun>;
|
|
95
|
+
/** Render a scan result in the requested format. */
|
|
96
|
+
export declare function renderReport(result: ScanResult, format: QscanOptions["format"], color?: boolean): string;
|
|
97
|
+
/** Re-export the core result types consumers commonly need. */
|
|
98
|
+
export type { Finding, ScanResult, ScanOptions } from "@quantakrypto/core";
|
|
99
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAI7F,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAG9C,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAClF,YAAY,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EACL,QAAQ,EACR,QAAQ,EACR,KAAK,EACL,UAAU,EACV,cAAc,EACd,cAAc,EACd,SAAS,EACT,YAAY,EACZ,cAAc,GACf,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,aAAa,EACb,oBAAoB,EACpB,gBAAgB,EAChB,aAAa,EACb,WAAW,EACX,kBAAkB,EAClB,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,aAAa,GACd,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/E,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACzD,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,YAAY,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAEjD,2CAA2C;AAC3C,eAAO,MAAM,IAAI;IACf,iEAAiE;;IAEjE,4DAA4D;;IAE5D,kCAAkC;;CAE1B,CAAC;AAEX,mCAAmC;AACnC,MAAM,WAAW,QAAQ;IACvB,wEAAwE;IACxE,MAAM,EAAE,UAAU,CAAC;IACnB,yEAAyE;IACzE,UAAU,EAAE,OAAO,EAAE,CAAC;IACtB,kFAAkF;IAClF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yEAAyE;IACzE,eAAe,CAAC,EAAE,QAAQ,CAAC;IAC3B,mCAAmC;IACnC,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;GAIG;AACH,MAAM,MAAM,MAAM,GAAG,CAAC,OAAO,EAAE,mBAAmB,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;AAE3E;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;AAEjF,iEAAiE;AACjE,MAAM,WAAW,aAAa;IAC5B,+DAA+D;IAC/D,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,wFAAwF;IACxF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2FAA2F;IAC3F,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAsBD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAsB,QAAQ,CAC5B,IAAI,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,EAC9C,KAAK,GAAE,aAAkB,GACxB,OAAO,CAAC,QAAQ,CAAC,CA+CnB;AAED,oDAAoD;AACpD,wBAAgB,YAAY,CAC1B,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,YAAY,CAAC,QAAQ,CAAC,EAC9B,KAAK,UAAQ,GACZ,MAAM,CAYR;AAED,+DAA+D;AAC/D,YAAY,EAAE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC"}
|