@quantakrypto/qscan 0.4.4 → 0.6.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/cli.js CHANGED
@@ -14,9 +14,10 @@ import process from "node:process";
14
14
  import { fileURLToPath } from "node:url";
15
15
  import { ConfigError } from "@quantakrypto/core";
16
16
  import { ArgError, parseArgs } from "./args.js";
17
+ import { resolveColor } from "./color.js";
17
18
  import { resolveConfig } from "./config.js";
18
19
  import { HELP_TEXT, versionLine } from "./help.js";
19
- import { EXIT, runQscan } from "./index.js";
20
+ import { EXIT, runCryptoAgilityEmit, runCryptoAgilityValidate, runHndlInit, runQscan, } from "./index.js";
20
21
  /** Run the CLI and return the desired process exit code (never throws). */
21
22
  export async function main(argv) {
22
23
  let parsed;
@@ -39,6 +40,85 @@ export async function main(argv) {
39
40
  process.stdout.write(`${versionLine()}\n`);
40
41
  return EXIT.OK;
41
42
  }
43
+ // `qscan hndl init`: scaffold an hndl.yml from a seeding scan. Refuses to
44
+ // overwrite an existing file.
45
+ if (parsed.kind === "hndl-init") {
46
+ try {
47
+ const init = await runHndlInit(parsed.options);
48
+ if (init.exists) {
49
+ process.stderr.write(`qscan: ${init.path} already exists; refusing to overwrite. Edit it, or remove it and re-run.\n`);
50
+ return EXIT.ERROR;
51
+ }
52
+ await writeFile(init.path, init.content, "utf8");
53
+ process.stderr.write(`qscan: wrote ${init.path} (seeded from ${init.seededFindings} data-adjacent finding(s) of ${init.findingsScanned} scanned).\n`);
54
+ process.stderr.write(`qscan: review the assets, then run "qscan ${parsed.options.path} --hndl" for exposure scores.\n`);
55
+ return EXIT.OK;
56
+ }
57
+ catch (err) {
58
+ if (isErrno(err) && err.code === "ENOENT") {
59
+ process.stderr.write(`qscan: path not found: ${parsed.options.path}\n`);
60
+ return EXIT.ERROR;
61
+ }
62
+ const message = err instanceof Error ? err.message : String(err);
63
+ process.stderr.write(`qscan: ${message}\n`);
64
+ return EXIT.ERROR;
65
+ }
66
+ }
67
+ // `qscan crypto-agility emit` / `--crypto-agility`: derive a posture manifest and
68
+ // write it to stdout (or `--output`). Additive: always exits 0, never consulting
69
+ // the severity threshold.
70
+ if (parsed.kind === "crypto-agility-emit") {
71
+ try {
72
+ const { manifest } = await runCryptoAgilityEmit(parsed.options);
73
+ const out = manifest.endsWith("\n") ? manifest : `${manifest}\n`;
74
+ if (parsed.options.output) {
75
+ await writeFile(parsed.options.output, out, "utf8");
76
+ if (!parsed.options.quiet) {
77
+ process.stderr.write(`qscan: wrote crypto-agility manifest to ${parsed.options.output}\n`);
78
+ }
79
+ }
80
+ else {
81
+ await writeStdout(out);
82
+ }
83
+ return EXIT.OK;
84
+ }
85
+ catch (err) {
86
+ if (isErrno(err) && err.code === "ENOENT") {
87
+ const missing = typeof err.path === "string" && err.path ? err.path : parsed.options.path;
88
+ process.stderr.write(`qscan: path not found: ${missing}\n`);
89
+ return EXIT.ERROR;
90
+ }
91
+ const message = err instanceof Error ? err.message : String(err);
92
+ process.stderr.write(`qscan: ${message}\n`);
93
+ return EXIT.ERROR;
94
+ }
95
+ }
96
+ // `qscan crypto-agility validate <file>`: check a LOCAL manifest against the
97
+ // schema. Exit 0 when valid, 1 when the manifest is invalid, 2 on an I/O error.
98
+ if (parsed.kind === "crypto-agility-validate") {
99
+ let validation;
100
+ try {
101
+ validation = await runCryptoAgilityValidate(parsed.file);
102
+ }
103
+ catch (err) {
104
+ if (isErrno(err) && err.code === "ENOENT") {
105
+ process.stderr.write(`qscan: path not found: ${parsed.file}\n`);
106
+ return EXIT.ERROR;
107
+ }
108
+ const message = err instanceof Error ? err.message : String(err);
109
+ process.stderr.write(`qscan: ${message}\n`);
110
+ return EXIT.ERROR;
111
+ }
112
+ if (validation.valid) {
113
+ process.stdout.write(`qscan: ${parsed.file} is a valid crypto-agility manifest\n`);
114
+ return EXIT.OK;
115
+ }
116
+ process.stderr.write(`qscan: ${parsed.file} is not a valid crypto-agility manifest:\n`);
117
+ for (const e of validation.errors) {
118
+ process.stderr.write(` - ${e}\n`);
119
+ }
120
+ return EXIT.FINDINGS;
121
+ }
42
122
  // Resolve `quantakrypto.config.json` (flags > config > defaults) before scanning.
43
123
  let options;
44
124
  try {
@@ -62,21 +142,29 @@ export async function main(argv) {
62
142
  process.stderr.write(`qscan: ${message}\n`);
63
143
  return EXIT.ERROR;
64
144
  }
65
- // Color only when writing the human report to an interactive stdout.
66
- const color = options.format === "human" &&
67
- !options.output &&
68
- Boolean(process.stdout.isTTY) &&
69
- process.env.NO_COLOR === undefined;
145
+ // Color policy: --color/--no-color > NO_COLOR/FORCE_COLOR > interactive stdout.
146
+ // Color is decoration only (every signal is also text), so this is purely an
147
+ // accessibility / pipe-safety control. See resolveColor for the precedence.
148
+ const color = resolveColor({
149
+ choice: options.colorChoice,
150
+ format: options.format,
151
+ toFile: Boolean(options.output),
152
+ isTTY: Boolean(process.stdout.isTTY),
153
+ env: { NO_COLOR: process.env.NO_COLOR, FORCE_COLOR: process.env.FORCE_COLOR },
154
+ });
70
155
  let run;
71
156
  try {
72
157
  run = await runQscan(options, { color });
73
158
  }
74
159
  catch (err) {
75
- // A missing scan path is the most common failure; a raw
76
- // "ENOENT: no such file or directory, stat '…'" reads like a tool bug.
77
- // Turn it into a plain, actionable line (still exit 2).
160
+ // A missing file is the most common failure; a raw "ENOENT: no such file or
161
+ // directory, stat '…'" reads like a tool bug. Turn it into a plain, actionable
162
+ // line (still exit 2). Report the ACTUAL missing path from `err.path` when present
163
+ // — an ENOENT can come from `--policy`, `--baseline`, or `--write-baseline`, not
164
+ // just the scan path, and blaming the (existing) scan path misdirects the user.
78
165
  if (isErrno(err) && err.code === "ENOENT") {
79
- process.stderr.write(`qscan: path not found: ${options.path}\n`);
166
+ const missing = typeof err.path === "string" && err.path ? err.path : options.path;
167
+ process.stderr.write(`qscan: path not found: ${missing}\n`);
80
168
  return EXIT.ERROR;
81
169
  }
82
170
  const message = err instanceof Error ? err.message : String(err);
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;GAQG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,OAAO,MAAM,cAAc,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEhD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAG5C,2EAA2E;AAC3E,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,IAAuB;IAChD,IAAI,MAAkB,CAAC;IACvB,IAAI,CAAC;QACH,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAC3B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,QAAQ,EAAE,CAAC;YAC5B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,OAAO,IAAI,CAAC,CAAC;YAChD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iCAAiC,CAAC,CAAC;YACxD,OAAO,IAAI,CAAC,KAAK,CAAC;QACpB,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;IAED,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QAC3B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAChC,OAAO,IAAI,CAAC,EAAE,CAAC;IACjB,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC9B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,WAAW,EAAE,IAAI,CAAC,CAAC;QAC3C,OAAO,IAAI,CAAC,EAAE,CAAC;IACjB,CAAC;IAED,kFAAkF;IAClF,IAAI,OAAqB,CAAC;IAC1B,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,aAAa,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;QACtE,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC;QAC3B,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YACnB,KAAK,MAAM,CAAC,IAAI,QAAQ,CAAC,QAAQ,EAAE,CAAC;gBAClC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,0BAA0B,CAAC,IAAI,CAAC,CAAC;YACxD,CAAC;YACD,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC;gBACxB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,uBAAuB,QAAQ,CAAC,UAAU,IAAI,CAAC,CAAC;YACvE,CAAC;QACH,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,WAAW,EAAE,CAAC;YAC/B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,OAAO,IAAI,CAAC,CAAC;YAChD,OAAO,IAAI,CAAC,KAAK,CAAC;QACpB,CAAC;QACD,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,OAAO,IAAI,CAAC,CAAC;QAC5C,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED,qEAAqE;IACrE,MAAM,KAAK,GACT,OAAO,CAAC,MAAM,KAAK,OAAO;QAC1B,CAAC,OAAO,CAAC,MAAM;QACf,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC;QAC7B,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,SAAS,CAAC;IAErC,IAAI,GAAa,CAAC;IAClB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,QAAQ,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;IAC3C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,wDAAwD;QACxD,uEAAuE;QACvE,wDAAwD;QACxD,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC1C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,0BAA0B,OAAO,CAAC,IAAI,IAAI,CAAC,CAAC;YACjE,OAAO,IAAI,CAAC,KAAK,CAAC;QACpB,CAAC;QACD,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,OAAO,IAAI,CAAC,CAAC;QAC5C,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED,sDAAsD;IACtD,IAAI,GAAG,CAAC,eAAe,EAAE,CAAC;QACxB,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YACnB,MAAM,CAAC,GAAG,GAAG,CAAC,eAAe,CAAC,YAAY,CAAC,MAAM,CAAC;YAClD,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,8BAA8B,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,OAAO,OAAO,CAAC,aAAa,IAAI,CACjG,CAAC;QACJ,CAAC;QACD,OAAO,GAAG,CAAC,QAAQ,CAAC;IACtB,CAAC;IAED,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC;IAChC,IAAI,CAAC;QACH,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;YACnB,MAAM,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,EAAE,MAAM,CAAC,CAAC;YACxF,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;gBACnB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,gBAAgB,OAAO,CAAC,MAAM,cAAc,OAAO,CAAC,MAAM,IAAI,CAAC,CAAC;YACvF,CAAC;QACH,CAAC;aAAM,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,MAAM,KAAK,OAAO,EAAE,CAAC;YACxD,wEAAwE;YACxE,wCAAwC;YACxC,MAAM,WAAW,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,CAAC,CAAC;QACpE,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,OAAO,IAAI,CAAC,CAAC;QAC5C,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAChD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,qBAAqB,GAAG,CAAC,UAAU,CAAC,MAAM,4BAA4B,CAAC,CAAC;IAC/F,CAAC;IAED,OAAO,GAAG,CAAC,QAAQ,CAAC;AACtB,CAAC;AAED,sFAAsF;AACtF,SAAS,OAAO,CAAC,GAAY;IAC3B,OAAO,GAAG,YAAY,KAAK,IAAI,OAAQ,GAA6B,CAAC,IAAI,KAAK,QAAQ,CAAC;AACzF,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAS,WAAW,CAAC,KAAa;IAChC,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC5C,IAAI,OAAO;QAAE,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IACtC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;AACzE,CAAC;AAED;;;;;GAKG;AACH,SAAS,YAAY;IACnB,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC9B,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACtC,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAChD,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,KAAK,CAAC,KAAK,YAAY,CAAC,QAAQ,CAAC,CAAC;IACxD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,KAAK,QAAQ,CAAC;IAC5B,CAAC;AACH,CAAC;AAED,wEAAwE;AACxE,MAAM,eAAe,GAAG,YAAY,EAAE,CAAC;AAEvC,IAAI,eAAe,EAAE,CAAC;IACpB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;SACxB,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE;QACb,sEAAsE;QACtE,wEAAwE;QACxE,wEAAwE;QACxE,0EAA0E;QAC1E,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAC1B,CAAC,CAAC;SACD,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAChF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iBAAiB,OAAO,IAAI,CAAC,CAAC;QACnD,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC;IAChC,CAAC,CAAC,CAAC;AACP,CAAC","sourcesContent":["#!/usr/bin/env node\n/**\n * qScan command-line entry point.\n *\n * Thin shell over the programmatic API in `./index.ts`:\n * parse argv → runQscan → print/write report → process.exit(code).\n *\n * All policy (scanning, baseline, thresholds, rendering) lives in `index.ts`;\n * this file only deals with argv, stdout/stderr, files, and exit codes.\n */\n\nimport { realpathSync } from \"node:fs\";\nimport { writeFile } from \"node:fs/promises\";\nimport process from \"node:process\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { ConfigError } from \"@quantakrypto/core\";\n\nimport { ArgError, parseArgs } from \"./args.js\";\nimport type { ParsedArgs, QscanOptions } from \"./args.js\";\nimport { resolveConfig } from \"./config.js\";\nimport { HELP_TEXT, versionLine } from \"./help.js\";\nimport { EXIT, runQscan } from \"./index.js\";\nimport type { QscanRun } from \"./index.js\";\n\n/** Run the CLI and return the desired process exit code (never throws). */\nexport async function main(argv: readonly string[]): Promise<number> {\n let parsed: ParsedArgs;\n try {\n parsed = parseArgs(argv);\n } catch (err) {\n if (err instanceof ArgError) {\n process.stderr.write(`qscan: ${err.message}\\n`);\n process.stderr.write(`Run \"qscan --help\" for usage.\\n`);\n return EXIT.ERROR;\n }\n throw err;\n }\n\n if (parsed.kind === \"help\") {\n process.stdout.write(HELP_TEXT);\n return EXIT.OK;\n }\n if (parsed.kind === \"version\") {\n process.stdout.write(`${versionLine()}\\n`);\n return EXIT.OK;\n }\n\n // Resolve `quantakrypto.config.json` (flags > config > defaults) before scanning.\n let options: QscanOptions;\n try {\n const resolved = await resolveConfig(parsed.options, parsed.explicit);\n options = resolved.options;\n if (!options.quiet) {\n for (const w of resolved.warnings) {\n process.stderr.write(`qscan: config warning: ${w}\\n`);\n }\n if (resolved.configPath) {\n process.stderr.write(`qscan: using config ${resolved.configPath}\\n`);\n }\n }\n } catch (err) {\n if (err instanceof ConfigError) {\n process.stderr.write(`qscan: ${err.message}\\n`);\n return EXIT.ERROR;\n }\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(`qscan: ${message}\\n`);\n return EXIT.ERROR;\n }\n\n // Color only when writing the human report to an interactive stdout.\n const color =\n options.format === \"human\" &&\n !options.output &&\n Boolean(process.stdout.isTTY) &&\n process.env.NO_COLOR === undefined;\n\n let run: QscanRun;\n try {\n run = await runQscan(options, { color });\n } catch (err) {\n // A missing scan path is the most common failure; a raw\n // \"ENOENT: no such file or directory, stat '…'\" reads like a tool bug.\n // Turn it into a plain, actionable line (still exit 2).\n if (isErrno(err) && err.code === \"ENOENT\") {\n process.stderr.write(`qscan: path not found: ${options.path}\\n`);\n return EXIT.ERROR;\n }\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(`qscan: ${message}\\n`);\n return EXIT.ERROR;\n }\n\n // --write-baseline: report what was written and stop.\n if (run.baselineWritten) {\n if (!options.quiet) {\n const n = run.baselineWritten.fingerprints.length;\n process.stderr.write(\n `qscan: wrote baseline with ${n} fingerprint${n === 1 ? \"\" : \"s\"} to ${options.writeBaseline}\\n`,\n );\n }\n return run.exitCode;\n }\n\n const report = run.report ?? \"\";\n try {\n if (options.output) {\n await writeFile(options.output, report.endsWith(\"\\n\") ? report : `${report}\\n`, \"utf8\");\n if (!options.quiet) {\n process.stderr.write(`qscan: wrote ${options.format} report to ${options.output}\\n`);\n }\n } else if (!options.quiet || options.format !== \"human\") {\n // In quiet mode we still emit machine formats to stdout (the point of a\n // pipe), but suppress the human banner.\n await writeStdout(report.endsWith(\"\\n\") ? report : `${report}\\n`);\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(`qscan: ${message}\\n`);\n return EXIT.ERROR;\n }\n\n if (!options.quiet && run.suppressed.length > 0) {\n process.stderr.write(`qscan: suppressed ${run.suppressed.length} finding(s) via baseline\\n`);\n }\n\n return run.exitCode;\n}\n\n/** True for Node system errors (fs/os), which carry a string `code` like `ENOENT`. */\nfunction isErrno(err: unknown): err is NodeJS.ErrnoException {\n return err instanceof Error && typeof (err as NodeJS.ErrnoException).code === \"string\";\n}\n\n/**\n * Write to stdout, awaiting `drain` when the kernel buffer is full.\n *\n * `process.stdout.write` returns `false` when the OS buffer can't accept the\n * whole chunk (typical for large reports down a pipe or file redirect). If the\n * process then exits before the buffer flushes, the tail of the report is lost.\n * When the write is not fully flushed we wait for the `drain` event, so the\n * bytes are handed to the OS before we return and the report is never\n * truncated, regardless of how the caller exits. A fully-flushed write (the\n * common case, and a TTY) resolves synchronously on the next microtask.\n */\nfunction writeStdout(chunk: string): Promise<void> {\n const flushed = process.stdout.write(chunk);\n if (flushed) return Promise.resolve();\n return new Promise((resolve) => process.stdout.once(\"drain\", resolve));\n}\n\n/**\n * True when this module is the program's entry point. Resolves symlinks so the\n * check also holds when launched via the `qscan` bin shim in node_modules/.bin\n * (npm symlinks it) or through /tmp -> /private/tmp on macOS — otherwise\n * `npx @quantakrypto/qscan` (and `npm i -g`) would be a silent no-op.\n */\nfunction isMainModule(): boolean {\n const argv1 = process.argv[1];\n if (argv1 === undefined) return false;\n const thisPath = fileURLToPath(import.meta.url);\n try {\n return realpathSync(argv1) === realpathSync(thisPath);\n } catch {\n return argv1 === thisPath;\n }\n}\n\n// Only auto-run when invoked as a script (not when imported by a test).\nconst invokedDirectly = isMainModule();\n\nif (invokedDirectly) {\n main(process.argv.slice(2))\n .then((code) => {\n // Set the exit code and return WITHOUT calling process.exit(): a bare\n // process.exit() tears down the event loop before stdout's async buffer\n // drains, truncating large SARIF/JSON reports written to a pipe or file\n // redirect. Letting the loop empty naturally lets the buffer flush first.\n process.exitCode = code;\n })\n .catch((err) => {\n const message = err instanceof Error ? (err.stack ?? err.message) : String(err);\n process.stderr.write(`qscan: fatal: ${message}\\n`);\n process.exitCode = EXIT.ERROR;\n });\n}\n"]}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n/**\n * qScan command-line entry point.\n *\n * Thin shell over the programmatic API in `./index.ts`:\n * parse argv → runQscan → print/write report → process.exit(code).\n *\n * All policy (scanning, baseline, thresholds, rendering) lives in `index.ts`;\n * this file only deals with argv, stdout/stderr, files, and exit codes.\n */\n\nimport { realpathSync } from \"node:fs\";\nimport { writeFile } from \"node:fs/promises\";\nimport process from \"node:process\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { ConfigError } from \"@quantakrypto/core\";\n\nimport { ArgError, parseArgs } from \"./args.js\";\nimport type { ParsedArgs, QscanOptions } from \"./args.js\";\nimport { resolveColor } from \"./color.js\";\nimport { resolveConfig } from \"./config.js\";\nimport { HELP_TEXT, versionLine } from \"./help.js\";\nimport {\n EXIT,\n runCryptoAgilityEmit,\n runCryptoAgilityValidate,\n runHndlInit,\n runQscan,\n} from \"./index.js\";\nimport type { QscanRun } from \"./index.js\";\n\n/** Run the CLI and return the desired process exit code (never throws). */\nexport async function main(argv: readonly string[]): Promise<number> {\n let parsed: ParsedArgs;\n try {\n parsed = parseArgs(argv);\n } catch (err) {\n if (err instanceof ArgError) {\n process.stderr.write(`qscan: ${err.message}\\n`);\n process.stderr.write(`Run \"qscan --help\" for usage.\\n`);\n return EXIT.ERROR;\n }\n throw err;\n }\n\n if (parsed.kind === \"help\") {\n process.stdout.write(HELP_TEXT);\n return EXIT.OK;\n }\n if (parsed.kind === \"version\") {\n process.stdout.write(`${versionLine()}\\n`);\n return EXIT.OK;\n }\n\n // `qscan hndl init`: scaffold an hndl.yml from a seeding scan. Refuses to\n // overwrite an existing file.\n if (parsed.kind === \"hndl-init\") {\n try {\n const init = await runHndlInit(parsed.options);\n if (init.exists) {\n process.stderr.write(\n `qscan: ${init.path} already exists; refusing to overwrite. Edit it, or remove it and re-run.\\n`,\n );\n return EXIT.ERROR;\n }\n await writeFile(init.path, init.content, \"utf8\");\n process.stderr.write(\n `qscan: wrote ${init.path} (seeded from ${init.seededFindings} data-adjacent finding(s) of ${init.findingsScanned} scanned).\\n`,\n );\n process.stderr.write(\n `qscan: review the assets, then run \"qscan ${parsed.options.path} --hndl\" for exposure scores.\\n`,\n );\n return EXIT.OK;\n } catch (err) {\n if (isErrno(err) && err.code === \"ENOENT\") {\n process.stderr.write(`qscan: path not found: ${parsed.options.path}\\n`);\n return EXIT.ERROR;\n }\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(`qscan: ${message}\\n`);\n return EXIT.ERROR;\n }\n }\n\n // `qscan crypto-agility emit` / `--crypto-agility`: derive a posture manifest and\n // write it to stdout (or `--output`). Additive: always exits 0, never consulting\n // the severity threshold.\n if (parsed.kind === \"crypto-agility-emit\") {\n try {\n const { manifest } = await runCryptoAgilityEmit(parsed.options);\n const out = manifest.endsWith(\"\\n\") ? manifest : `${manifest}\\n`;\n if (parsed.options.output) {\n await writeFile(parsed.options.output, out, \"utf8\");\n if (!parsed.options.quiet) {\n process.stderr.write(\n `qscan: wrote crypto-agility manifest to ${parsed.options.output}\\n`,\n );\n }\n } else {\n await writeStdout(out);\n }\n return EXIT.OK;\n } catch (err) {\n if (isErrno(err) && err.code === \"ENOENT\") {\n const missing = typeof err.path === \"string\" && err.path ? err.path : parsed.options.path;\n process.stderr.write(`qscan: path not found: ${missing}\\n`);\n return EXIT.ERROR;\n }\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(`qscan: ${message}\\n`);\n return EXIT.ERROR;\n }\n }\n\n // `qscan crypto-agility validate <file>`: check a LOCAL manifest against the\n // schema. Exit 0 when valid, 1 when the manifest is invalid, 2 on an I/O error.\n if (parsed.kind === \"crypto-agility-validate\") {\n let validation;\n try {\n validation = await runCryptoAgilityValidate(parsed.file);\n } catch (err) {\n if (isErrno(err) && err.code === \"ENOENT\") {\n process.stderr.write(`qscan: path not found: ${parsed.file}\\n`);\n return EXIT.ERROR;\n }\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(`qscan: ${message}\\n`);\n return EXIT.ERROR;\n }\n if (validation.valid) {\n process.stdout.write(`qscan: ${parsed.file} is a valid crypto-agility manifest\\n`);\n return EXIT.OK;\n }\n process.stderr.write(`qscan: ${parsed.file} is not a valid crypto-agility manifest:\\n`);\n for (const e of validation.errors) {\n process.stderr.write(` - ${e}\\n`);\n }\n return EXIT.FINDINGS;\n }\n\n // Resolve `quantakrypto.config.json` (flags > config > defaults) before scanning.\n let options: QscanOptions;\n try {\n const resolved = await resolveConfig(parsed.options, parsed.explicit);\n options = resolved.options;\n if (!options.quiet) {\n for (const w of resolved.warnings) {\n process.stderr.write(`qscan: config warning: ${w}\\n`);\n }\n if (resolved.configPath) {\n process.stderr.write(`qscan: using config ${resolved.configPath}\\n`);\n }\n }\n } catch (err) {\n if (err instanceof ConfigError) {\n process.stderr.write(`qscan: ${err.message}\\n`);\n return EXIT.ERROR;\n }\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(`qscan: ${message}\\n`);\n return EXIT.ERROR;\n }\n\n // Color policy: --color/--no-color > NO_COLOR/FORCE_COLOR > interactive stdout.\n // Color is decoration only (every signal is also text), so this is purely an\n // accessibility / pipe-safety control. See resolveColor for the precedence.\n const color = resolveColor({\n choice: options.colorChoice,\n format: options.format,\n toFile: Boolean(options.output),\n isTTY: Boolean(process.stdout.isTTY),\n env: { NO_COLOR: process.env.NO_COLOR, FORCE_COLOR: process.env.FORCE_COLOR },\n });\n\n let run: QscanRun;\n try {\n run = await runQscan(options, { color });\n } catch (err) {\n // A missing file is the most common failure; a raw \"ENOENT: no such file or\n // directory, stat '…'\" reads like a tool bug. Turn it into a plain, actionable\n // line (still exit 2). Report the ACTUAL missing path from `err.path` when present\n // — an ENOENT can come from `--policy`, `--baseline`, or `--write-baseline`, not\n // just the scan path, and blaming the (existing) scan path misdirects the user.\n if (isErrno(err) && err.code === \"ENOENT\") {\n const missing = typeof err.path === \"string\" && err.path ? err.path : options.path;\n process.stderr.write(`qscan: path not found: ${missing}\\n`);\n return EXIT.ERROR;\n }\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(`qscan: ${message}\\n`);\n return EXIT.ERROR;\n }\n\n // --write-baseline: report what was written and stop.\n if (run.baselineWritten) {\n if (!options.quiet) {\n const n = run.baselineWritten.fingerprints.length;\n process.stderr.write(\n `qscan: wrote baseline with ${n} fingerprint${n === 1 ? \"\" : \"s\"} to ${options.writeBaseline}\\n`,\n );\n }\n return run.exitCode;\n }\n\n const report = run.report ?? \"\";\n try {\n if (options.output) {\n await writeFile(options.output, report.endsWith(\"\\n\") ? report : `${report}\\n`, \"utf8\");\n if (!options.quiet) {\n process.stderr.write(`qscan: wrote ${options.format} report to ${options.output}\\n`);\n }\n } else if (!options.quiet || options.format !== \"human\") {\n // In quiet mode we still emit machine formats to stdout (the point of a\n // pipe), but suppress the human banner.\n await writeStdout(report.endsWith(\"\\n\") ? report : `${report}\\n`);\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(`qscan: ${message}\\n`);\n return EXIT.ERROR;\n }\n\n if (!options.quiet && run.suppressed.length > 0) {\n process.stderr.write(`qscan: suppressed ${run.suppressed.length} finding(s) via baseline\\n`);\n }\n\n return run.exitCode;\n}\n\n/** True for Node system errors (fs/os), which carry a string `code` like `ENOENT`. */\nfunction isErrno(err: unknown): err is NodeJS.ErrnoException {\n return err instanceof Error && typeof (err as NodeJS.ErrnoException).code === \"string\";\n}\n\n/**\n * Write to stdout, awaiting `drain` when the kernel buffer is full.\n *\n * `process.stdout.write` returns `false` when the OS buffer can't accept the\n * whole chunk (typical for large reports down a pipe or file redirect). If the\n * process then exits before the buffer flushes, the tail of the report is lost.\n * When the write is not fully flushed we wait for the `drain` event, so the\n * bytes are handed to the OS before we return and the report is never\n * truncated, regardless of how the caller exits. A fully-flushed write (the\n * common case, and a TTY) resolves synchronously on the next microtask.\n */\nfunction writeStdout(chunk: string): Promise<void> {\n const flushed = process.stdout.write(chunk);\n if (flushed) return Promise.resolve();\n return new Promise((resolve) => process.stdout.once(\"drain\", resolve));\n}\n\n/**\n * True when this module is the program's entry point. Resolves symlinks so the\n * check also holds when launched via the `qscan` bin shim in node_modules/.bin\n * (npm symlinks it) or through /tmp -> /private/tmp on macOS — otherwise\n * `npx @quantakrypto/qscan` (and `npm i -g`) would be a silent no-op.\n */\nfunction isMainModule(): boolean {\n const argv1 = process.argv[1];\n if (argv1 === undefined) return false;\n const thisPath = fileURLToPath(import.meta.url);\n try {\n return realpathSync(argv1) === realpathSync(thisPath);\n } catch {\n return argv1 === thisPath;\n }\n}\n\n// Only auto-run when invoked as a script (not when imported by a test).\nconst invokedDirectly = isMainModule();\n\nif (invokedDirectly) {\n main(process.argv.slice(2))\n .then((code) => {\n // Set the exit code and return WITHOUT calling process.exit(): a bare\n // process.exit() tears down the event loop before stdout's async buffer\n // drains, truncating large SARIF/JSON reports written to a pipe or file\n // redirect. Letting the loop empty naturally lets the buffer flush first.\n process.exitCode = code;\n })\n .catch((err) => {\n const message = err instanceof Error ? (err.stack ?? err.message) : String(err);\n process.stderr.write(`qscan: fatal: ${message}\\n`);\n process.exitCode = EXIT.ERROR;\n });\n}\n"]}
@@ -0,0 +1,38 @@
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
+ /** What the user asked for on the command line. `"auto"` = decide from context. */
18
+ export type ColorChoice = "always" | "never" | "auto";
19
+ /** Just the environment variables the decision reads. */
20
+ interface ColorEnv {
21
+ NO_COLOR?: string | undefined;
22
+ FORCE_COLOR?: string | undefined;
23
+ }
24
+ export interface ColorContext {
25
+ /** From `--color` / `--no-color`; defaults to `"auto"`. */
26
+ choice: ColorChoice;
27
+ /** Report format; only `"human"` is ever colored. */
28
+ format: string;
29
+ /** True when the report goes to an output file rather than stdout. */
30
+ toFile: boolean;
31
+ /** `process.stdout.isTTY`. */
32
+ isTTY: boolean;
33
+ env: ColorEnv;
34
+ }
35
+ /** Resolve whether the human report should emit ANSI color. */
36
+ export declare function resolveColor(ctx: ColorContext): boolean;
37
+ export {};
38
+ //# sourceMappingURL=color.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"color.d.ts","sourceRoot":"","sources":["../src/color.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,mFAAmF;AACnF,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;AAEtD,yDAAyD;AACzD,UAAU,QAAQ;IAChB,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC;AAED,MAAM,WAAW,YAAY;IAC3B,2DAA2D;IAC3D,MAAM,EAAE,WAAW,CAAC;IACpB,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAC;IACf,sEAAsE;IACtE,MAAM,EAAE,OAAO,CAAC;IAChB,8BAA8B;IAC9B,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,EAAE,QAAQ,CAAC;CACf;AAYD,+DAA+D;AAC/D,wBAAgB,YAAY,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAgBvD"}
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 qscan hndl init [path] Scaffold an hndl.yml data map (see --hndl)\n qscan crypto-agility emit [path] Write a crypto-agility manifest (exits 0)\n qscan crypto-agility validate <file> Check a local manifest against the schema\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 --hndl Score harvest-now-decrypt-later exposure per\n finding + a repo summary from hndl.yml (data\n assets, classification, retention + secrecy\n lifetime vs the quantum-threat horizon; Mosca's\n inequality). Adds exposure fields to json/sarif;\n additive, never changes the exit code. Scaffold\n the map with \"qscan hndl init\". See docs/HNDL.md\n --crypto-agility Emit a crypto-agility posture manifest instead of a\n scan report (equivalent to \"crypto-agility emit\";\n always exits 0). A well-known-URL JSON document any\n agent/CI bot can read like security.txt: readiness\n score, quantum-vulnerable findings by severity, CBOM\n algorithm families, policy deadlines. Combine with\n --attestation / --hybrid-kex / --policy / -o.\n See docs/CRYPTO-AGILITY-MANIFEST.md\n --attestation <url> Record a posture-credential URL in the manifest\n (recorded verbatim, never fetched; offline boundary)\n --hybrid-kex / --no-hybrid-kex\n Assert hybrid post-quantum key exchange is / is not\n in use in the manifest (default: null / undetermined,\n since a static scan can't observe a negotiated group)\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 qscan hndl init Scaffold hndl.yml seeded with detected assets\n qscan . --hndl --format json Emit per-finding HNDL exposure + a repo summary\n qscan . --crypto-agility -o .well-known/crypto-agility.json\n qscan crypto-agility validate .well-known/crypto-agility.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,u1QA6HrB,CAAC;AAEF,4BAA4B;AAC5B,wBAAgB,WAAW,IAAI,MAAM,CAEpC"}
package/dist/help.js CHANGED
@@ -10,19 +10,34 @@ export const HELP_TEXT = `qscan — find quantum-vulnerable cryptography in any
10
10
 
11
11
  USAGE
12
12
  qscan [path] [options]
13
+ qscan hndl init [path] Scaffold an hndl.yml data map (see --hndl)
14
+ qscan crypto-agility emit [path] Write a crypto-agility manifest (exits 0)
15
+ qscan crypto-agility validate <file> Check a local manifest against the schema
13
16
 
14
17
  ARGUMENTS
15
18
  path Directory or file to scan (default: ".")
16
19
 
17
20
  OPTIONS
18
- --format <human|json|sarif|cbom|evidence>
21
+ --format <human|json|sarif|cbom|evidence|vex>
19
22
  Output format (default: human)
20
23
  --cbom Alias for --format cbom (CycloneDX CBOM)
24
+ --format vex OpenVEX 0.2.0 document — one statement per rule,
25
+ status "affected", with remediation + any
26
+ --triage verdict for supply-chain VEX pipelines
27
+ --merge <cbom.json> Merge an external CBOM (e.g. a qprobe endpoint
28
+ CBOM) into the --cbom output via CycloneDX
29
+ bom-link — one combined code + infra CBOM.
30
+ Repeatable.
21
31
  --format evidence ISO 27001 A.8.24 readiness report — findings +
22
32
  inventory + CBOM + a deterministic content hash
23
33
  (sign/timestamp it with an external signer)
24
34
  --policy <file> Crypto-policy JSON; adds conformant/violation/
25
35
  transition verdicts to the evidence report
36
+ --sign <command> Sign the evidence report: its contentHash is
37
+ piped to <command> on stdin; stdout is recorded
38
+ as the detached signature (needs --format evidence)
39
+ --timestamp <command> Like --sign, but records an RFC-3161 timestamp
40
+ token from <command> (needs --format evidence)
26
41
  -o, --output <file> Write the report to a file instead of stdout
27
42
  --severity-threshold <level> Fail (exit 1) on findings at/above this level;
28
43
  one of critical|high|medium|low|info
@@ -45,7 +60,12 @@ OPTIONS
45
60
  0 forces the in-process serial path
46
61
  --top <n> List <n> findings in the human report (default: 5)
47
62
  --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)
63
+ (category-5 = CNSA 2.0: ML-KEM-1024 / ML-DSA-87;
64
+ an alias for --profile cnsa-2.0)
65
+ --profile <id> Tailor migration guidance to a standards regime:
66
+ nist (default) | cnsa-2.0 | bsi-tr-02102 | anssi |
67
+ uk-ncsc. Sets the parameter sets, deadlines, and
68
+ whether hybridization is required/recommended/optional
49
69
  --cache [file] Reuse findings for unchanged files across runs
50
70
  (default file: .quantakrypto-cache.json)
51
71
  --triage BYOK LLM pass that re-ranks findings by real
@@ -60,10 +80,34 @@ OPTIONS
60
80
  be sent and exit without calling the provider
61
81
  --llm-provider <name> anthropic | openai-compatible (default: anthropic)
62
82
  --llm-model <id> Model id for the BYOK provider
83
+ --hndl Score harvest-now-decrypt-later exposure per
84
+ finding + a repo summary from hndl.yml (data
85
+ assets, classification, retention + secrecy
86
+ lifetime vs the quantum-threat horizon; Mosca's
87
+ inequality). Adds exposure fields to json/sarif;
88
+ additive, never changes the exit code. Scaffold
89
+ the map with "qscan hndl init". See docs/HNDL.md
90
+ --crypto-agility Emit a crypto-agility posture manifest instead of a
91
+ scan report (equivalent to "crypto-agility emit";
92
+ always exits 0). A well-known-URL JSON document any
93
+ agent/CI bot can read like security.txt: readiness
94
+ score, quantum-vulnerable findings by severity, CBOM
95
+ algorithm families, policy deadlines. Combine with
96
+ --attestation / --hybrid-kex / --policy / -o.
97
+ See docs/CRYPTO-AGILITY-MANIFEST.md
98
+ --attestation <url> Record a posture-credential URL in the manifest
99
+ (recorded verbatim, never fetched; offline boundary)
100
+ --hybrid-kex / --no-hybrid-kex
101
+ Assert hybrid post-quantum key exchange is / is not
102
+ in use in the manifest (default: null / undetermined,
103
+ since a static scan can't observe a negotiated group)
63
104
  --baseline <file> Suppress findings listed in a baseline file
64
105
  --write-baseline <file> Write current findings as a baseline, then exit 0
65
106
  --quiet Suppress the human summary banner
66
107
  --no-snippets Omit code snippets from the json/sarif report
108
+ --color Force ANSI color in the human report
109
+ --no-color Disable ANSI color (also: NO_COLOR env). Color is
110
+ decoration only — every signal is printed as text
67
111
  -v, --version Print version and exit
68
112
  -h, --help Print this help and exit
69
113
 
@@ -83,6 +127,10 @@ EXAMPLES
83
127
  qscan . --changed --since origin/main
84
128
  qscan . --parallel --concurrency 4
85
129
  qscan . --cbom -o qscan-cbom.json
130
+ qscan hndl init Scaffold hndl.yml seeded with detected assets
131
+ qscan . --hndl --format json Emit per-finding HNDL exposure + a repo summary
132
+ qscan . --crypto-agility -o .well-known/crypto-agility.json
133
+ qscan crypto-agility validate .well-known/crypto-agility.json
86
134
  `;
87
135
  /** The `--version` line. */
88
136
  export function versionLine() {
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6HxB,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 qscan hndl init [path] Scaffold an hndl.yml data map (see --hndl)\n qscan crypto-agility emit [path] Write a crypto-agility manifest (exits 0)\n qscan crypto-agility validate <file> Check a local manifest against the schema\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 --hndl Score harvest-now-decrypt-later exposure per\n finding + a repo summary from hndl.yml (data\n assets, classification, retention + secrecy\n lifetime vs the quantum-threat horizon; Mosca's\n inequality). Adds exposure fields to json/sarif;\n additive, never changes the exit code. Scaffold\n the map with \"qscan hndl init\". See docs/HNDL.md\n --crypto-agility Emit a crypto-agility posture manifest instead of a\n scan report (equivalent to \"crypto-agility emit\";\n always exits 0). A well-known-URL JSON document any\n agent/CI bot can read like security.txt: readiness\n score, quantum-vulnerable findings by severity, CBOM\n algorithm families, policy deadlines. Combine with\n --attestation / --hybrid-kex / --policy / -o.\n See docs/CRYPTO-AGILITY-MANIFEST.md\n --attestation <url> Record a posture-credential URL in the manifest\n (recorded verbatim, never fetched; offline boundary)\n --hybrid-kex / --no-hybrid-kex\n Assert hybrid post-quantum key exchange is / is not\n in use in the manifest (default: null / undetermined,\n since a static scan can't observe a negotiated group)\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 qscan hndl init Scaffold hndl.yml seeded with detected assets\n qscan . --hndl --format json Emit per-finding HNDL exposure + a repo summary\n qscan . --crypto-agility -o .well-known/crypto-agility.json\n qscan crypto-agility validate .well-known/crypto-agility.json\n`;\n\n/** The `--version` line. */\nexport function versionLine(): string {\n return `qscan ${VERSION}`;\n}\n"]}