@quantakrypto/qscan 0.4.2 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/report.d.ts CHANGED
@@ -10,7 +10,7 @@
10
10
  * Only `human` lives here; `json`/`sarif` delegate to `@quantakrypto/core` so the
11
11
  * serialized shape stays consistent across every tool in the monorepo.
12
12
  */
13
- import type { ReportOptions, ScanResult } from "@quantakrypto/core";
13
+ import type { ReportOptions, ScanResult, SecurityTier } from "@quantakrypto/core";
14
14
  /**
15
15
  * Render the JSON report (pretty-printed, no trailing newline).
16
16
  *
@@ -42,5 +42,6 @@ export declare function renderCbom(result: ScanResult): string;
42
42
  export declare function renderHuman(result: ScanResult, opts?: {
43
43
  color?: boolean;
44
44
  topN?: number;
45
+ tier?: SecurityTier;
45
46
  }): string;
46
47
  //# sourceMappingURL=report.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAYH,OAAO,KAAK,EAAW,aAAa,EAAE,UAAU,EAAY,MAAM,oBAAoB,CAAC;AAwBvF;;;;;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,CAAA;CAAO,GAC5C,MAAM,CAgGR"}
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"}
package/dist/report.js CHANGED
@@ -10,7 +10,7 @@
10
10
  * Only `human` lives here; `json`/`sarif` delegate to `@quantakrypto/core` so the
11
11
  * serialized shape stays consistent across every tool in the monorepo.
12
12
  */
13
- import { ANALYZABLE_LANGUAGES_LABEL, defaultRegistry, DEP_VULNERABLE_RULE, SEVERITY_ORDER, severityRank, toCbom, toJson, toSarif, } from "@quantakrypto/core";
13
+ import { ANALYZABLE_LANGUAGES_LABEL, defaultRegistry, DEP_VULNERABLE_RULE, formatTierGuidance, PQC_TRANSITION_NOTE, SEVERITY_ORDER, severityRank, STATEFUL_HBS_NOTE, toCbom, toJson, toSarif, } from "@quantakrypto/core";
14
14
  const PLAIN = { reset: "", bold: "", dim: "", red: "", yellow: "", green: "", cyan: "" };
15
15
  const COLOR = {
16
16
  reset: "\x1b[0m",
@@ -71,6 +71,18 @@ export function renderHuman(result, opts = {}) {
71
71
  // readiness score reflects no analyzable code — say so rather than imply safe.
72
72
  const analyzedFiles = result.analyzedFiles;
73
73
  const noAnalyzable = analyzedFiles === 0;
74
+ // Partial-coverage honesty: when the analyzable subset is only a small slice of
75
+ // what was scanned, a high score reflects that slice, not the whole tree. We
76
+ // surface a one-line caveat next to the score so the number isn't over-trusted.
77
+ // Skips the zero case (handled explicitly below) and normal repos where most
78
+ // files are analyzable.
79
+ const lowCoverage = analyzedFiles !== undefined &&
80
+ analyzedFiles > 0 &&
81
+ filesScanned > 0 &&
82
+ analyzedFiles / filesScanned < 0.25;
83
+ const coverageCaveat = lowCoverage
84
+ ? `${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}`
85
+ : "";
74
86
  const lines = [];
75
87
  lines.push(`${c.bold}qScan — quantum-vulnerable cryptography report${c.reset}`);
76
88
  const coverage = analyzedFiles === undefined
@@ -103,6 +115,8 @@ export function renderHuman(result, opts = {}) {
103
115
  }
104
116
  lines.push(`${c.green}No quantum-vulnerable cryptography detected.${c.reset}`);
105
117
  lines.push(`${c.bold}Readiness score: ${readiness(inventory.readinessScore, c)}/100${c.reset}`);
118
+ if (coverageCaveat)
119
+ lines.push(coverageCaveat);
106
120
  lines.push("");
107
121
  lines.push(`${c.dim}Next step:${c.reset} keep scanning in CI to catch regressions.`);
108
122
  return lines.join("\n");
@@ -117,6 +131,8 @@ export function renderHuman(result, opts = {}) {
117
131
  lines.push(`${c.yellow}${inventory.hndlCount}${c.reset} exposed to harvest-now-decrypt-later (HNDL).`);
118
132
  }
119
133
  lines.push(`${c.bold}Readiness score: ${readiness(inventory.readinessScore, c)}/100${c.reset}`);
134
+ if (coverageCaveat)
135
+ lines.push(coverageCaveat);
120
136
  lines.push("");
121
137
  // Top findings, sorted by severity then file/line for determinism.
122
138
  const top = [...findings].sort(compareFindings).slice(0, topN);
@@ -134,6 +150,22 @@ export function renderHuman(result, opts = {}) {
134
150
  }
135
151
  lines.push("");
136
152
  lines.push(`${c.dim}Next step:${c.reset} ${nextStep(findings)}`);
153
+ // CNSA security-tier migration targets (`--tier`), surfacing remediationForTier.
154
+ if (opts.tier) {
155
+ lines.push("");
156
+ const g = formatTierGuidance(inventory.byAlgorithm, opts.tier);
157
+ lines.push(`${c.bold}${g[0]}${c.reset}`);
158
+ for (const t of g.slice(1))
159
+ lines.push(`${c.cyan}${t}${c.reset}`);
160
+ }
161
+ // Forward-looking standards + the IR 8547 migration deadline (HQC / FN-DSA /
162
+ // X-Wing) — the long-horizon guidance behind anything flagged above.
163
+ lines.push("");
164
+ lines.push(`${c.bold}Standards & timeline${c.reset}`);
165
+ lines.push(`${c.dim}${PQC_TRANSITION_NOTE}${c.reset}`);
166
+ if (findings.some((f) => f.category === "signature")) {
167
+ lines.push(`${c.dim}${STATEFUL_HBS_NOTE}${c.reset}`);
168
+ }
137
169
  return lines.join("\n");
138
170
  }
139
171
  /** Suggest a single concrete next action based on the worst finding. */
@@ -1 +1 @@
1
- {"version":3,"file":"report.js","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"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"}
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"]}
@@ -1,5 +1,7 @@
1
1
  import type { ContextLevel, Finding, ScanResult, Severity, TriageVerdict } from "@quantakrypto/core";
2
2
  import type { LlmProvider } from "./args.js";
3
+ /** Default cap on findings sent to the LLM per triage run (spend/DoS guard). */
4
+ export declare const DEFAULT_MAX_TRIAGE = 100;
3
5
  /** Injectable triage function (default wraps `@quantakrypto/agent`). */
4
6
  export type TriageFn = (findings: readonly Finding[]) => Promise<Map<string, TriageVerdict>>;
5
7
  export interface RunTriageOptions {
@@ -9,6 +11,8 @@ export interface RunTriageOptions {
9
11
  provider?: LlmProvider;
10
12
  model?: string;
11
13
  cacheFile?: string;
14
+ /** Cap on findings sent to the LLM (spend/DoS guard; default DEFAULT_MAX_TRIAGE). */
15
+ maxFindings?: number;
12
16
  /** Base directory for resolving finding file paths (defaults to result.root). */
13
17
  root?: string;
14
18
  triageFn?: TriageFn;
@@ -1 +1 @@
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,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,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,CAsE1B"}
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"}
@@ -18,6 +18,16 @@ const SEVERITY_RANK = {
18
18
  low: 3,
19
19
  info: 4,
20
20
  };
21
+ /** Default cap on findings sent to the LLM per triage run (spend/DoS guard). */
22
+ export const DEFAULT_MAX_TRIAGE = 100;
23
+ /** The model's `rationale` is untrusted text that lands in JSON/SARIF output.
24
+ * Strip control characters and clamp length so a prompt-injected rationale can't
25
+ * smuggle escape sequences or unbounded content into a downstream consumer. */
26
+ function sanitizeRationale(s) {
27
+ // eslint-disable-next-line no-control-regex
28
+ const clean = s.replace(/[\u0000-\u001f\u007f]+/g, " ").trim();
29
+ return clean.length > 500 ? `${clean.slice(0, 497)}…` : clean;
30
+ }
21
31
  function envKey(provider) {
22
32
  return (process.env.QK_LLM_API_KEY ??
23
33
  (provider === "anthropic" ? process.env.ANTHROPIC_API_KEY : process.env.OPENAI_API_KEY));
@@ -72,12 +82,25 @@ export async function runTriage(result, opts) {
72
82
  model,
73
83
  });
74
84
  });
85
+ // Spend/DoS guard: triage only up to maxFindings (top by severity among those
86
+ // at/above the floor). The rest keep their deterministic order and no annotation.
87
+ const maxFindings = opts.maxFindings ?? DEFAULT_MAX_TRIAGE;
88
+ const toTriage = targets.length > maxFindings
89
+ ? [...targets].sort(compareFindings).slice(0, maxFindings)
90
+ : result.findings;
91
+ if (targets.length > maxFindings) {
92
+ stderr(`qscan: --triage capped at ${maxFindings} findings (${targets.length} at/above floor); raise --max-findings to triage more.\n`);
93
+ }
75
94
  try {
76
- const verdicts = await triageFn(result.findings);
95
+ const verdicts = await triageFn(toTriage);
77
96
  for (const f of result.findings) {
78
97
  const v = verdicts.get(fingerprintFinding(f));
79
98
  if (v) {
80
- f.triage = { exposureScore: v.exposureScore, priority: v.priority, rationale: v.rationale };
99
+ f.triage = {
100
+ exposureScore: v.exposureScore,
101
+ priority: v.priority,
102
+ rationale: sanitizeRationale(v.rationale),
103
+ };
81
104
  }
82
105
  }
83
106
  // Re-sort by exposure (desc), falling back to the stable finding order.
@@ -1 +1 @@
1
- {"version":3,"file":"triage-run.js","sourceRoot":"","sources":["../src/triage-run.ts"],"names":[],"mappings":"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"}
1
+ {"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,GAChB,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,CAAC,MAAM,kBAAkB,GAAG,GAAG,CAAC;AAEtC;;+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,8EAA8E;IAC9E,kFAAkF;IAClF,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,IAAI,kBAAkB,CAAC;IAC3D,MAAM,QAAQ,GACZ,OAAO,CAAC,MAAM,GAAG,WAAW;QAC1B,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,WAAW,CAAC;QAC1D,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC;IACtB,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} 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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quantakrypto/qscan",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "description": "qScan — find quantum-vulnerable cryptography in any codebase (CLI). Zero runtime dependencies.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Dandelion Labs <hello@dandelionlabs.io> (https://dandelionlabs.io)",
@@ -37,8 +37,8 @@
37
37
  "access": "public"
38
38
  },
39
39
  "dependencies": {
40
- "@quantakrypto/agent": "0.4.2",
41
- "@quantakrypto/core": "0.4.2"
40
+ "@quantakrypto/agent": "0.4.4",
41
+ "@quantakrypto/core": "0.4.4"
42
42
  },
43
43
  "scripts": {
44
44
  "build": "tsc -b",