@quantakrypto/qscan 0.4.4 → 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/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"]}
@@ -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,CAkBzB;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,YAAY,EACrB,MAAM,EAAE,sBAAsB,EAC9B,QAAQ,EAAE,WAAW,CAAC,eAAe,CAAC,GACrC,YAAY,CAwCd"}
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
- return { options: merged, configPath: loaded.path, warnings: loaded.warnings };
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
@@ -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 --policy <file> Crypto-policy JSON; adds conformant/violation/\n transition verdicts to the evidence report\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
@@ -1 +1 @@
1
- {"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../src/help.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,gCAAgC;AAChC,eAAO,MAAM,SAAS,+oJA6ErB,CAAC;AAEF,4BAA4B;AAC5B,wBAAgB,WAAW,IAAI,MAAM,CAEpC"}
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,14 +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)
24
31
  --policy <file> Crypto-policy JSON; adds conformant/violation/
25
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)
26
38
  -o, --output <file> Write the report to a file instead of stdout
27
39
  --severity-threshold <level> Fail (exit 1) on findings at/above this level;
28
40
  one of critical|high|medium|low|info
@@ -45,7 +57,12 @@ OPTIONS
45
57
  0 forces the in-process serial path
46
58
  --top <n> List <n> findings in the human report (default: 5)
47
59
  --tier <category-3|category-5> Add CNSA migration targets to the report footer
48
- (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
49
66
  --cache [file] Reuse findings for unchanged files across runs
50
67
  (default file: .quantakrypto-cache.json)
51
68
  --triage BYOK LLM pass that re-ranks findings by real
@@ -64,6 +81,9 @@ OPTIONS
64
81
  --write-baseline <file> Write current findings as a baseline, then exit 0
65
82
  --quiet Suppress the human summary banner
66
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
67
87
  -v, --version Print version and exit
68
88
  -h, --help Print this help and exit
69
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6ExB,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>\n Output format (default: human)\n --cbom Alias for --format cbom (CycloneDX CBOM)\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 -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`;\n\n/** The `--version` line. */\nexport function versionLine(): string {\n return `qscan ${VERSION}`;\n}\n"]}
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, CryptoPolicy, 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";
@@ -109,8 +109,12 @@ export interface RenderReportOptions {
109
109
  topN?: number;
110
110
  /** CNSA security tier for the migration-targets footer (`--tier`). */
111
111
  tier?: SecurityTier;
112
+ /** Standards regime for the migration-targets footer (`--profile`). */
113
+ profile?: string;
112
114
  /** Org cryptography policy for the evidence report's §4 verdicts (`--policy`). */
113
115
  policy?: CryptoPolicy;
116
+ /** External CBOMs to merge into the `cbom` output (CycloneDX bom-link). */
117
+ mergeCboms?: CycloneDxBom[];
114
118
  }
115
119
  /** Render a scan result in the requested format. */
116
120
  export declare function renderReport(result: ScanResult, format: QscanOptions["format"], opts?: RenderReportOptions | boolean): string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAYH,OAAO,KAAK,EACV,QAAQ,EACR,YAAY,EACZ,OAAO,EACP,mBAAmB,EACnB,UAAU,EACV,YAAY,EACb,MAAM,oBAAoB,CAAC;AAI5B,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,EACL,YAAY,EACZ,kBAAkB,EAClB,WAAW,EACX,cAAc,EACd,cAAc,GACf,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,cAAc,GACf,MAAM,oBAAoB,CAAC;AAC5B,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;IAChC;;;iFAG6E;IAC7E,QAAQ,CAAC,EAAE,OAAO,iBAAiB,EAAE,QAAQ,CAAC;CAC/C;AA0BD;;;;;;;;;;;;;;;;;;;;;;;;;;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,CA4FnB;AAED,mDAAmD;AACnD,MAAM,WAAW,mBAAmB;IAClC,+DAA+D;IAC/D,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,uEAAuE;IACvE,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,4DAA4D;IAC5D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,sEAAsE;IACtE,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,kFAAkF;IAClF,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB;AAED,oDAAoD;AACpD,wBAAgB,YAAY,CAC1B,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,YAAY,CAAC,QAAQ,CAAC,EAC9B,IAAI,GAAE,mBAAmB,GAAG,OAAY,GACvC,MAAM,CAgCR;AAED,+DAA+D;AAC/D,YAAY,EAAE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -11,13 +11,14 @@
11
11
  */
12
12
  import { readFile } from "node:fs/promises";
13
13
  import process from "node:process";
14
- import { buildReadinessReport, changedFiles, parseCryptoPolicy, scan, scanParallel, } from "@quantakrypto/core";
14
+ import { buildReadinessReport, changedFiles, parseCryptoPolicy, scan, scanParallel, signReadinessReport, } from "@quantakrypto/core";
15
+ import { commandSigner } from "./sign.js";
15
16
  import { applyBaseline, readBaseline, saveBaseline } from "./baseline.js";
16
17
  import { defaultOptions, meetsThreshold } from "./args.js";
17
- import { renderCbom, renderHuman, renderJson, renderSarif } from "./report.js";
18
+ import { renderCbom, renderHuman, renderJson, renderSarif, renderVex } from "./report.js";
18
19
  export { ArgError, asFormat, asInt, asSeverity, defaultOptions, meetsThreshold, parseArgs, severityRank, SEVERITY_ORDER, } from "./args.js";
19
20
  export { applyBaseline, baselineFromFindings, BASELINE_VERSION, buildBaseline, fingerprint, fingerprintFinding, loadBaseline, readBaseline, saveBaseline, writeBaseline, } from "./baseline.js";
20
- export { renderCbom, renderHuman, renderJson, renderSarif } from "./report.js";
21
+ export { renderCbom, renderHuman, renderJson, renderSarif, renderVex } from "./report.js";
21
22
  export { HELP_TEXT, versionLine } from "./help.js";
22
23
  export { runRemediate, parseRemediateArgs, unifiedDiff, REMEDIATE_HELP, REMEDIATE_EXIT, } from "./remediate-cli.js";
23
24
  export { applyConfig, resolveConfig } from "./config.js";
@@ -154,41 +155,98 @@ export async function runQscan(opts, hooks = {}) {
154
155
  return { result, suppressed, report: triaged.preflight, exitCode: EXIT.OK };
155
156
  }
156
157
  }
157
- return {
158
- result,
159
- suppressed,
160
- report: renderReport(result, options.format, {
161
- color: hooks.color ?? false,
162
- redactSnippets: options.noSnippets,
163
- topN: options.topN,
164
- tier: options.tier,
165
- ...(policy ? { policy } : {}),
166
- }),
167
- exitCode,
168
- };
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 };
169
221
  }
170
222
  /** Render a scan result in the requested format. */
171
223
  export function renderReport(result, format, opts = {}) {
172
224
  // Back-compat: `renderReport(result, format, true)` used to mean "color on".
173
- const { color = false, redactSnippets = false, topN = undefined, tier = undefined, policy = undefined, } = typeof opts === "boolean" ? { color: opts, policy: undefined } : 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;
174
226
  switch (format) {
175
227
  case "json":
176
228
  return renderJson(result, { redactSnippets });
177
229
  case "sarif":
178
230
  return renderSarif(result, { redactSnippets });
179
231
  case "cbom":
180
- return renderCbom(result);
181
- case "evidence":
232
+ return renderCbom(result, mergeCboms);
233
+ case "vex":
234
+ return renderVex(result);
235
+ case "evidence": {
182
236
  // ISO A.8.24 readiness report; repo/commit come from CI env when present.
183
- // A `--policy` file adds the §4 conformant/violation/transition verdicts.
184
- return JSON.stringify(buildReadinessReport(result, {
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, {
185
241
  repository: process.env.GITHUB_REPOSITORY,
186
242
  commit: process.env.GITHUB_SHA,
187
243
  ...(policy ? { policy } : {}),
188
- }), null, 2);
244
+ });
245
+ return JSON.stringify(report, null, 2);
246
+ }
189
247
  case "human":
190
248
  default:
191
- return renderHuman(result, { color, topN, tier });
249
+ return renderHuman(result, { color, topN, tier, profile });
192
250
  }
193
251
  }
194
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 { readFile } from \"node:fs/promises\";\nimport process from \"node:process\";\n\nimport {\n buildReadinessReport,\n changedFiles,\n parseCryptoPolicy,\n scan,\n scanParallel,\n} from \"@quantakrypto/core\";\nimport type {\n Baseline,\n CryptoPolicy,\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 /** 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 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 ...(policy ? { policy } : {}),\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 /** Org cryptography policy for the evidence report's §4 verdicts (`--policy`). */\n policy?: CryptoPolicy;\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 policy = 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);\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.\n return JSON.stringify(\n buildReadinessReport(result, {\n repository: process.env.GITHUB_REPOSITORY,\n commit: process.env.GITHUB_SHA,\n ...(policy ? { policy } : {}),\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"]}
@@ -21,7 +21,7 @@ export interface RemediateRun {
21
21
  written: string[];
22
22
  }
23
23
  /** A verified fix set ready to become a draft PR. */
24
- export interface DraftPrPlan {
24
+ interface DraftPrPlan {
25
25
  branch: string;
26
26
  title: string;
27
27
  body: string;
@@ -31,7 +31,7 @@ export interface DraftPrPlan {
31
31
  }[];
32
32
  }
33
33
  /** Open a draft PR for the plan (injectable; default shells git + gh in a worktree). */
34
- export type OpenDraftPr = (plan: DraftPrPlan) => Promise<{
34
+ type OpenDraftPr = (plan: DraftPrPlan) => Promise<{
35
35
  url?: string;
36
36
  }>;
37
37
  export interface RemediateHooks {
@@ -52,8 +52,6 @@ export declare const REMEDIATE_EXIT: {
52
52
  readonly CHANGES: 0;
53
53
  readonly ERROR: 2;
54
54
  };
55
- /** Default per-run cap on paid LLM fix proposals (spend/DoS guard; override with --max-llm). */
56
- export declare const DEFAULT_MAX_LLM = 25;
57
55
  /** Minimal unified diff for a localized change (3 lines of context). */
58
56
  export declare function unifiedDiff(relPath: string, before: string, after: string): string;
59
57
  /** Run a complete qremediate pass. Pure w.r.t. process; the bin prints + exits. */
@@ -71,4 +69,5 @@ export declare function parseRemediateArgs(argv: readonly string[]): {
71
69
  message: string;
72
70
  };
73
71
  export declare const REMEDIATE_HELP = "qremediate \u2014 apply verified codemod fixes (and, with --llm, crypto-verified LLM proposals) for insecure crypto findings\n\nUSAGE\n qremediate [path] [--mode diff|apply|pr] [--llm] [--apply-llm] [--max-llm N]\n [--llm-provider <p>] [--llm-model <m>]\n\nOPTIONS\n --mode diff Print a unified diff of every candidate fix (default; writes nothing)\n --mode apply Write deterministic codemod fixes into the working tree\n (LLM fixes are held back as diffs unless --apply-llm is given)\n --mode pr Commit fixes to a new branch and open a DRAFT PR (never merges)\n --llm Also let a BYOK LLM propose fixes codemods can't (needs an API key)\n --apply-llm In apply mode, also write LLM fixes (only after you've read them)\n --max-llm N Cap paid LLM proposals per run (default 25; spend guard)\n --llm-provider anthropic | openai-compatible (default: anthropic)\n --llm-model Model id for the BYOK provider\n -h, --help Show this help\n -v, --version Show version\n\nEvery fix must clear the verify_fix gate (target finding gone, no new finding) and\nthe patch policy (only files with findings + dependency manifests). Codemod fixes\nare deterministic; LLM fixes are **crypto-verified, not security-reviewed** \u2014 the\ngate proves the crypto is gone, not that the rewrite is safe, and the pipeline\nrejects any LLM patch that adds a network/exec sink or rewrites too much. Review\nLLM diffs before applying. Never merges.\n";
72
+ export {};
74
73
  //# sourceMappingURL=remediate-cli.d.ts.map