@quantakrypto/qscan 0.4.3 → 0.5.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 +39 -10
- package/dist/args.d.ts +30 -8
- package/dist/args.d.ts.map +1 -1
- package/dist/args.js +38 -5
- package/dist/args.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +18 -9
- package/dist/cli.js.map +1 -1
- package/dist/color.d.ts +38 -0
- package/dist/color.d.ts.map +1 -0
- package/dist/color.js +44 -0
- package/dist/color.js.map +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +41 -1
- package/dist/config.js.map +1 -1
- package/dist/help.d.ts +1 -1
- package/dist/help.d.ts.map +1 -1
- package/dist/help.js +24 -2
- package/dist/help.js.map +1 -1
- package/dist/index.d.ts +13 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +90 -20
- package/dist/index.js.map +1 -1
- package/dist/remediate-cli.d.ts +3 -4
- package/dist/remediate-cli.d.ts.map +1 -1
- package/dist/remediate-cli.js +1 -1
- package/dist/remediate-cli.js.map +1 -1
- package/dist/report.d.ts +13 -2
- package/dist/report.d.ts.map +1 -1
- package/dist/report.js +59 -6
- package/dist/report.js.map +1 -1
- package/dist/sign.d.ts +4 -0
- package/dist/sign.d.ts.map +1 -0
- package/dist/sign.js +54 -0
- package/dist/sign.js.map +1 -0
- package/dist/triage-run.d.ts +3 -4
- package/dist/triage-run.d.ts.map +1 -1
- package/dist/triage-run.js +13 -6
- package/dist/triage-run.js.map +1 -1
- package/package.json +3 -3
package/dist/color.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Color-output policy for the human report.
|
|
3
|
+
*
|
|
4
|
+
* ANSI color is decoration, never the sole carrier of meaning (every severity,
|
|
5
|
+
* count, and score is also printed as text), so turning it off never loses
|
|
6
|
+
* information — it's an accessibility and pipe-safety control. This module is the
|
|
7
|
+
* single place that decides whether to emit it, with an explicit precedence so
|
|
8
|
+
* the behaviour is predictable and testable:
|
|
9
|
+
*
|
|
10
|
+
* 1. Non-`human` formats are NEVER colored — ANSI would corrupt JSON/SARIF/CBOM.
|
|
11
|
+
* 2. An explicit `--color` / `--no-color` flag wins over everything else.
|
|
12
|
+
* 3. `NO_COLOR` (present, non-empty) disables — https://no-color.org.
|
|
13
|
+
* 4. `FORCE_COLOR` enables (Node/supports-color convention: `0`/`false` disable,
|
|
14
|
+
* any other value — including empty — enables).
|
|
15
|
+
* 5. Otherwise: color only a live terminal (`stdout.isTTY`), never a file or pipe.
|
|
16
|
+
*/
|
|
17
|
+
/** NO_COLOR counts when present and non-empty (per the no-color.org wording). */
|
|
18
|
+
function noColorRequested(v) {
|
|
19
|
+
return v !== undefined && v !== "";
|
|
20
|
+
}
|
|
21
|
+
/** FORCE_COLOR enables unless it is `0`/`false` (Node/supports-color semantics). */
|
|
22
|
+
function forceColorRequested(v) {
|
|
23
|
+
return v !== undefined && v !== "0" && v.toLowerCase() !== "false";
|
|
24
|
+
}
|
|
25
|
+
/** Resolve whether the human report should emit ANSI color. */
|
|
26
|
+
export function resolveColor(ctx) {
|
|
27
|
+
// (1) Machine-readable formats must stay byte-clean.
|
|
28
|
+
if (ctx.format !== "human")
|
|
29
|
+
return false;
|
|
30
|
+
// (2) Explicit CLI intent is absolute.
|
|
31
|
+
if (ctx.choice === "never")
|
|
32
|
+
return false;
|
|
33
|
+
if (ctx.choice === "always")
|
|
34
|
+
return true;
|
|
35
|
+
// (3) Accessibility opt-out wins over FORCE_COLOR when both are set.
|
|
36
|
+
if (noColorRequested(ctx.env.NO_COLOR))
|
|
37
|
+
return false;
|
|
38
|
+
// (4) FORCE_COLOR, when present, decides (enables, unless 0/false).
|
|
39
|
+
if (ctx.env.FORCE_COLOR !== undefined)
|
|
40
|
+
return forceColorRequested(ctx.env.FORCE_COLOR);
|
|
41
|
+
// (5) Auto: a live terminal only.
|
|
42
|
+
return ctx.isTTY && !ctx.toFile;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=color.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"color.js","sourceRoot":"","sources":["../src/color.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAuBH,iFAAiF;AACjF,SAAS,gBAAgB,CAAC,CAAqB;IAC7C,OAAO,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,EAAE,CAAC;AACrC,CAAC;AAED,oFAAoF;AACpF,SAAS,mBAAmB,CAAC,CAAqB;IAChD,OAAO,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE,KAAK,OAAO,CAAC;AACrE,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,YAAY,CAAC,GAAiB;IAC5C,qDAAqD;IACrD,IAAI,GAAG,CAAC,MAAM,KAAK,OAAO;QAAE,OAAO,KAAK,CAAC;IAEzC,uCAAuC;IACvC,IAAI,GAAG,CAAC,MAAM,KAAK,OAAO;QAAE,OAAO,KAAK,CAAC;IACzC,IAAI,GAAG,CAAC,MAAM,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAEzC,qEAAqE;IACrE,IAAI,gBAAgB,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC;QAAE,OAAO,KAAK,CAAC;IAErD,oEAAoE;IACpE,IAAI,GAAG,CAAC,GAAG,CAAC,WAAW,KAAK,SAAS;QAAE,OAAO,mBAAmB,CAAC,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAEvF,kCAAkC;IAClC,OAAO,GAAG,CAAC,KAAK,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC;AAClC,CAAC","sourcesContent":["/**\n * Color-output policy for the human report.\n *\n * ANSI color is decoration, never the sole carrier of meaning (every severity,\n * count, and score is also printed as text), so turning it off never loses\n * information — it's an accessibility and pipe-safety control. This module is the\n * single place that decides whether to emit it, with an explicit precedence so\n * the behaviour is predictable and testable:\n *\n * 1. Non-`human` formats are NEVER colored — ANSI would corrupt JSON/SARIF/CBOM.\n * 2. An explicit `--color` / `--no-color` flag wins over everything else.\n * 3. `NO_COLOR` (present, non-empty) disables — https://no-color.org.\n * 4. `FORCE_COLOR` enables (Node/supports-color convention: `0`/`false` disable,\n * any other value — including empty — enables).\n * 5. Otherwise: color only a live terminal (`stdout.isTTY`), never a file or pipe.\n */\n\n/** What the user asked for on the command line. `\"auto\"` = decide from context. */\nexport type ColorChoice = \"always\" | \"never\" | \"auto\";\n\n/** Just the environment variables the decision reads. */\ninterface ColorEnv {\n NO_COLOR?: string | undefined;\n FORCE_COLOR?: string | undefined;\n}\n\nexport interface ColorContext {\n /** From `--color` / `--no-color`; defaults to `\"auto\"`. */\n choice: ColorChoice;\n /** Report format; only `\"human\"` is ever colored. */\n format: string;\n /** True when the report goes to an output file rather than stdout. */\n toFile: boolean;\n /** `process.stdout.isTTY`. */\n isTTY: boolean;\n env: ColorEnv;\n}\n\n/** NO_COLOR counts when present and non-empty (per the no-color.org wording). */\nfunction noColorRequested(v: string | undefined): boolean {\n return v !== undefined && v !== \"\";\n}\n\n/** FORCE_COLOR enables unless it is `0`/`false` (Node/supports-color semantics). */\nfunction forceColorRequested(v: string | undefined): boolean {\n return v !== undefined && v !== \"0\" && v.toLowerCase() !== \"false\";\n}\n\n/** Resolve whether the human report should emit ANSI color. */\nexport function resolveColor(ctx: ColorContext): boolean {\n // (1) Machine-readable formats must stay byte-clean.\n if (ctx.format !== \"human\") return false;\n\n // (2) Explicit CLI intent is absolute.\n if (ctx.choice === \"never\") return false;\n if (ctx.choice === \"always\") return true;\n\n // (3) Accessibility opt-out wins over FORCE_COLOR when both are set.\n if (noColorRequested(ctx.env.NO_COLOR)) return false;\n\n // (4) FORCE_COLOR, when present, decides (enables, unless 0/false).\n if (ctx.env.FORCE_COLOR !== undefined) return forceColorRequested(ctx.env.FORCE_COLOR);\n\n // (5) Auto: a live terminal only.\n return ctx.isTTY && !ctx.toFile;\n}\n"]}
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +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,
|
|
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,CA4BzB;AAwCD;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,YAAY,EACrB,MAAM,EAAE,sBAAsB,EAC9B,QAAQ,EAAE,WAAW,CAAC,eAAe,CAAC,GACrC,YAAY,CAwCd"}
|
package/dist/config.js
CHANGED
|
@@ -36,7 +36,47 @@ export async function resolveConfig(options, explicit) {
|
|
|
36
36
|
return { options, warnings: loaded.warnings };
|
|
37
37
|
}
|
|
38
38
|
const merged = applyConfig(options, loaded.config, explicit);
|
|
39
|
-
|
|
39
|
+
// Security: an AUTO-DISCOVERED config (no explicit `--config`) can come from the
|
|
40
|
+
// scanned tree itself, so a hostile repo could silently WEAKEN its own scan
|
|
41
|
+
// (disable rules, raise the severity threshold, exclude files → flip exit 1→0).
|
|
42
|
+
// Never silently: warn loudly for each policy-weakening key it applied. An explicit
|
|
43
|
+
// `--config` is the operator's own choice and stays quiet. Use `--no-config-file`
|
|
44
|
+
// to ignore a discovered config entirely.
|
|
45
|
+
const warnings = [...loaded.warnings];
|
|
46
|
+
if (options.configFile === undefined) {
|
|
47
|
+
warnings.push(...weakeningWarnings(loaded.config, explicit));
|
|
48
|
+
}
|
|
49
|
+
return { options: merged, configPath: loaded.path, warnings };
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Warnings for each SCAN-WEAKENING key an auto-discovered config actually applied
|
|
53
|
+
* (present in the file and not overridden by a CLI flag). These are the keys that can
|
|
54
|
+
* make a scan pass that would otherwise fail, so an operator scanning an untrusted
|
|
55
|
+
* tree must see them rather than have the verdict silently softened.
|
|
56
|
+
*/
|
|
57
|
+
function weakeningWarnings(config, explicit) {
|
|
58
|
+
const w = [];
|
|
59
|
+
const applied = (key) => !explicit.has(key);
|
|
60
|
+
const note = (s) => w.push(`auto-discovered config ${s} — a scanned repo may author this; re-run with --no-config-file to ignore it`);
|
|
61
|
+
if (config.disabledRules && config.disabledRules.length > 0) {
|
|
62
|
+
note(`disabled ${config.disabledRules.length} detection rule(s): ${config.disabledRules.join(", ")}`);
|
|
63
|
+
}
|
|
64
|
+
if (config.severityThreshold !== undefined && applied("severityThreshold")) {
|
|
65
|
+
note(`set the failure severity-threshold to "${config.severityThreshold}"`);
|
|
66
|
+
}
|
|
67
|
+
if (config.exclude && config.exclude.length > 0) {
|
|
68
|
+
note(`excluded ${config.exclude.length} path pattern(s) from the scan`);
|
|
69
|
+
}
|
|
70
|
+
if (config.source === false && applied("source"))
|
|
71
|
+
note("disabled source scanning");
|
|
72
|
+
if (config.dependencies === false && applied("dependencies"))
|
|
73
|
+
note("disabled dependency scanning");
|
|
74
|
+
if (config.config === false && applied("config"))
|
|
75
|
+
note("disabled config-file scanning");
|
|
76
|
+
if (config.maxFileSize !== undefined && applied("maxFileSize")) {
|
|
77
|
+
note(`capped scanned file size at ${config.maxFileSize} bytes`);
|
|
78
|
+
}
|
|
79
|
+
return w;
|
|
40
80
|
}
|
|
41
81
|
/**
|
|
42
82
|
* Apply a parsed config onto options under the precedence rule. Pure; returns a
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * qScan-side resolution of `quantakrypto.config.json` (ROADMAP P2-9, docs/CONFIG.md).\n *\n * core's {@link loadConfig} does the reading + type-validation; this module\n * applies the file's values onto parsed CLI options with the documented\n * precedence:\n *\n * CLI flags > quantakrypto.config.json > built-in defaults\n *\n * Resolution is **per-key**: a key set by a flag (tracked in the `explicit` set\n * from `parseArgs`) is left alone; otherwise the config value fills it. The\n * list-valued keys (`include` / `ignore`→`exclude`) *append* — the config\n * provides a base set and any CLI flags add to it (docs/CONFIG.md §4.2).\n */\n\nimport { loadConfig } from \"@quantakrypto/core\";\nimport type { QuantakryptoFileConfig } from \"@quantakrypto/core\";\n\nimport type { ConfigurableKey, QscanOptions } from \"./args.js\";\n\n/** What {@link resolveConfig} returns: the merged options + provenance. */\nexport interface ResolvedConfig {\n /** Options with config applied under the flags > config > defaults rule. */\n options: QscanOptions;\n /** Absolute path of the config file that was applied, when one was. */\n configPath?: string;\n /** Non-fatal warnings from parsing (unknown keys, future version, …). */\n warnings: string[];\n}\n\n/**\n * Load and merge `quantakrypto.config.json` into the parsed CLI options.\n *\n * @param options Fully-resolved options from {@link parseArgs} (defaults filled).\n * @param explicit The set of configurable keys the user set via a flag.\n * @returns The merged options plus the applied config path + any warnings.\n * @throws {ConfigError} (from core) on a malformed config or a missing\n * explicitly-named `--config` file. The CLI maps this to exit 2.\n */\nexport async function resolveConfig(\n options: QscanOptions,\n explicit: ReadonlySet<ConfigurableKey>,\n): Promise<ResolvedConfig> {\n // `--no-config-file` disables discovery entirely; nothing to merge.\n if (options.noConfigFile && options.configFile === undefined) {\n return { options, warnings: [] };\n }\n\n // `--config <path>` names the file explicitly (a missing file is then fatal);\n // otherwise auto-discover at the scan root.\n const target = options.configFile ?? options.path;\n const loaded = await loadConfig(target, { explicit: options.configFile !== undefined });\n\n if (loaded.path === undefined) {\n // No file found (auto-discovery, tolerant): options unchanged.\n return { options, warnings: loaded.warnings };\n }\n\n const merged = applyConfig(options, loaded.config, explicit);\n return { options: merged, configPath: loaded.path, warnings: loaded.warnings };\n}\n\n/**\n * Apply a parsed config onto options under the precedence rule. Pure; returns a\n * new options object. Scalars: config fills only keys NOT set by a flag. Lists:\n * config provides the base and the CLI flag values are appended.\n */\nexport function applyConfig(\n options: QscanOptions,\n config: QuantakryptoFileConfig,\n explicit: ReadonlySet<ConfigurableKey>,\n): QscanOptions {\n const out: QscanOptions = {\n ...options,\n ignore: [...options.ignore],\n include: [...options.include],\n };\n\n /** Set a scalar key from config only when the user didn't set it via a flag. */\n const fillScalar = <K extends ConfigurableKey & keyof QscanOptions>(\n key: K,\n value: QscanOptions[K] | undefined,\n ): void => {\n if (value === undefined) return;\n if (explicit.has(key)) return; // flag wins.\n out[key] = value;\n };\n\n fillScalar(\"severityThreshold\", config.severityThreshold);\n fillScalar(\"source\", config.source);\n fillScalar(\"dependencies\", config.dependencies);\n fillScalar(\"config\", config.config);\n fillScalar(\"noDefaultIgnores\", config.noDefaultIgnores);\n fillScalar(\"scanMinified\", config.scanMinified);\n fillScalar(\"maxFileSize\", config.maxFileSize);\n fillScalar(\"baseline\", config.baseline);\n\n // List-valued keys: config is the base, CLI flags append (config first so the\n // committed policy reads as the baseline, ad-hoc CLI excludes/includes after).\n if (config.exclude && config.exclude.length > 0) {\n out.ignore = [...config.exclude, ...options.ignore];\n }\n if (config.include && config.include.length > 0) {\n out.include = [...config.include, ...options.include];\n }\n // disabledRules is config-only (no CLI flag today) — set it straight through.\n if (config.disabledRules && config.disabledRules.length > 0) {\n out.disabledRules = [...config.disabledRules];\n }\n\n return out;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * qScan-side resolution of `quantakrypto.config.json` (ROADMAP P2-9, docs/CONFIG.md).\n *\n * core's {@link loadConfig} does the reading + type-validation; this module\n * applies the file's values onto parsed CLI options with the documented\n * precedence:\n *\n * CLI flags > quantakrypto.config.json > built-in defaults\n *\n * Resolution is **per-key**: a key set by a flag (tracked in the `explicit` set\n * from `parseArgs`) is left alone; otherwise the config value fills it. The\n * list-valued keys (`include` / `ignore`→`exclude`) *append* — the config\n * provides a base set and any CLI flags add to it (docs/CONFIG.md §4.2).\n */\n\nimport { loadConfig } from \"@quantakrypto/core\";\nimport type { QuantakryptoFileConfig } from \"@quantakrypto/core\";\n\nimport type { ConfigurableKey, QscanOptions } from \"./args.js\";\n\n/** What {@link resolveConfig} returns: the merged options + provenance. */\nexport interface ResolvedConfig {\n /** Options with config applied under the flags > config > defaults rule. */\n options: QscanOptions;\n /** Absolute path of the config file that was applied, when one was. */\n configPath?: string;\n /** Non-fatal warnings from parsing (unknown keys, future version, …). */\n warnings: string[];\n}\n\n/**\n * Load and merge `quantakrypto.config.json` into the parsed CLI options.\n *\n * @param options Fully-resolved options from {@link parseArgs} (defaults filled).\n * @param explicit The set of configurable keys the user set via a flag.\n * @returns The merged options plus the applied config path + any warnings.\n * @throws {ConfigError} (from core) on a malformed config or a missing\n * explicitly-named `--config` file. The CLI maps this to exit 2.\n */\nexport async function resolveConfig(\n options: QscanOptions,\n explicit: ReadonlySet<ConfigurableKey>,\n): Promise<ResolvedConfig> {\n // `--no-config-file` disables discovery entirely; nothing to merge.\n if (options.noConfigFile && options.configFile === undefined) {\n return { options, warnings: [] };\n }\n\n // `--config <path>` names the file explicitly (a missing file is then fatal);\n // otherwise auto-discover at the scan root.\n const target = options.configFile ?? options.path;\n const loaded = await loadConfig(target, { explicit: options.configFile !== undefined });\n\n if (loaded.path === undefined) {\n // No file found (auto-discovery, tolerant): options unchanged.\n return { options, warnings: loaded.warnings };\n }\n\n const merged = applyConfig(options, loaded.config, explicit);\n // Security: an AUTO-DISCOVERED config (no explicit `--config`) can come from the\n // scanned tree itself, so a hostile repo could silently WEAKEN its own scan\n // (disable rules, raise the severity threshold, exclude files → flip exit 1→0).\n // Never silently: warn loudly for each policy-weakening key it applied. An explicit\n // `--config` is the operator's own choice and stays quiet. Use `--no-config-file`\n // to ignore a discovered config entirely.\n const warnings = [...loaded.warnings];\n if (options.configFile === undefined) {\n warnings.push(...weakeningWarnings(loaded.config, explicit));\n }\n return { options: merged, configPath: loaded.path, warnings };\n}\n\n/**\n * Warnings for each SCAN-WEAKENING key an auto-discovered config actually applied\n * (present in the file and not overridden by a CLI flag). These are the keys that can\n * make a scan pass that would otherwise fail, so an operator scanning an untrusted\n * tree must see them rather than have the verdict silently softened.\n */\nfunction weakeningWarnings(\n config: QuantakryptoFileConfig,\n explicit: ReadonlySet<ConfigurableKey>,\n): string[] {\n const w: string[] = [];\n const applied = <K extends ConfigurableKey>(key: K): boolean => !explicit.has(key);\n const note = (s: string) =>\n w.push(\n `auto-discovered config ${s} — a scanned repo may author this; re-run with --no-config-file to ignore it`,\n );\n\n if (config.disabledRules && config.disabledRules.length > 0) {\n note(\n `disabled ${config.disabledRules.length} detection rule(s): ${config.disabledRules.join(\", \")}`,\n );\n }\n if (config.severityThreshold !== undefined && applied(\"severityThreshold\")) {\n note(`set the failure severity-threshold to \"${config.severityThreshold}\"`);\n }\n if (config.exclude && config.exclude.length > 0) {\n note(`excluded ${config.exclude.length} path pattern(s) from the scan`);\n }\n if (config.source === false && applied(\"source\")) note(\"disabled source scanning\");\n if (config.dependencies === false && applied(\"dependencies\"))\n note(\"disabled dependency scanning\");\n if (config.config === false && applied(\"config\")) note(\"disabled config-file scanning\");\n if (config.maxFileSize !== undefined && applied(\"maxFileSize\")) {\n note(`capped scanned file size at ${config.maxFileSize} bytes`);\n }\n return w;\n}\n\n/**\n * Apply a parsed config onto options under the precedence rule. Pure; returns a\n * new options object. Scalars: config fills only keys NOT set by a flag. Lists:\n * config provides the base and the CLI flag values are appended.\n */\nexport function applyConfig(\n options: QscanOptions,\n config: QuantakryptoFileConfig,\n explicit: ReadonlySet<ConfigurableKey>,\n): QscanOptions {\n const out: QscanOptions = {\n ...options,\n ignore: [...options.ignore],\n include: [...options.include],\n };\n\n /** Set a scalar key from config only when the user didn't set it via a flag. */\n const fillScalar = <K extends ConfigurableKey & keyof QscanOptions>(\n key: K,\n value: QscanOptions[K] | undefined,\n ): void => {\n if (value === undefined) return;\n if (explicit.has(key)) return; // flag wins.\n out[key] = value;\n };\n\n fillScalar(\"severityThreshold\", config.severityThreshold);\n fillScalar(\"source\", config.source);\n fillScalar(\"dependencies\", config.dependencies);\n fillScalar(\"config\", config.config);\n fillScalar(\"noDefaultIgnores\", config.noDefaultIgnores);\n fillScalar(\"scanMinified\", config.scanMinified);\n fillScalar(\"maxFileSize\", config.maxFileSize);\n fillScalar(\"baseline\", config.baseline);\n\n // List-valued keys: config is the base, CLI flags append (config first so the\n // committed policy reads as the baseline, ad-hoc CLI excludes/includes after).\n if (config.exclude && config.exclude.length > 0) {\n out.ignore = [...config.exclude, ...options.ignore];\n }\n if (config.include && config.include.length > 0) {\n out.include = [...config.include, ...options.include];\n }\n // disabledRules is config-only (no CLI flag today) — set it straight through.\n if (config.disabledRules && config.disabledRules.length > 0) {\n out.disabledRules = [...config.disabledRules];\n }\n\n return out;\n}\n"]}
|
package/dist/help.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* filesystem or process side effects.
|
|
6
6
|
*/
|
|
7
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|evidence>\n Output format (default: human)\n --cbom Alias for --format cbom (CycloneDX CBOM)\n --format evidence ISO 27001 A.8.24 readiness report \u2014 findings +\n inventory + CBOM + a deterministic content hash\n (sign/timestamp it with an external signer)\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 0 forces the in-process serial path\n --top <n> List <n> findings in the human report (default: 5)\n --tier <category-3|category-5> Add CNSA migration targets to the report footer\n (category-5 = CNSA 2.0: ML-KEM-1024 / ML-DSA-87)\n --cache [file] Reuse findings for unchanged files across runs\n (default file: .quantakrypto-cache.json)\n --triage BYOK LLM pass that re-ranks findings by real\n exposure and explains them (never suppresses,\n never changes the exit code). Needs an API key in\n QK_LLM_API_KEY / ANTHROPIC_API_KEY / OPENAI_API_KEY\n --triage-floor <level> Only triage findings at/above this level (default: medium)\n --max-findings <n> Cap findings sent to the LLM during triage (default: 100; spend guard)\n --context <level> Source shared with the LLM: metadata|snippet|\n function|file (default: snippet; secrets always redacted)\n --dry-run With --triage, print the exact payload that would\n be sent and exit without calling the provider\n --llm-provider <name> anthropic | openai-compatible (default: anthropic)\n --llm-model <id> Model id for the BYOK provider\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 --no-snippets Omit code snippets from the json/sarif report\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";
|
|
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|evidence|vex>\n Output format (default: human)\n --cbom Alias for --format cbom (CycloneDX CBOM)\n --format vex OpenVEX 0.2.0 document \u2014 one statement per rule,\n status \"affected\", with remediation + any\n --triage verdict for supply-chain VEX pipelines\n --merge <cbom.json> Merge an external CBOM (e.g. a qprobe endpoint\n CBOM) into the --cbom output via CycloneDX\n bom-link \u2014 one combined code + infra CBOM.\n Repeatable.\n --format evidence ISO 27001 A.8.24 readiness report \u2014 findings +\n inventory + CBOM + a deterministic content hash\n (sign/timestamp it with an external signer)\n --policy <file> Crypto-policy JSON; adds conformant/violation/\n transition verdicts to the evidence report\n --sign <command> Sign the evidence report: its contentHash is\n piped to <command> on stdin; stdout is recorded\n as the detached signature (needs --format evidence)\n --timestamp <command> Like --sign, but records an RFC-3161 timestamp\n token from <command> (needs --format evidence)\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 0 forces the in-process serial path\n --top <n> List <n> findings in the human report (default: 5)\n --tier <category-3|category-5> Add CNSA migration targets to the report footer\n (category-5 = CNSA 2.0: ML-KEM-1024 / ML-DSA-87;\n an alias for --profile cnsa-2.0)\n --profile <id> Tailor migration guidance to a standards regime:\n nist (default) | cnsa-2.0 | bsi-tr-02102 | anssi |\n uk-ncsc. Sets the parameter sets, deadlines, and\n whether hybridization is required/recommended/optional\n --cache [file] Reuse findings for unchanged files across runs\n (default file: .quantakrypto-cache.json)\n --triage BYOK LLM pass that re-ranks findings by real\n exposure and explains them (never suppresses,\n never changes the exit code). Needs an API key in\n QK_LLM_API_KEY / ANTHROPIC_API_KEY / OPENAI_API_KEY\n --triage-floor <level> Only triage findings at/above this level (default: medium)\n --max-findings <n> Cap findings sent to the LLM during triage (default: 100; spend guard)\n --context <level> Source shared with the LLM: metadata|snippet|\n function|file (default: snippet; secrets always redacted)\n --dry-run With --triage, print the exact payload that would\n be sent and exit without calling the provider\n --llm-provider <name> anthropic | openai-compatible (default: anthropic)\n --llm-model <id> Model id for the BYOK provider\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 --no-snippets Omit code snippets from the json/sarif report\n --color Force ANSI color in the human report\n --no-color Disable ANSI color (also: NO_COLOR env). Color is\n decoration only \u2014 every signal is printed as text\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
9
|
/** The `--version` line. */
|
|
10
10
|
export declare function versionLine(): string;
|
|
11
11
|
//# sourceMappingURL=help.d.ts.map
|
package/dist/help.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../src/help.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,gCAAgC;AAChC,eAAO,MAAM,SAAS,
|
|
1
|
+
{"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../src/help.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,gCAAgC;AAChC,eAAO,MAAM,SAAS,yrMAiGrB,CAAC;AAEF,4BAA4B;AAC5B,wBAAgB,WAAW,IAAI,MAAM,CAEpC"}
|
package/dist/help.js
CHANGED
|
@@ -15,12 +15,26 @@ ARGUMENTS
|
|
|
15
15
|
path Directory or file to scan (default: ".")
|
|
16
16
|
|
|
17
17
|
OPTIONS
|
|
18
|
-
--format <human|json|sarif|cbom|evidence>
|
|
18
|
+
--format <human|json|sarif|cbom|evidence|vex>
|
|
19
19
|
Output format (default: human)
|
|
20
20
|
--cbom Alias for --format cbom (CycloneDX CBOM)
|
|
21
|
+
--format vex OpenVEX 0.2.0 document — one statement per rule,
|
|
22
|
+
status "affected", with remediation + any
|
|
23
|
+
--triage verdict for supply-chain VEX pipelines
|
|
24
|
+
--merge <cbom.json> Merge an external CBOM (e.g. a qprobe endpoint
|
|
25
|
+
CBOM) into the --cbom output via CycloneDX
|
|
26
|
+
bom-link — one combined code + infra CBOM.
|
|
27
|
+
Repeatable.
|
|
21
28
|
--format evidence ISO 27001 A.8.24 readiness report — findings +
|
|
22
29
|
inventory + CBOM + a deterministic content hash
|
|
23
30
|
(sign/timestamp it with an external signer)
|
|
31
|
+
--policy <file> Crypto-policy JSON; adds conformant/violation/
|
|
32
|
+
transition verdicts to the evidence report
|
|
33
|
+
--sign <command> Sign the evidence report: its contentHash is
|
|
34
|
+
piped to <command> on stdin; stdout is recorded
|
|
35
|
+
as the detached signature (needs --format evidence)
|
|
36
|
+
--timestamp <command> Like --sign, but records an RFC-3161 timestamp
|
|
37
|
+
token from <command> (needs --format evidence)
|
|
24
38
|
-o, --output <file> Write the report to a file instead of stdout
|
|
25
39
|
--severity-threshold <level> Fail (exit 1) on findings at/above this level;
|
|
26
40
|
one of critical|high|medium|low|info
|
|
@@ -43,7 +57,12 @@ OPTIONS
|
|
|
43
57
|
0 forces the in-process serial path
|
|
44
58
|
--top <n> List <n> findings in the human report (default: 5)
|
|
45
59
|
--tier <category-3|category-5> Add CNSA migration targets to the report footer
|
|
46
|
-
(category-5 = CNSA 2.0: ML-KEM-1024 / ML-DSA-87
|
|
60
|
+
(category-5 = CNSA 2.0: ML-KEM-1024 / ML-DSA-87;
|
|
61
|
+
an alias for --profile cnsa-2.0)
|
|
62
|
+
--profile <id> Tailor migration guidance to a standards regime:
|
|
63
|
+
nist (default) | cnsa-2.0 | bsi-tr-02102 | anssi |
|
|
64
|
+
uk-ncsc. Sets the parameter sets, deadlines, and
|
|
65
|
+
whether hybridization is required/recommended/optional
|
|
47
66
|
--cache [file] Reuse findings for unchanged files across runs
|
|
48
67
|
(default file: .quantakrypto-cache.json)
|
|
49
68
|
--triage BYOK LLM pass that re-ranks findings by real
|
|
@@ -62,6 +81,9 @@ OPTIONS
|
|
|
62
81
|
--write-baseline <file> Write current findings as a baseline, then exit 0
|
|
63
82
|
--quiet Suppress the human summary banner
|
|
64
83
|
--no-snippets Omit code snippets from the json/sarif report
|
|
84
|
+
--color Force ANSI color in the human report
|
|
85
|
+
--no-color Disable ANSI color (also: NO_COLOR env). Color is
|
|
86
|
+
decoration only — every signal is printed as text
|
|
65
87
|
-v, --version Print version and exit
|
|
66
88
|
-h, --help Print this help and exit
|
|
67
89
|
|
package/dist/help.js.map
CHANGED
|
@@ -1 +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
|
|
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiGxB,CAAC;AAEF,4BAA4B;AAC5B,MAAM,UAAU,WAAW;IACzB,OAAO,SAAS,OAAO,EAAE,CAAC;AAC5B,CAAC","sourcesContent":["/**\n * Static help / usage text for the qScan CLI.\n *\n * Kept in its own module so it can be unit-tested and reused without pulling in\n * filesystem or process side effects.\n */\n\nimport { VERSION } from \"@quantakrypto/core\";\n\n/** The full `--help` screen. */\nexport const HELP_TEXT = `qscan — 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|evidence|vex>\n Output format (default: human)\n --cbom Alias for --format cbom (CycloneDX CBOM)\n --format vex OpenVEX 0.2.0 document — one statement per rule,\n status \"affected\", with remediation + any\n --triage verdict for supply-chain VEX pipelines\n --merge <cbom.json> Merge an external CBOM (e.g. a qprobe endpoint\n CBOM) into the --cbom output via CycloneDX\n bom-link — one combined code + infra CBOM.\n Repeatable.\n --format evidence ISO 27001 A.8.24 readiness report — findings +\n inventory + CBOM + a deterministic content hash\n (sign/timestamp it with an external signer)\n --policy <file> Crypto-policy JSON; adds conformant/violation/\n transition verdicts to the evidence report\n --sign <command> Sign the evidence report: its contentHash is\n piped to <command> on stdin; stdout is recorded\n as the detached signature (needs --format evidence)\n --timestamp <command> Like --sign, but records an RFC-3161 timestamp\n token from <command> (needs --format evidence)\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 0 forces the in-process serial path\n --top <n> List <n> findings in the human report (default: 5)\n --tier <category-3|category-5> Add CNSA migration targets to the report footer\n (category-5 = CNSA 2.0: ML-KEM-1024 / ML-DSA-87;\n an alias for --profile cnsa-2.0)\n --profile <id> Tailor migration guidance to a standards regime:\n nist (default) | cnsa-2.0 | bsi-tr-02102 | anssi |\n uk-ncsc. Sets the parameter sets, deadlines, and\n whether hybridization is required/recommended/optional\n --cache [file] Reuse findings for unchanged files across runs\n (default file: .quantakrypto-cache.json)\n --triage BYOK LLM pass that re-ranks findings by real\n exposure and explains them (never suppresses,\n never changes the exit code). Needs an API key in\n QK_LLM_API_KEY / ANTHROPIC_API_KEY / OPENAI_API_KEY\n --triage-floor <level> Only triage findings at/above this level (default: medium)\n --max-findings <n> Cap findings sent to the LLM during triage (default: 100; spend guard)\n --context <level> Source shared with the LLM: metadata|snippet|\n function|file (default: snippet; secrets always redacted)\n --dry-run With --triage, print the exact payload that would\n be sent and exit without calling the provider\n --llm-provider <name> anthropic | openai-compatible (default: anthropic)\n --llm-model <id> Model id for the BYOK provider\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 --no-snippets Omit code snippets from the json/sarif report\n --color Force ANSI color in the human report\n --no-color Disable ANSI color (also: NO_COLOR env). Color is\n decoration only — every signal is printed as text\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`;\n\n/** The `--version` line. */\nexport function versionLine(): string {\n return `qscan ${VERSION}`;\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -9,13 +9,13 @@
|
|
|
9
9
|
* The module also re-exports the argument-parsing and baseline helpers so
|
|
10
10
|
* downstream tools can reuse them without reaching into internal paths.
|
|
11
11
|
*/
|
|
12
|
-
import type { Baseline, Finding, ParallelScanOptions, ScanResult, SecurityTier } from "@quantakrypto/core";
|
|
12
|
+
import type { Baseline, CryptoPolicy, CycloneDxBom, Finding, ParallelScanOptions, ScanResult, SecurityTier } from "@quantakrypto/core";
|
|
13
13
|
import type { QscanOptions } from "./args.js";
|
|
14
14
|
export type { QscanOptions, ParsedArgs, ParsedRun, QscanFormat } from "./args.js";
|
|
15
15
|
export type { Baseline } from "./baseline.js";
|
|
16
16
|
export { ArgError, asFormat, asInt, asSeverity, defaultOptions, meetsThreshold, parseArgs, severityRank, SEVERITY_ORDER, } from "./args.js";
|
|
17
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";
|
|
18
|
+
export { renderCbom, renderHuman, renderJson, renderSarif, renderVex } from "./report.js";
|
|
19
19
|
export { HELP_TEXT, versionLine } from "./help.js";
|
|
20
20
|
export { runRemediate, parseRemediateArgs, unifiedDiff, REMEDIATE_HELP, REMEDIATE_EXIT, } from "./remediate-cli.js";
|
|
21
21
|
export type { RemediateMode, RemediateOptions, RemediateRun, RemediateHooks, } from "./remediate-cli.js";
|
|
@@ -63,6 +63,11 @@ export interface RunQscanHooks {
|
|
|
63
63
|
scanFn?: ScanFn;
|
|
64
64
|
/** Override changed-file resolution. Default: `changedFiles` from `@quantakrypto/core`. */
|
|
65
65
|
changedFilesFn?: ChangedFilesFn;
|
|
66
|
+
/** Inject the triage function (offline testing of the `--triage` path, so the
|
|
67
|
+
* exit-code invariant can be exercised without a network client or API key).
|
|
68
|
+
* `import type` keeps this a compile-time-only reference — the networked agent
|
|
69
|
+
* package is still only loaded via the dynamic import inside `runTriage`. */
|
|
70
|
+
triageFn?: import("./triage-run.js").TriageFn;
|
|
66
71
|
}
|
|
67
72
|
/**
|
|
68
73
|
* Run a complete qScan pass: scan → baseline → threshold → render.
|
|
@@ -104,6 +109,12 @@ export interface RenderReportOptions {
|
|
|
104
109
|
topN?: number;
|
|
105
110
|
/** CNSA security tier for the migration-targets footer (`--tier`). */
|
|
106
111
|
tier?: SecurityTier;
|
|
112
|
+
/** Standards regime for the migration-targets footer (`--profile`). */
|
|
113
|
+
profile?: string;
|
|
114
|
+
/** Org cryptography policy for the evidence report's §4 verdicts (`--policy`). */
|
|
115
|
+
policy?: CryptoPolicy;
|
|
116
|
+
/** External CBOMs to merge into the `cbom` output (CycloneDX bom-link). */
|
|
117
|
+
mergeCboms?: CycloneDxBom[];
|
|
107
118
|
}
|
|
108
119
|
/** Render a scan result in the requested format. */
|
|
109
120
|
export declare function renderReport(result: ScanResult, format: QscanOptions["format"], opts?: RenderReportOptions | boolean): string;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -9,14 +9,16 @@
|
|
|
9
9
|
* The module also re-exports the argument-parsing and baseline helpers so
|
|
10
10
|
* downstream tools can reuse them without reaching into internal paths.
|
|
11
11
|
*/
|
|
12
|
+
import { readFile } from "node:fs/promises";
|
|
12
13
|
import process from "node:process";
|
|
13
|
-
import { buildReadinessReport, changedFiles, scan, scanParallel } from "@quantakrypto/core";
|
|
14
|
+
import { buildReadinessReport, changedFiles, parseCryptoPolicy, scan, scanParallel, signReadinessReport, } from "@quantakrypto/core";
|
|
15
|
+
import { commandSigner } from "./sign.js";
|
|
14
16
|
import { applyBaseline, readBaseline, saveBaseline } from "./baseline.js";
|
|
15
17
|
import { defaultOptions, meetsThreshold } from "./args.js";
|
|
16
|
-
import { renderCbom, renderHuman, renderJson, renderSarif } from "./report.js";
|
|
18
|
+
import { renderCbom, renderHuman, renderJson, renderSarif, renderVex } from "./report.js";
|
|
17
19
|
export { ArgError, asFormat, asInt, asSeverity, defaultOptions, meetsThreshold, parseArgs, severityRank, SEVERITY_ORDER, } from "./args.js";
|
|
18
20
|
export { applyBaseline, baselineFromFindings, BASELINE_VERSION, buildBaseline, fingerprint, fingerprintFinding, loadBaseline, readBaseline, saveBaseline, writeBaseline, } from "./baseline.js";
|
|
19
|
-
export { renderCbom, renderHuman, renderJson, renderSarif } from "./report.js";
|
|
21
|
+
export { renderCbom, renderHuman, renderJson, renderSarif, renderVex } from "./report.js";
|
|
20
22
|
export { HELP_TEXT, versionLine } from "./help.js";
|
|
21
23
|
export { runRemediate, parseRemediateArgs, unifiedDiff, REMEDIATE_HELP, REMEDIATE_EXIT, } from "./remediate-cli.js";
|
|
22
24
|
export { applyConfig, resolveConfig } from "./config.js";
|
|
@@ -119,6 +121,13 @@ export async function runQscan(opts, hooks = {}) {
|
|
|
119
121
|
result.findings = split.kept;
|
|
120
122
|
suppressed = split.suppressed;
|
|
121
123
|
}
|
|
124
|
+
// --policy: the org cryptography policy for the evidence report's §4 verdicts.
|
|
125
|
+
// Parsed strictly — a malformed policy fails loudly rather than silently
|
|
126
|
+
// dropping the verdicts from the attested evidence.
|
|
127
|
+
let policy;
|
|
128
|
+
if (options.policy) {
|
|
129
|
+
policy = parseCryptoPolicy(JSON.parse(await readFile(options.policy, "utf8")));
|
|
130
|
+
}
|
|
122
131
|
// Exit code is computed from RAW severities, BEFORE triage runs, so the
|
|
123
132
|
// (optional) LLM triage pass can never make a failing scan pass CI.
|
|
124
133
|
const exitCode = result.findings.some((f) => meetsThreshold(f.severity, options.severityThreshold))
|
|
@@ -140,43 +149,104 @@ export async function runQscan(opts, hooks = {}) {
|
|
|
140
149
|
// run, defeating both (audit: arch #1). Derive a sibling path.
|
|
141
150
|
cacheFile: options.cacheFile ? `${options.cacheFile}.responses.json` : undefined,
|
|
142
151
|
root: options.path,
|
|
152
|
+
triageFn: hooks.triageFn,
|
|
143
153
|
});
|
|
144
154
|
if (triaged.preflight !== undefined) {
|
|
145
155
|
return { result, suppressed, report: triaged.preflight, exitCode: EXIT.OK };
|
|
146
156
|
}
|
|
147
157
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
158
|
+
// `--merge` only has an effect on a `--cbom` output. If the user asked to merge but
|
|
159
|
+
// the format is not cbom, that is almost certainly a mistake (a typo'd `--cbom`, or a
|
|
160
|
+
// pipeline that forgot it) — the merge files would be silently ignored and the
|
|
161
|
+
// combined bill of materials never produced. Fail loudly instead of dropping data.
|
|
162
|
+
if (options.mergeCboms && options.mergeCboms.length > 0 && options.format !== "cbom") {
|
|
163
|
+
throw new Error(`--merge requires --format cbom (got ${options.format ?? "the human report"})`);
|
|
164
|
+
}
|
|
165
|
+
// `--sign` / `--timestamp` fill the evidence attestation, so they only make sense
|
|
166
|
+
// with `--format evidence`. Fail loudly rather than silently ignore the signer.
|
|
167
|
+
if ((options.sign || options.timestamp) && options.format !== "evidence") {
|
|
168
|
+
throw new Error(`--sign/--timestamp require --format evidence (got ${options.format ?? "the human report"})`);
|
|
169
|
+
}
|
|
170
|
+
const signer = options.sign ? commandSigner(options.sign) : undefined;
|
|
171
|
+
const timestamper = options.timestamp
|
|
172
|
+
? commandSigner(options.timestamp)
|
|
173
|
+
: undefined;
|
|
174
|
+
// Load any external CBOMs to merge into a `--cbom` output (combined
|
|
175
|
+
// code + infrastructure bill of materials). Only relevant for the cbom format.
|
|
176
|
+
let mergeCbomsData;
|
|
177
|
+
if (options.format === "cbom" && options.mergeCboms && options.mergeCboms.length > 0) {
|
|
178
|
+
mergeCbomsData = [];
|
|
179
|
+
for (const path of options.mergeCboms) {
|
|
180
|
+
let text;
|
|
181
|
+
try {
|
|
182
|
+
text = await readFile(path, "utf8");
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
throw new Error(`--merge: cannot read CBOM file "${path}"`);
|
|
186
|
+
}
|
|
187
|
+
let parsed;
|
|
188
|
+
try {
|
|
189
|
+
parsed = JSON.parse(text);
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
throw new Error(`--merge: "${path}" is not valid JSON`);
|
|
193
|
+
}
|
|
194
|
+
const bom = parsed;
|
|
195
|
+
if (bom?.bomFormat !== "CycloneDX") {
|
|
196
|
+
throw new Error(`--merge: "${path}" is not a CycloneDX CBOM (missing bomFormat)`);
|
|
197
|
+
}
|
|
198
|
+
mergeCbomsData.push(bom);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
let report = renderReport(result, options.format, {
|
|
202
|
+
color: hooks.color ?? false,
|
|
203
|
+
redactSnippets: options.noSnippets,
|
|
204
|
+
topN: options.topN,
|
|
205
|
+
tier: options.tier,
|
|
206
|
+
...(options.profile ? { profile: options.profile } : {}),
|
|
207
|
+
...(policy ? { policy } : {}),
|
|
208
|
+
...(mergeCbomsData ? { mergeCboms: mergeCbomsData } : {}),
|
|
209
|
+
});
|
|
210
|
+
// Evidence signing is orchestrated here (async: an external signer may be async),
|
|
211
|
+
// after the synchronous renderer has produced the unsigned report (ADR-0004: the
|
|
212
|
+
// tool orchestrates a signer, it does not implement crypto).
|
|
213
|
+
if (options.format === "evidence" && (signer || timestamper)) {
|
|
214
|
+
const signed = await signReadinessReport(JSON.parse(report), {
|
|
215
|
+
signer,
|
|
216
|
+
timestamper,
|
|
217
|
+
});
|
|
218
|
+
report = JSON.stringify(signed, null, 2);
|
|
219
|
+
}
|
|
220
|
+
return { result, suppressed, report, exitCode };
|
|
159
221
|
}
|
|
160
222
|
/** Render a scan result in the requested format. */
|
|
161
223
|
export function renderReport(result, format, opts = {}) {
|
|
162
224
|
// Back-compat: `renderReport(result, format, true)` used to mean "color on".
|
|
163
|
-
const { color = false, redactSnippets = false, topN = undefined, tier = undefined, } = typeof opts === "boolean" ? { color: opts } : opts;
|
|
225
|
+
const { color = false, redactSnippets = false, topN = undefined, tier = undefined, profile = undefined, policy = undefined, mergeCboms = undefined, } = typeof opts === "boolean" ? { color: opts, policy: undefined } : opts;
|
|
164
226
|
switch (format) {
|
|
165
227
|
case "json":
|
|
166
228
|
return renderJson(result, { redactSnippets });
|
|
167
229
|
case "sarif":
|
|
168
230
|
return renderSarif(result, { redactSnippets });
|
|
169
231
|
case "cbom":
|
|
170
|
-
return renderCbom(result);
|
|
171
|
-
case "
|
|
232
|
+
return renderCbom(result, mergeCboms);
|
|
233
|
+
case "vex":
|
|
234
|
+
return renderVex(result);
|
|
235
|
+
case "evidence": {
|
|
172
236
|
// ISO A.8.24 readiness report; repo/commit come from CI env when present.
|
|
173
|
-
|
|
237
|
+
// A `--policy` file adds the §4 conformant/violation/transition verdicts. The
|
|
238
|
+
// attestation is left unsigned here; signing is an async step in runQscan (an
|
|
239
|
+
// external signer may be async), so this renderer stays synchronous.
|
|
240
|
+
const report = buildReadinessReport(result, {
|
|
174
241
|
repository: process.env.GITHUB_REPOSITORY,
|
|
175
242
|
commit: process.env.GITHUB_SHA,
|
|
176
|
-
|
|
243
|
+
...(policy ? { policy } : {}),
|
|
244
|
+
});
|
|
245
|
+
return JSON.stringify(report, null, 2);
|
|
246
|
+
}
|
|
177
247
|
case "human":
|
|
178
248
|
default:
|
|
179
|
-
return renderHuman(result, { color, topN, tier });
|
|
249
|
+
return renderHuman(result, { color, topN, tier, profile });
|
|
180
250
|
}
|
|
181
251
|
}
|
|
182
252
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @quantakrypto/qscan — programmatic API.\n *\n * `runQscan` is the single entry point shared by the CLI (`src/cli.ts`) and by\n * `@quantakrypto/action`. It runs a scan via `@quantakrypto/core`, applies an optional\n * baseline, decides an exit code from the severity threshold, and (optionally)\n * renders a report. The CLI is a thin shell around it.\n *\n * The module also re-exports the argument-parsing and baseline helpers so\n * downstream tools can reuse them without reaching into internal paths.\n */\n\nimport process from \"node:process\";\n\nimport { buildReadinessReport, changedFiles, scan, scanParallel } from \"@quantakrypto/core\";\nimport type {\n Baseline,\n Finding,\n ParallelScanOptions,\n ScanResult,\n SecurityTier,\n} from \"@quantakrypto/core\";\n\nimport { applyBaseline, readBaseline, saveBaseline } from \"./baseline.js\";\nimport { defaultOptions, meetsThreshold } from \"./args.js\";\nimport type { QscanOptions } from \"./args.js\";\nimport { renderCbom, renderHuman, renderJson, renderSarif } from \"./report.js\";\n\nexport type { QscanOptions, ParsedArgs, ParsedRun, QscanFormat } from \"./args.js\";\nexport type { Baseline } from \"./baseline.js\";\nexport {\n ArgError,\n asFormat,\n asInt,\n asSeverity,\n defaultOptions,\n meetsThreshold,\n parseArgs,\n severityRank,\n SEVERITY_ORDER,\n} from \"./args.js\";\nexport {\n applyBaseline,\n baselineFromFindings,\n BASELINE_VERSION,\n buildBaseline,\n fingerprint,\n fingerprintFinding,\n loadBaseline,\n readBaseline,\n saveBaseline,\n writeBaseline,\n} from \"./baseline.js\";\nexport { renderCbom, renderHuman, renderJson, renderSarif } from \"./report.js\";\nexport { HELP_TEXT, versionLine } from \"./help.js\";\nexport {\n runRemediate,\n parseRemediateArgs,\n unifiedDiff,\n REMEDIATE_HELP,\n REMEDIATE_EXIT,\n} from \"./remediate-cli.js\";\nexport type {\n RemediateMode,\n RemediateOptions,\n RemediateRun,\n RemediateHooks,\n} from \"./remediate-cli.js\";\nexport { applyConfig, resolveConfig } from \"./config.js\";\nexport type { ResolvedConfig } from \"./config.js\";\nexport type { ConfigurableKey } from \"./args.js\";\n\n/** Process-style exit codes qScan uses. */\nexport const EXIT = {\n /** No findings at/above threshold, or a baseline was written. */\n OK: 0,\n /** One or more findings at/above the severity threshold. */\n FINDINGS: 1,\n /** Usage error or I/O failure. */\n ERROR: 2,\n} as const;\n\n/** Outcome of {@link runQscan}. */\nexport interface QscanRun {\n /** The scan result, with the baseline already applied to `findings`. */\n result: ScanResult;\n /** Findings suppressed because their fingerprint was in the baseline. */\n suppressed: Finding[];\n /** Rendered report in the requested format (`undefined` for a baseline write). */\n report?: string;\n /** The baseline that was written, when `writeBaseline` was requested. */\n baselineWritten?: Baseline;\n /** Suggested process exit code. */\n exitCode: number;\n}\n\n/**\n * The scan implementation `runQscan` calls. Matches `@quantakrypto/core`'s `scan` /\n * `scanParallel` (parallel options are a superset of `ScanOptions`).\n * Injectable so the GitHub Action and tests can supply a custom scanner.\n */\nexport type ScanFn = (options: ParallelScanOptions) => Promise<ScanResult>;\n\n/**\n * Resolve the changed-file list for incremental scans. Injectable for testing;\n * defaults to core's git-aware {@link changedFiles}.\n */\nexport type ChangedFilesFn = (root: string, since?: string) => Promise<string[]>;\n\n/** Behavioral hooks for {@link runQscan}, mainly for testing. */\nexport interface RunQscanHooks {\n /** Emit raw ANSI color in the human report. Default: false. */\n color?: boolean;\n /** Override the scanner. Default: `scan` / `scanParallel` from `@quantakrypto/core`. */\n scanFn?: ScanFn;\n /** Override changed-file resolution. Default: `changedFiles` from `@quantakrypto/core`. */\n changedFilesFn?: ChangedFilesFn;\n}\n\n/**\n * Translate resolved {@link QscanOptions} into core {@link ParallelScanOptions}.\n * `files` (the incremental file list) is layered on by {@link runQscan}.\n */\nfunction toScanOptions(options: QscanOptions): ParallelScanOptions {\n const scanOptions: ParallelScanOptions = {\n root: options.path,\n source: options.source,\n dependencies: options.dependencies,\n config: options.config,\n noDefaultIgnores: options.noDefaultIgnores,\n scanMinified: options.scanMinified,\n };\n if (options.ignore.length > 0) scanOptions.exclude = options.ignore;\n if (options.include.length > 0) scanOptions.include = options.include;\n if (options.maxFileSize !== undefined) scanOptions.maxFileSize = options.maxFileSize;\n if (options.concurrency !== undefined) scanOptions.concurrency = options.concurrency;\n if (options.disabledRules && options.disabledRules.length > 0) {\n scanOptions.disabledRules = options.disabledRules;\n }\n if (options.cacheFile) scanOptions.cacheFile = options.cacheFile;\n return scanOptions;\n}\n\n/**\n * Run a complete qScan pass: scan → baseline → threshold → render.\n *\n * This never touches `process` or stdout; the CLI is responsible for printing\n * `report`/writing `output` and calling `process.exit(exitCode)`. That keeps\n * the function pure enough to unit-test and to embed in the GitHub Action.\n *\n * Behavior:\n * - The walk is configured by `include` / `ignore` / `maxFileSize` /\n * `noDefaultIgnores` / `scanMinified`.\n * - With `changed` set, only the files git reports as changed (relative to\n * `since`, if given) are scanned via `ScanOptions.files`. A non-git tree\n * yields an empty list, so nothing is scanned.\n * - With `parallel` (or `concurrency`) set, the scan is routed through core's\n * `scanParallel`, which itself falls back to the serial path for small\n * inputs.\n * - When `opts.writeBaseline` is set, the scan runs, a baseline is built from\n * *all* findings, written to disk, and `exitCode` is {@link EXIT.OK}. No\n * report is rendered.\n * - When `opts.baseline` is set, its fingerprints are loaded and matching\n * findings are moved to `suppressed` (and removed from `result.findings`).\n * - `exitCode` is {@link EXIT.FINDINGS} when any *kept* finding meets the\n * severity threshold, else {@link EXIT.OK}.\n *\n * @throws {Error} Propagates scan / baseline I/O errors; the CLI maps these to\n * {@link EXIT.ERROR}.\n */\nexport async function runQscan(\n opts: Partial<QscanOptions> & { path: string },\n hooks: RunQscanHooks = {},\n): Promise<QscanRun> {\n const options: QscanOptions = { ...defaultOptions(), ...opts };\n // Route to the parallel pool when requested; both share the ScanOptions shape.\n const scanFn: ScanFn = hooks.scanFn ?? (options.parallel ? scanParallel : scan);\n const resolveChanged: ChangedFilesFn = hooks.changedFilesFn ?? changedFiles;\n\n const scanOptions = toScanOptions(options);\n\n // Incremental mode: restrict the scan to git-changed files.\n if (options.changed) {\n scanOptions.files = await resolveChanged(options.path, options.since);\n }\n\n const result = await scanFn(scanOptions);\n\n // --write-baseline: snapshot every finding, persist, and exit cleanly.\n if (options.writeBaseline) {\n const baseline = await saveBaseline(options.writeBaseline, result.findings);\n return {\n result,\n suppressed: [],\n baselineWritten: baseline,\n exitCode: EXIT.OK,\n };\n }\n\n // --baseline: suppress previously-accepted findings.\n //\n // The explicit `--baseline <path>` is read STRICTLY via `readBaseline`: a\n // missing or malformed file is an error (surfaced by the CLI as exit 2), not\n // silently treated as an empty baseline. Using core's tolerant `loadBaseline`\n // here would let a typo'd path (`--baseline typo.json`) suppress nothing and\n // still exit 0 — a CI footgun where a broken baseline reads as \"all clear\".\n let suppressed: Finding[] = [];\n if (options.baseline) {\n const fingerprints = await readBaseline(options.baseline);\n const split = applyBaseline(result.findings, fingerprints);\n result.findings = split.kept;\n suppressed = split.suppressed;\n }\n\n // Exit code is computed from RAW severities, BEFORE triage runs, so the\n // (optional) LLM triage pass can never make a failing scan pass CI.\n const exitCode = result.findings.some((f) =>\n meetsThreshold(f.severity, options.severityThreshold),\n )\n ? EXIT.FINDINGS\n : EXIT.OK;\n\n // Optional BYOK triage: annotate + re-sort findings (never suppresses). The\n // agent (networked) package is loaded only here, via dynamic import.\n if (options.triage) {\n const { runTriage } = await import(\"./triage-run.js\");\n const triaged = await runTriage(result, {\n level: options.contextLevel ?? \"snippet\",\n floor: options.triageFloor,\n maxFindings: options.maxFindings,\n dryRun: options.dryRun,\n provider: options.llmProvider,\n model: options.llmModel,\n // The triage RESPONSE cache must not share a path with the scan cache —\n // they are different on-disk formats and would clobber each other every\n // run, defeating both (audit: arch #1). Derive a sibling path.\n cacheFile: options.cacheFile ? `${options.cacheFile}.responses.json` : undefined,\n root: options.path,\n });\n if (triaged.preflight !== undefined) {\n return { result, suppressed, report: triaged.preflight, exitCode: EXIT.OK };\n }\n }\n\n return {\n result,\n suppressed,\n report: renderReport(result, options.format, {\n color: hooks.color ?? false,\n redactSnippets: options.noSnippets,\n topN: options.topN,\n tier: options.tier,\n }),\n exitCode,\n };\n}\n\n/** Rendering controls for {@link renderReport}. */\nexport interface RenderReportOptions {\n /** Emit raw ANSI color in the human report. Default: false. */\n color?: boolean;\n /** Omit code snippets from the JSON/SARIF report (`--no-snippets`). */\n redactSnippets?: boolean;\n /** How many findings the human report lists (`--top N`). */\n topN?: number;\n /** CNSA security tier for the migration-targets footer (`--tier`). */\n tier?: SecurityTier;\n}\n\n/** Render a scan result in the requested format. */\nexport function renderReport(\n result: ScanResult,\n format: QscanOptions[\"format\"],\n opts: RenderReportOptions | boolean = {},\n): string {\n // Back-compat: `renderReport(result, format, true)` used to mean \"color on\".\n const {\n color = false,\n redactSnippets = false,\n topN = undefined,\n tier = undefined,\n } = typeof opts === \"boolean\" ? { color: opts } : opts;\n switch (format) {\n case \"json\":\n return renderJson(result, { redactSnippets });\n case \"sarif\":\n return renderSarif(result, { redactSnippets });\n case \"cbom\":\n return renderCbom(result);\n case \"evidence\":\n // ISO A.8.24 readiness report; repo/commit come from CI env when present.\n return JSON.stringify(\n buildReadinessReport(result, {\n repository: process.env.GITHUB_REPOSITORY,\n commit: process.env.GITHUB_SHA,\n }),\n null,\n 2,\n );\n case \"human\":\n default:\n return renderHuman(result, { color, topN, tier });\n }\n}\n\n/** Re-export the core result types consumers commonly need. */\nexport type { Finding, ScanResult, ScanOptions } from \"@quantakrypto/core\";\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @quantakrypto/qscan — programmatic API.\n *\n * `runQscan` is the single entry point shared by the CLI (`src/cli.ts`) and by\n * `@quantakrypto/action`. It runs a scan via `@quantakrypto/core`, applies an optional\n * baseline, decides an exit code from the severity threshold, and (optionally)\n * renders a report. The CLI is a thin shell around it.\n *\n * The module also re-exports the argument-parsing and baseline helpers so\n * downstream tools can reuse them without reaching into internal paths.\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport process from \"node:process\";\n\nimport {\n buildReadinessReport,\n changedFiles,\n parseCryptoPolicy,\n scan,\n scanParallel,\n signReadinessReport,\n} from \"@quantakrypto/core\";\nimport type {\n Baseline,\n CryptoPolicy,\n CycloneDxBom,\n EvidenceSigner,\n Finding,\n ParallelScanOptions,\n ReadinessReport,\n ScanResult,\n SecurityTier,\n} from \"@quantakrypto/core\";\nimport { commandSigner } from \"./sign.js\";\n\nimport { applyBaseline, readBaseline, saveBaseline } from \"./baseline.js\";\nimport { defaultOptions, meetsThreshold } from \"./args.js\";\nimport type { QscanOptions } from \"./args.js\";\nimport { renderCbom, renderHuman, renderJson, renderSarif, renderVex } from \"./report.js\";\n\nexport type { QscanOptions, ParsedArgs, ParsedRun, QscanFormat } from \"./args.js\";\nexport type { Baseline } from \"./baseline.js\";\nexport {\n ArgError,\n asFormat,\n asInt,\n asSeverity,\n defaultOptions,\n meetsThreshold,\n parseArgs,\n severityRank,\n SEVERITY_ORDER,\n} from \"./args.js\";\nexport {\n applyBaseline,\n baselineFromFindings,\n BASELINE_VERSION,\n buildBaseline,\n fingerprint,\n fingerprintFinding,\n loadBaseline,\n readBaseline,\n saveBaseline,\n writeBaseline,\n} from \"./baseline.js\";\nexport { renderCbom, renderHuman, renderJson, renderSarif, renderVex } from \"./report.js\";\nexport { HELP_TEXT, versionLine } from \"./help.js\";\nexport {\n runRemediate,\n parseRemediateArgs,\n unifiedDiff,\n REMEDIATE_HELP,\n REMEDIATE_EXIT,\n} from \"./remediate-cli.js\";\nexport type {\n RemediateMode,\n RemediateOptions,\n RemediateRun,\n RemediateHooks,\n} from \"./remediate-cli.js\";\nexport { applyConfig, resolveConfig } from \"./config.js\";\nexport type { ResolvedConfig } from \"./config.js\";\nexport type { ConfigurableKey } from \"./args.js\";\n\n/** Process-style exit codes qScan uses. */\nexport const EXIT = {\n /** No findings at/above threshold, or a baseline was written. */\n OK: 0,\n /** One or more findings at/above the severity threshold. */\n FINDINGS: 1,\n /** Usage error or I/O failure. */\n ERROR: 2,\n} as const;\n\n/** Outcome of {@link runQscan}. */\nexport interface QscanRun {\n /** The scan result, with the baseline already applied to `findings`. */\n result: ScanResult;\n /** Findings suppressed because their fingerprint was in the baseline. */\n suppressed: Finding[];\n /** Rendered report in the requested format (`undefined` for a baseline write). */\n report?: string;\n /** The baseline that was written, when `writeBaseline` was requested. */\n baselineWritten?: Baseline;\n /** Suggested process exit code. */\n exitCode: number;\n}\n\n/**\n * The scan implementation `runQscan` calls. Matches `@quantakrypto/core`'s `scan` /\n * `scanParallel` (parallel options are a superset of `ScanOptions`).\n * Injectable so the GitHub Action and tests can supply a custom scanner.\n */\nexport type ScanFn = (options: ParallelScanOptions) => Promise<ScanResult>;\n\n/**\n * Resolve the changed-file list for incremental scans. Injectable for testing;\n * defaults to core's git-aware {@link changedFiles}.\n */\nexport type ChangedFilesFn = (root: string, since?: string) => Promise<string[]>;\n\n/** Behavioral hooks for {@link runQscan}, mainly for testing. */\nexport interface RunQscanHooks {\n /** Emit raw ANSI color in the human report. Default: false. */\n color?: boolean;\n /** Override the scanner. Default: `scan` / `scanParallel` from `@quantakrypto/core`. */\n scanFn?: ScanFn;\n /** Override changed-file resolution. Default: `changedFiles` from `@quantakrypto/core`. */\n changedFilesFn?: ChangedFilesFn;\n /** Inject the triage function (offline testing of the `--triage` path, so the\n * exit-code invariant can be exercised without a network client or API key).\n * `import type` keeps this a compile-time-only reference — the networked agent\n * package is still only loaded via the dynamic import inside `runTriage`. */\n triageFn?: import(\"./triage-run.js\").TriageFn;\n}\n\n/**\n * Translate resolved {@link QscanOptions} into core {@link ParallelScanOptions}.\n * `files` (the incremental file list) is layered on by {@link runQscan}.\n */\nfunction toScanOptions(options: QscanOptions): ParallelScanOptions {\n const scanOptions: ParallelScanOptions = {\n root: options.path,\n source: options.source,\n dependencies: options.dependencies,\n config: options.config,\n noDefaultIgnores: options.noDefaultIgnores,\n scanMinified: options.scanMinified,\n };\n if (options.ignore.length > 0) scanOptions.exclude = options.ignore;\n if (options.include.length > 0) scanOptions.include = options.include;\n if (options.maxFileSize !== undefined) scanOptions.maxFileSize = options.maxFileSize;\n if (options.concurrency !== undefined) scanOptions.concurrency = options.concurrency;\n if (options.disabledRules && options.disabledRules.length > 0) {\n scanOptions.disabledRules = options.disabledRules;\n }\n if (options.cacheFile) scanOptions.cacheFile = options.cacheFile;\n return scanOptions;\n}\n\n/**\n * Run a complete qScan pass: scan → baseline → threshold → render.\n *\n * This never touches `process` or stdout; the CLI is responsible for printing\n * `report`/writing `output` and calling `process.exit(exitCode)`. That keeps\n * the function pure enough to unit-test and to embed in the GitHub Action.\n *\n * Behavior:\n * - The walk is configured by `include` / `ignore` / `maxFileSize` /\n * `noDefaultIgnores` / `scanMinified`.\n * - With `changed` set, only the files git reports as changed (relative to\n * `since`, if given) are scanned via `ScanOptions.files`. A non-git tree\n * yields an empty list, so nothing is scanned.\n * - With `parallel` (or `concurrency`) set, the scan is routed through core's\n * `scanParallel`, which itself falls back to the serial path for small\n * inputs.\n * - When `opts.writeBaseline` is set, the scan runs, a baseline is built from\n * *all* findings, written to disk, and `exitCode` is {@link EXIT.OK}. No\n * report is rendered.\n * - When `opts.baseline` is set, its fingerprints are loaded and matching\n * findings are moved to `suppressed` (and removed from `result.findings`).\n * - `exitCode` is {@link EXIT.FINDINGS} when any *kept* finding meets the\n * severity threshold, else {@link EXIT.OK}.\n *\n * @throws {Error} Propagates scan / baseline I/O errors; the CLI maps these to\n * {@link EXIT.ERROR}.\n */\nexport async function runQscan(\n opts: Partial<QscanOptions> & { path: string },\n hooks: RunQscanHooks = {},\n): Promise<QscanRun> {\n const options: QscanOptions = { ...defaultOptions(), ...opts };\n // Route to the parallel pool when requested; both share the ScanOptions shape.\n const scanFn: ScanFn = hooks.scanFn ?? (options.parallel ? scanParallel : scan);\n const resolveChanged: ChangedFilesFn = hooks.changedFilesFn ?? changedFiles;\n\n const scanOptions = toScanOptions(options);\n\n // Incremental mode: restrict the scan to git-changed files.\n if (options.changed) {\n scanOptions.files = await resolveChanged(options.path, options.since);\n }\n\n const result = await scanFn(scanOptions);\n\n // --write-baseline: snapshot every finding, persist, and exit cleanly.\n if (options.writeBaseline) {\n const baseline = await saveBaseline(options.writeBaseline, result.findings);\n return {\n result,\n suppressed: [],\n baselineWritten: baseline,\n exitCode: EXIT.OK,\n };\n }\n\n // --baseline: suppress previously-accepted findings.\n //\n // The explicit `--baseline <path>` is read STRICTLY via `readBaseline`: a\n // missing or malformed file is an error (surfaced by the CLI as exit 2), not\n // silently treated as an empty baseline. Using core's tolerant `loadBaseline`\n // here would let a typo'd path (`--baseline typo.json`) suppress nothing and\n // still exit 0 — a CI footgun where a broken baseline reads as \"all clear\".\n let suppressed: Finding[] = [];\n if (options.baseline) {\n const fingerprints = await readBaseline(options.baseline);\n const split = applyBaseline(result.findings, fingerprints);\n result.findings = split.kept;\n suppressed = split.suppressed;\n }\n\n // --policy: the org cryptography policy for the evidence report's §4 verdicts.\n // Parsed strictly — a malformed policy fails loudly rather than silently\n // dropping the verdicts from the attested evidence.\n let policy: CryptoPolicy | undefined;\n if (options.policy) {\n policy = parseCryptoPolicy(JSON.parse(await readFile(options.policy, \"utf8\")));\n }\n\n // Exit code is computed from RAW severities, BEFORE triage runs, so the\n // (optional) LLM triage pass can never make a failing scan pass CI.\n const exitCode = result.findings.some((f) =>\n meetsThreshold(f.severity, options.severityThreshold),\n )\n ? EXIT.FINDINGS\n : EXIT.OK;\n\n // Optional BYOK triage: annotate + re-sort findings (never suppresses). The\n // agent (networked) package is loaded only here, via dynamic import.\n if (options.triage) {\n const { runTriage } = await import(\"./triage-run.js\");\n const triaged = await runTriage(result, {\n level: options.contextLevel ?? \"snippet\",\n floor: options.triageFloor,\n maxFindings: options.maxFindings,\n dryRun: options.dryRun,\n provider: options.llmProvider,\n model: options.llmModel,\n // The triage RESPONSE cache must not share a path with the scan cache —\n // they are different on-disk formats and would clobber each other every\n // run, defeating both (audit: arch #1). Derive a sibling path.\n cacheFile: options.cacheFile ? `${options.cacheFile}.responses.json` : undefined,\n root: options.path,\n triageFn: hooks.triageFn,\n });\n if (triaged.preflight !== undefined) {\n return { result, suppressed, report: triaged.preflight, exitCode: EXIT.OK };\n }\n }\n\n // `--merge` only has an effect on a `--cbom` output. If the user asked to merge but\n // the format is not cbom, that is almost certainly a mistake (a typo'd `--cbom`, or a\n // pipeline that forgot it) — the merge files would be silently ignored and the\n // combined bill of materials never produced. Fail loudly instead of dropping data.\n if (options.mergeCboms && options.mergeCboms.length > 0 && options.format !== \"cbom\") {\n throw new Error(`--merge requires --format cbom (got ${options.format ?? \"the human report\"})`);\n }\n\n // `--sign` / `--timestamp` fill the evidence attestation, so they only make sense\n // with `--format evidence`. Fail loudly rather than silently ignore the signer.\n if ((options.sign || options.timestamp) && options.format !== \"evidence\") {\n throw new Error(\n `--sign/--timestamp require --format evidence (got ${options.format ?? \"the human report\"})`,\n );\n }\n const signer: EvidenceSigner | undefined = options.sign ? commandSigner(options.sign) : undefined;\n const timestamper: EvidenceSigner | undefined = options.timestamp\n ? commandSigner(options.timestamp)\n : undefined;\n\n // Load any external CBOMs to merge into a `--cbom` output (combined\n // code + infrastructure bill of materials). Only relevant for the cbom format.\n let mergeCbomsData: CycloneDxBom[] | undefined;\n if (options.format === \"cbom\" && options.mergeCboms && options.mergeCboms.length > 0) {\n mergeCbomsData = [];\n for (const path of options.mergeCboms) {\n let text: string;\n try {\n text = await readFile(path, \"utf8\");\n } catch {\n throw new Error(`--merge: cannot read CBOM file \"${path}\"`);\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n throw new Error(`--merge: \"${path}\" is not valid JSON`);\n }\n const bom = parsed as CycloneDxBom;\n if (bom?.bomFormat !== \"CycloneDX\") {\n throw new Error(`--merge: \"${path}\" is not a CycloneDX CBOM (missing bomFormat)`);\n }\n mergeCbomsData.push(bom);\n }\n }\n\n let report = renderReport(result, options.format, {\n color: hooks.color ?? false,\n redactSnippets: options.noSnippets,\n topN: options.topN,\n tier: options.tier,\n ...(options.profile ? { profile: options.profile } : {}),\n ...(policy ? { policy } : {}),\n ...(mergeCbomsData ? { mergeCboms: mergeCbomsData } : {}),\n });\n // Evidence signing is orchestrated here (async: an external signer may be async),\n // after the synchronous renderer has produced the unsigned report (ADR-0004: the\n // tool orchestrates a signer, it does not implement crypto).\n if (options.format === \"evidence\" && (signer || timestamper)) {\n const signed = await signReadinessReport(JSON.parse(report) as ReadinessReport, {\n signer,\n timestamper,\n });\n report = JSON.stringify(signed, null, 2);\n }\n\n return { result, suppressed, report, exitCode };\n}\n\n/** Rendering controls for {@link renderReport}. */\nexport interface RenderReportOptions {\n /** Emit raw ANSI color in the human report. Default: false. */\n color?: boolean;\n /** Omit code snippets from the JSON/SARIF report (`--no-snippets`). */\n redactSnippets?: boolean;\n /** How many findings the human report lists (`--top N`). */\n topN?: number;\n /** CNSA security tier for the migration-targets footer (`--tier`). */\n tier?: SecurityTier;\n /** Standards regime for the migration-targets footer (`--profile`). */\n profile?: string;\n /** Org cryptography policy for the evidence report's §4 verdicts (`--policy`). */\n policy?: CryptoPolicy;\n /** External CBOMs to merge into the `cbom` output (CycloneDX bom-link). */\n mergeCboms?: CycloneDxBom[];\n}\n\n/** Render a scan result in the requested format. */\nexport function renderReport(\n result: ScanResult,\n format: QscanOptions[\"format\"],\n opts: RenderReportOptions | boolean = {},\n): string {\n // Back-compat: `renderReport(result, format, true)` used to mean \"color on\".\n const {\n color = false,\n redactSnippets = false,\n topN = undefined,\n tier = undefined,\n profile = undefined,\n policy = undefined,\n mergeCboms = undefined,\n } = typeof opts === \"boolean\" ? { color: opts, policy: undefined } : opts;\n switch (format) {\n case \"json\":\n return renderJson(result, { redactSnippets });\n case \"sarif\":\n return renderSarif(result, { redactSnippets });\n case \"cbom\":\n return renderCbom(result, mergeCboms);\n case \"vex\":\n return renderVex(result);\n case \"evidence\": {\n // ISO A.8.24 readiness report; repo/commit come from CI env when present.\n // A `--policy` file adds the §4 conformant/violation/transition verdicts. The\n // attestation is left unsigned here; signing is an async step in runQscan (an\n // external signer may be async), so this renderer stays synchronous.\n const report = buildReadinessReport(result, {\n repository: process.env.GITHUB_REPOSITORY,\n commit: process.env.GITHUB_SHA,\n ...(policy ? { policy } : {}),\n });\n return JSON.stringify(report, null, 2);\n }\n case \"human\":\n default:\n return renderHuman(result, { color, topN, tier, profile });\n }\n}\n\n/** Re-export the core result types consumers commonly need. */\nexport type { Finding, ScanResult, ScanOptions } from \"@quantakrypto/core\";\n"]}
|