@quantakrypto/qscan
Advanced tools
| /** | ||
| * Color-output policy for the human report. | ||
| * | ||
| * ANSI color is decoration, never the sole carrier of meaning (every severity, | ||
| * count, and score is also printed as text), so turning it off never loses | ||
| * information — it's an accessibility and pipe-safety control. This module is the | ||
| * single place that decides whether to emit it, with an explicit precedence so | ||
| * the behaviour is predictable and testable: | ||
| * | ||
| * 1. Non-`human` formats are NEVER colored — ANSI would corrupt JSON/SARIF/CBOM. | ||
| * 2. An explicit `--color` / `--no-color` flag wins over everything else. | ||
| * 3. `NO_COLOR` (present, non-empty) disables — https://no-color.org. | ||
| * 4. `FORCE_COLOR` enables (Node/supports-color convention: `0`/`false` disable, | ||
| * any other value — including empty — enables). | ||
| * 5. Otherwise: color only a live terminal (`stdout.isTTY`), never a file or pipe. | ||
| */ | ||
| /** What the user asked for on the command line. `"auto"` = decide from context. */ | ||
| export type ColorChoice = "always" | "never" | "auto"; | ||
| /** Just the environment variables the decision reads. */ | ||
| interface ColorEnv { | ||
| NO_COLOR?: string | undefined; | ||
| FORCE_COLOR?: string | undefined; | ||
| } | ||
| export interface ColorContext { | ||
| /** From `--color` / `--no-color`; defaults to `"auto"`. */ | ||
| choice: ColorChoice; | ||
| /** Report format; only `"human"` is ever colored. */ | ||
| format: string; | ||
| /** True when the report goes to an output file rather than stdout. */ | ||
| toFile: boolean; | ||
| /** `process.stdout.isTTY`. */ | ||
| isTTY: boolean; | ||
| env: ColorEnv; | ||
| } | ||
| /** Resolve whether the human report should emit ANSI color. */ | ||
| export declare function resolveColor(ctx: ColorContext): boolean; | ||
| export {}; | ||
| //# sourceMappingURL=color.d.ts.map |
| {"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"} |
| /** | ||
| * Color-output policy for the human report. | ||
| * | ||
| * ANSI color is decoration, never the sole carrier of meaning (every severity, | ||
| * count, and score is also printed as text), so turning it off never loses | ||
| * information — it's an accessibility and pipe-safety control. This module is the | ||
| * single place that decides whether to emit it, with an explicit precedence so | ||
| * the behaviour is predictable and testable: | ||
| * | ||
| * 1. Non-`human` formats are NEVER colored — ANSI would corrupt JSON/SARIF/CBOM. | ||
| * 2. An explicit `--color` / `--no-color` flag wins over everything else. | ||
| * 3. `NO_COLOR` (present, non-empty) disables — https://no-color.org. | ||
| * 4. `FORCE_COLOR` enables (Node/supports-color convention: `0`/`false` disable, | ||
| * any other value — including empty — enables). | ||
| * 5. Otherwise: color only a live terminal (`stdout.isTTY`), never a file or pipe. | ||
| */ | ||
| /** NO_COLOR counts when present and non-empty (per the no-color.org wording). */ | ||
| function noColorRequested(v) { | ||
| return v !== undefined && v !== ""; | ||
| } | ||
| /** FORCE_COLOR enables unless it is `0`/`false` (Node/supports-color semantics). */ | ||
| function forceColorRequested(v) { | ||
| return v !== undefined && v !== "0" && v.toLowerCase() !== "false"; | ||
| } | ||
| /** Resolve whether the human report should emit ANSI color. */ | ||
| export function resolveColor(ctx) { | ||
| // (1) Machine-readable formats must stay byte-clean. | ||
| if (ctx.format !== "human") | ||
| return false; | ||
| // (2) Explicit CLI intent is absolute. | ||
| if (ctx.choice === "never") | ||
| return false; | ||
| if (ctx.choice === "always") | ||
| return true; | ||
| // (3) Accessibility opt-out wins over FORCE_COLOR when both are set. | ||
| if (noColorRequested(ctx.env.NO_COLOR)) | ||
| return false; | ||
| // (4) FORCE_COLOR, when present, decides (enables, unless 0/false). | ||
| if (ctx.env.FORCE_COLOR !== undefined) | ||
| return forceColorRequested(ctx.env.FORCE_COLOR); | ||
| // (5) Auto: a live terminal only. | ||
| return ctx.isTTY && !ctx.toFile; | ||
| } | ||
| //# sourceMappingURL=color.js.map |
| {"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"]} |
| import type { EvidenceSigner } from "@quantakrypto/core"; | ||
| /** An EvidenceSigner that shells out to `command`, piping the payload on stdin. */ | ||
| export declare function commandSigner(command: string): EvidenceSigner; | ||
| //# sourceMappingURL=sign.d.ts.map |
| {"version":3,"file":"sign.d.ts","sourceRoot":"","sources":["../src/sign.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAiBzD,mFAAmF;AACnF,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,CA+B7D"} |
+54
| /** | ||
| * Build an {@link EvidenceSigner} from an operator-provided shell command, for the | ||
| * evidence attestation's detached signature / RFC-3161 timestamp (`--sign` / | ||
| * `--timestamp`). Per ADR-0004 the tool implements NO cryptography: it pipes the | ||
| * report's `contentHash` to the operator's own command (an `openssl` / `cosign` | ||
| * invocation, a TSA client, …) on stdin and records that command's stdout verbatim. | ||
| * | ||
| * The command is run through the platform shell so it can pipe (`… | base64`). The | ||
| * payload is passed on STDIN, never interpolated into the command string, so the | ||
| * (tool-controlled) contentHash can't alter the (operator-controlled) command. | ||
| */ | ||
| import { spawnSync } from "node:child_process"; | ||
| const SIGN_TIMEOUT_MS = 30_000; | ||
| const SIGN_MAX_BUFFER = 1 << 20; // 1 MiB — a signature / timestamp token is small | ||
| /** Non-sensitive provenance label: the program name of the command (no args/paths). */ | ||
| function signerLabel(command) { | ||
| // Skip any leading `KEY=value` env-assignment prefix (e.g. `AWS_SECRET=… cosign …`) | ||
| // so a secret in it never lands in the recorded `signedWith`, then take the program. | ||
| const prog = command | ||
| .trim() | ||
| .split(/\s+/) | ||
| .find((t) => !/^[A-Za-z_][A-Za-z0-9_]*=/.test(t)) || "external-signer"; | ||
| return prog.replace(/^.*[/\\]/, ""); // basename, so a key path in args never leaks | ||
| } | ||
| /** An EvidenceSigner that shells out to `command`, piping the payload on stdin. */ | ||
| export function commandSigner(command) { | ||
| const label = signerLabel(command); | ||
| return { | ||
| label, | ||
| sign(payload) { | ||
| const res = spawnSync(command, { | ||
| shell: true, | ||
| input: payload, | ||
| encoding: "utf8", | ||
| timeout: SIGN_TIMEOUT_MS, | ||
| maxBuffer: SIGN_MAX_BUFFER, | ||
| }); | ||
| if (res.error) { | ||
| throw new Error(`--sign/--timestamp: command "${label}" failed to run: ${res.error.message}`); | ||
| } | ||
| if (res.status !== 0) { | ||
| const how = res.status !== null ? `exited ${res.status}` : `terminated on ${res.signal}`; | ||
| const detail = (res.stderr || "").trim().slice(0, 200); | ||
| throw new Error(`--sign/--timestamp: command "${label}" ${how}${detail ? `: ${detail}` : ""}`); | ||
| } | ||
| const out = (res.stdout || "").trim(); | ||
| if (!out) { | ||
| throw new Error(`--sign/--timestamp: command "${label}" produced no output`); | ||
| } | ||
| return out; | ||
| }, | ||
| }; | ||
| } | ||
| //# sourceMappingURL=sign.js.map |
| {"version":3,"file":"sign.js","sourceRoot":"","sources":["../src/sign.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAI/C,MAAM,eAAe,GAAG,MAAM,CAAC;AAC/B,MAAM,eAAe,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,iDAAiD;AAElF,uFAAuF;AACvF,SAAS,WAAW,CAAC,OAAe;IAClC,oFAAoF;IACpF,qFAAqF;IACrF,MAAM,IAAI,GACR,OAAO;SACJ,IAAI,EAAE;SACN,KAAK,CAAC,KAAK,CAAC;SACZ,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,0BAA0B,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,iBAAiB,CAAC;IAC3E,OAAO,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,CAAC,8CAA8C;AACrF,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,aAAa,CAAC,OAAe;IAC3C,MAAM,KAAK,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IACnC,OAAO;QACL,KAAK;QACL,IAAI,CAAC,OAAe;YAClB,MAAM,GAAG,GAAG,SAAS,CAAC,OAAO,EAAE;gBAC7B,KAAK,EAAE,IAAI;gBACX,KAAK,EAAE,OAAO;gBACd,QAAQ,EAAE,MAAM;gBAChB,OAAO,EAAE,eAAe;gBACxB,SAAS,EAAE,eAAe;aAC3B,CAAC,CAAC;YACH,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;gBACd,MAAM,IAAI,KAAK,CACb,gCAAgC,KAAK,oBAAoB,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,CAC7E,CAAC;YACJ,CAAC;YACD,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACrB,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,UAAU,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,iBAAiB,GAAG,CAAC,MAAM,EAAE,CAAC;gBACzF,MAAM,MAAM,GAAG,CAAC,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;gBACvD,MAAM,IAAI,KAAK,CACb,gCAAgC,KAAK,KAAK,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,KAAK,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAC9E,CAAC;YACJ,CAAC;YACD,MAAM,GAAG,GAAG,CAAC,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;YACtC,IAAI,CAAC,GAAG,EAAE,CAAC;gBACT,MAAM,IAAI,KAAK,CAAC,gCAAgC,KAAK,sBAAsB,CAAC,CAAC;YAC/E,CAAC;YACD,OAAO,GAAG,CAAC;QACb,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["/**\n * Build an {@link EvidenceSigner} from an operator-provided shell command, for the\n * evidence attestation's detached signature / RFC-3161 timestamp (`--sign` /\n * `--timestamp`). Per ADR-0004 the tool implements NO cryptography: it pipes the\n * report's `contentHash` to the operator's own command (an `openssl` / `cosign`\n * invocation, a TSA client, …) on stdin and records that command's stdout verbatim.\n *\n * The command is run through the platform shell so it can pipe (`… | base64`). The\n * payload is passed on STDIN, never interpolated into the command string, so the\n * (tool-controlled) contentHash can't alter the (operator-controlled) command.\n */\nimport { spawnSync } from \"node:child_process\";\n\nimport type { EvidenceSigner } from \"@quantakrypto/core\";\n\nconst SIGN_TIMEOUT_MS = 30_000;\nconst SIGN_MAX_BUFFER = 1 << 20; // 1 MiB — a signature / timestamp token is small\n\n/** Non-sensitive provenance label: the program name of the command (no args/paths). */\nfunction signerLabel(command: string): string {\n // Skip any leading `KEY=value` env-assignment prefix (e.g. `AWS_SECRET=… cosign …`)\n // so a secret in it never lands in the recorded `signedWith`, then take the program.\n const prog =\n command\n .trim()\n .split(/\\s+/)\n .find((t) => !/^[A-Za-z_][A-Za-z0-9_]*=/.test(t)) || \"external-signer\";\n return prog.replace(/^.*[/\\\\]/, \"\"); // basename, so a key path in args never leaks\n}\n\n/** An EvidenceSigner that shells out to `command`, piping the payload on stdin. */\nexport function commandSigner(command: string): EvidenceSigner {\n const label = signerLabel(command);\n return {\n label,\n sign(payload: string): string {\n const res = spawnSync(command, {\n shell: true,\n input: payload,\n encoding: \"utf8\",\n timeout: SIGN_TIMEOUT_MS,\n maxBuffer: SIGN_MAX_BUFFER,\n });\n if (res.error) {\n throw new Error(\n `--sign/--timestamp: command \"${label}\" failed to run: ${res.error.message}`,\n );\n }\n if (res.status !== 0) {\n const how = res.status !== null ? `exited ${res.status}` : `terminated on ${res.signal}`;\n const detail = (res.stderr || \"\").trim().slice(0, 200);\n throw new Error(\n `--sign/--timestamp: command \"${label}\" ${how}${detail ? `: ${detail}` : \"\"}`,\n );\n }\n const out = (res.stdout || \"\").trim();\n if (!out) {\n throw new Error(`--sign/--timestamp: command \"${label}\" produced no output`);\n }\n return out;\n },\n };\n}\n"]} |
+28
-8
@@ -12,2 +12,3 @@ /** | ||
| import type { ContextLevel, ReportFormat, SecurityTier, Severity } from "@quantakrypto/core"; | ||
| import type { ColorChoice } from "./color.js"; | ||
| /** Valid `--llm-provider` values. */ | ||
@@ -20,5 +21,7 @@ declare const LLM_PROVIDERS: readonly ["anthropic", "openai-compatible"]; | ||
| * {@link ReportFormat} with `"cbom"` (a CycloneDX cryptographic bill of | ||
| * materials), which qScan renders locally via core's `toCbom`. | ||
| * materials), `"evidence"` (ISO A.8.24 readiness report), and `"vex"` (an | ||
| * OpenVEX 0.2.0 document) — all rendered locally via core (`toCbom`, | ||
| * `buildReadinessReport`, `toOpenVex`). | ||
| */ | ||
| export type QscanFormat = ReportFormat | "cbom" | "evidence"; | ||
| export type QscanFormat = ReportFormat | "cbom" | "evidence" | "vex"; | ||
| /** Fully-resolved options the CLI/programmatic runner operates on. */ | ||
@@ -32,2 +35,4 @@ export interface QscanOptions { | ||
| output?: string; | ||
| /** External CBOM files to merge into the `--cbom` output (CycloneDX bom-link). */ | ||
| mergeCboms?: string[]; | ||
| /** Findings at or above this severity cause a non-zero exit. */ | ||
@@ -74,2 +79,8 @@ severityThreshold: Severity; | ||
| quiet: boolean; | ||
| /** | ||
| * ANSI color policy for the human report (`--color` / `--no-color`). `"auto"` | ||
| * (default) colors only a live terminal and honors `NO_COLOR` / `FORCE_COLOR`; | ||
| * see {@link resolveColor}. Color is decoration only — never the sole signal. | ||
| */ | ||
| colorChoice: ColorChoice; | ||
| /** How many findings the human report lists (`--top N`). Default: 5. */ | ||
@@ -89,4 +100,19 @@ topN?: number; | ||
| tier?: SecurityTier; | ||
| /** | ||
| * Standards regime the migration guidance is tailored to (`--profile`): one of the | ||
| * built-in ids (nist / cnsa-2.0 / bsi-tr-02102 / anssi / uk-ncsc). Default: none | ||
| * (`--tier` maps to a profile for back-compat). Governs parameter sets, deadlines, | ||
| * and the hybrid stance surfaced in remediation. | ||
| */ | ||
| profile?: string; | ||
| /** Org cryptography policy file (`--policy`) for the evidence report's §4 verdicts. */ | ||
| policy?: string; | ||
| /** | ||
| * External signer command for the evidence attestation (`--sign`). The report's | ||
| * contentHash is piped to it on stdin; its stdout is recorded as the detached | ||
| * signature. Only valid with `--format evidence`. | ||
| */ | ||
| sign?: string; | ||
| /** External RFC-3161 timestamper command for the evidence attestation (`--timestamp`). */ | ||
| timestamp?: string; | ||
| /** How much source context leaves the machine (`--context`). Default: snippet. */ | ||
@@ -154,8 +180,2 @@ contextLevel?: ContextLevel; | ||
| export declare function asFormat(value: string): QscanFormat; | ||
| /** Validate/normalize a `--context` level. */ | ||
| export declare function asContextLevel(value: string): ContextLevel; | ||
| /** Validate/normalize a `--tier` value. */ | ||
| export declare function asTier(value: string): SecurityTier; | ||
| /** Validate/normalize a `--llm-provider` value. */ | ||
| export declare function asProvider(value: string): LlmProvider; | ||
| /** Validate/normalize a non-negative integer flag value. */ | ||
@@ -162,0 +182,0 @@ export declare function asInt(value: string, flag: string): number; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"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"} | ||
| {"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"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"} |
+35
-5
@@ -10,3 +10,3 @@ /** | ||
| */ | ||
| import { meetsThreshold, SEVERITY_ORDER, severityRank } from "@quantakrypto/core"; | ||
| import { meetsThreshold, SEVERITY_ORDER, severityRank, standardsProfileIds, } from "@quantakrypto/core"; | ||
| /** Valid context levels for `--context` (how much source triage/remediate sends). */ | ||
@@ -23,3 +23,3 @@ const CONTEXT_LEVELS = ["metadata", "snippet", "function", "file"]; | ||
| export { meetsThreshold, SEVERITY_ORDER, severityRank }; | ||
| const FORMATS = ["human", "json", "sarif", "cbom", "evidence"]; | ||
| const FORMATS = ["human", "json", "sarif", "cbom", "evidence", "vex"]; | ||
| /** Thrown on malformed input; the CLI maps this to exit code 2. */ | ||
@@ -45,2 +45,3 @@ export class ArgError extends Error { | ||
| quiet: false, | ||
| colorChoice: "auto", | ||
| noSnippets: false, | ||
@@ -109,2 +110,7 @@ noConfigFile: false, | ||
| break; | ||
| case "--merge": | ||
| // Merge an external CBOM (e.g. a qprobe endpoint CBOM) into the --cbom | ||
| // output via CycloneDX bom-link. Repeatable. | ||
| (options.mergeCboms ??= []).push(takeValue()); | ||
| break; | ||
| case "-o": | ||
@@ -161,2 +167,5 @@ case "--output": | ||
| break; | ||
| case "--profile": | ||
| options.profile = asProfile(takeValue()); | ||
| break; | ||
| case "--context": | ||
@@ -240,2 +249,8 @@ options.contextLevel = asContextLevel(takeValue()); | ||
| break; | ||
| case "--sign": | ||
| options.sign = takeValue(); | ||
| break; | ||
| case "--timestamp": | ||
| options.timestamp = takeValue(); | ||
| break; | ||
| case "--write-baseline": | ||
@@ -248,2 +263,10 @@ options.writeBaseline = takeValue(); | ||
| break; | ||
| case "--color": | ||
| rejectInlineValue(); | ||
| options.colorChoice = "always"; | ||
| break; | ||
| case "--no-color": | ||
| rejectInlineValue(); | ||
| options.colorChoice = "never"; | ||
| break; | ||
| case "--no-snippets": | ||
@@ -275,3 +298,3 @@ rejectInlineValue(); | ||
| /** Validate/normalize a `--context` level. */ | ||
| export function asContextLevel(value) { | ||
| function asContextLevel(value) { | ||
| if (CONTEXT_LEVELS.includes(value)) | ||
@@ -282,3 +305,3 @@ return value; | ||
| /** Validate/normalize a `--tier` value. */ | ||
| export function asTier(value) { | ||
| function asTier(value) { | ||
| if (SECURITY_TIERS.includes(value)) | ||
@@ -288,4 +311,11 @@ return value; | ||
| } | ||
| /** Validate a `--profile` value against the built-in standards regimes. */ | ||
| function asProfile(value) { | ||
| const ids = standardsProfileIds(); | ||
| if (ids.includes(value)) | ||
| return value; | ||
| throw new ArgError(`invalid --profile "${value}" (expected one of: ${ids.join(", ")})`); | ||
| } | ||
| /** Validate/normalize a `--llm-provider` value. */ | ||
| export function asProvider(value) { | ||
| function asProvider(value) { | ||
| if (LLM_PROVIDERS.includes(value)) | ||
@@ -292,0 +322,0 @@ return value; |
+1
-1
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"args.js","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Zero-dependency command-line argument parsing for qScan.\n *\n * Hand-rolled rather than pulled from a library to keep the package free of\n * runtime dependencies. The grammar is deliberately small: long flags\n * (`--flag`, `--flag value`, `--flag=value`), a couple of short aliases\n * (`-o`, `-v`, `-h`), repeatable `--ignore`, and a single optional positional\n * path. Unknown flags are a usage error.\n */\n\nimport { meetsThreshold, SEVERITY_ORDER, severityRank } from \"@quantakrypto/core\";\nimport type { ContextLevel, ReportFormat, SecurityTier, Severity } from \"@quantakrypto/core\";\n\n/** Valid context levels for `--context` (how much source triage/remediate sends). */\nconst CONTEXT_LEVELS: readonly ContextLevel[] = [\"metadata\", \"snippet\", \"function\", \"file\"];\n/** Valid CNSA security tiers for `--tier` (report migration-target guidance). */\nconst SECURITY_TIERS: readonly SecurityTier[] = [\"category-3\", \"category-5\"];\n/** Valid `--llm-provider` values. */\nconst LLM_PROVIDERS = [\"anthropic\", \"openai-compatible\"] as const;\nexport type LlmProvider = (typeof LLM_PROVIDERS)[number];\n\n// Severity ordering, ranking, and threshold logic are the monorepo's single\n// source of truth in `@quantakrypto/core`. Re-export them here so existing\n// `@quantakrypto/qscan` callers (and tests) keep importing them from `./args.js`\n// without qScan maintaining a second, drift-prone copy.\nexport { meetsThreshold, SEVERITY_ORDER, severityRank };\n\n/**\n * Output formats qScan accepts on the command line. Extends core's\n * {@link ReportFormat} with `\"cbom\"` (a CycloneDX cryptographic bill of\n * materials), which qScan renders locally via core's `toCbom`.\n */\nexport type QscanFormat = ReportFormat | \"cbom\" | \"evidence\";\n\nconst FORMATS: readonly QscanFormat[] = [\"human\", \"json\", \"sarif\", \"cbom\", \"evidence\"];\n\n/** Fully-resolved options the CLI/programmatic runner operates on. */\nexport interface QscanOptions {\n /** Directory or file to scan. */\n path: string;\n /** Report format. */\n format: QscanFormat;\n /** Write the report to this file instead of stdout, when set. */\n output?: string;\n /** Findings at or above this severity cause a non-zero exit. */\n severityThreshold: Severity;\n /** Scan source files for inline crypto usage. */\n source: boolean;\n /** Scan dependency manifests for vulnerable libraries. */\n dependencies: boolean;\n /** Scan config files (TLS/certificates). */\n config: boolean;\n /** Extra exclude patterns (repeatable `--ignore`). */\n ignore: string[];\n /**\n * Restrict the walk to paths matching one of these include patterns\n * (repeatable `--include`). When empty, every non-excluded file is scanned.\n */\n include: string[];\n /** Max file size to read, in bytes (`--max-file-size`). */\n maxFileSize?: number;\n /** Disable the built-in ignore list (`--no-default-ignores`). */\n noDefaultIgnores: boolean;\n /** Scan minified/generated/bundled files instead of skipping them. */\n scanMinified: boolean;\n /**\n * Incremental mode: scan only the files git reports as changed\n * (`--changed`), optionally relative to {@link since}.\n */\n changed: boolean;\n /** Git ref/range the `--changed` diff is taken against (`--since`). */\n since?: string;\n /** Route the scan through core's worker-thread pool (`--parallel`). */\n parallel: boolean;\n /**\n * Worker count for parallel scanning (`--concurrency`). Implies parallel.\n * A value of 0 or 1 forces the in-process serial path.\n */\n concurrency?: number;\n /** Suppress findings whose fingerprint is in this baseline file. */\n baseline?: string;\n /** Write current findings as a baseline to this file, then exit 0. */\n writeBaseline?: string;\n /** Suppress the human summary banner (still writes reports/output files). */\n quiet: boolean;\n /** How many findings the human report lists (`--top N`). Default: 5. */\n topN?: number;\n /** Rule ids to suppress (from `quantakrypto.config.json` `disabledRules`). */\n disabledRules?: string[];\n /** Content-hash scan cache path (`--cache [path]`); reuse unchanged files. */\n cacheFile?: string;\n /** Run the BYOK LLM triage pass (`--triage`): annotate + re-sort, never suppress. */\n triage: boolean;\n /** Only triage findings at/above this seriousness (`--triage-floor`). Default: medium. */\n triageFloor?: Severity;\n /** Cap findings sent to the LLM during triage (`--max-findings`; spend guard). */\n maxFindings?: number;\n /** CNSA security tier for the migration-targets footer (`--tier`). Default: none. */\n tier?: SecurityTier;\n /** Org cryptography policy file (`--policy`) for the evidence report's §4 verdicts. */\n policy?: string;\n /** How much source context leaves the machine (`--context`). Default: snippet. */\n contextLevel?: ContextLevel;\n /** Print the exact triage payload and exit without calling the provider (`--dry-run`). */\n dryRun: boolean;\n /** BYOK provider (`--llm-provider`). Default: anthropic. */\n llmProvider?: LlmProvider;\n /** BYOK model id (`--llm-model`). */\n llmModel?: string;\n /**\n * Omit code snippets from the JSON/SARIF report (`--no-snippets`). Passed to\n * core's reporters as `{ redactSnippets: true }`. Snippets of `sensitive`\n * findings are always omitted regardless of this flag.\n */\n noSnippets: boolean;\n /**\n * Explicit path to a `quantakrypto.config.json` (`--config <path>`). Overrides\n * auto-discovery at the scan root. Distinct from `--no-config`, which toggles\n * config/TLS *detector* scanning — this names the config FILE.\n */\n configFile?: string;\n /**\n * Disable `quantakrypto.config.json` auto-discovery (`--no-config-file`). Distinct\n * from `--no-config` (which skips config-file *detectors*).\n */\n noConfigFile: boolean;\n}\n\n/**\n * Option keys that a `quantakrypto.config.json` may also set. When such a key was set\n * by a CLI flag, the flag wins (precedence: flags > config > defaults); when it\n * was left at its default, config may fill it. {@link parseArgs} records which\n * of these keys came from an explicit flag in {@link ParsedRun.explicit}.\n */\nexport type ConfigurableKey =\n | \"severityThreshold\"\n | \"source\"\n | \"dependencies\"\n | \"config\"\n | \"include\"\n | \"ignore\"\n | \"maxFileSize\"\n | \"noDefaultIgnores\"\n | \"scanMinified\"\n | \"baseline\";\n\n/** A successful parse: resolved options plus which configurable keys were explicit. */\nexport interface ParsedRun {\n kind: \"run\";\n options: QscanOptions;\n /** The set of {@link ConfigurableKey}s the user set via a flag. */\n explicit: Set<ConfigurableKey>;\n}\n\n/** Result of {@link parseArgs}: either resolved options or a meta action. */\nexport type ParsedArgs = ParsedRun | { kind: \"help\" } | { kind: \"version\" };\n\n/** Thrown on malformed input; the CLI maps this to exit code 2. */\nexport class ArgError extends Error {\n override readonly name = \"ArgError\";\n}\n\n/** Default options, before any flags are applied. */\nexport function defaultOptions(): QscanOptions {\n return {\n path: \".\",\n format: \"human\",\n severityThreshold: \"high\",\n source: true,\n dependencies: true,\n config: true,\n ignore: [],\n include: [],\n noDefaultIgnores: false,\n scanMinified: false,\n changed: false,\n parallel: false,\n quiet: false,\n noSnippets: false,\n noConfigFile: false,\n triage: false,\n dryRun: false,\n };\n}\n\n/**\n * Parse a raw argv slice (i.e. without `node` and the script path).\n *\n * @throws {ArgError} On unknown flags, missing values, or invalid enum values.\n */\n/** Default scan-cache file when `--cache` is given without a path. */\nexport const DEFAULT_CACHE_FILE = \".quantakrypto-cache.json\";\n\nexport function parseArgs(argv: readonly string[]): ParsedArgs {\n const options = defaultOptions();\n const explicit = new Set<ConfigurableKey>();\n let positional: string | undefined;\n\n // Manual index walk so flags can consume the following token as a value.\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i] as string;\n\n // `--flag=value` → split into flag + inline value.\n let inlineValue: string | undefined;\n let flag = arg;\n if (arg.startsWith(\"--\") && arg.includes(\"=\")) {\n const eq = arg.indexOf(\"=\");\n flag = arg.slice(0, eq);\n inlineValue = arg.slice(eq + 1);\n }\n\n /** Consume a value for `flag`: prefer the inline `=value`, else the next token. */\n const takeValue = (): string => {\n if (inlineValue !== undefined) return inlineValue;\n const next = argv[i + 1];\n if (next === undefined || (next.startsWith(\"-\") && next !== \"-\")) {\n throw new ArgError(`option \"${flag}\" requires a value`);\n }\n i++;\n return next;\n };\n\n /**\n * Reject an inline `=value` on a boolean flag. Without this, `--quiet=false`\n * would silently ignore the value and turn the flag ON — the opposite of the\n * caller's intent. Boolean flags take no value, so any `=value` is an error.\n */\n const rejectInlineValue = (): void => {\n if (inlineValue !== undefined) {\n throw new ArgError(`option \"${flag}\" is a boolean flag and takes no value`);\n }\n };\n\n switch (flag) {\n case \"-h\":\n case \"--help\":\n return { kind: \"help\" };\n case \"-v\":\n case \"--version\":\n return { kind: \"version\" };\n\n case \"--format\":\n options.format = asFormat(takeValue());\n break;\n case \"--cbom\":\n rejectInlineValue();\n options.format = \"cbom\";\n break;\n case \"-o\":\n case \"--output\":\n options.output = takeValue();\n break;\n case \"--severity-threshold\":\n options.severityThreshold = asSeverity(takeValue());\n explicit.add(\"severityThreshold\");\n break;\n\n case \"--no-source\":\n rejectInlineValue();\n options.source = false;\n explicit.add(\"source\");\n break;\n case \"--no-deps\":\n rejectInlineValue();\n options.dependencies = false;\n explicit.add(\"dependencies\");\n break;\n case \"--no-config\":\n rejectInlineValue();\n options.config = false;\n explicit.add(\"config\");\n break;\n\n case \"--ignore\":\n options.ignore.push(takeValue());\n explicit.add(\"ignore\");\n break;\n case \"--include\":\n options.include.push(takeValue());\n explicit.add(\"include\");\n break;\n case \"--max-file-size\":\n options.maxFileSize = asInt(takeValue(), \"--max-file-size\");\n explicit.add(\"maxFileSize\");\n break;\n case \"--top\":\n options.topN = asInt(takeValue(), \"--top\");\n break;\n case \"--triage\":\n rejectInlineValue();\n options.triage = true;\n break;\n case \"--triage-floor\":\n options.triageFloor = asSeverity(takeValue());\n break;\n case \"--max-findings\":\n options.maxFindings = asInt(takeValue(), \"--max-findings\");\n break;\n case \"--tier\":\n options.tier = asTier(takeValue());\n break;\n case \"--context\":\n options.contextLevel = asContextLevel(takeValue());\n break;\n case \"--dry-run\":\n rejectInlineValue();\n options.dryRun = true;\n break;\n case \"--llm-provider\":\n options.llmProvider = asProvider(takeValue());\n break;\n case \"--llm-model\":\n options.llmModel = takeValue();\n break;\n case \"--cache\": {\n // `--cache` alone uses the default file; `--cache <path>` names it.\n if (inlineValue !== undefined) {\n options.cacheFile = inlineValue;\n } else {\n const next = argv[i + 1];\n if (next !== undefined && !(next.startsWith(\"-\") && next !== \"-\")) {\n options.cacheFile = next;\n i++;\n } else {\n options.cacheFile = DEFAULT_CACHE_FILE;\n }\n }\n break;\n }\n case \"--no-default-ignores\":\n rejectInlineValue();\n options.noDefaultIgnores = true;\n explicit.add(\"noDefaultIgnores\");\n break;\n case \"--scan-minified\":\n rejectInlineValue();\n options.scanMinified = true;\n explicit.add(\"scanMinified\");\n break;\n\n // `quantakrypto.config.json` FILE controls (distinct from `--no-config`, which\n // toggles config/TLS *detector* scanning above).\n case \"--config\":\n options.configFile = takeValue();\n break;\n case \"--no-config-file\":\n rejectInlineValue();\n options.noConfigFile = true;\n break;\n\n case \"--changed\":\n rejectInlineValue();\n options.changed = true;\n break;\n case \"--since\":\n options.since = takeValue();\n options.changed = true; // --since implies incremental mode\n break;\n\n case \"--parallel\":\n rejectInlineValue();\n options.parallel = true;\n break;\n case \"--concurrency\": {\n const n = asInt(takeValue(), \"--concurrency\");\n options.concurrency = n;\n // `--concurrency 0` documents \"serial\": core treats <1 as \"auto\" (full\n // parallelism), so without this special-case 0 would do the OPPOSITE of\n // what's documented. 0 forces the in-process serial path; any value >= 1\n // implies parallel.\n options.parallel = n >= 1;\n break;\n }\n\n case \"--baseline\":\n options.baseline = takeValue();\n explicit.add(\"baseline\");\n break;\n case \"--policy\":\n options.policy = takeValue();\n break;\n case \"--write-baseline\":\n options.writeBaseline = takeValue();\n break;\n case \"--quiet\":\n rejectInlineValue();\n options.quiet = true;\n break;\n case \"--no-snippets\":\n rejectInlineValue();\n options.noSnippets = true;\n break;\n\n default:\n if (flag.startsWith(\"-\") && flag !== \"-\") {\n throw new ArgError(`unknown option \"${flag}\"`);\n }\n if (positional !== undefined) {\n throw new ArgError(\n `unexpected extra argument \"${arg}\" (path already set to \"${positional}\")`,\n );\n }\n positional = arg;\n break;\n }\n }\n\n if (positional !== undefined) options.path = positional;\n return { kind: \"run\", options, explicit };\n}\n\n/** Validate/normalize a `--format` value. */\nexport function asFormat(value: string): QscanFormat {\n if ((FORMATS as readonly string[]).includes(value)) return value as QscanFormat;\n throw new ArgError(`invalid --format \"${value}\" (expected one of: ${FORMATS.join(\", \")})`);\n}\n\n/** Validate/normalize a `--context` level. */\nexport function asContextLevel(value: string): ContextLevel {\n if ((CONTEXT_LEVELS as readonly string[]).includes(value)) return value as ContextLevel;\n throw new ArgError(\n `invalid --context \"${value}\" (expected one of: ${CONTEXT_LEVELS.join(\", \")})`,\n );\n}\n\n/** Validate/normalize a `--tier` value. */\nexport function asTier(value: string): SecurityTier {\n if ((SECURITY_TIERS as readonly string[]).includes(value)) return value as SecurityTier;\n throw new ArgError(`invalid --tier \"${value}\" (expected one of: ${SECURITY_TIERS.join(\", \")})`);\n}\n\n/** Validate/normalize a `--llm-provider` value. */\nexport function asProvider(value: string): LlmProvider {\n if ((LLM_PROVIDERS as readonly string[]).includes(value)) return value as LlmProvider;\n throw new ArgError(\n `invalid --llm-provider \"${value}\" (expected one of: ${LLM_PROVIDERS.join(\", \")})`,\n );\n}\n\n/** Validate/normalize a non-negative integer flag value. */\nexport function asInt(value: string, flag: string): number {\n if (!/^\\d+$/.test(value)) {\n throw new ArgError(`invalid ${flag} \"${value}\" (expected a non-negative integer)`);\n }\n return Number.parseInt(value, 10);\n}\n\n/** Validate/normalize a severity value. */\nexport function asSeverity(value: string): Severity {\n if ((SEVERITY_ORDER as readonly string[]).includes(value)) return value as Severity;\n throw new ArgError(`invalid severity \"${value}\" (expected one of: ${SEVERITY_ORDER.join(\", \")})`);\n}\n"]} | ||
| {"version":3,"file":"args.js","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Zero-dependency command-line argument parsing for qScan.\n *\n * Hand-rolled rather than pulled from a library to keep the package free of\n * runtime dependencies. The grammar is deliberately small: long flags\n * (`--flag`, `--flag value`, `--flag=value`), a couple of short aliases\n * (`-o`, `-v`, `-h`), repeatable `--ignore`, and a single optional positional\n * path. Unknown flags are a usage error.\n */\n\nimport {\n meetsThreshold,\n SEVERITY_ORDER,\n severityRank,\n standardsProfileIds,\n} from \"@quantakrypto/core\";\nimport type { ContextLevel, ReportFormat, SecurityTier, Severity } from \"@quantakrypto/core\";\nimport type { ColorChoice } from \"./color.js\";\n\n/** Valid context levels for `--context` (how much source triage/remediate sends). */\nconst CONTEXT_LEVELS: readonly ContextLevel[] = [\"metadata\", \"snippet\", \"function\", \"file\"];\n/** Valid CNSA security tiers for `--tier` (report migration-target guidance). */\nconst SECURITY_TIERS: readonly SecurityTier[] = [\"category-3\", \"category-5\"];\n/** Valid `--llm-provider` values. */\nconst LLM_PROVIDERS = [\"anthropic\", \"openai-compatible\"] as const;\nexport type LlmProvider = (typeof LLM_PROVIDERS)[number];\n\n// Severity ordering, ranking, and threshold logic are the monorepo's single\n// source of truth in `@quantakrypto/core`. Re-export them here so existing\n// `@quantakrypto/qscan` callers (and tests) keep importing them from `./args.js`\n// without qScan maintaining a second, drift-prone copy.\nexport { meetsThreshold, SEVERITY_ORDER, severityRank };\n\n/**\n * Output formats qScan accepts on the command line. Extends core's\n * {@link ReportFormat} with `\"cbom\"` (a CycloneDX cryptographic bill of\n * materials), `\"evidence\"` (ISO A.8.24 readiness report), and `\"vex\"` (an\n * OpenVEX 0.2.0 document) — all rendered locally via core (`toCbom`,\n * `buildReadinessReport`, `toOpenVex`).\n */\nexport type QscanFormat = ReportFormat | \"cbom\" | \"evidence\" | \"vex\";\n\nconst FORMATS: readonly QscanFormat[] = [\"human\", \"json\", \"sarif\", \"cbom\", \"evidence\", \"vex\"];\n\n/** Fully-resolved options the CLI/programmatic runner operates on. */\nexport interface QscanOptions {\n /** Directory or file to scan. */\n path: string;\n /** Report format. */\n format: QscanFormat;\n /** Write the report to this file instead of stdout, when set. */\n output?: string;\n /** External CBOM files to merge into the `--cbom` output (CycloneDX bom-link). */\n mergeCboms?: string[];\n /** Findings at or above this severity cause a non-zero exit. */\n severityThreshold: Severity;\n /** Scan source files for inline crypto usage. */\n source: boolean;\n /** Scan dependency manifests for vulnerable libraries. */\n dependencies: boolean;\n /** Scan config files (TLS/certificates). */\n config: boolean;\n /** Extra exclude patterns (repeatable `--ignore`). */\n ignore: string[];\n /**\n * Restrict the walk to paths matching one of these include patterns\n * (repeatable `--include`). When empty, every non-excluded file is scanned.\n */\n include: string[];\n /** Max file size to read, in bytes (`--max-file-size`). */\n maxFileSize?: number;\n /** Disable the built-in ignore list (`--no-default-ignores`). */\n noDefaultIgnores: boolean;\n /** Scan minified/generated/bundled files instead of skipping them. */\n scanMinified: boolean;\n /**\n * Incremental mode: scan only the files git reports as changed\n * (`--changed`), optionally relative to {@link since}.\n */\n changed: boolean;\n /** Git ref/range the `--changed` diff is taken against (`--since`). */\n since?: string;\n /** Route the scan through core's worker-thread pool (`--parallel`). */\n parallel: boolean;\n /**\n * Worker count for parallel scanning (`--concurrency`). Implies parallel.\n * A value of 0 or 1 forces the in-process serial path.\n */\n concurrency?: number;\n /** Suppress findings whose fingerprint is in this baseline file. */\n baseline?: string;\n /** Write current findings as a baseline to this file, then exit 0. */\n writeBaseline?: string;\n /** Suppress the human summary banner (still writes reports/output files). */\n quiet: boolean;\n /**\n * ANSI color policy for the human report (`--color` / `--no-color`). `\"auto\"`\n * (default) colors only a live terminal and honors `NO_COLOR` / `FORCE_COLOR`;\n * see {@link resolveColor}. Color is decoration only — never the sole signal.\n */\n colorChoice: ColorChoice;\n /** How many findings the human report lists (`--top N`). Default: 5. */\n topN?: number;\n /** Rule ids to suppress (from `quantakrypto.config.json` `disabledRules`). */\n disabledRules?: string[];\n /** Content-hash scan cache path (`--cache [path]`); reuse unchanged files. */\n cacheFile?: string;\n /** Run the BYOK LLM triage pass (`--triage`): annotate + re-sort, never suppress. */\n triage: boolean;\n /** Only triage findings at/above this seriousness (`--triage-floor`). Default: medium. */\n triageFloor?: Severity;\n /** Cap findings sent to the LLM during triage (`--max-findings`; spend guard). */\n maxFindings?: number;\n /** CNSA security tier for the migration-targets footer (`--tier`). Default: none. */\n tier?: SecurityTier;\n /**\n * Standards regime the migration guidance is tailored to (`--profile`): one of the\n * built-in ids (nist / cnsa-2.0 / bsi-tr-02102 / anssi / uk-ncsc). Default: none\n * (`--tier` maps to a profile for back-compat). Governs parameter sets, deadlines,\n * and the hybrid stance surfaced in remediation.\n */\n profile?: string;\n /** Org cryptography policy file (`--policy`) for the evidence report's §4 verdicts. */\n policy?: string;\n /**\n * External signer command for the evidence attestation (`--sign`). The report's\n * contentHash is piped to it on stdin; its stdout is recorded as the detached\n * signature. Only valid with `--format evidence`.\n */\n sign?: string;\n /** External RFC-3161 timestamper command for the evidence attestation (`--timestamp`). */\n timestamp?: string;\n /** How much source context leaves the machine (`--context`). Default: snippet. */\n contextLevel?: ContextLevel;\n /** Print the exact triage payload and exit without calling the provider (`--dry-run`). */\n dryRun: boolean;\n /** BYOK provider (`--llm-provider`). Default: anthropic. */\n llmProvider?: LlmProvider;\n /** BYOK model id (`--llm-model`). */\n llmModel?: string;\n /**\n * Omit code snippets from the JSON/SARIF report (`--no-snippets`). Passed to\n * core's reporters as `{ redactSnippets: true }`. Snippets of `sensitive`\n * findings are always omitted regardless of this flag.\n */\n noSnippets: boolean;\n /**\n * Explicit path to a `quantakrypto.config.json` (`--config <path>`). Overrides\n * auto-discovery at the scan root. Distinct from `--no-config`, which toggles\n * config/TLS *detector* scanning — this names the config FILE.\n */\n configFile?: string;\n /**\n * Disable `quantakrypto.config.json` auto-discovery (`--no-config-file`). Distinct\n * from `--no-config` (which skips config-file *detectors*).\n */\n noConfigFile: boolean;\n}\n\n/**\n * Option keys that a `quantakrypto.config.json` may also set. When such a key was set\n * by a CLI flag, the flag wins (precedence: flags > config > defaults); when it\n * was left at its default, config may fill it. {@link parseArgs} records which\n * of these keys came from an explicit flag in {@link ParsedRun.explicit}.\n */\nexport type ConfigurableKey =\n | \"severityThreshold\"\n | \"source\"\n | \"dependencies\"\n | \"config\"\n | \"include\"\n | \"ignore\"\n | \"maxFileSize\"\n | \"noDefaultIgnores\"\n | \"scanMinified\"\n | \"baseline\";\n\n/** A successful parse: resolved options plus which configurable keys were explicit. */\nexport interface ParsedRun {\n kind: \"run\";\n options: QscanOptions;\n /** The set of {@link ConfigurableKey}s the user set via a flag. */\n explicit: Set<ConfigurableKey>;\n}\n\n/** Result of {@link parseArgs}: either resolved options or a meta action. */\nexport type ParsedArgs = ParsedRun | { kind: \"help\" } | { kind: \"version\" };\n\n/** Thrown on malformed input; the CLI maps this to exit code 2. */\nexport class ArgError extends Error {\n override readonly name = \"ArgError\";\n}\n\n/** Default options, before any flags are applied. */\nexport function defaultOptions(): QscanOptions {\n return {\n path: \".\",\n format: \"human\",\n severityThreshold: \"high\",\n source: true,\n dependencies: true,\n config: true,\n ignore: [],\n include: [],\n noDefaultIgnores: false,\n scanMinified: false,\n changed: false,\n parallel: false,\n quiet: false,\n colorChoice: \"auto\",\n noSnippets: false,\n noConfigFile: false,\n triage: false,\n dryRun: false,\n };\n}\n\n/**\n * Parse a raw argv slice (i.e. without `node` and the script path).\n *\n * @throws {ArgError} On unknown flags, missing values, or invalid enum values.\n */\n/** Default scan-cache file when `--cache` is given without a path. */\nexport const DEFAULT_CACHE_FILE = \".quantakrypto-cache.json\";\n\nexport function parseArgs(argv: readonly string[]): ParsedArgs {\n const options = defaultOptions();\n const explicit = new Set<ConfigurableKey>();\n let positional: string | undefined;\n\n // Manual index walk so flags can consume the following token as a value.\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i] as string;\n\n // `--flag=value` → split into flag + inline value.\n let inlineValue: string | undefined;\n let flag = arg;\n if (arg.startsWith(\"--\") && arg.includes(\"=\")) {\n const eq = arg.indexOf(\"=\");\n flag = arg.slice(0, eq);\n inlineValue = arg.slice(eq + 1);\n }\n\n /** Consume a value for `flag`: prefer the inline `=value`, else the next token. */\n const takeValue = (): string => {\n if (inlineValue !== undefined) return inlineValue;\n const next = argv[i + 1];\n if (next === undefined || (next.startsWith(\"-\") && next !== \"-\")) {\n throw new ArgError(`option \"${flag}\" requires a value`);\n }\n i++;\n return next;\n };\n\n /**\n * Reject an inline `=value` on a boolean flag. Without this, `--quiet=false`\n * would silently ignore the value and turn the flag ON — the opposite of the\n * caller's intent. Boolean flags take no value, so any `=value` is an error.\n */\n const rejectInlineValue = (): void => {\n if (inlineValue !== undefined) {\n throw new ArgError(`option \"${flag}\" is a boolean flag and takes no value`);\n }\n };\n\n switch (flag) {\n case \"-h\":\n case \"--help\":\n return { kind: \"help\" };\n case \"-v\":\n case \"--version\":\n return { kind: \"version\" };\n\n case \"--format\":\n options.format = asFormat(takeValue());\n break;\n case \"--cbom\":\n rejectInlineValue();\n options.format = \"cbom\";\n break;\n case \"--merge\":\n // Merge an external CBOM (e.g. a qprobe endpoint CBOM) into the --cbom\n // output via CycloneDX bom-link. Repeatable.\n (options.mergeCboms ??= []).push(takeValue());\n break;\n case \"-o\":\n case \"--output\":\n options.output = takeValue();\n break;\n case \"--severity-threshold\":\n options.severityThreshold = asSeverity(takeValue());\n explicit.add(\"severityThreshold\");\n break;\n\n case \"--no-source\":\n rejectInlineValue();\n options.source = false;\n explicit.add(\"source\");\n break;\n case \"--no-deps\":\n rejectInlineValue();\n options.dependencies = false;\n explicit.add(\"dependencies\");\n break;\n case \"--no-config\":\n rejectInlineValue();\n options.config = false;\n explicit.add(\"config\");\n break;\n\n case \"--ignore\":\n options.ignore.push(takeValue());\n explicit.add(\"ignore\");\n break;\n case \"--include\":\n options.include.push(takeValue());\n explicit.add(\"include\");\n break;\n case \"--max-file-size\":\n options.maxFileSize = asInt(takeValue(), \"--max-file-size\");\n explicit.add(\"maxFileSize\");\n break;\n case \"--top\":\n options.topN = asInt(takeValue(), \"--top\");\n break;\n case \"--triage\":\n rejectInlineValue();\n options.triage = true;\n break;\n case \"--triage-floor\":\n options.triageFloor = asSeverity(takeValue());\n break;\n case \"--max-findings\":\n options.maxFindings = asInt(takeValue(), \"--max-findings\");\n break;\n case \"--tier\":\n options.tier = asTier(takeValue());\n break;\n case \"--profile\":\n options.profile = asProfile(takeValue());\n break;\n case \"--context\":\n options.contextLevel = asContextLevel(takeValue());\n break;\n case \"--dry-run\":\n rejectInlineValue();\n options.dryRun = true;\n break;\n case \"--llm-provider\":\n options.llmProvider = asProvider(takeValue());\n break;\n case \"--llm-model\":\n options.llmModel = takeValue();\n break;\n case \"--cache\": {\n // `--cache` alone uses the default file; `--cache <path>` names it.\n if (inlineValue !== undefined) {\n options.cacheFile = inlineValue;\n } else {\n const next = argv[i + 1];\n if (next !== undefined && !(next.startsWith(\"-\") && next !== \"-\")) {\n options.cacheFile = next;\n i++;\n } else {\n options.cacheFile = DEFAULT_CACHE_FILE;\n }\n }\n break;\n }\n case \"--no-default-ignores\":\n rejectInlineValue();\n options.noDefaultIgnores = true;\n explicit.add(\"noDefaultIgnores\");\n break;\n case \"--scan-minified\":\n rejectInlineValue();\n options.scanMinified = true;\n explicit.add(\"scanMinified\");\n break;\n\n // `quantakrypto.config.json` FILE controls (distinct from `--no-config`, which\n // toggles config/TLS *detector* scanning above).\n case \"--config\":\n options.configFile = takeValue();\n break;\n case \"--no-config-file\":\n rejectInlineValue();\n options.noConfigFile = true;\n break;\n\n case \"--changed\":\n rejectInlineValue();\n options.changed = true;\n break;\n case \"--since\":\n options.since = takeValue();\n options.changed = true; // --since implies incremental mode\n break;\n\n case \"--parallel\":\n rejectInlineValue();\n options.parallel = true;\n break;\n case \"--concurrency\": {\n const n = asInt(takeValue(), \"--concurrency\");\n options.concurrency = n;\n // `--concurrency 0` documents \"serial\": core treats <1 as \"auto\" (full\n // parallelism), so without this special-case 0 would do the OPPOSITE of\n // what's documented. 0 forces the in-process serial path; any value >= 1\n // implies parallel.\n options.parallel = n >= 1;\n break;\n }\n\n case \"--baseline\":\n options.baseline = takeValue();\n explicit.add(\"baseline\");\n break;\n case \"--policy\":\n options.policy = takeValue();\n break;\n case \"--sign\":\n options.sign = takeValue();\n break;\n case \"--timestamp\":\n options.timestamp = takeValue();\n break;\n case \"--write-baseline\":\n options.writeBaseline = takeValue();\n break;\n case \"--quiet\":\n rejectInlineValue();\n options.quiet = true;\n break;\n case \"--color\":\n rejectInlineValue();\n options.colorChoice = \"always\";\n break;\n case \"--no-color\":\n rejectInlineValue();\n options.colorChoice = \"never\";\n break;\n case \"--no-snippets\":\n rejectInlineValue();\n options.noSnippets = true;\n break;\n\n default:\n if (flag.startsWith(\"-\") && flag !== \"-\") {\n throw new ArgError(`unknown option \"${flag}\"`);\n }\n if (positional !== undefined) {\n throw new ArgError(\n `unexpected extra argument \"${arg}\" (path already set to \"${positional}\")`,\n );\n }\n positional = arg;\n break;\n }\n }\n\n if (positional !== undefined) options.path = positional;\n return { kind: \"run\", options, explicit };\n}\n\n/** Validate/normalize a `--format` value. */\nexport function asFormat(value: string): QscanFormat {\n if ((FORMATS as readonly string[]).includes(value)) return value as QscanFormat;\n throw new ArgError(`invalid --format \"${value}\" (expected one of: ${FORMATS.join(\", \")})`);\n}\n\n/** Validate/normalize a `--context` level. */\nfunction asContextLevel(value: string): ContextLevel {\n if ((CONTEXT_LEVELS as readonly string[]).includes(value)) return value as ContextLevel;\n throw new ArgError(\n `invalid --context \"${value}\" (expected one of: ${CONTEXT_LEVELS.join(\", \")})`,\n );\n}\n\n/** Validate/normalize a `--tier` value. */\nfunction asTier(value: string): SecurityTier {\n if ((SECURITY_TIERS as readonly string[]).includes(value)) return value as SecurityTier;\n throw new ArgError(`invalid --tier \"${value}\" (expected one of: ${SECURITY_TIERS.join(\", \")})`);\n}\n\n/** Validate a `--profile` value against the built-in standards regimes. */\nfunction asProfile(value: string): string {\n const ids = standardsProfileIds();\n if (ids.includes(value)) return value;\n throw new ArgError(`invalid --profile \"${value}\" (expected one of: ${ids.join(\", \")})`);\n}\n\n/** Validate/normalize a `--llm-provider` value. */\nfunction asProvider(value: string): LlmProvider {\n if ((LLM_PROVIDERS as readonly string[]).includes(value)) return value as LlmProvider;\n throw new ArgError(\n `invalid --llm-provider \"${value}\" (expected one of: ${LLM_PROVIDERS.join(\", \")})`,\n );\n}\n\n/** Validate/normalize a non-negative integer flag value. */\nexport function asInt(value: string, flag: string): number {\n if (!/^\\d+$/.test(value)) {\n throw new ArgError(`invalid ${flag} \"${value}\" (expected a non-negative integer)`);\n }\n return Number.parseInt(value, 10);\n}\n\n/** Validate/normalize a severity value. */\nexport function asSeverity(value: string): Severity {\n if ((SEVERITY_ORDER as readonly string[]).includes(value)) return value as Severity;\n throw new ArgError(`invalid severity \"${value}\" (expected one of: ${SEVERITY_ORDER.join(\", \")})`);\n}\n"]} |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;GAQG;AAgBH,2EAA2E;AAC3E,wBAAsB,IAAI,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAsGnE"} | ||
| {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;GAQG;AAiBH,2EAA2E;AAC3E,wBAAsB,IAAI,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA6GnE"} |
+18
-9
@@ -17,2 +17,3 @@ #!/usr/bin/env node | ||
| import { ArgError, parseArgs } from "./args.js"; | ||
| import { resolveColor } from "./color.js"; | ||
| import { resolveConfig } from "./config.js"; | ||
@@ -66,7 +67,12 @@ import { HELP_TEXT, versionLine } from "./help.js"; | ||
| } | ||
| // Color only when writing the human report to an interactive stdout. | ||
| const color = options.format === "human" && | ||
| !options.output && | ||
| Boolean(process.stdout.isTTY) && | ||
| process.env.NO_COLOR === undefined; | ||
| // Color policy: --color/--no-color > NO_COLOR/FORCE_COLOR > interactive stdout. | ||
| // Color is decoration only (every signal is also text), so this is purely an | ||
| // accessibility / pipe-safety control. See resolveColor for the precedence. | ||
| const color = resolveColor({ | ||
| choice: options.colorChoice, | ||
| format: options.format, | ||
| toFile: Boolean(options.output), | ||
| isTTY: Boolean(process.stdout.isTTY), | ||
| env: { NO_COLOR: process.env.NO_COLOR, FORCE_COLOR: process.env.FORCE_COLOR }, | ||
| }); | ||
| let run; | ||
@@ -77,7 +83,10 @@ try { | ||
| catch (err) { | ||
| // A missing scan path is the most common failure; a raw | ||
| // "ENOENT: no such file or directory, stat '…'" reads like a tool bug. | ||
| // Turn it into a plain, actionable line (still exit 2). | ||
| // A missing file is the most common failure; a raw "ENOENT: no such file or | ||
| // directory, stat '…'" reads like a tool bug. Turn it into a plain, actionable | ||
| // line (still exit 2). Report the ACTUAL missing path from `err.path` when present | ||
| // — an ENOENT can come from `--policy`, `--baseline`, or `--write-baseline`, not | ||
| // just the scan path, and blaming the (existing) scan path misdirects the user. | ||
| if (isErrno(err) && err.code === "ENOENT") { | ||
| process.stderr.write(`qscan: path not found: ${options.path}\n`); | ||
| const missing = typeof err.path === "string" && err.path ? err.path : options.path; | ||
| process.stderr.write(`qscan: path not found: ${missing}\n`); | ||
| return EXIT.ERROR; | ||
@@ -84,0 +93,0 @@ } |
+1
-1
@@ -1,1 +0,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 { 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"]} | ||
| {"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 { 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 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"]} |
@@ -1,1 +0,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"} | ||
| {"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"} |
+41
-1
@@ -39,5 +39,45 @@ /** | ||
| const merged = applyConfig(options, loaded.config, explicit); | ||
| return { options: merged, configPath: loaded.path, warnings: loaded.warnings }; | ||
| // Security: an AUTO-DISCOVERED config (no explicit `--config`) can come from the | ||
| // scanned tree itself, so a hostile repo could silently WEAKEN its own scan | ||
| // (disable rules, raise the severity threshold, exclude files → flip exit 1→0). | ||
| // Never silently: warn loudly for each policy-weakening key it applied. An explicit | ||
| // `--config` is the operator's own choice and stays quiet. Use `--no-config-file` | ||
| // to ignore a discovered config entirely. | ||
| const warnings = [...loaded.warnings]; | ||
| if (options.configFile === undefined) { | ||
| warnings.push(...weakeningWarnings(loaded.config, explicit)); | ||
| } | ||
| return { options: merged, configPath: loaded.path, warnings }; | ||
| } | ||
| /** | ||
| * Warnings for each SCAN-WEAKENING key an auto-discovered config actually applied | ||
| * (present in the file and not overridden by a CLI flag). These are the keys that can | ||
| * make a scan pass that would otherwise fail, so an operator scanning an untrusted | ||
| * tree must see them rather than have the verdict silently softened. | ||
| */ | ||
| function weakeningWarnings(config, explicit) { | ||
| const w = []; | ||
| const applied = (key) => !explicit.has(key); | ||
| const note = (s) => w.push(`auto-discovered config ${s} — a scanned repo may author this; re-run with --no-config-file to ignore it`); | ||
| if (config.disabledRules && config.disabledRules.length > 0) { | ||
| note(`disabled ${config.disabledRules.length} detection rule(s): ${config.disabledRules.join(", ")}`); | ||
| } | ||
| if (config.severityThreshold !== undefined && applied("severityThreshold")) { | ||
| note(`set the failure severity-threshold to "${config.severityThreshold}"`); | ||
| } | ||
| if (config.exclude && config.exclude.length > 0) { | ||
| note(`excluded ${config.exclude.length} path pattern(s) from the scan`); | ||
| } | ||
| if (config.source === false && applied("source")) | ||
| note("disabled source scanning"); | ||
| if (config.dependencies === false && applied("dependencies")) | ||
| note("disabled dependency scanning"); | ||
| if (config.config === false && applied("config")) | ||
| note("disabled config-file scanning"); | ||
| if (config.maxFileSize !== undefined && applied("maxFileSize")) { | ||
| note(`capped scanned file size at ${config.maxFileSize} bytes`); | ||
| } | ||
| return w; | ||
| } | ||
| /** | ||
| * Apply a parsed config onto options under the precedence rule. Pure; returns a | ||
@@ -44,0 +84,0 @@ * new options object. Scalars: config fills only keys NOT set by a flag. Lists: |
@@ -1,1 +0,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"]} | ||
| {"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"]} |
+1
-1
@@ -8,5 +8,5 @@ /** | ||
| /** The full `--help` screen. */ | ||
| 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"; | ||
| 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"; | ||
| /** The `--version` line. */ | ||
| export declare function versionLine(): string; | ||
| //# sourceMappingURL=help.d.ts.map |
@@ -1,1 +0,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"} | ||
| {"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"} |
+22
-2
@@ -18,5 +18,12 @@ /** | ||
| OPTIONS | ||
| --format <human|json|sarif|cbom|evidence> | ||
| --format <human|json|sarif|cbom|evidence|vex> | ||
| Output format (default: human) | ||
| --cbom Alias for --format cbom (CycloneDX CBOM) | ||
| --format vex OpenVEX 0.2.0 document — one statement per rule, | ||
| status "affected", with remediation + any | ||
| --triage verdict for supply-chain VEX pipelines | ||
| --merge <cbom.json> Merge an external CBOM (e.g. a qprobe endpoint | ||
| CBOM) into the --cbom output via CycloneDX | ||
| bom-link — one combined code + infra CBOM. | ||
| Repeatable. | ||
| --format evidence ISO 27001 A.8.24 readiness report — findings + | ||
@@ -27,2 +34,7 @@ inventory + CBOM + a deterministic content hash | ||
| transition verdicts to the evidence report | ||
| --sign <command> Sign the evidence report: its contentHash is | ||
| piped to <command> on stdin; stdout is recorded | ||
| as the detached signature (needs --format evidence) | ||
| --timestamp <command> Like --sign, but records an RFC-3161 timestamp | ||
| token from <command> (needs --format evidence) | ||
| -o, --output <file> Write the report to a file instead of stdout | ||
@@ -50,3 +62,8 @@ --severity-threshold <level> Fail (exit 1) on findings at/above this level; | ||
| --tier <category-3|category-5> Add CNSA migration targets to the report footer | ||
| (category-5 = CNSA 2.0: ML-KEM-1024 / ML-DSA-87) | ||
| (category-5 = CNSA 2.0: ML-KEM-1024 / ML-DSA-87; | ||
| an alias for --profile cnsa-2.0) | ||
| --profile <id> Tailor migration guidance to a standards regime: | ||
| nist (default) | cnsa-2.0 | bsi-tr-02102 | anssi | | ||
| uk-ncsc. Sets the parameter sets, deadlines, and | ||
| whether hybridization is required/recommended/optional | ||
| --cache [file] Reuse findings for unchanged files across runs | ||
@@ -70,2 +87,5 @@ (default file: .quantakrypto-cache.json) | ||
| --no-snippets Omit code snippets from the json/sarif report | ||
| --color Force ANSI color in the human report | ||
| --no-color Disable ANSI color (also: NO_COLOR env). Color is | ||
| decoration only — every signal is printed as text | ||
| -v, --version Print version and exit | ||
@@ -72,0 +92,0 @@ -h, --help Print this help and exit |
+1
-1
@@ -1,1 +0,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"]} | ||
| {"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"]} |
+6
-2
@@ -12,3 +12,3 @@ /** | ||
| */ | ||
| import type { Baseline, CryptoPolicy, Finding, ParallelScanOptions, ScanResult, SecurityTier } from "@quantakrypto/core"; | ||
| import type { Baseline, CryptoPolicy, CycloneDxBom, Finding, ParallelScanOptions, ScanResult, SecurityTier } from "@quantakrypto/core"; | ||
| import type { QscanOptions } from "./args.js"; | ||
@@ -19,3 +19,3 @@ export type { QscanOptions, ParsedArgs, ParsedRun, QscanFormat } from "./args.js"; | ||
| export { applyBaseline, baselineFromFindings, BASELINE_VERSION, buildBaseline, fingerprint, fingerprintFinding, loadBaseline, readBaseline, saveBaseline, writeBaseline, } from "./baseline.js"; | ||
| export { renderCbom, renderHuman, renderJson, renderSarif } from "./report.js"; | ||
| export { renderCbom, renderHuman, renderJson, renderSarif, renderVex } from "./report.js"; | ||
| export { HELP_TEXT, versionLine } from "./help.js"; | ||
@@ -114,4 +114,8 @@ export { runRemediate, parseRemediateArgs, unifiedDiff, REMEDIATE_HELP, REMEDIATE_EXIT, } from "./remediate-cli.js"; | ||
| tier?: SecurityTier; | ||
| /** Standards regime for the migration-targets footer (`--profile`). */ | ||
| profile?: string; | ||
| /** Org cryptography policy for the evidence report's §4 verdicts (`--policy`). */ | ||
| policy?: CryptoPolicy; | ||
| /** External CBOMs to merge into the `cbom` output (CycloneDX bom-link). */ | ||
| mergeCboms?: CycloneDxBom[]; | ||
| } | ||
@@ -118,0 +122,0 @@ /** Render a scan result in the requested format. */ |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"} | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"} |
+80
-22
@@ -14,9 +14,10 @@ /** | ||
| import process from "node:process"; | ||
| import { buildReadinessReport, changedFiles, parseCryptoPolicy, scan, scanParallel, } from "@quantakrypto/core"; | ||
| import { buildReadinessReport, changedFiles, parseCryptoPolicy, scan, scanParallel, signReadinessReport, } from "@quantakrypto/core"; | ||
| import { commandSigner } from "./sign.js"; | ||
| import { applyBaseline, readBaseline, saveBaseline } from "./baseline.js"; | ||
| import { defaultOptions, meetsThreshold } from "./args.js"; | ||
| import { renderCbom, renderHuman, renderJson, renderSarif } from "./report.js"; | ||
| import { renderCbom, renderHuman, renderJson, renderSarif, renderVex } from "./report.js"; | ||
| export { ArgError, asFormat, asInt, asSeverity, defaultOptions, meetsThreshold, parseArgs, severityRank, SEVERITY_ORDER, } from "./args.js"; | ||
| export { applyBaseline, baselineFromFindings, BASELINE_VERSION, buildBaseline, fingerprint, fingerprintFinding, loadBaseline, readBaseline, saveBaseline, writeBaseline, } from "./baseline.js"; | ||
| export { renderCbom, renderHuman, renderJson, renderSarif } from "./report.js"; | ||
| export { renderCbom, renderHuman, renderJson, renderSarif, renderVex } from "./report.js"; | ||
| export { HELP_TEXT, versionLine } from "./help.js"; | ||
@@ -158,14 +159,65 @@ export { runRemediate, parseRemediateArgs, unifiedDiff, REMEDIATE_HELP, REMEDIATE_EXIT, } from "./remediate-cli.js"; | ||
| } | ||
| return { | ||
| result, | ||
| suppressed, | ||
| report: renderReport(result, options.format, { | ||
| color: hooks.color ?? false, | ||
| redactSnippets: options.noSnippets, | ||
| topN: options.topN, | ||
| tier: options.tier, | ||
| ...(policy ? { policy } : {}), | ||
| }), | ||
| exitCode, | ||
| }; | ||
| // `--merge` only has an effect on a `--cbom` output. If the user asked to merge but | ||
| // the format is not cbom, that is almost certainly a mistake (a typo'd `--cbom`, or a | ||
| // pipeline that forgot it) — the merge files would be silently ignored and the | ||
| // combined bill of materials never produced. Fail loudly instead of dropping data. | ||
| if (options.mergeCboms && options.mergeCboms.length > 0 && options.format !== "cbom") { | ||
| throw new Error(`--merge requires --format cbom (got ${options.format ?? "the human report"})`); | ||
| } | ||
| // `--sign` / `--timestamp` fill the evidence attestation, so they only make sense | ||
| // with `--format evidence`. Fail loudly rather than silently ignore the signer. | ||
| if ((options.sign || options.timestamp) && options.format !== "evidence") { | ||
| throw new Error(`--sign/--timestamp require --format evidence (got ${options.format ?? "the human report"})`); | ||
| } | ||
| const signer = options.sign ? commandSigner(options.sign) : undefined; | ||
| const timestamper = options.timestamp | ||
| ? commandSigner(options.timestamp) | ||
| : undefined; | ||
| // Load any external CBOMs to merge into a `--cbom` output (combined | ||
| // code + infrastructure bill of materials). Only relevant for the cbom format. | ||
| let mergeCbomsData; | ||
| if (options.format === "cbom" && options.mergeCboms && options.mergeCboms.length > 0) { | ||
| mergeCbomsData = []; | ||
| for (const path of options.mergeCboms) { | ||
| let text; | ||
| try { | ||
| text = await readFile(path, "utf8"); | ||
| } | ||
| catch { | ||
| throw new Error(`--merge: cannot read CBOM file "${path}"`); | ||
| } | ||
| let parsed; | ||
| try { | ||
| parsed = JSON.parse(text); | ||
| } | ||
| catch { | ||
| throw new Error(`--merge: "${path}" is not valid JSON`); | ||
| } | ||
| const bom = parsed; | ||
| if (bom?.bomFormat !== "CycloneDX") { | ||
| throw new Error(`--merge: "${path}" is not a CycloneDX CBOM (missing bomFormat)`); | ||
| } | ||
| mergeCbomsData.push(bom); | ||
| } | ||
| } | ||
| let report = renderReport(result, options.format, { | ||
| color: hooks.color ?? false, | ||
| redactSnippets: options.noSnippets, | ||
| topN: options.topN, | ||
| tier: options.tier, | ||
| ...(options.profile ? { profile: options.profile } : {}), | ||
| ...(policy ? { policy } : {}), | ||
| ...(mergeCbomsData ? { mergeCboms: mergeCbomsData } : {}), | ||
| }); | ||
| // Evidence signing is orchestrated here (async: an external signer may be async), | ||
| // after the synchronous renderer has produced the unsigned report (ADR-0004: the | ||
| // tool orchestrates a signer, it does not implement crypto). | ||
| if (options.format === "evidence" && (signer || timestamper)) { | ||
| const signed = await signReadinessReport(JSON.parse(report), { | ||
| signer, | ||
| timestamper, | ||
| }); | ||
| report = JSON.stringify(signed, null, 2); | ||
| } | ||
| return { result, suppressed, report, exitCode }; | ||
| } | ||
@@ -175,3 +227,3 @@ /** Render a scan result in the requested format. */ | ||
| // Back-compat: `renderReport(result, format, true)` used to mean "color on". | ||
| const { color = false, redactSnippets = false, topN = undefined, tier = undefined, policy = undefined, } = typeof opts === "boolean" ? { color: opts, policy: undefined } : opts; | ||
| const { color = false, redactSnippets = false, topN = undefined, tier = undefined, profile = undefined, policy = undefined, mergeCboms = undefined, } = typeof opts === "boolean" ? { color: opts, policy: undefined } : opts; | ||
| switch (format) { | ||
@@ -183,16 +235,22 @@ case "json": | ||
| case "cbom": | ||
| return renderCbom(result); | ||
| case "evidence": | ||
| return renderCbom(result, mergeCboms); | ||
| case "vex": | ||
| return renderVex(result); | ||
| case "evidence": { | ||
| // ISO A.8.24 readiness report; repo/commit come from CI env when present. | ||
| // A `--policy` file adds the §4 conformant/violation/transition verdicts. | ||
| return JSON.stringify(buildReadinessReport(result, { | ||
| // A `--policy` file adds the §4 conformant/violation/transition verdicts. The | ||
| // attestation is left unsigned here; signing is an async step in runQscan (an | ||
| // external signer may be async), so this renderer stays synchronous. | ||
| const report = buildReadinessReport(result, { | ||
| repository: process.env.GITHUB_REPOSITORY, | ||
| commit: process.env.GITHUB_SHA, | ||
| ...(policy ? { policy } : {}), | ||
| }), null, 2); | ||
| }); | ||
| return JSON.stringify(report, null, 2); | ||
| } | ||
| case "human": | ||
| default: | ||
| return renderHuman(result, { color, topN, tier }); | ||
| return renderHuman(result, { color, topN, tier, profile }); | ||
| } | ||
| } | ||
| //# sourceMappingURL=index.js.map |
@@ -1,1 +0,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"]} | ||
| {"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"]} |
@@ -24,3 +24,3 @@ import type { Finding, Patch, ScanResult } from "@quantakrypto/core"; | ||
| /** A verified fix set ready to become a draft PR. */ | ||
| export interface DraftPrPlan { | ||
| interface DraftPrPlan { | ||
| branch: string; | ||
@@ -35,3 +35,3 @@ title: string; | ||
| /** Open a draft PR for the plan (injectable; default shells git + gh in a worktree). */ | ||
| export type OpenDraftPr = (plan: DraftPrPlan) => Promise<{ | ||
| type OpenDraftPr = (plan: DraftPrPlan) => Promise<{ | ||
| url?: string; | ||
@@ -57,4 +57,2 @@ }>; | ||
| }; | ||
| /** Default per-run cap on paid LLM fix proposals (spend/DoS guard; override with --max-llm). */ | ||
| export declare const DEFAULT_MAX_LLM = 25; | ||
| /** Minimal unified diff for a localized change (3 lines of context). */ | ||
@@ -77,2 +75,3 @@ export declare function unifiedDiff(relPath: string, before: string, after: string): string; | ||
| 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"; | ||
| export {}; | ||
| //# sourceMappingURL=remediate-cli.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"remediate-cli.d.ts","sourceRoot":"","sources":["../src/remediate-cli.ts"],"names":[],"mappings":"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"} | ||
| {"version":3,"file":"remediate-cli.d.ts","sourceRoot":"","sources":["../src/remediate-cli.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAgC,MAAM,oBAAoB,CAAC;AAEnG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAI7C,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;AAEpD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,aAAa,CAAC;IACpB,sDAAsD;IACtD,GAAG,EAAE,OAAO,CAAC;IACb;sFACkF;IAClF,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,mEAAmE;IACnE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,WAAW,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,iEAAiE;IACjE,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,qDAAqD;AACrD,UAAU,WAAW;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACjD;AAED,wFAAwF;AACxF,KAAK,WAAW,GAAG,CAAC,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEpE,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IAC/C,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5C,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,2EAA2E;IAC3E,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;IAC9E,qDAAqD;IACrD,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACtC,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,wDAAwD;IACxD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,eAAO,MAAM,cAAc;;;;CAA2C,CAAC;AAgBvE,wEAAwE;AACxE,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAuBlF;AAmED,mFAAmF;AACnF,wBAAsB,YAAY,CAChC,OAAO,EAAE,gBAAgB,EACzB,KAAK,GAAE,cAAmB,GACzB,OAAO,CAAC,YAAY,CAAC,CAgKvB;AAkDD,6BAA6B;AAC7B,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,SAAS,MAAM,EAAE,GAErB;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,gBAAgB,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAkErC;AAED,eAAO,MAAM,cAAc,y9CAyB1B,CAAC"} |
@@ -18,3 +18,3 @@ /** | ||
| /** Default per-run cap on paid LLM fix proposals (spend/DoS guard; override with --max-llm). */ | ||
| export const DEFAULT_MAX_LLM = 25; | ||
| const DEFAULT_MAX_LLM = 25; | ||
| function envKey(provider) { | ||
@@ -21,0 +21,0 @@ return (process.env.QK_LLM_API_KEY ?? |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"remediate-cli.js","sourceRoot":"","sources":["../src/remediate-cli.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `qremediate` — apply deterministic, verified fixes to quantum-vulnerable /\n * insecure crypto findings.\n *\n * Pipeline: scan → for each finding, a codemod (then optionally the LLM with\n * `--llm`) proposes a patch → the core remediation pipeline gates it\n * (patch-policy + verify_fix) → verified patches are shown (`diff`), written\n * (`apply`), or opened as a DRAFT PR (`pr`). Never auto-merges.\n */\nimport { readFile as fsReadFile, writeFile as fsWriteFile } from \"node:fs/promises\";\nimport { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport path from \"node:path\";\n\nimport {\n scan,\n codemodFor,\n remediateFindings,\n isManifestFile,\n fingerprintFinding,\n withWorktree,\n} from \"@quantakrypto/core\";\nimport type { Finding, Patch, ScanResult, VerifiedPatch, RejectedPatch } from \"@quantakrypto/core\";\n\nimport type { LlmProvider } from \"./args.js\";\n\nconst exec = promisify(execFile);\n\nexport type RemediateMode = \"diff\" | \"apply\" | \"pr\";\n\nexport interface RemediateOptions {\n path: string;\n mode: RemediateMode;\n /** Use a BYOK LLM to propose fixes codemods can't. */\n llm: boolean;\n /** Actually write LLM-proposed fixes in `apply` mode (they are otherwise shown\n * as diffs and held back — an LLM rewrite must be reviewed, not auto-applied). */\n applyLlm?: boolean;\n /** Cap how many findings are sent to the LLM (spend/DoS guard). */\n maxLlm?: number;\n provider?: LlmProvider;\n model?: string;\n}\n\nexport interface RemediateRun {\n output: string;\n exitCode: number;\n /** Relative paths written (apply mode) or included in the PR. */\n written: string[];\n}\n\n/** A verified fix set ready to become a draft PR. */\nexport interface DraftPrPlan {\n branch: string;\n title: string;\n body: string;\n patches: { path: string; newContent: string }[];\n}\n\n/** Open a draft PR for the plan (injectable; default shells git + gh in a worktree). */\nexport type OpenDraftPr = (plan: DraftPrPlan) => Promise<{ url?: string }>;\n\nexport interface RemediateHooks {\n scanFn?: (root: string) => Promise<ScanResult>;\n readFile?: (abs: string) => Promise<string>;\n writeFile?: (abs: string, content: string) => Promise<void>;\n /** LLM patch source (default wraps `@quantakrypto/agent`'s proposeFix). */\n llmPatchSource?: (finding: Finding, content: string) => Promise<Patch | null>;\n /** Draft-PR backend (default: git worktree + gh). */\n openDraftPr?: OpenDraftPr;\n resolveKey?: () => string | undefined;\n stderr?: (s: string) => void;\n /** Branch suffix (injected for deterministic tests). */\n branchSuffix?: string;\n}\n\nexport const REMEDIATE_EXIT = { OK: 0, CHANGES: 0, ERROR: 2 } as const;\n\n/** Default per-run cap on paid LLM fix proposals (spend/DoS guard; override with --max-llm). */\nexport const DEFAULT_MAX_LLM = 25;\n\nfunction envKey(provider: LlmProvider): string | undefined {\n return (\n process.env.QK_LLM_API_KEY ??\n (provider === \"anthropic\" ? process.env.ANTHROPIC_API_KEY : process.env.OPENAI_API_KEY)\n );\n}\n\nfunction defaultModel(provider: LlmProvider): string {\n return provider === \"anthropic\" ? \"claude-sonnet-5\" : \"gpt-4o-mini\";\n}\n\n/** Minimal unified diff for a localized change (3 lines of context). */\nexport function unifiedDiff(relPath: string, before: string, after: string): string {\n if (before === after) return \"\";\n const a = before.split(\"\\n\");\n const b = after.split(\"\\n\");\n let start = 0;\n while (start < a.length && start < b.length && a[start] === b[start]) start++;\n let endA = a.length - 1;\n let endB = b.length - 1;\n while (endA >= start && endB >= start && a[endA] === b[endB]) {\n endA--;\n endB--;\n }\n const ctx = 3;\n const from = Math.max(0, start - ctx);\n const toA = Math.min(a.length - 1, endA + ctx);\n const toB = Math.min(b.length - 1, endB + ctx);\n const lines: string[] = [`--- a/${relPath}`, `+++ b/${relPath}`];\n lines.push(`@@ -${from + 1},${toA - from + 1} +${from + 1},${toB - from + 1} @@`);\n for (let i = from; i < start; i++) lines.push(` ${a[i]}`);\n for (let i = start; i <= endA; i++) lines.push(`-${a[i]}`);\n for (let i = start; i <= endB; i++) lines.push(`+${b[i]}`);\n for (let i = endA + 1; i <= toA; i++) lines.push(` ${a[i]}`);\n return lines.join(\"\\n\");\n}\n\n/**\n * Default draft-PR backend: apply the patches inside an ephemeral worktree\n * (never touching the user's checkout), commit on a new branch, push it, and\n * `gh pr create --draft`. NEVER merges.\n */\nasync function defaultOpenDraftPr(root: string, plan: DraftPrPlan): Promise<{ url?: string }> {\n return withWorktree(root, async (dir) => {\n await exec(\"git\", [\"-C\", dir, \"checkout\", \"-b\", plan.branch]);\n for (const p of plan.patches) {\n await fsWriteFile(path.resolve(dir, p.path), p.newContent, \"utf8\");\n }\n await exec(\"git\", [\"-C\", dir, \"add\", \"--\", ...plan.patches.map((p) => p.path)]);\n await exec(\"git\", [\"-C\", dir, \"commit\", \"-m\", plan.title]);\n await exec(\"git\", [\"-C\", dir, \"push\", \"-u\", \"origin\", plan.branch]);\n const { stdout } = await exec(\n \"gh\",\n [\n \"pr\",\n \"create\",\n \"--draft\",\n \"--head\",\n plan.branch,\n \"--title\",\n plan.title,\n \"--body\",\n plan.body,\n ],\n { cwd: dir },\n );\n return { url: stdout.trim() };\n });\n}\n\nfunction prBody(\n patches: VerifiedPatch[],\n rejected: RejectedPatch[],\n options: RemediateOptions,\n): string {\n const codemodN = patches.filter((p) => p.patch.source === \"codemod\").length;\n const llmN = patches.length - codemodN;\n const lines: string[] = [\n \"Automated post-quantum remediation from `qremediate`.\",\n \"\",\n `**${patches.length} fix(es)** — ${codemodN} deterministic codemod fix(es) and ${llmN} LLM-proposed. ` +\n `Each cleared the verify_fix gate (target finding gone, no new finding) and the patch policy.`,\n \"\",\n ];\n for (const vp of patches) {\n lines.push(`- \\`${vp.patch.path}\\` — ${vp.finding.ruleId} (${vp.patch.source})`);\n }\n if (llmN > 0) {\n lines.push(\n \"\",\n `⚠️ The ${llmN} LLM-proposed fix(es) are **crypto-verified, not security-reviewed**: the gate ` +\n `only proves the crypto finding is gone, not that the rest of the rewrite is safe. Read every ` +\n `LLM diff before merging. Context was shared at the \\`file\\` level with secrets redacted (best-effort).`,\n );\n }\n if (rejected.length) {\n lines.push(\"\", `${rejected.length} finding(s) were not auto-fixable and need review.`);\n }\n lines.push(\"\", \"This is a **draft** PR and was NOT merged. Review every change before merging.\");\n return lines.join(\"\\n\");\n}\n\n/** Run a complete qremediate pass. Pure w.r.t. process; the bin prints + exits. */\nexport async function runRemediate(\n options: RemediateOptions,\n hooks: RemediateHooks = {},\n): Promise<RemediateRun> {\n const root = path.resolve(options.path);\n const readFile = hooks.readFile ?? ((abs: string) => fsReadFile(abs, \"utf8\"));\n const writeFile =\n hooks.writeFile ?? ((abs: string, content: string) => fsWriteFile(abs, content, \"utf8\"));\n const scanFn = hooks.scanFn ?? ((r: string) => scan({ root: r }));\n const stderr = hooks.stderr ?? ((s: string) => void process.stderr.write(s));\n\n const result = await scanFn(root);\n const findings = result.findings;\n\n const findingFiles = new Set(findings.map((f) => f.location.file));\n const manifestFiles = new Set(\n findings.filter((f) => isManifestFile(f.location.file)).map((f) => f.location.file),\n );\n\n // Build the optional LLM patch source (codemods always run first).\n const provider: LlmProvider = options.provider ?? \"anthropic\";\n const model = options.model ?? defaultModel(provider);\n const maxLlm = options.maxLlm ?? DEFAULT_MAX_LLM;\n let llmCalls = 0;\n let llmCapHit = false;\n let baseLlmSource = hooks.llmPatchSource;\n if (!baseLlmSource && options.llm) {\n const key = hooks.resolveKey ? hooks.resolveKey() : envKey(provider);\n if (!key) {\n stderr(\n \"qremediate: --llm needs an API key (QK_LLM_API_KEY / ANTHROPIC_API_KEY / OPENAI_API_KEY). Using codemods only.\\n\",\n );\n } else {\n baseLlmSource = async (finding) => {\n const agent = await import(\"@quantakrypto/agent\");\n const client = agent.resolveClient({ provider, model, apiKey: key });\n const proposal = await agent.proposeFix(finding, {\n client,\n readFile: (rel) => readFile(path.resolve(root, rel)),\n fingerprint: fingerprintFinding,\n });\n if (!proposal) return null;\n return {\n path: proposal.path,\n newContent: proposal.newContent,\n ruleId: finding.ruleId,\n source: \"llm\",\n };\n };\n }\n }\n // Spend/DoS guard: cap the number of paid LLM proposals per run.\n const llmSource: ((finding: Finding, content: string) => Promise<Patch | null>) | undefined =\n baseLlmSource\n ? async (finding, content) => {\n if (llmCalls >= maxLlm) {\n llmCapHit = true;\n return null;\n }\n llmCalls++;\n return baseLlmSource!(finding, content);\n }\n : undefined;\n\n const patchSource = async (finding: Finding, content: string): Promise<Patch | null> => {\n const codemod = codemodFor(finding);\n if (codemod) return codemod.apply(content, finding);\n if (llmSource) return llmSource(finding, content);\n return null;\n };\n\n const contentCache = new Map<string, string>();\n const readContent = async (finding: Finding): Promise<string> => {\n const abs = path.resolve(root, finding.location.file);\n let c = contentCache.get(abs);\n if (c === undefined) {\n c = await readFile(abs);\n contentCache.set(abs, c);\n }\n return c;\n };\n\n const rem = await remediateFindings(findings, {\n readContent,\n patchSource,\n policy: { findingFiles, manifestFiles },\n });\n\n const byPath = new Map<string, VerifiedPatch>();\n for (const vp of rem.applied) if (!byPath.has(vp.patch.path)) byPath.set(vp.patch.path, vp);\n const patches = [...byPath.values()];\n\n if (patches.length === 0) {\n return {\n output: summarize(findings, patches, rem.rejected, options.mode, []),\n exitCode: REMEDIATE_EXIT.OK,\n written: [],\n };\n }\n\n if (options.mode === \"pr\") {\n const openPr = hooks.openDraftPr ?? ((plan: DraftPrPlan) => defaultOpenDraftPr(root, plan));\n const suffix = hooks.branchSuffix ?? `${Date.now()}`;\n const plan: DraftPrPlan = {\n branch: `quantakrypto/remediate-${suffix}`,\n title: `qremediate: migrate ${patches.length} quantum-vulnerable finding(s)`,\n body: prBody(patches, rem.rejected, options),\n patches: patches.map((vp) => ({ path: vp.patch.path, newContent: vp.patch.newContent })),\n };\n try {\n const { url } = await openPr(plan);\n return {\n output: `qremediate: opened a DRAFT PR${url ? ` (${url})` : \"\"} on branch ${plan.branch} with ${patches.length} verified fix(es). Nothing was merged — review it.`,\n exitCode: REMEDIATE_EXIT.OK,\n written: plan.patches.map((p) => p.path),\n };\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return {\n output: `qremediate: could not open a draft PR (${msg}). No changes were pushed; run with --mode diff/apply instead.`,\n exitCode: REMEDIATE_EXIT.ERROR,\n written: [],\n };\n }\n }\n\n const written: string[] = [];\n const diffs: string[] = [];\n const heldBack: VerifiedPatch[] = [];\n for (const vp of patches) {\n const abs = path.resolve(root, vp.patch.path);\n const before = contentCache.get(abs) ?? (await readContent(vp.finding));\n // An LLM rewrite is only crypto-verified, not security-reviewed — never write\n // it in `apply` mode without an explicit `--apply-llm` acknowledgement. It is\n // shown as a diff to review instead. Deterministic codemods write normally.\n const holdForReview =\n options.mode === \"apply\" && vp.patch.source === \"llm\" && !options.applyLlm;\n if (options.mode === \"apply\" && !holdForReview) {\n await writeFile(abs, vp.patch.newContent);\n written.push(vp.patch.path);\n } else {\n if (holdForReview) heldBack.push(vp);\n diffs.push(unifiedDiff(vp.patch.path, before, vp.patch.newContent));\n }\n }\n\n const showDiffs = options.mode === \"diff\" || heldBack.length > 0;\n const body = showDiffs ? `${diffs.join(\"\\n\\n\")}\\n\\n` : \"\";\n return {\n output:\n body +\n summarize(\n findings,\n patches,\n rem.rejected,\n options.mode,\n written,\n heldBack,\n llmCapHit ? maxLlm : 0,\n ),\n exitCode: REMEDIATE_EXIT.OK,\n written,\n };\n}\n\nfunction summarize(\n findings: readonly Finding[],\n patches: VerifiedPatch[],\n rejected: RejectedPatch[],\n mode: RemediateMode,\n written: string[],\n heldBack: VerifiedPatch[] = [],\n llmCapMax = 0,\n): string {\n const codemodN = patches.filter((p) => p.patch.source === \"codemod\").length;\n const llmN = patches.length - codemodN;\n const lines: string[] = [];\n lines.push(\n `qremediate: ${findings.length} finding(s), ${patches.length} candidate fix(es) ` +\n `(${codemodN} codemod-verified, ${llmN} LLM-proposed), ${rejected.length} not auto-fixable.`,\n );\n if (mode === \"apply\" && written.length) {\n lines.push(`Wrote: ${written.join(\", \")}`);\n }\n if (heldBack.length) {\n lines.push(\n `Held back ${heldBack.length} LLM fix(es) (shown as diffs above): crypto-verified but ` +\n `NOT security-reviewed — read them, then re-run with --apply-llm to write them.`,\n );\n } else if (mode === \"diff\" && patches.length) {\n lines.push(\n llmN > 0\n ? \"Review the diff above — codemod fixes are deterministic; LLM fixes need a human read. Then --mode apply (add --apply-llm for the LLM ones).\"\n : \"Review the diff above, then re-run with --mode apply to write it.\",\n );\n }\n if (llmCapMax) {\n lines.push(\n `Note: hit the --max-llm cap (${llmCapMax}); some findings were not sent to the LLM — raise it to cover more.`,\n );\n }\n if (rejected.length) {\n lines.push(\"Not auto-fixed (needs review or the LLM layer):\");\n for (const r of rejected.slice(0, 10)) {\n lines.push(\n ` - ${r.finding.ruleId} ${r.finding.location.file}:${r.finding.location.line} — ${r.reason}`,\n );\n }\n if (rejected.length > 10) lines.push(` … and ${rejected.length - 10} more.`);\n }\n return lines.join(\"\\n\");\n}\n\n/** Parse qremediate argv. */\nexport function parseRemediateArgs(\n argv: readonly string[],\n):\n | { kind: \"run\"; options: RemediateOptions }\n | { kind: \"help\" }\n | { kind: \"version\" }\n | { kind: \"error\"; message: string } {\n const options: RemediateOptions = { path: \".\", mode: \"diff\", llm: false };\n let positional: string | undefined;\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i] as string;\n let flag = arg;\n let inline: string | undefined;\n if (arg.startsWith(\"--\") && arg.includes(\"=\")) {\n const eq = arg.indexOf(\"=\");\n flag = arg.slice(0, eq);\n inline = arg.slice(eq + 1);\n }\n const take = (): string | undefined => inline ?? argv[++i];\n switch (flag) {\n case \"-h\":\n case \"--help\":\n return { kind: \"help\" };\n case \"-v\":\n case \"--version\":\n return { kind: \"version\" };\n case \"--mode\": {\n const v = take();\n if (v !== \"diff\" && v !== \"apply\" && v !== \"pr\") {\n return { kind: \"error\", message: `invalid --mode \"${v ?? \"\"}\" (expected diff|apply|pr)` };\n }\n options.mode = v;\n break;\n }\n case \"--llm\":\n options.llm = true;\n break;\n case \"--apply-llm\":\n options.applyLlm = true;\n break;\n case \"--max-llm\": {\n const v = take();\n const n = Number(v);\n if (!Number.isInteger(n) || n < 0) {\n return {\n kind: \"error\",\n message: `invalid --max-llm \"${v ?? \"\"}\" (expected a non-negative integer)`,\n };\n }\n options.maxLlm = n;\n break;\n }\n case \"--llm-provider\": {\n const v = take();\n if (v !== \"anthropic\" && v !== \"openai-compatible\") {\n return { kind: \"error\", message: `invalid --llm-provider \"${v ?? \"\"}\"` };\n }\n options.provider = v;\n break;\n }\n case \"--llm-model\":\n options.model = take();\n break;\n default:\n if (flag.startsWith(\"-\")) return { kind: \"error\", message: `unknown option \"${flag}\"` };\n if (positional !== undefined)\n return { kind: \"error\", message: `unexpected argument \"${arg}\"` };\n positional = arg;\n }\n }\n if (positional !== undefined) options.path = positional;\n return { kind: \"run\", options };\n}\n\nexport const REMEDIATE_HELP = `qremediate — 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 ${DEFAULT_MAX_LLM}; 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** — 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`;\n"]} | ||
| {"version":3,"file":"remediate-cli.js","sourceRoot":"","sources":["../src/remediate-cli.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,QAAQ,IAAI,UAAU,EAAE,SAAS,IAAI,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpF,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EACL,IAAI,EACJ,UAAU,EACV,iBAAiB,EACjB,cAAc,EACd,kBAAkB,EAClB,YAAY,GACb,MAAM,oBAAoB,CAAC;AAK5B,MAAM,IAAI,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAkDjC,MAAM,CAAC,MAAM,cAAc,GAAG,EAAE,EAAE,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAW,CAAC;AAEvE,gGAAgG;AAChG,MAAM,eAAe,GAAG,EAAE,CAAC;AAE3B,SAAS,MAAM,CAAC,QAAqB;IACnC,OAAO,CACL,OAAO,CAAC,GAAG,CAAC,cAAc;QAC1B,CAAC,QAAQ,KAAK,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CACxF,CAAC;AACJ,CAAC;AAED,SAAS,YAAY,CAAC,QAAqB;IACzC,OAAO,QAAQ,KAAK,WAAW,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,aAAa,CAAC;AACtE,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,WAAW,CAAC,OAAe,EAAE,MAAc,EAAE,KAAa;IACxE,IAAI,MAAM,KAAK,KAAK;QAAE,OAAO,EAAE,CAAC;IAChC,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC7B,MAAM,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC5B,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,OAAO,KAAK,GAAG,CAAC,CAAC,MAAM,IAAI,KAAK,GAAG,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC;QAAE,KAAK,EAAE,CAAC;IAC9E,IAAI,IAAI,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;IACxB,IAAI,IAAI,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;IACxB,OAAO,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7D,IAAI,EAAE,CAAC;QACP,IAAI,EAAE,CAAC;IACT,CAAC;IACD,MAAM,GAAG,GAAG,CAAC,CAAC;IACd,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,GAAG,CAAC,CAAC;IACtC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,IAAI,GAAG,GAAG,CAAC,CAAC;IAC/C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,IAAI,GAAG,GAAG,CAAC,CAAC;IAC/C,MAAM,KAAK,GAAa,CAAC,SAAS,OAAO,EAAE,EAAE,SAAS,OAAO,EAAE,CAAC,CAAC;IACjE,KAAK,CAAC,IAAI,CAAC,OAAO,IAAI,GAAG,CAAC,IAAI,GAAG,GAAG,IAAI,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,IAAI,GAAG,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;IAClF,KAAK,IAAI,CAAC,GAAG,IAAI,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE;QAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC1D,KAAK,IAAI,CAAC,GAAG,KAAK,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,EAAE;QAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC3D,KAAK,IAAI,CAAC,GAAG,KAAK,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,EAAE;QAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC3D,KAAK,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,EAAE,CAAC,IAAI,GAAG,EAAE,CAAC,EAAE;QAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC7D,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED;;;;GAIG;AACH,KAAK,UAAU,kBAAkB,CAAC,IAAY,EAAE,IAAiB;IAC/D,OAAO,YAAY,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;QACtC,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;QAC9D,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAC7B,MAAM,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QACrE,CAAC;QACD,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAChF,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QAC3D,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;QACpE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,IAAI,CAC3B,IAAI,EACJ;YACE,IAAI;YACJ,QAAQ;YACR,SAAS;YACT,QAAQ;YACR,IAAI,CAAC,MAAM;YACX,SAAS;YACT,IAAI,CAAC,KAAK;YACV,QAAQ;YACR,IAAI,CAAC,IAAI;SACV,EACD,EAAE,GAAG,EAAE,GAAG,EAAE,CACb,CAAC;QACF,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;IAChC,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,MAAM,CACb,OAAwB,EACxB,QAAyB,EACzB,OAAyB;IAEzB,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,MAAM,CAAC;IAC5E,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,QAAQ,CAAC;IACvC,MAAM,KAAK,GAAa;QACtB,uDAAuD;QACvD,EAAE;QACF,KAAK,OAAO,CAAC,MAAM,gBAAgB,QAAQ,sCAAsC,IAAI,iBAAiB;YACpG,8FAA8F;QAChG,EAAE;KACH,CAAC;IACF,KAAK,MAAM,EAAE,IAAI,OAAO,EAAE,CAAC;QACzB,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,IAAI,QAAQ,EAAE,CAAC,OAAO,CAAC,MAAM,KAAK,EAAE,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;IACnF,CAAC;IACD,IAAI,IAAI,GAAG,CAAC,EAAE,CAAC;QACb,KAAK,CAAC,IAAI,CACR,EAAE,EACF,UAAU,IAAI,iFAAiF;YAC7F,+FAA+F;YAC/F,wGAAwG,CAC3G,CAAC;IACJ,CAAC;IACD,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;QACpB,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,GAAG,QAAQ,CAAC,MAAM,oDAAoD,CAAC,CAAC;IACzF,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,gFAAgF,CAAC,CAAC;IACjG,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,mFAAmF;AACnF,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,OAAyB,EACzB,QAAwB,EAAE;IAE1B,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,IAAI,CAAC,CAAC,GAAW,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC;IAC9E,MAAM,SAAS,GACb,KAAK,CAAC,SAAS,IAAI,CAAC,CAAC,GAAW,EAAE,OAAe,EAAE,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;IAC3F,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAClE,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAE7E,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;IAClC,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;IAEjC,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;IACnE,MAAM,aAAa,GAAG,IAAI,GAAG,CAC3B,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CACpF,CAAC;IAEF,mEAAmE;IACnE,MAAM,QAAQ,GAAgB,OAAO,CAAC,QAAQ,IAAI,WAAW,CAAC;IAC9D,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,YAAY,CAAC,QAAQ,CAAC,CAAC;IACtD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,eAAe,CAAC;IACjD,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,IAAI,SAAS,GAAG,KAAK,CAAC;IACtB,IAAI,aAAa,GAAG,KAAK,CAAC,cAAc,CAAC;IACzC,IAAI,CAAC,aAAa,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;QAClC,MAAM,GAAG,GAAG,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QACrE,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,MAAM,CACJ,kHAAkH,CACnH,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,aAAa,GAAG,KAAK,EAAE,OAAO,EAAE,EAAE;gBAChC,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,qBAAqB,CAAC,CAAC;gBAClD,MAAM,MAAM,GAAG,KAAK,CAAC,aAAa,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;gBACrE,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,UAAU,CAAC,OAAO,EAAE;oBAC/C,MAAM;oBACN,QAAQ,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;oBACpD,WAAW,EAAE,kBAAkB;iBAChC,CAAC,CAAC;gBACH,IAAI,CAAC,QAAQ;oBAAE,OAAO,IAAI,CAAC;gBAC3B,OAAO;oBACL,IAAI,EAAE,QAAQ,CAAC,IAAI;oBACnB,UAAU,EAAE,QAAQ,CAAC,UAAU;oBAC/B,MAAM,EAAE,OAAO,CAAC,MAAM;oBACtB,MAAM,EAAE,KAAK;iBACd,CAAC;YACJ,CAAC,CAAC;QACJ,CAAC;IACH,CAAC;IACD,iEAAiE;IACjE,MAAM,SAAS,GACb,aAAa;QACX,CAAC,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE;YACzB,IAAI,QAAQ,IAAI,MAAM,EAAE,CAAC;gBACvB,SAAS,GAAG,IAAI,CAAC;gBACjB,OAAO,IAAI,CAAC;YACd,CAAC;YACD,QAAQ,EAAE,CAAC;YACX,OAAO,aAAc,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC1C,CAAC;QACH,CAAC,CAAC,SAAS,CAAC;IAEhB,MAAM,WAAW,GAAG,KAAK,EAAE,OAAgB,EAAE,OAAe,EAAyB,EAAE;QACrF,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC;QACpC,IAAI,OAAO;YAAE,OAAO,OAAO,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACpD,IAAI,SAAS;YAAE,OAAO,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAClD,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;IAEF,MAAM,YAAY,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC/C,MAAM,WAAW,GAAG,KAAK,EAAE,OAAgB,EAAmB,EAAE;QAC9D,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACtD,IAAI,CAAC,GAAG,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC9B,IAAI,CAAC,KAAK,SAAS,EAAE,CAAC;YACpB,CAAC,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,CAAC;YACxB,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QAC3B,CAAC;QACD,OAAO,CAAC,CAAC;IACX,CAAC,CAAC;IAEF,MAAM,GAAG,GAAG,MAAM,iBAAiB,CAAC,QAAQ,EAAE;QAC5C,WAAW;QACX,WAAW;QACX,MAAM,EAAE,EAAE,YAAY,EAAE,aAAa,EAAE;KACxC,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,IAAI,GAAG,EAAyB,CAAC;IAChD,KAAK,MAAM,EAAE,IAAI,GAAG,CAAC,OAAO;QAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC;YAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC5F,MAAM,OAAO,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IAErC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO;YACL,MAAM,EAAE,SAAS,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;YACpE,QAAQ,EAAE,cAAc,CAAC,EAAE;YAC3B,OAAO,EAAE,EAAE;SACZ,CAAC;IACJ,CAAC;IAED,IAAI,OAAO,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;QAC1B,MAAM,MAAM,GAAG,KAAK,CAAC,WAAW,IAAI,CAAC,CAAC,IAAiB,EAAE,EAAE,CAAC,kBAAkB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;QAC5F,MAAM,MAAM,GAAG,KAAK,CAAC,YAAY,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;QACrD,MAAM,IAAI,GAAgB;YACxB,MAAM,EAAE,0BAA0B,MAAM,EAAE;YAC1C,KAAK,EAAE,uBAAuB,OAAO,CAAC,MAAM,gCAAgC;YAC5E,IAAI,EAAE,MAAM,CAAC,OAAO,EAAE,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC;YAC5C,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC;SACzF,CAAC;QACF,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;YACnC,OAAO;gBACL,MAAM,EAAE,gCAAgC,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG,GAAG,CAAC,CAAC,CAAC,EAAE,cAAc,IAAI,CAAC,MAAM,SAAS,OAAO,CAAC,MAAM,oDAAoD;gBAClK,QAAQ,EAAE,cAAc,CAAC,EAAE;gBAC3B,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;aACzC,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC7D,OAAO;gBACL,MAAM,EAAE,0CAA0C,GAAG,gEAAgE;gBACrH,QAAQ,EAAE,cAAc,CAAC,KAAK;gBAC9B,OAAO,EAAE,EAAE;aACZ,CAAC;QACJ,CAAC;IACH,CAAC;IAED,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,QAAQ,GAAoB,EAAE,CAAC;IACrC,KAAK,MAAM,EAAE,IAAI,OAAO,EAAE,CAAC;QACzB,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC9C,MAAM,MAAM,GAAG,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,WAAW,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;QACxE,8EAA8E;QAC9E,8EAA8E;QAC9E,4EAA4E;QAC5E,MAAM,aAAa,GACjB,OAAO,CAAC,IAAI,KAAK,OAAO,IAAI,EAAE,CAAC,KAAK,CAAC,MAAM,KAAK,KAAK,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC;QAC7E,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO,IAAI,CAAC,aAAa,EAAE,CAAC;YAC/C,MAAM,SAAS,CAAC,GAAG,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;YAC1C,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC9B,CAAC;aAAM,CAAC;YACN,IAAI,aAAa;gBAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACrC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;IAED,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,KAAK,MAAM,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;IACjE,MAAM,IAAI,GAAG,SAAS,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1D,OAAO;QACL,MAAM,EACJ,IAAI;YACJ,SAAS,CACP,QAAQ,EACR,OAAO,EACP,GAAG,CAAC,QAAQ,EACZ,OAAO,CAAC,IAAI,EACZ,OAAO,EACP,QAAQ,EACR,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CACvB;QACH,QAAQ,EAAE,cAAc,CAAC,EAAE;QAC3B,OAAO;KACR,CAAC;AACJ,CAAC;AAED,SAAS,SAAS,CAChB,QAA4B,EAC5B,OAAwB,EACxB,QAAyB,EACzB,IAAmB,EACnB,OAAiB,EACjB,WAA4B,EAAE,EAC9B,SAAS,GAAG,CAAC;IAEb,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,MAAM,CAAC;IAC5E,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,QAAQ,CAAC;IACvC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,CAAC,IAAI,CACR,eAAe,QAAQ,CAAC,MAAM,gBAAgB,OAAO,CAAC,MAAM,qBAAqB;QAC/E,IAAI,QAAQ,sBAAsB,IAAI,mBAAmB,QAAQ,CAAC,MAAM,oBAAoB,CAC/F,CAAC;IACF,IAAI,IAAI,KAAK,OAAO,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACvC,KAAK,CAAC,IAAI,CAAC,UAAU,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC7C,CAAC;IACD,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;QACpB,KAAK,CAAC,IAAI,CACR,aAAa,QAAQ,CAAC,MAAM,2DAA2D;YACrF,gFAAgF,CACnF,CAAC;IACJ,CAAC;SAAM,IAAI,IAAI,KAAK,MAAM,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QAC7C,KAAK,CAAC,IAAI,CACR,IAAI,GAAG,CAAC;YACN,CAAC,CAAC,6IAA6I;YAC/I,CAAC,CAAC,mEAAmE,CACxE,CAAC;IACJ,CAAC;IACD,IAAI,SAAS,EAAE,CAAC;QACd,KAAK,CAAC,IAAI,CACR,gCAAgC,SAAS,qEAAqE,CAC/G,CAAC;IACJ,CAAC;IACD,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;QACpB,KAAK,CAAC,IAAI,CAAC,iDAAiD,CAAC,CAAC;QAC9D,KAAK,MAAM,CAAC,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;YACtC,KAAK,CAAC,IAAI,CACR,OAAO,CAAC,CAAC,OAAO,CAAC,MAAM,IAAI,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,CAAC,MAAM,EAAE,CAC9F,CAAC;QACJ,CAAC;QACD,IAAI,QAAQ,CAAC,MAAM,GAAG,EAAE;YAAE,KAAK,CAAC,IAAI,CAAC,WAAW,QAAQ,CAAC,MAAM,GAAG,EAAE,QAAQ,CAAC,CAAC;IAChF,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,6BAA6B;AAC7B,MAAM,UAAU,kBAAkB,CAChC,IAAuB;IAMvB,MAAM,OAAO,GAAqB,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;IAC1E,IAAI,UAA8B,CAAC;IACnC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAW,CAAC;QAC9B,IAAI,IAAI,GAAG,GAAG,CAAC;QACf,IAAI,MAA0B,CAAC;QAC/B,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YAC9C,MAAM,EAAE,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YAC5B,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACxB,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;QAC7B,CAAC;QACD,MAAM,IAAI,GAAG,GAAuB,EAAE,CAAC,MAAM,IAAI,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;QAC3D,QAAQ,IAAI,EAAE,CAAC;YACb,KAAK,IAAI,CAAC;YACV,KAAK,QAAQ;gBACX,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;YAC1B,KAAK,IAAI,CAAC;YACV,KAAK,WAAW;gBACd,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;YAC7B,KAAK,QAAQ,CAAC,CAAC,CAAC;gBACd,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC;gBACjB,IAAI,CAAC,KAAK,MAAM,IAAI,CAAC,KAAK,OAAO,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;oBAChD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,mBAAmB,CAAC,IAAI,EAAE,4BAA4B,EAAE,CAAC;gBAC5F,CAAC;gBACD,OAAO,CAAC,IAAI,GAAG,CAAC,CAAC;gBACjB,MAAM;YACR,CAAC;YACD,KAAK,OAAO;gBACV,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC;gBACnB,MAAM;YACR,KAAK,aAAa;gBAChB,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;gBACxB,MAAM;YACR,KAAK,WAAW,CAAC,CAAC,CAAC;gBACjB,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC;gBACjB,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;gBACpB,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;oBAClC,OAAO;wBACL,IAAI,EAAE,OAAO;wBACb,OAAO,EAAE,sBAAsB,CAAC,IAAI,EAAE,qCAAqC;qBAC5E,CAAC;gBACJ,CAAC;gBACD,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;gBACnB,MAAM;YACR,CAAC;YACD,KAAK,gBAAgB,CAAC,CAAC,CAAC;gBACtB,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC;gBACjB,IAAI,CAAC,KAAK,WAAW,IAAI,CAAC,KAAK,mBAAmB,EAAE,CAAC;oBACnD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,2BAA2B,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC;gBAC3E,CAAC;gBACD,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;gBACrB,MAAM;YACR,CAAC;YACD,KAAK,aAAa;gBAChB,OAAO,CAAC,KAAK,GAAG,IAAI,EAAE,CAAC;gBACvB,MAAM;YACR;gBACE,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;oBAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,mBAAmB,IAAI,GAAG,EAAE,CAAC;gBACxF,IAAI,UAAU,KAAK,SAAS;oBAC1B,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,wBAAwB,GAAG,GAAG,EAAE,CAAC;gBACpE,UAAU,GAAG,GAAG,CAAC;QACrB,CAAC;IACH,CAAC;IACD,IAAI,UAAU,KAAK,SAAS;QAAE,OAAO,CAAC,IAAI,GAAG,UAAU,CAAC;IACxD,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;AAClC,CAAC;AAED,MAAM,CAAC,MAAM,cAAc,GAAG;;;;;;;;;;;;;2DAa6B,eAAe;;;;;;;;;;;;CAYzE,CAAC","sourcesContent":["/**\n * `qremediate` — apply deterministic, verified fixes to quantum-vulnerable /\n * insecure crypto findings.\n *\n * Pipeline: scan → for each finding, a codemod (then optionally the LLM with\n * `--llm`) proposes a patch → the core remediation pipeline gates it\n * (patch-policy + verify_fix) → verified patches are shown (`diff`), written\n * (`apply`), or opened as a DRAFT PR (`pr`). Never auto-merges.\n */\nimport { readFile as fsReadFile, writeFile as fsWriteFile } from \"node:fs/promises\";\nimport { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport path from \"node:path\";\n\nimport {\n scan,\n codemodFor,\n remediateFindings,\n isManifestFile,\n fingerprintFinding,\n withWorktree,\n} from \"@quantakrypto/core\";\nimport type { Finding, Patch, ScanResult, VerifiedPatch, RejectedPatch } from \"@quantakrypto/core\";\n\nimport type { LlmProvider } from \"./args.js\";\n\nconst exec = promisify(execFile);\n\nexport type RemediateMode = \"diff\" | \"apply\" | \"pr\";\n\nexport interface RemediateOptions {\n path: string;\n mode: RemediateMode;\n /** Use a BYOK LLM to propose fixes codemods can't. */\n llm: boolean;\n /** Actually write LLM-proposed fixes in `apply` mode (they are otherwise shown\n * as diffs and held back — an LLM rewrite must be reviewed, not auto-applied). */\n applyLlm?: boolean;\n /** Cap how many findings are sent to the LLM (spend/DoS guard). */\n maxLlm?: number;\n provider?: LlmProvider;\n model?: string;\n}\n\nexport interface RemediateRun {\n output: string;\n exitCode: number;\n /** Relative paths written (apply mode) or included in the PR. */\n written: string[];\n}\n\n/** A verified fix set ready to become a draft PR. */\ninterface DraftPrPlan {\n branch: string;\n title: string;\n body: string;\n patches: { path: string; newContent: string }[];\n}\n\n/** Open a draft PR for the plan (injectable; default shells git + gh in a worktree). */\ntype OpenDraftPr = (plan: DraftPrPlan) => Promise<{ url?: string }>;\n\nexport interface RemediateHooks {\n scanFn?: (root: string) => Promise<ScanResult>;\n readFile?: (abs: string) => Promise<string>;\n writeFile?: (abs: string, content: string) => Promise<void>;\n /** LLM patch source (default wraps `@quantakrypto/agent`'s proposeFix). */\n llmPatchSource?: (finding: Finding, content: string) => Promise<Patch | null>;\n /** Draft-PR backend (default: git worktree + gh). */\n openDraftPr?: OpenDraftPr;\n resolveKey?: () => string | undefined;\n stderr?: (s: string) => void;\n /** Branch suffix (injected for deterministic tests). */\n branchSuffix?: string;\n}\n\nexport const REMEDIATE_EXIT = { OK: 0, CHANGES: 0, ERROR: 2 } as const;\n\n/** Default per-run cap on paid LLM fix proposals (spend/DoS guard; override with --max-llm). */\nconst DEFAULT_MAX_LLM = 25;\n\nfunction envKey(provider: LlmProvider): string | undefined {\n return (\n process.env.QK_LLM_API_KEY ??\n (provider === \"anthropic\" ? process.env.ANTHROPIC_API_KEY : process.env.OPENAI_API_KEY)\n );\n}\n\nfunction defaultModel(provider: LlmProvider): string {\n return provider === \"anthropic\" ? \"claude-sonnet-5\" : \"gpt-4o-mini\";\n}\n\n/** Minimal unified diff for a localized change (3 lines of context). */\nexport function unifiedDiff(relPath: string, before: string, after: string): string {\n if (before === after) return \"\";\n const a = before.split(\"\\n\");\n const b = after.split(\"\\n\");\n let start = 0;\n while (start < a.length && start < b.length && a[start] === b[start]) start++;\n let endA = a.length - 1;\n let endB = b.length - 1;\n while (endA >= start && endB >= start && a[endA] === b[endB]) {\n endA--;\n endB--;\n }\n const ctx = 3;\n const from = Math.max(0, start - ctx);\n const toA = Math.min(a.length - 1, endA + ctx);\n const toB = Math.min(b.length - 1, endB + ctx);\n const lines: string[] = [`--- a/${relPath}`, `+++ b/${relPath}`];\n lines.push(`@@ -${from + 1},${toA - from + 1} +${from + 1},${toB - from + 1} @@`);\n for (let i = from; i < start; i++) lines.push(` ${a[i]}`);\n for (let i = start; i <= endA; i++) lines.push(`-${a[i]}`);\n for (let i = start; i <= endB; i++) lines.push(`+${b[i]}`);\n for (let i = endA + 1; i <= toA; i++) lines.push(` ${a[i]}`);\n return lines.join(\"\\n\");\n}\n\n/**\n * Default draft-PR backend: apply the patches inside an ephemeral worktree\n * (never touching the user's checkout), commit on a new branch, push it, and\n * `gh pr create --draft`. NEVER merges.\n */\nasync function defaultOpenDraftPr(root: string, plan: DraftPrPlan): Promise<{ url?: string }> {\n return withWorktree(root, async (dir) => {\n await exec(\"git\", [\"-C\", dir, \"checkout\", \"-b\", plan.branch]);\n for (const p of plan.patches) {\n await fsWriteFile(path.resolve(dir, p.path), p.newContent, \"utf8\");\n }\n await exec(\"git\", [\"-C\", dir, \"add\", \"--\", ...plan.patches.map((p) => p.path)]);\n await exec(\"git\", [\"-C\", dir, \"commit\", \"-m\", plan.title]);\n await exec(\"git\", [\"-C\", dir, \"push\", \"-u\", \"origin\", plan.branch]);\n const { stdout } = await exec(\n \"gh\",\n [\n \"pr\",\n \"create\",\n \"--draft\",\n \"--head\",\n plan.branch,\n \"--title\",\n plan.title,\n \"--body\",\n plan.body,\n ],\n { cwd: dir },\n );\n return { url: stdout.trim() };\n });\n}\n\nfunction prBody(\n patches: VerifiedPatch[],\n rejected: RejectedPatch[],\n options: RemediateOptions,\n): string {\n const codemodN = patches.filter((p) => p.patch.source === \"codemod\").length;\n const llmN = patches.length - codemodN;\n const lines: string[] = [\n \"Automated post-quantum remediation from `qremediate`.\",\n \"\",\n `**${patches.length} fix(es)** — ${codemodN} deterministic codemod fix(es) and ${llmN} LLM-proposed. ` +\n `Each cleared the verify_fix gate (target finding gone, no new finding) and the patch policy.`,\n \"\",\n ];\n for (const vp of patches) {\n lines.push(`- \\`${vp.patch.path}\\` — ${vp.finding.ruleId} (${vp.patch.source})`);\n }\n if (llmN > 0) {\n lines.push(\n \"\",\n `⚠️ The ${llmN} LLM-proposed fix(es) are **crypto-verified, not security-reviewed**: the gate ` +\n `only proves the crypto finding is gone, not that the rest of the rewrite is safe. Read every ` +\n `LLM diff before merging. Context was shared at the \\`file\\` level with secrets redacted (best-effort).`,\n );\n }\n if (rejected.length) {\n lines.push(\"\", `${rejected.length} finding(s) were not auto-fixable and need review.`);\n }\n lines.push(\"\", \"This is a **draft** PR and was NOT merged. Review every change before merging.\");\n return lines.join(\"\\n\");\n}\n\n/** Run a complete qremediate pass. Pure w.r.t. process; the bin prints + exits. */\nexport async function runRemediate(\n options: RemediateOptions,\n hooks: RemediateHooks = {},\n): Promise<RemediateRun> {\n const root = path.resolve(options.path);\n const readFile = hooks.readFile ?? ((abs: string) => fsReadFile(abs, \"utf8\"));\n const writeFile =\n hooks.writeFile ?? ((abs: string, content: string) => fsWriteFile(abs, content, \"utf8\"));\n const scanFn = hooks.scanFn ?? ((r: string) => scan({ root: r }));\n const stderr = hooks.stderr ?? ((s: string) => void process.stderr.write(s));\n\n const result = await scanFn(root);\n const findings = result.findings;\n\n const findingFiles = new Set(findings.map((f) => f.location.file));\n const manifestFiles = new Set(\n findings.filter((f) => isManifestFile(f.location.file)).map((f) => f.location.file),\n );\n\n // Build the optional LLM patch source (codemods always run first).\n const provider: LlmProvider = options.provider ?? \"anthropic\";\n const model = options.model ?? defaultModel(provider);\n const maxLlm = options.maxLlm ?? DEFAULT_MAX_LLM;\n let llmCalls = 0;\n let llmCapHit = false;\n let baseLlmSource = hooks.llmPatchSource;\n if (!baseLlmSource && options.llm) {\n const key = hooks.resolveKey ? hooks.resolveKey() : envKey(provider);\n if (!key) {\n stderr(\n \"qremediate: --llm needs an API key (QK_LLM_API_KEY / ANTHROPIC_API_KEY / OPENAI_API_KEY). Using codemods only.\\n\",\n );\n } else {\n baseLlmSource = async (finding) => {\n const agent = await import(\"@quantakrypto/agent\");\n const client = agent.resolveClient({ provider, model, apiKey: key });\n const proposal = await agent.proposeFix(finding, {\n client,\n readFile: (rel) => readFile(path.resolve(root, rel)),\n fingerprint: fingerprintFinding,\n });\n if (!proposal) return null;\n return {\n path: proposal.path,\n newContent: proposal.newContent,\n ruleId: finding.ruleId,\n source: \"llm\",\n };\n };\n }\n }\n // Spend/DoS guard: cap the number of paid LLM proposals per run.\n const llmSource: ((finding: Finding, content: string) => Promise<Patch | null>) | undefined =\n baseLlmSource\n ? async (finding, content) => {\n if (llmCalls >= maxLlm) {\n llmCapHit = true;\n return null;\n }\n llmCalls++;\n return baseLlmSource!(finding, content);\n }\n : undefined;\n\n const patchSource = async (finding: Finding, content: string): Promise<Patch | null> => {\n const codemod = codemodFor(finding);\n if (codemod) return codemod.apply(content, finding);\n if (llmSource) return llmSource(finding, content);\n return null;\n };\n\n const contentCache = new Map<string, string>();\n const readContent = async (finding: Finding): Promise<string> => {\n const abs = path.resolve(root, finding.location.file);\n let c = contentCache.get(abs);\n if (c === undefined) {\n c = await readFile(abs);\n contentCache.set(abs, c);\n }\n return c;\n };\n\n const rem = await remediateFindings(findings, {\n readContent,\n patchSource,\n policy: { findingFiles, manifestFiles },\n });\n\n const byPath = new Map<string, VerifiedPatch>();\n for (const vp of rem.applied) if (!byPath.has(vp.patch.path)) byPath.set(vp.patch.path, vp);\n const patches = [...byPath.values()];\n\n if (patches.length === 0) {\n return {\n output: summarize(findings, patches, rem.rejected, options.mode, []),\n exitCode: REMEDIATE_EXIT.OK,\n written: [],\n };\n }\n\n if (options.mode === \"pr\") {\n const openPr = hooks.openDraftPr ?? ((plan: DraftPrPlan) => defaultOpenDraftPr(root, plan));\n const suffix = hooks.branchSuffix ?? `${Date.now()}`;\n const plan: DraftPrPlan = {\n branch: `quantakrypto/remediate-${suffix}`,\n title: `qremediate: migrate ${patches.length} quantum-vulnerable finding(s)`,\n body: prBody(patches, rem.rejected, options),\n patches: patches.map((vp) => ({ path: vp.patch.path, newContent: vp.patch.newContent })),\n };\n try {\n const { url } = await openPr(plan);\n return {\n output: `qremediate: opened a DRAFT PR${url ? ` (${url})` : \"\"} on branch ${plan.branch} with ${patches.length} verified fix(es). Nothing was merged — review it.`,\n exitCode: REMEDIATE_EXIT.OK,\n written: plan.patches.map((p) => p.path),\n };\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return {\n output: `qremediate: could not open a draft PR (${msg}). No changes were pushed; run with --mode diff/apply instead.`,\n exitCode: REMEDIATE_EXIT.ERROR,\n written: [],\n };\n }\n }\n\n const written: string[] = [];\n const diffs: string[] = [];\n const heldBack: VerifiedPatch[] = [];\n for (const vp of patches) {\n const abs = path.resolve(root, vp.patch.path);\n const before = contentCache.get(abs) ?? (await readContent(vp.finding));\n // An LLM rewrite is only crypto-verified, not security-reviewed — never write\n // it in `apply` mode without an explicit `--apply-llm` acknowledgement. It is\n // shown as a diff to review instead. Deterministic codemods write normally.\n const holdForReview =\n options.mode === \"apply\" && vp.patch.source === \"llm\" && !options.applyLlm;\n if (options.mode === \"apply\" && !holdForReview) {\n await writeFile(abs, vp.patch.newContent);\n written.push(vp.patch.path);\n } else {\n if (holdForReview) heldBack.push(vp);\n diffs.push(unifiedDiff(vp.patch.path, before, vp.patch.newContent));\n }\n }\n\n const showDiffs = options.mode === \"diff\" || heldBack.length > 0;\n const body = showDiffs ? `${diffs.join(\"\\n\\n\")}\\n\\n` : \"\";\n return {\n output:\n body +\n summarize(\n findings,\n patches,\n rem.rejected,\n options.mode,\n written,\n heldBack,\n llmCapHit ? maxLlm : 0,\n ),\n exitCode: REMEDIATE_EXIT.OK,\n written,\n };\n}\n\nfunction summarize(\n findings: readonly Finding[],\n patches: VerifiedPatch[],\n rejected: RejectedPatch[],\n mode: RemediateMode,\n written: string[],\n heldBack: VerifiedPatch[] = [],\n llmCapMax = 0,\n): string {\n const codemodN = patches.filter((p) => p.patch.source === \"codemod\").length;\n const llmN = patches.length - codemodN;\n const lines: string[] = [];\n lines.push(\n `qremediate: ${findings.length} finding(s), ${patches.length} candidate fix(es) ` +\n `(${codemodN} codemod-verified, ${llmN} LLM-proposed), ${rejected.length} not auto-fixable.`,\n );\n if (mode === \"apply\" && written.length) {\n lines.push(`Wrote: ${written.join(\", \")}`);\n }\n if (heldBack.length) {\n lines.push(\n `Held back ${heldBack.length} LLM fix(es) (shown as diffs above): crypto-verified but ` +\n `NOT security-reviewed — read them, then re-run with --apply-llm to write them.`,\n );\n } else if (mode === \"diff\" && patches.length) {\n lines.push(\n llmN > 0\n ? \"Review the diff above — codemod fixes are deterministic; LLM fixes need a human read. Then --mode apply (add --apply-llm for the LLM ones).\"\n : \"Review the diff above, then re-run with --mode apply to write it.\",\n );\n }\n if (llmCapMax) {\n lines.push(\n `Note: hit the --max-llm cap (${llmCapMax}); some findings were not sent to the LLM — raise it to cover more.`,\n );\n }\n if (rejected.length) {\n lines.push(\"Not auto-fixed (needs review or the LLM layer):\");\n for (const r of rejected.slice(0, 10)) {\n lines.push(\n ` - ${r.finding.ruleId} ${r.finding.location.file}:${r.finding.location.line} — ${r.reason}`,\n );\n }\n if (rejected.length > 10) lines.push(` … and ${rejected.length - 10} more.`);\n }\n return lines.join(\"\\n\");\n}\n\n/** Parse qremediate argv. */\nexport function parseRemediateArgs(\n argv: readonly string[],\n):\n | { kind: \"run\"; options: RemediateOptions }\n | { kind: \"help\" }\n | { kind: \"version\" }\n | { kind: \"error\"; message: string } {\n const options: RemediateOptions = { path: \".\", mode: \"diff\", llm: false };\n let positional: string | undefined;\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i] as string;\n let flag = arg;\n let inline: string | undefined;\n if (arg.startsWith(\"--\") && arg.includes(\"=\")) {\n const eq = arg.indexOf(\"=\");\n flag = arg.slice(0, eq);\n inline = arg.slice(eq + 1);\n }\n const take = (): string | undefined => inline ?? argv[++i];\n switch (flag) {\n case \"-h\":\n case \"--help\":\n return { kind: \"help\" };\n case \"-v\":\n case \"--version\":\n return { kind: \"version\" };\n case \"--mode\": {\n const v = take();\n if (v !== \"diff\" && v !== \"apply\" && v !== \"pr\") {\n return { kind: \"error\", message: `invalid --mode \"${v ?? \"\"}\" (expected diff|apply|pr)` };\n }\n options.mode = v;\n break;\n }\n case \"--llm\":\n options.llm = true;\n break;\n case \"--apply-llm\":\n options.applyLlm = true;\n break;\n case \"--max-llm\": {\n const v = take();\n const n = Number(v);\n if (!Number.isInteger(n) || n < 0) {\n return {\n kind: \"error\",\n message: `invalid --max-llm \"${v ?? \"\"}\" (expected a non-negative integer)`,\n };\n }\n options.maxLlm = n;\n break;\n }\n case \"--llm-provider\": {\n const v = take();\n if (v !== \"anthropic\" && v !== \"openai-compatible\") {\n return { kind: \"error\", message: `invalid --llm-provider \"${v ?? \"\"}\"` };\n }\n options.provider = v;\n break;\n }\n case \"--llm-model\":\n options.model = take();\n break;\n default:\n if (flag.startsWith(\"-\")) return { kind: \"error\", message: `unknown option \"${flag}\"` };\n if (positional !== undefined)\n return { kind: \"error\", message: `unexpected argument \"${arg}\"` };\n positional = arg;\n }\n }\n if (positional !== undefined) options.path = positional;\n return { kind: \"run\", options };\n}\n\nexport const REMEDIATE_HELP = `qremediate — 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 ${DEFAULT_MAX_LLM}; 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** — 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`;\n"]} |
+13
-2
@@ -13,3 +13,3 @@ /** | ||
| */ | ||
| import type { ReportOptions, ScanResult, SecurityTier } from "@quantakrypto/core"; | ||
| import type { CycloneDxBom, ReportOptions, ScanResult, SecurityTier } from "@quantakrypto/core"; | ||
| /** | ||
@@ -34,5 +34,15 @@ * Render the JSON report (pretty-printed, no trailing newline). | ||
| * serialized shape stays consistent across every tool in the monorepo. | ||
| * | ||
| * When `extra` CBOMs are supplied (e.g. a qProbe endpoint CBOM), they are merged | ||
| * with the scan CBOM via core's `mergeCboms`, producing a single combined | ||
| * code + infrastructure bill of materials linked by CycloneDX bom-link. | ||
| */ | ||
| export declare function renderCbom(result: ScanResult): string; | ||
| export declare function renderCbom(result: ScanResult, extra?: readonly CycloneDxBom[]): string; | ||
| /** | ||
| * Render an OpenVEX 0.2.0 document for the scan (pretty-printed, no trailing | ||
| * newline). Delegates to core's `toOpenVex` so the VEX shape stays consistent | ||
| * across the monorepo. Carries any `--triage` verdicts into `status_notes`. | ||
| */ | ||
| export declare function renderVex(result: ScanResult): string; | ||
| /** | ||
| * Render the human-readable banner. | ||
@@ -48,3 +58,4 @@ * | ||
| tier?: SecurityTier; | ||
| profile?: string; | ||
| }): string; | ||
| //# sourceMappingURL=report.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAeH,OAAO,KAAK,EAEV,aAAa,EACb,UAAU,EACV,YAAY,EAEb,MAAM,oBAAoB,CAAC;AAwB5B;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,MAAM,CAE3E;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,MAAM,CAS5E;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAErD;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,UAAU,EAClB,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,YAAY,CAAA;CAAO,GACjE,MAAM,CAgIR"} | ||
| {"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAkBH,OAAO,KAAK,EACV,YAAY,EAEZ,aAAa,EACb,UAAU,EACV,YAAY,EAGb,MAAM,oBAAoB,CAAC;AAqC5B;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,MAAM,CAE3E;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,MAAM,CAS5E;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,GAAE,SAAS,YAAY,EAAO,GAAG,MAAM,CAI1F;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAEpD;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,UAAU,EAClB,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,YAAY,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAO,GACnF,MAAM,CAkIR"} |
+35
-6
@@ -13,3 +13,16 @@ /** | ||
| */ | ||
| import { ANALYZABLE_LANGUAGES_LABEL, defaultRegistry, DEP_VULNERABLE_RULE, formatTierGuidance, PQC_TRANSITION_NOTE, SEVERITY_ORDER, severityRank, STATEFUL_HBS_NOTE, toCbom, toJson, toSarif, } from "@quantakrypto/core"; | ||
| import { ANALYZABLE_LANGUAGES_LABEL, defaultRegistry, DEP_VULNERABLE_RULE, formatProfileGuidance, getStandardsProfile, PQC_TRANSITION_NOTE, SEVERITY_ORDER, severityRank, STATEFUL_HBS_NOTE, toCbom, toJson, toOpenVex, toSarif, mergeCboms, } from "@quantakrypto/core"; | ||
| /** Map the legacy `--tier` to its equivalent standards profile (back-compat alias). */ | ||
| const TIER_TO_PROFILE = { | ||
| "category-3": "nist", | ||
| "category-5": "cnsa-2.0", | ||
| }; | ||
| /** Resolve the effective standards profile from `--profile` or the `--tier` alias. */ | ||
| function resolveProfile(profileId, tier) { | ||
| if (profileId) | ||
| return getStandardsProfile(profileId); | ||
| if (tier) | ||
| return getStandardsProfile(TIER_TO_PROFILE[tier]); | ||
| return undefined; | ||
| } | ||
| const PLAIN = { reset: "", bold: "", dim: "", red: "", yellow: "", green: "", cyan: "" }; | ||
@@ -55,7 +68,21 @@ const COLOR = { | ||
| * serialized shape stays consistent across every tool in the monorepo. | ||
| * | ||
| * When `extra` CBOMs are supplied (e.g. a qProbe endpoint CBOM), they are merged | ||
| * with the scan CBOM via core's `mergeCboms`, producing a single combined | ||
| * code + infrastructure bill of materials linked by CycloneDX bom-link. | ||
| */ | ||
| export function renderCbom(result) { | ||
| return JSON.stringify(toCbom(result), null, 2); | ||
| export function renderCbom(result, extra = []) { | ||
| const scanBom = toCbom(result); | ||
| const bom = extra.length > 0 ? mergeCboms([scanBom, ...extra]) : scanBom; | ||
| return JSON.stringify(bom, null, 2); | ||
| } | ||
| /** | ||
| * Render an OpenVEX 0.2.0 document for the scan (pretty-printed, no trailing | ||
| * newline). Delegates to core's `toOpenVex` so the VEX shape stays consistent | ||
| * across the monorepo. Carries any `--triage` verdicts into `status_notes`. | ||
| */ | ||
| export function renderVex(result) { | ||
| return JSON.stringify(toOpenVex(result), null, 2); | ||
| } | ||
| /** | ||
| * Render the human-readable banner. | ||
@@ -155,6 +182,8 @@ * | ||
| lines.push(`${c.dim}Next step:${c.reset} ${nextStep(findings)}`); | ||
| // CNSA security-tier migration targets (`--tier`), surfacing remediationForTier. | ||
| if (opts.tier) { | ||
| // Regime-tailored migration targets (`--profile`, or the `--tier` alias). Surfaces | ||
| // the parameter sets AND the regime's hybrid stance so guidance isn't regime-wrong. | ||
| const profile = resolveProfile(opts.profile, opts.tier); | ||
| if (profile) { | ||
| lines.push(""); | ||
| const g = formatTierGuidance(inventory.byAlgorithm, opts.tier); | ||
| const g = formatProfileGuidance(inventory.byAlgorithm, profile); | ||
| lines.push(`${c.bold}${g[0]}${c.reset}`); | ||
@@ -161,0 +190,0 @@ for (const t of g.slice(1)) |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"report.js","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * qScan report rendering.\n *\n * Produces the three output formats the CLI supports:\n * - `human` — a tasteful plain-text banner (counts, top findings, readiness\n * score, and a one-line next step). Optional raw ANSI color.\n * - `json` — the structured scan result via core's `toJson`.\n * - `sarif` — SARIF 2.1.0 via core's `toSarif`.\n *\n * Only `human` lives here; `json`/`sarif` delegate to `@quantakrypto/core` so the\n * serialized shape stays consistent across every tool in the monorepo.\n */\n\nimport {\n ANALYZABLE_LANGUAGES_LABEL,\n defaultRegistry,\n DEP_VULNERABLE_RULE,\n formatTierGuidance,\n PQC_TRANSITION_NOTE,\n SEVERITY_ORDER,\n severityRank,\n STATEFUL_HBS_NOTE,\n toCbom,\n toJson,\n toSarif,\n} from \"@quantakrypto/core\";\nimport type {\n Finding,\n ReportOptions,\n ScanResult,\n SecurityTier,\n Severity,\n} from \"@quantakrypto/core\";\n\n/** Minimal ANSI palette. Empty strings when color is disabled. */\ninterface Palette {\n reset: string;\n bold: string;\n dim: string;\n red: string;\n yellow: string;\n green: string;\n cyan: string;\n}\n\nconst PLAIN: Palette = { reset: \"\", bold: \"\", dim: \"\", red: \"\", yellow: \"\", green: \"\", cyan: \"\" };\nconst COLOR: Palette = {\n reset: \"\\x1b[0m\",\n bold: \"\\x1b[1m\",\n dim: \"\\x1b[2m\",\n red: \"\\x1b[31m\",\n yellow: \"\\x1b[33m\",\n green: \"\\x1b[32m\",\n cyan: \"\\x1b[36m\",\n};\n\n/**\n * Render the JSON report (pretty-printed, no trailing newline).\n *\n * Delegates to core's `toJson` for a monorepo-consistent shape. `opts` is passed\n * straight through (e.g. `{ redactSnippets: true }` for `--no-snippets`).\n */\nexport function renderJson(result: ScanResult, opts?: ReportOptions): string {\n return JSON.stringify(toJson(result, opts), null, 2);\n}\n\n/**\n * Render the SARIF 2.1.0 report (pretty-printed, no trailing newline).\n *\n * Delegates to core's `toSarif` — the monorepo's single source of truth for the\n * SARIF shape (schema, tool driver, rules, taxonomies). `opts` is passed through\n * (e.g. `{ redactSnippets: true }` for `--no-snippets`).\n */\nexport function renderSarif(result: ScanResult, opts?: ReportOptions): string {\n // Advertise the full rule catalog (not just the rules that fired) so SARIF\n // consumers see complete metadata for every rule qScan can emit. The detector\n // registry's catalog is source/config rules only; `dep-vulnerable` comes from\n // the manifest scanner, so add its generic entry — otherwise SARIF would build\n // that rule from the first dependency finding and leak one package's specifics\n // into the shared rule description.\n const catalog = [...defaultRegistry.ruleCatalog(), DEP_VULNERABLE_RULE];\n return JSON.stringify(toSarif(result, { catalog, ...opts }), null, 2);\n}\n\n/**\n * Render a CycloneDX 1.6 CBOM (cryptographic bill of materials) for the scan,\n * pretty-printed with no trailing newline. Delegates to core's `toCbom` so the\n * serialized shape stays consistent across every tool in the monorepo.\n */\nexport function renderCbom(result: ScanResult): string {\n return JSON.stringify(toCbom(result), null, 2);\n}\n\n/**\n * Render the human-readable banner.\n *\n * @param result The scan result.\n * @param opts.color Emit raw ANSI escapes (default: false / plain text).\n * @param opts.topN How many findings to list (default: 5).\n */\nexport function renderHuman(\n result: ScanResult,\n opts: { color?: boolean; topN?: number; tier?: SecurityTier } = {},\n): string {\n const c = opts.color ? COLOR : PLAIN;\n const topN = opts.topN ?? 5;\n const { findings, inventory, filesScanned } = result;\n // `analyzedFiles`: of the scanned files, how many were in a language the\n // scanner can actually inspect for crypto (JS/TS, Python, Go, Java). When it's 0 the\n // readiness score reflects no analyzable code — say so rather than imply safe.\n const analyzedFiles = result.analyzedFiles;\n const noAnalyzable = analyzedFiles === 0;\n // Partial-coverage honesty: when the analyzable subset is only a small slice of\n // what was scanned, a high score reflects that slice, not the whole tree. We\n // surface a one-line caveat next to the score so the number isn't over-trusted.\n // Skips the zero case (handled explicitly below) and normal repos where most\n // files are analyzable.\n const lowCoverage =\n analyzedFiles !== undefined &&\n analyzedFiles > 0 &&\n filesScanned > 0 &&\n analyzedFiles / filesScanned < 0.25;\n const coverageCaveat = lowCoverage\n ? `${c.dim}Note: the score covers only ${analyzedFiles} analyzable of ${filesScanned} scanned files (${ANALYZABLE_LANGUAGES_LABEL}); crypto in unsupported languages is not reflected.${c.reset}`\n : \"\";\n const lines: string[] = [];\n\n lines.push(`${c.bold}qScan — quantum-vulnerable cryptography report${c.reset}`);\n const coverage =\n analyzedFiles === undefined\n ? \"\"\n : ` • analyzed: ${analyzedFiles} (${ANALYZABLE_LANGUAGES_LABEL})`;\n lines.push(\n `${c.dim}root: ${result.root} • files scanned: ${filesScanned}${coverage} • qscan v${result.toolVersion}${c.reset}`,\n );\n // Coverage diagnostics: warn when files were skipped, so a low finding count\n // isn't mistaken for a clean scan of the whole tree.\n const diag = result.diagnostics;\n if (diag && (diag.unreadable > 0 || diag.skippedMinified > 0)) {\n const parts: string[] = [];\n if (diag.unreadable > 0) parts.push(`${diag.unreadable} unreadable`);\n if (diag.skippedMinified > 0) parts.push(`${diag.skippedMinified} skipped (minified)`);\n lines.push(`${c.yellow}Coverage: ${parts.join(\", \")} — results may be incomplete.${c.reset}`);\n }\n lines.push(\"\");\n\n if (findings.length === 0) {\n if (noAnalyzable && filesScanned > 0) {\n // Honesty guard: don't let a 100/100 read as \"safe\" when nothing the\n // scanner understands was analyzed — the crypto may live in an\n // unsupported language (Go, Java, Rust, C#, …).\n lines.push(\n `${c.yellow}No analyzable source found.${c.reset} Scanned ${filesScanned} file${\n filesScanned === 1 ? \"\" : \"s\"\n }, but none were in a supported language (${ANALYZABLE_LANGUAGES_LABEL}).`,\n );\n lines.push(\n `${c.dim}The score below covers only what qScan can read today — it is NOT a clean bill of health for this codebase.${c.reset}`,\n );\n lines.push(\n `${c.bold}Readiness score: ${readiness(inventory.readinessScore, c)}/100 (no analyzable source)${c.reset}`,\n );\n lines.push(\"\");\n lines.push(\n `${c.dim}Next step:${c.reset} multi-language support is expanding; track coverage before relying on the score.`,\n );\n return lines.join(\"\\n\");\n }\n lines.push(`${c.green}No quantum-vulnerable cryptography detected.${c.reset}`);\n lines.push(`${c.bold}Readiness score: ${readiness(inventory.readinessScore, c)}/100${c.reset}`);\n if (coverageCaveat) lines.push(coverageCaveat);\n lines.push(\"\");\n lines.push(`${c.dim}Next step:${c.reset} keep scanning in CI to catch regressions.`);\n return lines.join(\"\\n\");\n }\n\n // Severity counts, most-severe first.\n const counts = SEVERITY_ORDER.map((sev) => {\n const n = inventory.bySeverity[sev] ?? 0;\n return n > 0 ? `${severityColor(sev, c)}${n} ${sev}${c.reset}` : null;\n }).filter((s): s is string => s !== null);\n\n lines.push(\n `${c.bold}${findings.length} finding${findings.length === 1 ? \"\" : \"s\"}${c.reset} (${counts.join(\", \")})`,\n );\n if (inventory.hndlCount > 0) {\n lines.push(\n `${c.yellow}${inventory.hndlCount}${c.reset} exposed to harvest-now-decrypt-later (HNDL).`,\n );\n }\n lines.push(`${c.bold}Readiness score: ${readiness(inventory.readinessScore, c)}/100${c.reset}`);\n if (coverageCaveat) lines.push(coverageCaveat);\n lines.push(\"\");\n\n // Top findings, sorted by severity then file/line for determinism.\n const top = [...findings].sort(compareFindings).slice(0, topN);\n lines.push(`${c.bold}Top findings${c.reset}`);\n for (const f of top) {\n const loc = `${f.location.file}:${f.location.line}`;\n lines.push(\n ` ${severityColor(f.severity, c)}${f.severity.padEnd(8)}${c.reset} ${c.cyan}${f.ruleId}${c.reset} ${loc}`,\n );\n lines.push(` ${f.message}`);\n if (f.remediation) {\n lines.push(` ${c.dim}→ ${f.remediation}${c.reset}`);\n }\n }\n if (findings.length > top.length) {\n lines.push(` ${c.dim}…and ${findings.length - top.length} more${c.reset}`);\n }\n lines.push(\"\");\n lines.push(`${c.dim}Next step:${c.reset} ${nextStep(findings)}`);\n\n // CNSA security-tier migration targets (`--tier`), surfacing remediationForTier.\n if (opts.tier) {\n lines.push(\"\");\n const g = formatTierGuidance(inventory.byAlgorithm, opts.tier);\n lines.push(`${c.bold}${g[0]}${c.reset}`);\n for (const t of g.slice(1)) lines.push(`${c.cyan}${t}${c.reset}`);\n }\n\n // Forward-looking standards + the IR 8547 migration deadline (HQC / FN-DSA /\n // X-Wing) — the long-horizon guidance behind anything flagged above.\n lines.push(\"\");\n lines.push(`${c.bold}Standards & timeline${c.reset}`);\n lines.push(`${c.dim}${PQC_TRANSITION_NOTE}${c.reset}`);\n if (findings.some((f) => f.category === \"signature\")) {\n lines.push(`${c.dim}${STATEFUL_HBS_NOTE}${c.reset}`);\n }\n\n return lines.join(\"\\n\");\n}\n\n/** Suggest a single concrete next action based on the worst finding. */\nfunction nextStep(findings: Finding[]): string {\n const worst = [...findings].sort(compareFindings)[0];\n if (!worst) return \"review the findings above.\";\n // A dependency finding points at a manifest — you replace the *package*, not\n // \"migrate package.json\". Phrase it as a dependency swap.\n if (worst.category === \"dependency\") {\n return worst.remediation\n ? `replace the vulnerable dependency in ${worst.location.file} — ${worst.remediation}`\n : `replace the vulnerable dependency in ${worst.location.file}.`;\n }\n if (worst.remediation) {\n return `migrate ${worst.location.file} — ${worst.remediation}`;\n }\n return `triage ${worst.ruleId} in ${worst.location.file}:${worst.location.line}.`;\n}\n\n/** Deterministic ordering: most severe first, then file, then line. */\nfunction compareFindings(a: Finding, b: Finding): number {\n const bySev = severityRank(a.severity) - severityRank(b.severity);\n if (bySev !== 0) return bySev;\n const byFile = a.location.file.localeCompare(b.location.file);\n if (byFile !== 0) return byFile;\n return a.location.line - b.location.line;\n}\n\n/** Color the readiness score green/yellow/red by band. */\nfunction readiness(score: number, c: Palette): string {\n const color = score >= 80 ? c.green : score >= 50 ? c.yellow : c.red;\n return `${color}${score}${c.reset}`;\n}\n\n/** Map a severity to its palette color. */\nfunction severityColor(severity: Severity, c: Palette): string {\n switch (severity) {\n case \"critical\":\n case \"high\":\n return c.red;\n case \"medium\":\n return c.yellow;\n default:\n return c.dim;\n }\n}\n"]} | ||
| {"version":3,"file":"report.js","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * qScan report rendering.\n *\n * Produces the three output formats the CLI supports:\n * - `human` — a tasteful plain-text banner (counts, top findings, readiness\n * score, and a one-line next step). Optional raw ANSI color.\n * - `json` — the structured scan result via core's `toJson`.\n * - `sarif` — SARIF 2.1.0 via core's `toSarif`.\n *\n * Only `human` lives here; `json`/`sarif` delegate to `@quantakrypto/core` so the\n * serialized shape stays consistent across every tool in the monorepo.\n */\n\nimport {\n ANALYZABLE_LANGUAGES_LABEL,\n defaultRegistry,\n DEP_VULNERABLE_RULE,\n formatProfileGuidance,\n getStandardsProfile,\n PQC_TRANSITION_NOTE,\n SEVERITY_ORDER,\n severityRank,\n STATEFUL_HBS_NOTE,\n toCbom,\n toJson,\n toOpenVex,\n toSarif,\n mergeCboms,\n} from \"@quantakrypto/core\";\nimport type {\n CycloneDxBom,\n Finding,\n ReportOptions,\n ScanResult,\n SecurityTier,\n Severity,\n StandardsProfile,\n} from \"@quantakrypto/core\";\n\n/** Map the legacy `--tier` to its equivalent standards profile (back-compat alias). */\nconst TIER_TO_PROFILE: Record<SecurityTier, string> = {\n \"category-3\": \"nist\",\n \"category-5\": \"cnsa-2.0\",\n};\n\n/** Resolve the effective standards profile from `--profile` or the `--tier` alias. */\nfunction resolveProfile(profileId?: string, tier?: SecurityTier): StandardsProfile | undefined {\n if (profileId) return getStandardsProfile(profileId);\n if (tier) return getStandardsProfile(TIER_TO_PROFILE[tier]);\n return undefined;\n}\n\n/** Minimal ANSI palette. Empty strings when color is disabled. */\ninterface Palette {\n reset: string;\n bold: string;\n dim: string;\n red: string;\n yellow: string;\n green: string;\n cyan: string;\n}\n\nconst PLAIN: Palette = { reset: \"\", bold: \"\", dim: \"\", red: \"\", yellow: \"\", green: \"\", cyan: \"\" };\nconst COLOR: Palette = {\n reset: \"\\x1b[0m\",\n bold: \"\\x1b[1m\",\n dim: \"\\x1b[2m\",\n red: \"\\x1b[31m\",\n yellow: \"\\x1b[33m\",\n green: \"\\x1b[32m\",\n cyan: \"\\x1b[36m\",\n};\n\n/**\n * Render the JSON report (pretty-printed, no trailing newline).\n *\n * Delegates to core's `toJson` for a monorepo-consistent shape. `opts` is passed\n * straight through (e.g. `{ redactSnippets: true }` for `--no-snippets`).\n */\nexport function renderJson(result: ScanResult, opts?: ReportOptions): string {\n return JSON.stringify(toJson(result, opts), null, 2);\n}\n\n/**\n * Render the SARIF 2.1.0 report (pretty-printed, no trailing newline).\n *\n * Delegates to core's `toSarif` — the monorepo's single source of truth for the\n * SARIF shape (schema, tool driver, rules, taxonomies). `opts` is passed through\n * (e.g. `{ redactSnippets: true }` for `--no-snippets`).\n */\nexport function renderSarif(result: ScanResult, opts?: ReportOptions): string {\n // Advertise the full rule catalog (not just the rules that fired) so SARIF\n // consumers see complete metadata for every rule qScan can emit. The detector\n // registry's catalog is source/config rules only; `dep-vulnerable` comes from\n // the manifest scanner, so add its generic entry — otherwise SARIF would build\n // that rule from the first dependency finding and leak one package's specifics\n // into the shared rule description.\n const catalog = [...defaultRegistry.ruleCatalog(), DEP_VULNERABLE_RULE];\n return JSON.stringify(toSarif(result, { catalog, ...opts }), null, 2);\n}\n\n/**\n * Render a CycloneDX 1.6 CBOM (cryptographic bill of materials) for the scan,\n * pretty-printed with no trailing newline. Delegates to core's `toCbom` so the\n * serialized shape stays consistent across every tool in the monorepo.\n *\n * When `extra` CBOMs are supplied (e.g. a qProbe endpoint CBOM), they are merged\n * with the scan CBOM via core's `mergeCboms`, producing a single combined\n * code + infrastructure bill of materials linked by CycloneDX bom-link.\n */\nexport function renderCbom(result: ScanResult, extra: readonly CycloneDxBom[] = []): string {\n const scanBom = toCbom(result);\n const bom = extra.length > 0 ? mergeCboms([scanBom, ...extra]) : scanBom;\n return JSON.stringify(bom, null, 2);\n}\n\n/**\n * Render an OpenVEX 0.2.0 document for the scan (pretty-printed, no trailing\n * newline). Delegates to core's `toOpenVex` so the VEX shape stays consistent\n * across the monorepo. Carries any `--triage` verdicts into `status_notes`.\n */\nexport function renderVex(result: ScanResult): string {\n return JSON.stringify(toOpenVex(result), null, 2);\n}\n\n/**\n * Render the human-readable banner.\n *\n * @param result The scan result.\n * @param opts.color Emit raw ANSI escapes (default: false / plain text).\n * @param opts.topN How many findings to list (default: 5).\n */\nexport function renderHuman(\n result: ScanResult,\n opts: { color?: boolean; topN?: number; tier?: SecurityTier; profile?: string } = {},\n): string {\n const c = opts.color ? COLOR : PLAIN;\n const topN = opts.topN ?? 5;\n const { findings, inventory, filesScanned } = result;\n // `analyzedFiles`: of the scanned files, how many were in a language the\n // scanner can actually inspect for crypto (JS/TS, Python, Go, Java). When it's 0 the\n // readiness score reflects no analyzable code — say so rather than imply safe.\n const analyzedFiles = result.analyzedFiles;\n const noAnalyzable = analyzedFiles === 0;\n // Partial-coverage honesty: when the analyzable subset is only a small slice of\n // what was scanned, a high score reflects that slice, not the whole tree. We\n // surface a one-line caveat next to the score so the number isn't over-trusted.\n // Skips the zero case (handled explicitly below) and normal repos where most\n // files are analyzable.\n const lowCoverage =\n analyzedFiles !== undefined &&\n analyzedFiles > 0 &&\n filesScanned > 0 &&\n analyzedFiles / filesScanned < 0.25;\n const coverageCaveat = lowCoverage\n ? `${c.dim}Note: the score covers only ${analyzedFiles} analyzable of ${filesScanned} scanned files (${ANALYZABLE_LANGUAGES_LABEL}); crypto in unsupported languages is not reflected.${c.reset}`\n : \"\";\n const lines: string[] = [];\n\n lines.push(`${c.bold}qScan — quantum-vulnerable cryptography report${c.reset}`);\n const coverage =\n analyzedFiles === undefined\n ? \"\"\n : ` • analyzed: ${analyzedFiles} (${ANALYZABLE_LANGUAGES_LABEL})`;\n lines.push(\n `${c.dim}root: ${result.root} • files scanned: ${filesScanned}${coverage} • qscan v${result.toolVersion}${c.reset}`,\n );\n // Coverage diagnostics: warn when files were skipped, so a low finding count\n // isn't mistaken for a clean scan of the whole tree.\n const diag = result.diagnostics;\n if (diag && (diag.unreadable > 0 || diag.skippedMinified > 0)) {\n const parts: string[] = [];\n if (diag.unreadable > 0) parts.push(`${diag.unreadable} unreadable`);\n if (diag.skippedMinified > 0) parts.push(`${diag.skippedMinified} skipped (minified)`);\n lines.push(`${c.yellow}Coverage: ${parts.join(\", \")} — results may be incomplete.${c.reset}`);\n }\n lines.push(\"\");\n\n if (findings.length === 0) {\n if (noAnalyzable && filesScanned > 0) {\n // Honesty guard: don't let a 100/100 read as \"safe\" when nothing the\n // scanner understands was analyzed — the crypto may live in an\n // unsupported language (Go, Java, Rust, C#, …).\n lines.push(\n `${c.yellow}No analyzable source found.${c.reset} Scanned ${filesScanned} file${\n filesScanned === 1 ? \"\" : \"s\"\n }, but none were in a supported language (${ANALYZABLE_LANGUAGES_LABEL}).`,\n );\n lines.push(\n `${c.dim}The score below covers only what qScan can read today — it is NOT a clean bill of health for this codebase.${c.reset}`,\n );\n lines.push(\n `${c.bold}Readiness score: ${readiness(inventory.readinessScore, c)}/100 (no analyzable source)${c.reset}`,\n );\n lines.push(\"\");\n lines.push(\n `${c.dim}Next step:${c.reset} multi-language support is expanding; track coverage before relying on the score.`,\n );\n return lines.join(\"\\n\");\n }\n lines.push(`${c.green}No quantum-vulnerable cryptography detected.${c.reset}`);\n lines.push(`${c.bold}Readiness score: ${readiness(inventory.readinessScore, c)}/100${c.reset}`);\n if (coverageCaveat) lines.push(coverageCaveat);\n lines.push(\"\");\n lines.push(`${c.dim}Next step:${c.reset} keep scanning in CI to catch regressions.`);\n return lines.join(\"\\n\");\n }\n\n // Severity counts, most-severe first.\n const counts = SEVERITY_ORDER.map((sev) => {\n const n = inventory.bySeverity[sev] ?? 0;\n return n > 0 ? `${severityColor(sev, c)}${n} ${sev}${c.reset}` : null;\n }).filter((s): s is string => s !== null);\n\n lines.push(\n `${c.bold}${findings.length} finding${findings.length === 1 ? \"\" : \"s\"}${c.reset} (${counts.join(\", \")})`,\n );\n if (inventory.hndlCount > 0) {\n lines.push(\n `${c.yellow}${inventory.hndlCount}${c.reset} exposed to harvest-now-decrypt-later (HNDL).`,\n );\n }\n lines.push(`${c.bold}Readiness score: ${readiness(inventory.readinessScore, c)}/100${c.reset}`);\n if (coverageCaveat) lines.push(coverageCaveat);\n lines.push(\"\");\n\n // Top findings, sorted by severity then file/line for determinism.\n const top = [...findings].sort(compareFindings).slice(0, topN);\n lines.push(`${c.bold}Top findings${c.reset}`);\n for (const f of top) {\n const loc = `${f.location.file}:${f.location.line}`;\n lines.push(\n ` ${severityColor(f.severity, c)}${f.severity.padEnd(8)}${c.reset} ${c.cyan}${f.ruleId}${c.reset} ${loc}`,\n );\n lines.push(` ${f.message}`);\n if (f.remediation) {\n lines.push(` ${c.dim}→ ${f.remediation}${c.reset}`);\n }\n }\n if (findings.length > top.length) {\n lines.push(` ${c.dim}…and ${findings.length - top.length} more${c.reset}`);\n }\n lines.push(\"\");\n lines.push(`${c.dim}Next step:${c.reset} ${nextStep(findings)}`);\n\n // Regime-tailored migration targets (`--profile`, or the `--tier` alias). Surfaces\n // the parameter sets AND the regime's hybrid stance so guidance isn't regime-wrong.\n const profile = resolveProfile(opts.profile, opts.tier);\n if (profile) {\n lines.push(\"\");\n const g = formatProfileGuidance(inventory.byAlgorithm, profile);\n lines.push(`${c.bold}${g[0]}${c.reset}`);\n for (const t of g.slice(1)) lines.push(`${c.cyan}${t}${c.reset}`);\n }\n\n // Forward-looking standards + the IR 8547 migration deadline (HQC / FN-DSA /\n // X-Wing) — the long-horizon guidance behind anything flagged above.\n lines.push(\"\");\n lines.push(`${c.bold}Standards & timeline${c.reset}`);\n lines.push(`${c.dim}${PQC_TRANSITION_NOTE}${c.reset}`);\n if (findings.some((f) => f.category === \"signature\")) {\n lines.push(`${c.dim}${STATEFUL_HBS_NOTE}${c.reset}`);\n }\n\n return lines.join(\"\\n\");\n}\n\n/** Suggest a single concrete next action based on the worst finding. */\nfunction nextStep(findings: Finding[]): string {\n const worst = [...findings].sort(compareFindings)[0];\n if (!worst) return \"review the findings above.\";\n // A dependency finding points at a manifest — you replace the *package*, not\n // \"migrate package.json\". Phrase it as a dependency swap.\n if (worst.category === \"dependency\") {\n return worst.remediation\n ? `replace the vulnerable dependency in ${worst.location.file} — ${worst.remediation}`\n : `replace the vulnerable dependency in ${worst.location.file}.`;\n }\n if (worst.remediation) {\n return `migrate ${worst.location.file} — ${worst.remediation}`;\n }\n return `triage ${worst.ruleId} in ${worst.location.file}:${worst.location.line}.`;\n}\n\n/** Deterministic ordering: most severe first, then file, then line. */\nfunction compareFindings(a: Finding, b: Finding): number {\n const bySev = severityRank(a.severity) - severityRank(b.severity);\n if (bySev !== 0) return bySev;\n const byFile = a.location.file.localeCompare(b.location.file);\n if (byFile !== 0) return byFile;\n return a.location.line - b.location.line;\n}\n\n/** Color the readiness score green/yellow/red by band. */\nfunction readiness(score: number, c: Palette): string {\n const color = score >= 80 ? c.green : score >= 50 ? c.yellow : c.red;\n return `${color}${score}${c.reset}`;\n}\n\n/** Map a severity to its palette color. */\nfunction severityColor(severity: Severity, c: Palette): string {\n switch (severity) {\n case \"critical\":\n case \"high\":\n return c.red;\n case \"medium\":\n return c.yellow;\n default:\n return c.dim;\n }\n}\n"]} |
| import type { ContextLevel, Finding, ScanResult, Severity, TriageVerdict } from "@quantakrypto/core"; | ||
| import type { LlmProvider } from "./args.js"; | ||
| /** Default cap on findings sent to the LLM per triage run (spend/DoS guard). */ | ||
| export declare const DEFAULT_MAX_TRIAGE = 100; | ||
| /** Injectable triage function (default wraps `@quantakrypto/agent`). */ | ||
| export type TriageFn = (findings: readonly Finding[]) => Promise<Map<string, TriageVerdict>>; | ||
| export interface RunTriageOptions { | ||
| interface RunTriageOptions { | ||
| level: ContextLevel; | ||
@@ -23,3 +21,3 @@ floor?: Severity; | ||
| } | ||
| export interface RunTriageResult { | ||
| interface RunTriageResult { | ||
| /** When `--dry-run`, the preflight text to show instead of a normal report. */ | ||
@@ -34,2 +32,3 @@ preflight?: string; | ||
| export declare function runTriage(result: ScanResult, opts: RunTriageOptions): Promise<RunTriageResult>; | ||
| export {}; | ||
| //# sourceMappingURL=triage-run.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"triage-run.d.ts","sourceRoot":"","sources":["../src/triage-run.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EACV,YAAY,EACZ,OAAO,EACP,UAAU,EACV,QAAQ,EACR,aAAa,EACd,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAU7C,gFAAgF;AAChF,eAAO,MAAM,kBAAkB,MAAM,CAAC;AAWtC,wEAAwE;AACxE,MAAM,MAAM,QAAQ,GAAG,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,KAAK,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC;AAE7F,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,YAAY,CAAC;IACpB,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,WAAW,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qFAAqF;IACrF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iFAAiF;IACjF,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACtC,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAChD,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B;AAED,MAAM,WAAW,eAAe;IAC9B,+EAA+E;IAC/E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAaD;;;;GAIG;AACH,wBAAsB,SAAS,CAC7B,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,gBAAgB,GACrB,OAAO,CAAC,eAAe,CAAC,CAuF1B"} | ||
| {"version":3,"file":"triage-run.d.ts","sourceRoot":"","sources":["../src/triage-run.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EACV,YAAY,EACZ,OAAO,EACP,UAAU,EACV,QAAQ,EACR,aAAa,EACd,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAsB7C,wEAAwE;AACxE,MAAM,MAAM,QAAQ,GAAG,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,KAAK,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC;AAE7F,UAAU,gBAAgB;IACxB,KAAK,EAAE,YAAY,CAAC;IACpB,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,WAAW,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qFAAqF;IACrF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iFAAiF;IACjF,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACtC,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAChD,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B;AAED,UAAU,eAAe;IACvB,+EAA+E;IAC/E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAaD;;;;GAIG;AACH,wBAAsB,SAAS,CAC7B,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,gBAAgB,GACrB,OAAO,CAAC,eAAe,CAAC,CA8F1B"} |
+13
-6
@@ -13,3 +13,3 @@ /** | ||
| import process from "node:process"; | ||
| import { buildContext, compareFindings, fingerprintFinding, renderPreflight, } from "@quantakrypto/core"; | ||
| import { buildContext, compareFindings, fingerprintFinding, renderPreflight, severityRank, } from "@quantakrypto/core"; | ||
| const SEVERITY_RANK = { | ||
@@ -23,3 +23,3 @@ critical: 0, | ||
| /** Default cap on findings sent to the LLM per triage run (spend/DoS guard). */ | ||
| export const DEFAULT_MAX_TRIAGE = 100; | ||
| const DEFAULT_MAX_TRIAGE = 100; | ||
| /** The model's `rationale` is untrusted text that lands in JSON/SARIF output. | ||
@@ -87,8 +87,15 @@ * Strip control characters and clamp length so a prompt-injected rationale can't | ||
| }); | ||
| // Spend/DoS guard: triage only up to maxFindings (top by severity among those | ||
| // at/above the floor). The rest keep their deterministic order and no annotation. | ||
| // Spend/DoS guard: triage only up to maxFindings, and when capping, pick the TOP by | ||
| // SEVERITY (most severe first) — not by file-path order — so a critical in a | ||
| // late-sorting file isn't silently dropped from triage and sunk to the bottom of the | ||
| // report. Under the cap, triage exactly the `targets` (findings at/above the floor), | ||
| // not the full result set. Ties fall back to the stable file/line/ruleId order. | ||
| const maxFindings = opts.maxFindings ?? DEFAULT_MAX_TRIAGE; | ||
| const bySeverityThenOrder = (a, b) => { | ||
| const d = severityRank(a.severity) - severityRank(b.severity); | ||
| return d !== 0 ? d : compareFindings(a, b); | ||
| }; | ||
| const toTriage = targets.length > maxFindings | ||
| ? [...targets].sort(compareFindings).slice(0, maxFindings) | ||
| : result.findings; | ||
| ? [...targets].sort(bySeverityThenOrder).slice(0, maxFindings) | ||
| : targets; | ||
| if (targets.length > maxFindings) { | ||
@@ -95,0 +102,0 @@ stderr(`qscan: --triage capped at ${maxFindings} findings (${targets.length} at/above floor); raise --max-findings to triage more.\n`); |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"triage-run.js","sourceRoot":"","sources":["../src/triage-run.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `qscan --triage` glue (BYOK plane). Runs the LLM triage pass over a scan\n * result and attaches an exposure annotation to each finding, then re-sorts by\n * exposure. It NEVER drops a finding and NEVER touches the exit code — the CLI\n * computes the exit code from raw severities before this ever runs.\n *\n * `@quantakrypto/agent` (the only networked package) is loaded via dynamic\n * `import()` so a plain scan never pulls in the network client.\n */\nimport { readFile as fsReadFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport process from \"node:process\";\n\nimport {\n buildContext,\n compareFindings,\n fingerprintFinding,\n renderPreflight,\n} from \"@quantakrypto/core\";\nimport type {\n ContextLevel,\n Finding,\n ScanResult,\n Severity,\n TriageVerdict,\n} from \"@quantakrypto/core\";\n\nimport type { LlmProvider } from \"./args.js\";\n\nconst SEVERITY_RANK: Record<Severity, number> = {\n critical: 0,\n high: 1,\n medium: 2,\n low: 3,\n info: 4,\n};\n\n/** Default cap on findings sent to the LLM per triage run (spend/DoS guard). */\nexport const DEFAULT_MAX_TRIAGE = 100;\n\n/** The model's `rationale` is untrusted text that lands in JSON/SARIF output.\n * Strip control characters and clamp length so a prompt-injected rationale can't\n * smuggle escape sequences or unbounded content into a downstream consumer. */\nfunction sanitizeRationale(s: string): string {\n // eslint-disable-next-line no-control-regex\n const clean = s.replace(/[\\u0000-\\u001f\\u007f]+/g, \" \").trim();\n return clean.length > 500 ? `${clean.slice(0, 497)}…` : clean;\n}\n\n/** Injectable triage function (default wraps `@quantakrypto/agent`). */\nexport type TriageFn = (findings: readonly Finding[]) => Promise<Map<string, TriageVerdict>>;\n\nexport interface RunTriageOptions {\n level: ContextLevel;\n floor?: Severity;\n dryRun?: boolean;\n provider?: LlmProvider;\n model?: string;\n cacheFile?: string;\n /** Cap on findings sent to the LLM (spend/DoS guard; default DEFAULT_MAX_TRIAGE). */\n maxFindings?: number;\n /** Base directory for resolving finding file paths (defaults to result.root). */\n root?: string;\n // --- injectables for testing ---\n triageFn?: TriageFn;\n resolveKey?: () => string | undefined;\n readFile?: (relPath: string) => Promise<string>;\n stderr?: (s: string) => void;\n}\n\nexport interface RunTriageResult {\n /** When `--dry-run`, the preflight text to show instead of a normal report. */\n preflight?: string;\n}\n\nfunction envKey(provider: LlmProvider): string | undefined {\n return (\n process.env.QK_LLM_API_KEY ??\n (provider === \"anthropic\" ? process.env.ANTHROPIC_API_KEY : process.env.OPENAI_API_KEY)\n );\n}\n\nfunction defaultModel(provider: LlmProvider): string {\n return provider === \"anthropic\" ? \"claude-sonnet-5\" : \"gpt-4o-mini\";\n}\n\n/**\n * Annotate `result.findings` with triage verdicts (mutating `result`). Returns a\n * preflight string when `--dry-run` is set (no provider is contacted). Failures\n * degrade gracefully: the scan/report proceed without triage.\n */\nexport async function runTriage(\n result: ScanResult,\n opts: RunTriageOptions,\n): Promise<RunTriageResult> {\n const level = opts.level;\n const floorRank = SEVERITY_RANK[opts.floor ?? \"medium\"];\n const targets = result.findings.filter((f) => SEVERITY_RANK[f.severity] <= floorRank);\n const stderr = opts.stderr ?? ((s: string) => void process.stderr.write(s));\n const root = opts.root ?? result.root ?? \".\";\n const readFile = opts.readFile ?? ((rel: string) => fsReadFile(path.resolve(root, rel), \"utf8\"));\n\n // --dry-run: show exactly what would be sent; contact nothing.\n if (opts.dryRun) {\n const contexts = [];\n for (const f of targets) {\n const content = level === \"metadata\" ? \"\" : await readFile(f.location.file).catch(() => \"\");\n contexts.push(buildContext(f, level, content));\n }\n return {\n preflight: contexts.length\n ? renderPreflight(contexts)\n : \"qscan --triage --dry-run: no findings at or above the triage floor.\",\n };\n }\n\n const provider: LlmProvider = opts.provider ?? \"anthropic\";\n const key = opts.resolveKey ? opts.resolveKey() : envKey(provider);\n\n // No key and no injected triage function → graceful degrade (never fatal).\n if (!opts.triageFn && !key) {\n stderr(\n \"qscan: --triage needs an API key (set QK_LLM_API_KEY, ANTHROPIC_API_KEY, or OPENAI_API_KEY). Skipping triage.\\n\",\n );\n return {};\n }\n\n const model = opts.model ?? defaultModel(provider);\n const triageFn: TriageFn =\n opts.triageFn ??\n (async (findings) => {\n const agent = await import(\"@quantakrypto/agent\");\n const client = agent.resolveClient({ provider, model, apiKey: key as string });\n return agent.triageFindings(findings, {\n client,\n level,\n readFile,\n fingerprint: fingerprintFinding,\n floor: opts.floor,\n cacheFile: opts.cacheFile,\n model,\n });\n });\n\n // Spend/DoS guard: triage only up to maxFindings (top by severity among those\n // at/above the floor). The rest keep their deterministic order and no annotation.\n const maxFindings = opts.maxFindings ?? DEFAULT_MAX_TRIAGE;\n const toTriage =\n targets.length > maxFindings\n ? [...targets].sort(compareFindings).slice(0, maxFindings)\n : result.findings;\n if (targets.length > maxFindings) {\n stderr(\n `qscan: --triage capped at ${maxFindings} findings (${targets.length} at/above floor); raise --max-findings to triage more.\\n`,\n );\n }\n\n try {\n const verdicts = await triageFn(toTriage);\n for (const f of result.findings) {\n const v = verdicts.get(fingerprintFinding(f));\n if (v) {\n f.triage = {\n exposureScore: v.exposureScore,\n priority: v.priority,\n rationale: sanitizeRationale(v.rationale),\n };\n }\n }\n // Re-sort by exposure (desc), falling back to the stable finding order.\n result.findings = [...result.findings].sort((a, b) => {\n const ea = a.triage?.exposureScore ?? -1;\n const eb = b.triage?.exposureScore ?? -1;\n if (eb !== ea) return eb - ea;\n return compareFindings(a, b);\n });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n stderr(`qscan: triage failed (${msg}); showing findings without triage.\\n`);\n }\n return {};\n}\n"]} | ||
| {"version":3,"file":"triage-run.js","sourceRoot":"","sources":["../src/triage-run.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,QAAQ,IAAI,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,OAAO,MAAM,cAAc,CAAC;AAEnC,OAAO,EACL,YAAY,EACZ,eAAe,EACf,kBAAkB,EAClB,eAAe,EACf,YAAY,GACb,MAAM,oBAAoB,CAAC;AAW5B,MAAM,aAAa,GAA6B;IAC9C,QAAQ,EAAE,CAAC;IACX,IAAI,EAAE,CAAC;IACP,MAAM,EAAE,CAAC;IACT,GAAG,EAAE,CAAC;IACN,IAAI,EAAE,CAAC;CACR,CAAC;AAEF,gFAAgF;AAChF,MAAM,kBAAkB,GAAG,GAAG,CAAC;AAE/B;;+EAE+E;AAC/E,SAAS,iBAAiB,CAAC,CAAS;IAClC,4CAA4C;IAC5C,MAAM,KAAK,GAAG,CAAC,CAAC,OAAO,CAAC,yBAAyB,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IAC/D,OAAO,KAAK,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC;AAChE,CAAC;AA4BD,SAAS,MAAM,CAAC,QAAqB;IACnC,OAAO,CACL,OAAO,CAAC,GAAG,CAAC,cAAc;QAC1B,CAAC,QAAQ,KAAK,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CACxF,CAAC;AACJ,CAAC;AAED,SAAS,YAAY,CAAC,QAAqB;IACzC,OAAO,QAAQ,KAAK,WAAW,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,aAAa,CAAC;AACtE,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,MAAkB,EAClB,IAAsB;IAEtB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;IACzB,MAAM,SAAS,GAAG,aAAa,CAAC,IAAI,CAAC,KAAK,IAAI,QAAQ,CAAC,CAAC;IACxD,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,SAAS,CAAC,CAAC;IACtF,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5E,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,MAAM,CAAC,IAAI,IAAI,GAAG,CAAC;IAC7C,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC,GAAW,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;IAEjG,+DAA+D;IAC/D,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAChB,MAAM,QAAQ,GAAG,EAAE,CAAC;QACpB,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACxB,MAAM,OAAO,GAAG,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;YAC5F,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC;QACjD,CAAC;QACD,OAAO;YACL,SAAS,EAAE,QAAQ,CAAC,MAAM;gBACxB,CAAC,CAAC,eAAe,CAAC,QAAQ,CAAC;gBAC3B,CAAC,CAAC,qEAAqE;SAC1E,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAgB,IAAI,CAAC,QAAQ,IAAI,WAAW,CAAC;IAC3D,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAEnE,2EAA2E;IAC3E,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,CAAC,GAAG,EAAE,CAAC;QAC3B,MAAM,CACJ,iHAAiH,CAClH,CAAC;QACF,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,YAAY,CAAC,QAAQ,CAAC,CAAC;IACnD,MAAM,QAAQ,GACZ,IAAI,CAAC,QAAQ;QACb,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE;YAClB,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,qBAAqB,CAAC,CAAC;YAClD,MAAM,MAAM,GAAG,KAAK,CAAC,aAAa,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,GAAa,EAAE,CAAC,CAAC;YAC/E,OAAO,KAAK,CAAC,cAAc,CAAC,QAAQ,EAAE;gBACpC,MAAM;gBACN,KAAK;gBACL,QAAQ;gBACR,WAAW,EAAE,kBAAkB;gBAC/B,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,KAAK;aACN,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IAEL,oFAAoF;IACpF,6EAA6E;IAC7E,qFAAqF;IACrF,qFAAqF;IACrF,gFAAgF;IAChF,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,IAAI,kBAAkB,CAAC;IAC3D,MAAM,mBAAmB,GAAG,CAAC,CAAU,EAAE,CAAU,EAAU,EAAE;QAC7D,MAAM,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QAC9D,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC7C,CAAC,CAAC;IACF,MAAM,QAAQ,GACZ,OAAO,CAAC,MAAM,GAAG,WAAW;QAC1B,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,WAAW,CAAC;QAC9D,CAAC,CAAC,OAAO,CAAC;IACd,IAAI,OAAO,CAAC,MAAM,GAAG,WAAW,EAAE,CAAC;QACjC,MAAM,CACJ,6BAA6B,WAAW,cAAc,OAAO,CAAC,MAAM,0DAA0D,CAC/H,CAAC;IACJ,CAAC;IAED,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAC1C,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;YAChC,MAAM,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAC;YAC9C,IAAI,CAAC,EAAE,CAAC;gBACN,CAAC,CAAC,MAAM,GAAG;oBACT,aAAa,EAAE,CAAC,CAAC,aAAa;oBAC9B,QAAQ,EAAE,CAAC,CAAC,QAAQ;oBACpB,SAAS,EAAE,iBAAiB,CAAC,CAAC,CAAC,SAAS,CAAC;iBAC1C,CAAC;YACJ,CAAC;QACH,CAAC;QACD,wEAAwE;QACxE,MAAM,CAAC,QAAQ,GAAG,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;YACnD,MAAM,EAAE,GAAG,CAAC,CAAC,MAAM,EAAE,aAAa,IAAI,CAAC,CAAC,CAAC;YACzC,MAAM,EAAE,GAAG,CAAC,CAAC,MAAM,EAAE,aAAa,IAAI,CAAC,CAAC,CAAC;YACzC,IAAI,EAAE,KAAK,EAAE;gBAAE,OAAO,EAAE,GAAG,EAAE,CAAC;YAC9B,OAAO,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAC/B,CAAC,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,MAAM,CAAC,yBAAyB,GAAG,uCAAuC,CAAC,CAAC;IAC9E,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC","sourcesContent":["/**\n * `qscan --triage` glue (BYOK plane). Runs the LLM triage pass over a scan\n * result and attaches an exposure annotation to each finding, then re-sorts by\n * exposure. It NEVER drops a finding and NEVER touches the exit code — the CLI\n * computes the exit code from raw severities before this ever runs.\n *\n * `@quantakrypto/agent` (the only networked package) is loaded via dynamic\n * `import()` so a plain scan never pulls in the network client.\n */\nimport { readFile as fsReadFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport process from \"node:process\";\n\nimport {\n buildContext,\n compareFindings,\n fingerprintFinding,\n renderPreflight,\n severityRank,\n} from \"@quantakrypto/core\";\nimport type {\n ContextLevel,\n Finding,\n ScanResult,\n Severity,\n TriageVerdict,\n} from \"@quantakrypto/core\";\n\nimport type { LlmProvider } from \"./args.js\";\n\nconst SEVERITY_RANK: Record<Severity, number> = {\n critical: 0,\n high: 1,\n medium: 2,\n low: 3,\n info: 4,\n};\n\n/** Default cap on findings sent to the LLM per triage run (spend/DoS guard). */\nconst DEFAULT_MAX_TRIAGE = 100;\n\n/** The model's `rationale` is untrusted text that lands in JSON/SARIF output.\n * Strip control characters and clamp length so a prompt-injected rationale can't\n * smuggle escape sequences or unbounded content into a downstream consumer. */\nfunction sanitizeRationale(s: string): string {\n // eslint-disable-next-line no-control-regex\n const clean = s.replace(/[\\u0000-\\u001f\\u007f]+/g, \" \").trim();\n return clean.length > 500 ? `${clean.slice(0, 497)}…` : clean;\n}\n\n/** Injectable triage function (default wraps `@quantakrypto/agent`). */\nexport type TriageFn = (findings: readonly Finding[]) => Promise<Map<string, TriageVerdict>>;\n\ninterface RunTriageOptions {\n level: ContextLevel;\n floor?: Severity;\n dryRun?: boolean;\n provider?: LlmProvider;\n model?: string;\n cacheFile?: string;\n /** Cap on findings sent to the LLM (spend/DoS guard; default DEFAULT_MAX_TRIAGE). */\n maxFindings?: number;\n /** Base directory for resolving finding file paths (defaults to result.root). */\n root?: string;\n // --- injectables for testing ---\n triageFn?: TriageFn;\n resolveKey?: () => string | undefined;\n readFile?: (relPath: string) => Promise<string>;\n stderr?: (s: string) => void;\n}\n\ninterface RunTriageResult {\n /** When `--dry-run`, the preflight text to show instead of a normal report. */\n preflight?: string;\n}\n\nfunction envKey(provider: LlmProvider): string | undefined {\n return (\n process.env.QK_LLM_API_KEY ??\n (provider === \"anthropic\" ? process.env.ANTHROPIC_API_KEY : process.env.OPENAI_API_KEY)\n );\n}\n\nfunction defaultModel(provider: LlmProvider): string {\n return provider === \"anthropic\" ? \"claude-sonnet-5\" : \"gpt-4o-mini\";\n}\n\n/**\n * Annotate `result.findings` with triage verdicts (mutating `result`). Returns a\n * preflight string when `--dry-run` is set (no provider is contacted). Failures\n * degrade gracefully: the scan/report proceed without triage.\n */\nexport async function runTriage(\n result: ScanResult,\n opts: RunTriageOptions,\n): Promise<RunTriageResult> {\n const level = opts.level;\n const floorRank = SEVERITY_RANK[opts.floor ?? \"medium\"];\n const targets = result.findings.filter((f) => SEVERITY_RANK[f.severity] <= floorRank);\n const stderr = opts.stderr ?? ((s: string) => void process.stderr.write(s));\n const root = opts.root ?? result.root ?? \".\";\n const readFile = opts.readFile ?? ((rel: string) => fsReadFile(path.resolve(root, rel), \"utf8\"));\n\n // --dry-run: show exactly what would be sent; contact nothing.\n if (opts.dryRun) {\n const contexts = [];\n for (const f of targets) {\n const content = level === \"metadata\" ? \"\" : await readFile(f.location.file).catch(() => \"\");\n contexts.push(buildContext(f, level, content));\n }\n return {\n preflight: contexts.length\n ? renderPreflight(contexts)\n : \"qscan --triage --dry-run: no findings at or above the triage floor.\",\n };\n }\n\n const provider: LlmProvider = opts.provider ?? \"anthropic\";\n const key = opts.resolveKey ? opts.resolveKey() : envKey(provider);\n\n // No key and no injected triage function → graceful degrade (never fatal).\n if (!opts.triageFn && !key) {\n stderr(\n \"qscan: --triage needs an API key (set QK_LLM_API_KEY, ANTHROPIC_API_KEY, or OPENAI_API_KEY). Skipping triage.\\n\",\n );\n return {};\n }\n\n const model = opts.model ?? defaultModel(provider);\n const triageFn: TriageFn =\n opts.triageFn ??\n (async (findings) => {\n const agent = await import(\"@quantakrypto/agent\");\n const client = agent.resolveClient({ provider, model, apiKey: key as string });\n return agent.triageFindings(findings, {\n client,\n level,\n readFile,\n fingerprint: fingerprintFinding,\n floor: opts.floor,\n cacheFile: opts.cacheFile,\n model,\n });\n });\n\n // Spend/DoS guard: triage only up to maxFindings, and when capping, pick the TOP by\n // SEVERITY (most severe first) — not by file-path order — so a critical in a\n // late-sorting file isn't silently dropped from triage and sunk to the bottom of the\n // report. Under the cap, triage exactly the `targets` (findings at/above the floor),\n // not the full result set. Ties fall back to the stable file/line/ruleId order.\n const maxFindings = opts.maxFindings ?? DEFAULT_MAX_TRIAGE;\n const bySeverityThenOrder = (a: Finding, b: Finding): number => {\n const d = severityRank(a.severity) - severityRank(b.severity);\n return d !== 0 ? d : compareFindings(a, b);\n };\n const toTriage =\n targets.length > maxFindings\n ? [...targets].sort(bySeverityThenOrder).slice(0, maxFindings)\n : targets;\n if (targets.length > maxFindings) {\n stderr(\n `qscan: --triage capped at ${maxFindings} findings (${targets.length} at/above floor); raise --max-findings to triage more.\\n`,\n );\n }\n\n try {\n const verdicts = await triageFn(toTriage);\n for (const f of result.findings) {\n const v = verdicts.get(fingerprintFinding(f));\n if (v) {\n f.triage = {\n exposureScore: v.exposureScore,\n priority: v.priority,\n rationale: sanitizeRationale(v.rationale),\n };\n }\n }\n // Re-sort by exposure (desc), falling back to the stable finding order.\n result.findings = [...result.findings].sort((a, b) => {\n const ea = a.triage?.exposureScore ?? -1;\n const eb = b.triage?.exposureScore ?? -1;\n if (eb !== ea) return eb - ea;\n return compareFindings(a, b);\n });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n stderr(`qscan: triage failed (${msg}); showing findings without triage.\\n`);\n }\n return {};\n}\n"]} |
+3
-3
| { | ||
| "name": "@quantakrypto/qscan", | ||
| "version": "0.4.4", | ||
| "version": "0.5.0", | ||
| "description": "qScan — find quantum-vulnerable cryptography in any codebase (CLI). Zero runtime dependencies.", | ||
@@ -40,4 +40,4 @@ "license": "Apache-2.0", | ||
| "dependencies": { | ||
| "@quantakrypto/agent": "0.4.4", | ||
| "@quantakrypto/core": "0.4.4" | ||
| "@quantakrypto/agent": "0.5.0", | ||
| "@quantakrypto/core": "0.5.0" | ||
| }, | ||
@@ -44,0 +44,0 @@ "scripts": { |
+37
-9
@@ -17,3 +17,4 @@ # @quantakrypto/qscan | ||
| - **Multiple formats.** `human` (default), `json`, SARIF 2.1.0 for code-scanning | ||
| dashboards, and a CycloneDX 1.6 **CBOM** for compliance tooling. | ||
| dashboards, a CycloneDX 1.6 **CBOM** for compliance tooling, an ISO 27001 | ||
| A.8.24 **evidence** report, and an **OpenVEX** 0.2.0 document for VEX pipelines. | ||
| - **Fast on big repos.** Optional worker-thread parallelism (`--parallel`) and | ||
@@ -27,6 +28,11 @@ git-aware incremental scanning (`--changed`). | ||
| **PHP** (openssl / phpseclib3 / libsodium), **Elixir** (`:crypto` / X509 / JOSE), | ||
| and **C/C++** (OpenSSL, Mbed TLS, wolfSSL) source. PEM key material, SSH keys, TLS/certificate | ||
| config, and dependency manifests for **six ecosystems** — npm (plus | ||
| `yarn.lock` / `pnpm-lock.yaml`), PyPI, Cargo, Go modules, Maven, and RubyGems — | ||
| are detected in **any** file regardless of language. | ||
| **C/C++** (OpenSSL, Mbed TLS, wolfSSL), **Swift** (CryptoKit / Security), | ||
| **Objective-C** (Security `SecKey*`), and **Dart/Flutter** (pointycastle / | ||
| `cryptography`), and **smart contracts** (Solidity/Move/Cairo on-chain | ||
| signature verification) source — **14 languages**. PEM key material, SSH keys (including | ||
| SSH-CA certificates), TLS/certificate config, reverse-proxy/gRPC TLS, WebAuthn/FIDO2, | ||
| code-signing, weak signature hashes (SHA-1/MD5), DKIM, SPIFFE/SPIRE, and dependency | ||
| manifests for **seven ecosystems** — npm (plus `yarn.lock` / `pnpm-lock.yaml`), | ||
| PyPI, Cargo, Go modules, Maven, RubyGems, and NuGet — are detected in **any** | ||
| file regardless of language. | ||
@@ -61,3 +67,3 @@ qScan is **honest about coverage**: if a scan walks files but finds none in a | ||
| | --- | --- | --- | | ||
| | `--format <human\|json\|sarif\|cbom>` | Output format. | `human` | | ||
| | `--format <human\|json\|sarif\|cbom\|evidence\|vex>` | Output format. | `human` | | ||
| | `--cbom` | Alias for `--format cbom` (CycloneDX 1.6 CBOM). | — | | ||
@@ -114,3 +120,3 @@ | `-o, --output <file>` | Write the report to a file instead of stdout. | stdout | | ||
| qScan — quantum-vulnerable cryptography report | ||
| root: ./examples/vulnerable-app • files scanned: 2 • qscan v0.4.2 | ||
| root: ./examples/vulnerable-app • files scanned: 2 • qscan v0.5.0 | ||
@@ -201,4 +207,7 @@ 3 findings (2 high, 1 medium) | ||
| Emit a CycloneDX 1.6 **cryptographic bill of materials** — one | ||
| `cryptographic-asset` component per distinct (algorithm, primitive) pair, with | ||
| file:line occurrence evidence — for compliance and supply-chain tooling: | ||
| `cryptographic-asset` component per distinct (assetType, algorithm, discriminator), | ||
| with file:line occurrence evidence — for compliance and supply-chain tooling. | ||
| Findings are classified into their proper CycloneDX `assetType`: `algorithm` | ||
| (crypto usage), `certificate` (X.509), `related-crypto-material` (private/public | ||
| key material), and `protocol` (TLS): | ||
@@ -214,2 +223,21 @@ ```bash | ||
| ## VEX (OpenVEX) | ||
| Emit an **OpenVEX 0.2.0** document so the quantum-readiness posture flows into the | ||
| same supply-chain pipeline that already ingests CVE-based VEX: | ||
| ```bash | ||
| qscan . --format vex -o qscan.openvex.json | ||
| ``` | ||
| One statement per rule (a synthetic `QK-<ruleId>` vulnerability), listing every | ||
| affected `file:line` product with `status: "affected"` and the rule's remediation | ||
| as the `action_statement`. PQC findings have no CVE, so qScan mints a stable | ||
| per-rule identifier rather than claiming one. qScan never reports `not_affected` | ||
| — only an operator can attest a mitigation — so downgrading a statement is left to | ||
| you to post-process. When `--triage` is also set, each verdict (exposure score / | ||
| priority / rationale) is carried in the statement's `status_notes`. Output is | ||
| deterministic (statements sorted by vulnerability, products deduped and sorted; | ||
| the `@id` derives from the finding set). | ||
| ## Triage (opt-in, BYOK) | ||
@@ -216,0 +244,0 @@ |
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
340194
16.8%51
18.6%2634
16.14%363
8.36%20
11.11%2
100%+ Added
+ Added
- Removed
- Removed
Updated
Updated