@quantakrypto/qscan 0.4.4 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -9
- package/dist/args.d.ts +69 -9
- package/dist/args.d.ts.map +1 -1
- package/dist/args.js +123 -5
- package/dist/args.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +98 -10
- package/dist/cli.js.map +1 -1
- package/dist/color.d.ts +38 -0
- package/dist/color.d.ts.map +1 -0
- package/dist/color.js +44 -0
- package/dist/color.js.map +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +41 -1
- package/dist/config.js.map +1 -1
- package/dist/help.d.ts +1 -1
- package/dist/help.d.ts.map +1 -1
- package/dist/help.js +50 -2
- package/dist/help.js.map +1 -1
- package/dist/index.d.ts +60 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +178 -23
- package/dist/index.js.map +1 -1
- package/dist/remediate-cli.d.ts +3 -4
- package/dist/remediate-cli.d.ts.map +1 -1
- package/dist/remediate-cli.js +1 -1
- package/dist/remediate-cli.js.map +1 -1
- package/dist/report.d.ts +14 -2
- package/dist/report.d.ts.map +1 -1
- package/dist/report.js +61 -6
- package/dist/report.js.map +1 -1
- package/dist/sign.d.ts +4 -0
- package/dist/sign.d.ts.map +1 -0
- package/dist/sign.js +61 -0
- package/dist/sign.js.map +1 -0
- package/dist/triage-run.d.ts +3 -4
- package/dist/triage-run.d.ts.map +1 -1
- package/dist/triage-run.js +13 -6
- package/dist/triage-run.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -9,13 +9,13 @@
|
|
|
9
9
|
* The module also re-exports the argument-parsing and baseline helpers so
|
|
10
10
|
* downstream tools can reuse them without reaching into internal paths.
|
|
11
11
|
*/
|
|
12
|
-
import type { Baseline, CryptoPolicy, Finding, ParallelScanOptions, ScanResult, SecurityTier } from "@quantakrypto/core";
|
|
12
|
+
import type { Baseline, CryptoPolicy, CycloneDxBom, Finding, HndlReport, ManifestValidation, ParallelScanOptions, ScanResult, SecurityTier } from "@quantakrypto/core";
|
|
13
13
|
import type { QscanOptions } from "./args.js";
|
|
14
14
|
export type { QscanOptions, ParsedArgs, ParsedRun, QscanFormat } from "./args.js";
|
|
15
15
|
export type { Baseline } from "./baseline.js";
|
|
16
16
|
export { ArgError, asFormat, asInt, asSeverity, defaultOptions, meetsThreshold, parseArgs, severityRank, SEVERITY_ORDER, } from "./args.js";
|
|
17
17
|
export { applyBaseline, baselineFromFindings, BASELINE_VERSION, buildBaseline, fingerprint, fingerprintFinding, loadBaseline, readBaseline, saveBaseline, writeBaseline, } from "./baseline.js";
|
|
18
|
-
export { renderCbom, renderHuman, renderJson, renderSarif } from "./report.js";
|
|
18
|
+
export { renderCbom, renderHuman, renderJson, renderSarif, renderVex } from "./report.js";
|
|
19
19
|
export { HELP_TEXT, versionLine } from "./help.js";
|
|
20
20
|
export { runRemediate, parseRemediateArgs, unifiedDiff, REMEDIATE_HELP, REMEDIATE_EXIT, } from "./remediate-cli.js";
|
|
21
21
|
export type { RemediateMode, RemediateOptions, RemediateRun, RemediateHooks, } from "./remediate-cli.js";
|
|
@@ -99,6 +99,58 @@ export interface RunQscanHooks {
|
|
|
99
99
|
export declare function runQscan(opts: Partial<QscanOptions> & {
|
|
100
100
|
path: string;
|
|
101
101
|
}, hooks?: RunQscanHooks): Promise<QscanRun>;
|
|
102
|
+
/** Outcome of {@link runHndlInit}: the scaffold plus where it should be written. */
|
|
103
|
+
export interface HndlInitResult {
|
|
104
|
+
/** Absolute path the `hndl.yml` should be written to. */
|
|
105
|
+
path: string;
|
|
106
|
+
/** True when a file already exists there (the CLI refuses to overwrite). */
|
|
107
|
+
exists: boolean;
|
|
108
|
+
/** The generated `hndl.yml` content. */
|
|
109
|
+
content: string;
|
|
110
|
+
/** How many findings the seeding scan produced. */
|
|
111
|
+
findingsScanned: number;
|
|
112
|
+
/** How many distinct data-adjacent (config-scope, HNDL) findings seeded stubs. */
|
|
113
|
+
seededFindings: number;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Scaffold an `hndl.yml` for a repo (`qscan hndl init`). Runs a scan to seed the
|
|
117
|
+
* template with detected data-adjacent findings, then returns the generated
|
|
118
|
+
* content and its target path WITHOUT writing it - the CLI owns file I/O and the
|
|
119
|
+
* refuse-to-overwrite decision.
|
|
120
|
+
*/
|
|
121
|
+
export declare function runHndlInit(opts: Partial<QscanOptions> & {
|
|
122
|
+
path: string;
|
|
123
|
+
}, hooks?: RunQscanHooks): Promise<HndlInitResult>;
|
|
124
|
+
/** Outcome of {@link runCryptoAgilityEmit}: the rendered manifest and the scan. */
|
|
125
|
+
export interface CryptoAgilityEmitResult {
|
|
126
|
+
/** The scan result the manifest was derived from. */
|
|
127
|
+
result: ScanResult;
|
|
128
|
+
/** The pretty-printed crypto-agility manifest JSON (no trailing newline). */
|
|
129
|
+
manifest: string;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Emit a crypto-agility manifest for a repo (`qscan crypto-agility emit` /
|
|
133
|
+
* `--crypto-agility`). Runs a scan and derives the manifest from its inventory +
|
|
134
|
+
* CBOM. This is deliberately additive: it NEVER consults the severity threshold and
|
|
135
|
+
* the CLI always exits 0 (publishing a posture manifest must not fail CI). The
|
|
136
|
+
* generation timestamp is stamped here (the CLI runtime), keeping the core builder
|
|
137
|
+
* pure. A `--policy` file overlays its `transitionDeadline`; `--attestation` records
|
|
138
|
+
* a credential URL verbatim (never fetched); `--hybrid-kex` / `--no-hybrid-kex`
|
|
139
|
+
* assert hybrid-KEX use.
|
|
140
|
+
*/
|
|
141
|
+
export declare function runCryptoAgilityEmit(opts: Partial<QscanOptions> & {
|
|
142
|
+
path: string;
|
|
143
|
+
}, hooks?: RunQscanHooks): Promise<CryptoAgilityEmitResult>;
|
|
144
|
+
/**
|
|
145
|
+
* Validate a LOCAL crypto-agility manifest file against the schema
|
|
146
|
+
* (`qscan crypto-agility validate <file>`). Reads and parses the file, then defers
|
|
147
|
+
* to core's {@link validateCryptoAgilityManifest}. Strictly offline: it never
|
|
148
|
+
* fetches a URL (the attestation link and any remote manifest are a website-side
|
|
149
|
+
* concern). Read / parse failures propagate to the CLI as an I/O error (exit 2); a
|
|
150
|
+
* successfully-parsed but non-conforming manifest returns `{ valid: false }` and the
|
|
151
|
+
* CLI exits non-zero.
|
|
152
|
+
*/
|
|
153
|
+
export declare function runCryptoAgilityValidate(file: string): Promise<ManifestValidation>;
|
|
102
154
|
/** Rendering controls for {@link renderReport}. */
|
|
103
155
|
export interface RenderReportOptions {
|
|
104
156
|
/** Emit raw ANSI color in the human report. Default: false. */
|
|
@@ -109,8 +161,14 @@ export interface RenderReportOptions {
|
|
|
109
161
|
topN?: number;
|
|
110
162
|
/** CNSA security tier for the migration-targets footer (`--tier`). */
|
|
111
163
|
tier?: SecurityTier;
|
|
164
|
+
/** Standards regime for the migration-targets footer (`--profile`). */
|
|
165
|
+
profile?: string;
|
|
112
166
|
/** Org cryptography policy for the evidence report's §4 verdicts (`--policy`). */
|
|
113
167
|
policy?: CryptoPolicy;
|
|
168
|
+
/** External CBOMs to merge into the `cbom` output (CycloneDX bom-link). */
|
|
169
|
+
mergeCboms?: CycloneDxBom[];
|
|
170
|
+
/** HNDL exposure analysis (`--hndl`); annotates JSON/SARIF/human output. */
|
|
171
|
+
hndl?: HndlReport;
|
|
114
172
|
}
|
|
115
173
|
/** Render a scan result in the requested format. */
|
|
116
174
|
export declare function renderReport(result: ScanResult, format: QscanOptions["format"], opts?: RenderReportOptions | boolean): string;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -9,15 +9,17 @@
|
|
|
9
9
|
* The module also re-exports the argument-parsing and baseline helpers so
|
|
10
10
|
* downstream tools can reuse them without reaching into internal paths.
|
|
11
11
|
*/
|
|
12
|
-
import { readFile } from "node:fs/promises";
|
|
12
|
+
import { readFile, stat } from "node:fs/promises";
|
|
13
|
+
import * as nodePath from "node:path";
|
|
13
14
|
import process from "node:process";
|
|
14
|
-
import { buildReadinessReport, changedFiles, parseCryptoPolicy, scan, scanParallel, } from "@quantakrypto/core";
|
|
15
|
+
import { buildCryptoAgilityManifest, buildReadinessReport, changedFiles, computeHndl, findingScope, HNDL_FILENAME, loadHndlMap, parseCryptoPolicy, scaffoldHndlYaml, scan, scanParallel, signReadinessReport, validateCryptoAgilityManifest, } from "@quantakrypto/core";
|
|
16
|
+
import { commandSigner } from "./sign.js";
|
|
15
17
|
import { applyBaseline, readBaseline, saveBaseline } from "./baseline.js";
|
|
16
18
|
import { defaultOptions, meetsThreshold } from "./args.js";
|
|
17
|
-
import { renderCbom, renderHuman, renderJson, renderSarif } from "./report.js";
|
|
19
|
+
import { renderCbom, renderHuman, renderJson, renderSarif, renderVex } from "./report.js";
|
|
18
20
|
export { ArgError, asFormat, asInt, asSeverity, defaultOptions, meetsThreshold, parseArgs, severityRank, SEVERITY_ORDER, } from "./args.js";
|
|
19
21
|
export { applyBaseline, baselineFromFindings, BASELINE_VERSION, buildBaseline, fingerprint, fingerprintFinding, loadBaseline, readBaseline, saveBaseline, writeBaseline, } from "./baseline.js";
|
|
20
|
-
export { renderCbom, renderHuman, renderJson, renderSarif } from "./report.js";
|
|
22
|
+
export { renderCbom, renderHuman, renderJson, renderSarif, renderVex } from "./report.js";
|
|
21
23
|
export { HELP_TEXT, versionLine } from "./help.js";
|
|
22
24
|
export { runRemediate, parseRemediateArgs, unifiedDiff, REMEDIATE_HELP, REMEDIATE_EXIT, } from "./remediate-cli.js";
|
|
23
25
|
export { applyConfig, resolveConfig } from "./config.js";
|
|
@@ -154,41 +156,194 @@ export async function runQscan(opts, hooks = {}) {
|
|
|
154
156
|
return { result, suppressed, report: triaged.preflight, exitCode: EXIT.OK };
|
|
155
157
|
}
|
|
156
158
|
}
|
|
159
|
+
// `--merge` only has an effect on a `--cbom` output. If the user asked to merge but
|
|
160
|
+
// the format is not cbom, that is almost certainly a mistake (a typo'd `--cbom`, or a
|
|
161
|
+
// pipeline that forgot it) — the merge files would be silently ignored and the
|
|
162
|
+
// combined bill of materials never produced. Fail loudly instead of dropping data.
|
|
163
|
+
if (options.mergeCboms && options.mergeCboms.length > 0 && options.format !== "cbom") {
|
|
164
|
+
throw new Error(`--merge requires --format cbom (got ${options.format ?? "the human report"})`);
|
|
165
|
+
}
|
|
166
|
+
// `--sign` / `--timestamp` fill the evidence attestation, so they only make sense
|
|
167
|
+
// with `--format evidence`. Fail loudly rather than silently ignore the signer.
|
|
168
|
+
if ((options.sign || options.timestamp) && options.format !== "evidence") {
|
|
169
|
+
throw new Error(`--sign/--timestamp require --format evidence (got ${options.format ?? "the human report"})`);
|
|
170
|
+
}
|
|
171
|
+
// HNDL exposure (`--hndl`): read the declared data map, score every finding
|
|
172
|
+
// and build the repo summary. Computed AFTER the exit code so it can never
|
|
173
|
+
// change CI pass/fail - it only annotates + ranks. A missing / malformed
|
|
174
|
+
// hndl.yml fails loudly (the user opted in), surfaced by the CLI as exit 2.
|
|
175
|
+
let hndl;
|
|
176
|
+
if (options.hndl) {
|
|
177
|
+
const { map } = await loadHndlMap(options.path);
|
|
178
|
+
hndl = computeHndl(result.findings, map);
|
|
179
|
+
}
|
|
180
|
+
const signer = options.sign ? commandSigner(options.sign) : undefined;
|
|
181
|
+
const timestamper = options.timestamp
|
|
182
|
+
? commandSigner(options.timestamp)
|
|
183
|
+
: undefined;
|
|
184
|
+
// Load any external CBOMs to merge into a `--cbom` output (combined
|
|
185
|
+
// code + infrastructure bill of materials). Only relevant for the cbom format.
|
|
186
|
+
let mergeCbomsData;
|
|
187
|
+
if (options.format === "cbom" && options.mergeCboms && options.mergeCboms.length > 0) {
|
|
188
|
+
mergeCbomsData = [];
|
|
189
|
+
for (const path of options.mergeCboms) {
|
|
190
|
+
let text;
|
|
191
|
+
try {
|
|
192
|
+
text = await readFile(path, "utf8");
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
throw new Error(`--merge: cannot read CBOM file "${path}"`);
|
|
196
|
+
}
|
|
197
|
+
let parsed;
|
|
198
|
+
try {
|
|
199
|
+
parsed = JSON.parse(text);
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
throw new Error(`--merge: "${path}" is not valid JSON`);
|
|
203
|
+
}
|
|
204
|
+
const bom = parsed;
|
|
205
|
+
if (bom?.bomFormat !== "CycloneDX") {
|
|
206
|
+
throw new Error(`--merge: "${path}" is not a CycloneDX CBOM (missing bomFormat)`);
|
|
207
|
+
}
|
|
208
|
+
mergeCbomsData.push(bom);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
let report = renderReport(result, options.format, {
|
|
212
|
+
color: hooks.color ?? false,
|
|
213
|
+
redactSnippets: options.noSnippets,
|
|
214
|
+
topN: options.topN,
|
|
215
|
+
tier: options.tier,
|
|
216
|
+
...(options.profile ? { profile: options.profile } : {}),
|
|
217
|
+
...(policy ? { policy } : {}),
|
|
218
|
+
...(mergeCbomsData ? { mergeCboms: mergeCbomsData } : {}),
|
|
219
|
+
...(hndl ? { hndl } : {}),
|
|
220
|
+
});
|
|
221
|
+
// Evidence signing is orchestrated here (async: an external signer may be async),
|
|
222
|
+
// after the synchronous renderer has produced the unsigned report (ADR-0004: the
|
|
223
|
+
// tool orchestrates a signer, it does not implement crypto).
|
|
224
|
+
if (options.format === "evidence" && (signer || timestamper)) {
|
|
225
|
+
const signed = await signReadinessReport(JSON.parse(report), {
|
|
226
|
+
signer,
|
|
227
|
+
timestamper,
|
|
228
|
+
});
|
|
229
|
+
report = JSON.stringify(signed, null, 2);
|
|
230
|
+
}
|
|
231
|
+
return { result, suppressed, report, exitCode };
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Scaffold an `hndl.yml` for a repo (`qscan hndl init`). Runs a scan to seed the
|
|
235
|
+
* template with detected data-adjacent findings, then returns the generated
|
|
236
|
+
* content and its target path WITHOUT writing it - the CLI owns file I/O and the
|
|
237
|
+
* refuse-to-overwrite decision.
|
|
238
|
+
*/
|
|
239
|
+
export async function runHndlInit(opts, hooks = {}) {
|
|
240
|
+
const options = { ...defaultOptions(), ...opts };
|
|
241
|
+
const scanFn = hooks.scanFn ?? (options.parallel ? scanParallel : scan);
|
|
242
|
+
const result = await scanFn(toScanOptions(options));
|
|
243
|
+
const content = scaffoldHndlYaml(result.findings);
|
|
244
|
+
const target = resolveHndlTarget(options.path);
|
|
245
|
+
let exists = false;
|
|
246
|
+
try {
|
|
247
|
+
await stat(target);
|
|
248
|
+
exists = true;
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
exists = false;
|
|
252
|
+
}
|
|
253
|
+
const seededFindings = result.findings.filter((f) => f.hndl && findingScope(f) === "config").length;
|
|
157
254
|
return {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
topN: options.topN,
|
|
164
|
-
tier: options.tier,
|
|
165
|
-
...(policy ? { policy } : {}),
|
|
166
|
-
}),
|
|
167
|
-
exitCode,
|
|
255
|
+
path: target,
|
|
256
|
+
exists,
|
|
257
|
+
content,
|
|
258
|
+
findingsScanned: result.findings.length,
|
|
259
|
+
seededFindings,
|
|
168
260
|
};
|
|
169
261
|
}
|
|
262
|
+
/** Resolve where `hndl init` writes: `<dir>/hndl.yml`, or an explicit `*.yml`. */
|
|
263
|
+
function resolveHndlTarget(root) {
|
|
264
|
+
const base = nodePath.basename(root);
|
|
265
|
+
if (base === HNDL_FILENAME || base.endsWith(".yml") || base.endsWith(".yaml")) {
|
|
266
|
+
return nodePath.resolve(root);
|
|
267
|
+
}
|
|
268
|
+
return nodePath.resolve(root, HNDL_FILENAME);
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Emit a crypto-agility manifest for a repo (`qscan crypto-agility emit` /
|
|
272
|
+
* `--crypto-agility`). Runs a scan and derives the manifest from its inventory +
|
|
273
|
+
* CBOM. This is deliberately additive: it NEVER consults the severity threshold and
|
|
274
|
+
* the CLI always exits 0 (publishing a posture manifest must not fail CI). The
|
|
275
|
+
* generation timestamp is stamped here (the CLI runtime), keeping the core builder
|
|
276
|
+
* pure. A `--policy` file overlays its `transitionDeadline`; `--attestation` records
|
|
277
|
+
* a credential URL verbatim (never fetched); `--hybrid-kex` / `--no-hybrid-kex`
|
|
278
|
+
* assert hybrid-KEX use.
|
|
279
|
+
*/
|
|
280
|
+
export async function runCryptoAgilityEmit(opts, hooks = {}) {
|
|
281
|
+
const options = { ...defaultOptions(), ...opts };
|
|
282
|
+
const scanFn = hooks.scanFn ?? (options.parallel ? scanParallel : scan);
|
|
283
|
+
const result = await scanFn(toScanOptions(options));
|
|
284
|
+
let policy;
|
|
285
|
+
if (options.policy) {
|
|
286
|
+
policy = parseCryptoPolicy(JSON.parse(await readFile(options.policy, "utf8")));
|
|
287
|
+
}
|
|
288
|
+
const manifest = buildCryptoAgilityManifest(result, {
|
|
289
|
+
generatedAt: new Date().toISOString(),
|
|
290
|
+
...(options.attestation ? { attestationUrl: options.attestation } : {}),
|
|
291
|
+
...(options.hybridKexInUse !== undefined ? { hybridKexInUse: options.hybridKexInUse } : {}),
|
|
292
|
+
...(policy ? { policy } : {}),
|
|
293
|
+
...(process.env.GITHUB_REPOSITORY ? { repository: process.env.GITHUB_REPOSITORY } : {}),
|
|
294
|
+
...(process.env.GITHUB_SHA ? { commit: process.env.GITHUB_SHA } : {}),
|
|
295
|
+
});
|
|
296
|
+
return { result, manifest: JSON.stringify(manifest, null, 2) };
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Validate a LOCAL crypto-agility manifest file against the schema
|
|
300
|
+
* (`qscan crypto-agility validate <file>`). Reads and parses the file, then defers
|
|
301
|
+
* to core's {@link validateCryptoAgilityManifest}. Strictly offline: it never
|
|
302
|
+
* fetches a URL (the attestation link and any remote manifest are a website-side
|
|
303
|
+
* concern). Read / parse failures propagate to the CLI as an I/O error (exit 2); a
|
|
304
|
+
* successfully-parsed but non-conforming manifest returns `{ valid: false }` and the
|
|
305
|
+
* CLI exits non-zero.
|
|
306
|
+
*/
|
|
307
|
+
export async function runCryptoAgilityValidate(file) {
|
|
308
|
+
const text = await readFile(file, "utf8");
|
|
309
|
+
let parsed;
|
|
310
|
+
try {
|
|
311
|
+
parsed = JSON.parse(text);
|
|
312
|
+
}
|
|
313
|
+
catch (err) {
|
|
314
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
315
|
+
return { valid: false, errors: [`not valid JSON: ${message}`] };
|
|
316
|
+
}
|
|
317
|
+
return validateCryptoAgilityManifest(parsed);
|
|
318
|
+
}
|
|
170
319
|
/** Render a scan result in the requested format. */
|
|
171
320
|
export function renderReport(result, format, opts = {}) {
|
|
172
321
|
// Back-compat: `renderReport(result, format, true)` used to mean "color on".
|
|
173
|
-
const { color = false, redactSnippets = false, topN = undefined, tier = undefined, policy = undefined, } = typeof opts === "boolean" ? { color: opts, policy: undefined } : opts;
|
|
322
|
+
const { color = false, redactSnippets = false, topN = undefined, tier = undefined, profile = undefined, policy = undefined, mergeCboms = undefined, hndl = undefined, } = typeof opts === "boolean" ? { color: opts, policy: undefined } : opts;
|
|
174
323
|
switch (format) {
|
|
175
324
|
case "json":
|
|
176
|
-
return renderJson(result, { redactSnippets });
|
|
325
|
+
return renderJson(result, { redactSnippets, ...(hndl ? { hndl } : {}) });
|
|
177
326
|
case "sarif":
|
|
178
|
-
return renderSarif(result, { redactSnippets });
|
|
327
|
+
return renderSarif(result, { redactSnippets, ...(hndl ? { hndl } : {}) });
|
|
179
328
|
case "cbom":
|
|
180
|
-
return renderCbom(result);
|
|
181
|
-
case "
|
|
329
|
+
return renderCbom(result, mergeCboms);
|
|
330
|
+
case "vex":
|
|
331
|
+
return renderVex(result);
|
|
332
|
+
case "evidence": {
|
|
182
333
|
// ISO A.8.24 readiness report; repo/commit come from CI env when present.
|
|
183
|
-
// A `--policy` file adds the §4 conformant/violation/transition verdicts.
|
|
184
|
-
|
|
334
|
+
// A `--policy` file adds the §4 conformant/violation/transition verdicts. The
|
|
335
|
+
// attestation is left unsigned here; signing is an async step in runQscan (an
|
|
336
|
+
// external signer may be async), so this renderer stays synchronous.
|
|
337
|
+
const report = buildReadinessReport(result, {
|
|
185
338
|
repository: process.env.GITHUB_REPOSITORY,
|
|
186
339
|
commit: process.env.GITHUB_SHA,
|
|
187
340
|
...(policy ? { policy } : {}),
|
|
188
|
-
})
|
|
341
|
+
});
|
|
342
|
+
return JSON.stringify(report, null, 2);
|
|
343
|
+
}
|
|
189
344
|
case "human":
|
|
190
345
|
default:
|
|
191
|
-
return renderHuman(result, { color, topN, tier });
|
|
346
|
+
return renderHuman(result, { color, topN, tier, profile, ...(hndl ? { hndl } : {}) });
|
|
192
347
|
}
|
|
193
348
|
}
|
|
194
349
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @quantakrypto/qscan — programmatic API.\n *\n * `runQscan` is the single entry point shared by the CLI (`src/cli.ts`) and by\n * `@quantakrypto/action`. It runs a scan via `@quantakrypto/core`, applies an optional\n * baseline, decides an exit code from the severity threshold, and (optionally)\n * renders a report. The CLI is a thin shell around it.\n *\n * The module also re-exports the argument-parsing and baseline helpers so\n * downstream tools can reuse them without reaching into internal paths.\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport process from \"node:process\";\n\nimport {\n buildReadinessReport,\n changedFiles,\n parseCryptoPolicy,\n scan,\n scanParallel,\n} from \"@quantakrypto/core\";\nimport type {\n Baseline,\n CryptoPolicy,\n Finding,\n ParallelScanOptions,\n ScanResult,\n SecurityTier,\n} from \"@quantakrypto/core\";\n\nimport { applyBaseline, readBaseline, saveBaseline } from \"./baseline.js\";\nimport { defaultOptions, meetsThreshold } from \"./args.js\";\nimport type { QscanOptions } from \"./args.js\";\nimport { renderCbom, renderHuman, renderJson, renderSarif } from \"./report.js\";\n\nexport type { QscanOptions, ParsedArgs, ParsedRun, QscanFormat } from \"./args.js\";\nexport type { Baseline } from \"./baseline.js\";\nexport {\n ArgError,\n asFormat,\n asInt,\n asSeverity,\n defaultOptions,\n meetsThreshold,\n parseArgs,\n severityRank,\n SEVERITY_ORDER,\n} from \"./args.js\";\nexport {\n applyBaseline,\n baselineFromFindings,\n BASELINE_VERSION,\n buildBaseline,\n fingerprint,\n fingerprintFinding,\n loadBaseline,\n readBaseline,\n saveBaseline,\n writeBaseline,\n} from \"./baseline.js\";\nexport { renderCbom, renderHuman, renderJson, renderSarif } from \"./report.js\";\nexport { HELP_TEXT, versionLine } from \"./help.js\";\nexport {\n runRemediate,\n parseRemediateArgs,\n unifiedDiff,\n REMEDIATE_HELP,\n REMEDIATE_EXIT,\n} from \"./remediate-cli.js\";\nexport type {\n RemediateMode,\n RemediateOptions,\n RemediateRun,\n RemediateHooks,\n} from \"./remediate-cli.js\";\nexport { applyConfig, resolveConfig } from \"./config.js\";\nexport type { ResolvedConfig } from \"./config.js\";\nexport type { ConfigurableKey } from \"./args.js\";\n\n/** Process-style exit codes qScan uses. */\nexport const EXIT = {\n /** No findings at/above threshold, or a baseline was written. */\n OK: 0,\n /** One or more findings at/above the severity threshold. */\n FINDINGS: 1,\n /** Usage error or I/O failure. */\n ERROR: 2,\n} as const;\n\n/** Outcome of {@link runQscan}. */\nexport interface QscanRun {\n /** The scan result, with the baseline already applied to `findings`. */\n result: ScanResult;\n /** Findings suppressed because their fingerprint was in the baseline. */\n suppressed: Finding[];\n /** Rendered report in the requested format (`undefined` for a baseline write). */\n report?: string;\n /** The baseline that was written, when `writeBaseline` was requested. */\n baselineWritten?: Baseline;\n /** Suggested process exit code. */\n exitCode: number;\n}\n\n/**\n * The scan implementation `runQscan` calls. Matches `@quantakrypto/core`'s `scan` /\n * `scanParallel` (parallel options are a superset of `ScanOptions`).\n * Injectable so the GitHub Action and tests can supply a custom scanner.\n */\nexport type ScanFn = (options: ParallelScanOptions) => Promise<ScanResult>;\n\n/**\n * Resolve the changed-file list for incremental scans. Injectable for testing;\n * defaults to core's git-aware {@link changedFiles}.\n */\nexport type ChangedFilesFn = (root: string, since?: string) => Promise<string[]>;\n\n/** Behavioral hooks for {@link runQscan}, mainly for testing. */\nexport interface RunQscanHooks {\n /** Emit raw ANSI color in the human report. Default: false. */\n color?: boolean;\n /** Override the scanner. Default: `scan` / `scanParallel` from `@quantakrypto/core`. */\n scanFn?: ScanFn;\n /** Override changed-file resolution. Default: `changedFiles` from `@quantakrypto/core`. */\n changedFilesFn?: ChangedFilesFn;\n /** Inject the triage function (offline testing of the `--triage` path, so the\n * exit-code invariant can be exercised without a network client or API key).\n * `import type` keeps this a compile-time-only reference — the networked agent\n * package is still only loaded via the dynamic import inside `runTriage`. */\n triageFn?: import(\"./triage-run.js\").TriageFn;\n}\n\n/**\n * Translate resolved {@link QscanOptions} into core {@link ParallelScanOptions}.\n * `files` (the incremental file list) is layered on by {@link runQscan}.\n */\nfunction toScanOptions(options: QscanOptions): ParallelScanOptions {\n const scanOptions: ParallelScanOptions = {\n root: options.path,\n source: options.source,\n dependencies: options.dependencies,\n config: options.config,\n noDefaultIgnores: options.noDefaultIgnores,\n scanMinified: options.scanMinified,\n };\n if (options.ignore.length > 0) scanOptions.exclude = options.ignore;\n if (options.include.length > 0) scanOptions.include = options.include;\n if (options.maxFileSize !== undefined) scanOptions.maxFileSize = options.maxFileSize;\n if (options.concurrency !== undefined) scanOptions.concurrency = options.concurrency;\n if (options.disabledRules && options.disabledRules.length > 0) {\n scanOptions.disabledRules = options.disabledRules;\n }\n if (options.cacheFile) scanOptions.cacheFile = options.cacheFile;\n return scanOptions;\n}\n\n/**\n * Run a complete qScan pass: scan → baseline → threshold → render.\n *\n * This never touches `process` or stdout; the CLI is responsible for printing\n * `report`/writing `output` and calling `process.exit(exitCode)`. That keeps\n * the function pure enough to unit-test and to embed in the GitHub Action.\n *\n * Behavior:\n * - The walk is configured by `include` / `ignore` / `maxFileSize` /\n * `noDefaultIgnores` / `scanMinified`.\n * - With `changed` set, only the files git reports as changed (relative to\n * `since`, if given) are scanned via `ScanOptions.files`. A non-git tree\n * yields an empty list, so nothing is scanned.\n * - With `parallel` (or `concurrency`) set, the scan is routed through core's\n * `scanParallel`, which itself falls back to the serial path for small\n * inputs.\n * - When `opts.writeBaseline` is set, the scan runs, a baseline is built from\n * *all* findings, written to disk, and `exitCode` is {@link EXIT.OK}. No\n * report is rendered.\n * - When `opts.baseline` is set, its fingerprints are loaded and matching\n * findings are moved to `suppressed` (and removed from `result.findings`).\n * - `exitCode` is {@link EXIT.FINDINGS} when any *kept* finding meets the\n * severity threshold, else {@link EXIT.OK}.\n *\n * @throws {Error} Propagates scan / baseline I/O errors; the CLI maps these to\n * {@link EXIT.ERROR}.\n */\nexport async function runQscan(\n opts: Partial<QscanOptions> & { path: string },\n hooks: RunQscanHooks = {},\n): Promise<QscanRun> {\n const options: QscanOptions = { ...defaultOptions(), ...opts };\n // Route to the parallel pool when requested; both share the ScanOptions shape.\n const scanFn: ScanFn = hooks.scanFn ?? (options.parallel ? scanParallel : scan);\n const resolveChanged: ChangedFilesFn = hooks.changedFilesFn ?? changedFiles;\n\n const scanOptions = toScanOptions(options);\n\n // Incremental mode: restrict the scan to git-changed files.\n if (options.changed) {\n scanOptions.files = await resolveChanged(options.path, options.since);\n }\n\n const result = await scanFn(scanOptions);\n\n // --write-baseline: snapshot every finding, persist, and exit cleanly.\n if (options.writeBaseline) {\n const baseline = await saveBaseline(options.writeBaseline, result.findings);\n return {\n result,\n suppressed: [],\n baselineWritten: baseline,\n exitCode: EXIT.OK,\n };\n }\n\n // --baseline: suppress previously-accepted findings.\n //\n // The explicit `--baseline <path>` is read STRICTLY via `readBaseline`: a\n // missing or malformed file is an error (surfaced by the CLI as exit 2), not\n // silently treated as an empty baseline. Using core's tolerant `loadBaseline`\n // here would let a typo'd path (`--baseline typo.json`) suppress nothing and\n // still exit 0 — a CI footgun where a broken baseline reads as \"all clear\".\n let suppressed: Finding[] = [];\n if (options.baseline) {\n const fingerprints = await readBaseline(options.baseline);\n const split = applyBaseline(result.findings, fingerprints);\n result.findings = split.kept;\n suppressed = split.suppressed;\n }\n\n // --policy: the org cryptography policy for the evidence report's §4 verdicts.\n // Parsed strictly — a malformed policy fails loudly rather than silently\n // dropping the verdicts from the attested evidence.\n let policy: CryptoPolicy | undefined;\n if (options.policy) {\n policy = parseCryptoPolicy(JSON.parse(await readFile(options.policy, \"utf8\")));\n }\n\n // Exit code is computed from RAW severities, BEFORE triage runs, so the\n // (optional) LLM triage pass can never make a failing scan pass CI.\n const exitCode = result.findings.some((f) =>\n meetsThreshold(f.severity, options.severityThreshold),\n )\n ? EXIT.FINDINGS\n : EXIT.OK;\n\n // Optional BYOK triage: annotate + re-sort findings (never suppresses). The\n // agent (networked) package is loaded only here, via dynamic import.\n if (options.triage) {\n const { runTriage } = await import(\"./triage-run.js\");\n const triaged = await runTriage(result, {\n level: options.contextLevel ?? \"snippet\",\n floor: options.triageFloor,\n maxFindings: options.maxFindings,\n dryRun: options.dryRun,\n provider: options.llmProvider,\n model: options.llmModel,\n // The triage RESPONSE cache must not share a path with the scan cache —\n // they are different on-disk formats and would clobber each other every\n // run, defeating both (audit: arch #1). Derive a sibling path.\n cacheFile: options.cacheFile ? `${options.cacheFile}.responses.json` : undefined,\n root: options.path,\n triageFn: hooks.triageFn,\n });\n if (triaged.preflight !== undefined) {\n return { result, suppressed, report: triaged.preflight, exitCode: EXIT.OK };\n }\n }\n\n return {\n result,\n suppressed,\n report: renderReport(result, options.format, {\n color: hooks.color ?? false,\n redactSnippets: options.noSnippets,\n topN: options.topN,\n tier: options.tier,\n ...(policy ? { policy } : {}),\n }),\n exitCode,\n };\n}\n\n/** Rendering controls for {@link renderReport}. */\nexport interface RenderReportOptions {\n /** Emit raw ANSI color in the human report. Default: false. */\n color?: boolean;\n /** Omit code snippets from the JSON/SARIF report (`--no-snippets`). */\n redactSnippets?: boolean;\n /** How many findings the human report lists (`--top N`). */\n topN?: number;\n /** CNSA security tier for the migration-targets footer (`--tier`). */\n tier?: SecurityTier;\n /** Org cryptography policy for the evidence report's §4 verdicts (`--policy`). */\n policy?: CryptoPolicy;\n}\n\n/** Render a scan result in the requested format. */\nexport function renderReport(\n result: ScanResult,\n format: QscanOptions[\"format\"],\n opts: RenderReportOptions | boolean = {},\n): string {\n // Back-compat: `renderReport(result, format, true)` used to mean \"color on\".\n const {\n color = false,\n redactSnippets = false,\n topN = undefined,\n tier = undefined,\n policy = undefined,\n } = typeof opts === \"boolean\" ? { color: opts, policy: undefined } : opts;\n switch (format) {\n case \"json\":\n return renderJson(result, { redactSnippets });\n case \"sarif\":\n return renderSarif(result, { redactSnippets });\n case \"cbom\":\n return renderCbom(result);\n case \"evidence\":\n // ISO A.8.24 readiness report; repo/commit come from CI env when present.\n // A `--policy` file adds the §4 conformant/violation/transition verdicts.\n return JSON.stringify(\n buildReadinessReport(result, {\n repository: process.env.GITHUB_REPOSITORY,\n commit: process.env.GITHUB_SHA,\n ...(policy ? { policy } : {}),\n }),\n null,\n 2,\n );\n case \"human\":\n default:\n return renderHuman(result, { color, topN, tier });\n }\n}\n\n/** Re-export the core result types consumers commonly need. */\nexport type { Finding, ScanResult, ScanOptions } from \"@quantakrypto/core\";\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @quantakrypto/qscan — programmatic API.\n *\n * `runQscan` is the single entry point shared by the CLI (`src/cli.ts`) and by\n * `@quantakrypto/action`. It runs a scan via `@quantakrypto/core`, applies an optional\n * baseline, decides an exit code from the severity threshold, and (optionally)\n * renders a report. The CLI is a thin shell around it.\n *\n * The module also re-exports the argument-parsing and baseline helpers so\n * downstream tools can reuse them without reaching into internal paths.\n */\n\nimport { readFile, stat } from \"node:fs/promises\";\nimport * as nodePath from \"node:path\";\nimport process from \"node:process\";\n\nimport {\n buildCryptoAgilityManifest,\n buildReadinessReport,\n changedFiles,\n computeHndl,\n findingScope,\n HNDL_FILENAME,\n loadHndlMap,\n parseCryptoPolicy,\n scaffoldHndlYaml,\n scan,\n scanParallel,\n signReadinessReport,\n validateCryptoAgilityManifest,\n} from \"@quantakrypto/core\";\nimport type {\n Baseline,\n CryptoPolicy,\n CycloneDxBom,\n EvidenceSigner,\n Finding,\n HndlReport,\n ManifestValidation,\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 // HNDL exposure (`--hndl`): read the declared data map, score every finding\n // and build the repo summary. Computed AFTER the exit code so it can never\n // change CI pass/fail - it only annotates + ranks. A missing / malformed\n // hndl.yml fails loudly (the user opted in), surfaced by the CLI as exit 2.\n let hndl: HndlReport | undefined;\n if (options.hndl) {\n const { map } = await loadHndlMap(options.path);\n hndl = computeHndl(result.findings, map);\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 ...(hndl ? { hndl } : {}),\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/** Outcome of {@link runHndlInit}: the scaffold plus where it should be written. */\nexport interface HndlInitResult {\n /** Absolute path the `hndl.yml` should be written to. */\n path: string;\n /** True when a file already exists there (the CLI refuses to overwrite). */\n exists: boolean;\n /** The generated `hndl.yml` content. */\n content: string;\n /** How many findings the seeding scan produced. */\n findingsScanned: number;\n /** How many distinct data-adjacent (config-scope, HNDL) findings seeded stubs. */\n seededFindings: number;\n}\n\n/**\n * Scaffold an `hndl.yml` for a repo (`qscan hndl init`). Runs a scan to seed the\n * template with detected data-adjacent findings, then returns the generated\n * content and its target path WITHOUT writing it - the CLI owns file I/O and the\n * refuse-to-overwrite decision.\n */\nexport async function runHndlInit(\n opts: Partial<QscanOptions> & { path: string },\n hooks: RunQscanHooks = {},\n): Promise<HndlInitResult> {\n const options: QscanOptions = { ...defaultOptions(), ...opts };\n const scanFn: ScanFn = hooks.scanFn ?? (options.parallel ? scanParallel : scan);\n const result = await scanFn(toScanOptions(options));\n\n const content = scaffoldHndlYaml(result.findings);\n const target = resolveHndlTarget(options.path);\n let exists = false;\n try {\n await stat(target);\n exists = true;\n } catch {\n exists = false;\n }\n const seededFindings = result.findings.filter(\n (f) => f.hndl && findingScope(f) === \"config\",\n ).length;\n return {\n path: target,\n exists,\n content,\n findingsScanned: result.findings.length,\n seededFindings,\n };\n}\n\n/** Resolve where `hndl init` writes: `<dir>/hndl.yml`, or an explicit `*.yml`. */\nfunction resolveHndlTarget(root: string): string {\n const base = nodePath.basename(root);\n if (base === HNDL_FILENAME || base.endsWith(\".yml\") || base.endsWith(\".yaml\")) {\n return nodePath.resolve(root);\n }\n return nodePath.resolve(root, HNDL_FILENAME);\n}\n\n/** Outcome of {@link runCryptoAgilityEmit}: the rendered manifest and the scan. */\nexport interface CryptoAgilityEmitResult {\n /** The scan result the manifest was derived from. */\n result: ScanResult;\n /** The pretty-printed crypto-agility manifest JSON (no trailing newline). */\n manifest: string;\n}\n\n/**\n * Emit a crypto-agility manifest for a repo (`qscan crypto-agility emit` /\n * `--crypto-agility`). Runs a scan and derives the manifest from its inventory +\n * CBOM. This is deliberately additive: it NEVER consults the severity threshold and\n * the CLI always exits 0 (publishing a posture manifest must not fail CI). The\n * generation timestamp is stamped here (the CLI runtime), keeping the core builder\n * pure. A `--policy` file overlays its `transitionDeadline`; `--attestation` records\n * a credential URL verbatim (never fetched); `--hybrid-kex` / `--no-hybrid-kex`\n * assert hybrid-KEX use.\n */\nexport async function runCryptoAgilityEmit(\n opts: Partial<QscanOptions> & { path: string },\n hooks: RunQscanHooks = {},\n): Promise<CryptoAgilityEmitResult> {\n const options: QscanOptions = { ...defaultOptions(), ...opts };\n const scanFn: ScanFn = hooks.scanFn ?? (options.parallel ? scanParallel : scan);\n const result = await scanFn(toScanOptions(options));\n\n let policy: CryptoPolicy | undefined;\n if (options.policy) {\n policy = parseCryptoPolicy(JSON.parse(await readFile(options.policy, \"utf8\")));\n }\n\n const manifest = buildCryptoAgilityManifest(result, {\n generatedAt: new Date().toISOString(),\n ...(options.attestation ? { attestationUrl: options.attestation } : {}),\n ...(options.hybridKexInUse !== undefined ? { hybridKexInUse: options.hybridKexInUse } : {}),\n ...(policy ? { policy } : {}),\n ...(process.env.GITHUB_REPOSITORY ? { repository: process.env.GITHUB_REPOSITORY } : {}),\n ...(process.env.GITHUB_SHA ? { commit: process.env.GITHUB_SHA } : {}),\n });\n return { result, manifest: JSON.stringify(manifest, null, 2) };\n}\n\n/**\n * Validate a LOCAL crypto-agility manifest file against the schema\n * (`qscan crypto-agility validate <file>`). Reads and parses the file, then defers\n * to core's {@link validateCryptoAgilityManifest}. Strictly offline: it never\n * fetches a URL (the attestation link and any remote manifest are a website-side\n * concern). Read / parse failures propagate to the CLI as an I/O error (exit 2); a\n * successfully-parsed but non-conforming manifest returns `{ valid: false }` and the\n * CLI exits non-zero.\n */\nexport async function runCryptoAgilityValidate(file: string): Promise<ManifestValidation> {\n const text = await readFile(file, \"utf8\");\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return { valid: false, errors: [`not valid JSON: ${message}`] };\n }\n return validateCryptoAgilityManifest(parsed);\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 /** HNDL exposure analysis (`--hndl`); annotates JSON/SARIF/human output. */\n hndl?: HndlReport;\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 hndl = undefined,\n } = typeof opts === \"boolean\" ? { color: opts, policy: undefined } : opts;\n switch (format) {\n case \"json\":\n return renderJson(result, { redactSnippets, ...(hndl ? { hndl } : {}) });\n case \"sarif\":\n return renderSarif(result, { redactSnippets, ...(hndl ? { hndl } : {}) });\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, ...(hndl ? { hndl } : {}) });\n }\n}\n\n/** Re-export the core result types consumers commonly need. */\nexport type { Finding, ScanResult, ScanOptions } from \"@quantakrypto/core\";\n"]}
|
package/dist/remediate-cli.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ export interface RemediateRun {
|
|
|
21
21
|
written: string[];
|
|
22
22
|
}
|
|
23
23
|
/** A verified fix set ready to become a draft PR. */
|
|
24
|
-
|
|
24
|
+
interface DraftPrPlan {
|
|
25
25
|
branch: string;
|
|
26
26
|
title: string;
|
|
27
27
|
body: string;
|
|
@@ -31,7 +31,7 @@ export interface DraftPrPlan {
|
|
|
31
31
|
}[];
|
|
32
32
|
}
|
|
33
33
|
/** Open a draft PR for the plan (injectable; default shells git + gh in a worktree). */
|
|
34
|
-
|
|
34
|
+
type OpenDraftPr = (plan: DraftPrPlan) => Promise<{
|
|
35
35
|
url?: string;
|
|
36
36
|
}>;
|
|
37
37
|
export interface RemediateHooks {
|
|
@@ -52,8 +52,6 @@ export declare const REMEDIATE_EXIT: {
|
|
|
52
52
|
readonly CHANGES: 0;
|
|
53
53
|
readonly ERROR: 2;
|
|
54
54
|
};
|
|
55
|
-
/** Default per-run cap on paid LLM fix proposals (spend/DoS guard; override with --max-llm). */
|
|
56
|
-
export declare const DEFAULT_MAX_LLM = 25;
|
|
57
55
|
/** Minimal unified diff for a localized change (3 lines of context). */
|
|
58
56
|
export declare function unifiedDiff(relPath: string, before: string, after: string): string;
|
|
59
57
|
/** Run a complete qremediate pass. Pure w.r.t. process; the bin prints + exits. */
|
|
@@ -71,4 +69,5 @@ export declare function parseRemediateArgs(argv: readonly string[]): {
|
|
|
71
69
|
message: string;
|
|
72
70
|
};
|
|
73
71
|
export declare const REMEDIATE_HELP = "qremediate \u2014 apply verified codemod fixes (and, with --llm, crypto-verified LLM proposals) for insecure crypto findings\n\nUSAGE\n qremediate [path] [--mode diff|apply|pr] [--llm] [--apply-llm] [--max-llm N]\n [--llm-provider <p>] [--llm-model <m>]\n\nOPTIONS\n --mode diff Print a unified diff of every candidate fix (default; writes nothing)\n --mode apply Write deterministic codemod fixes into the working tree\n (LLM fixes are held back as diffs unless --apply-llm is given)\n --mode pr Commit fixes to a new branch and open a DRAFT PR (never merges)\n --llm Also let a BYOK LLM propose fixes codemods can't (needs an API key)\n --apply-llm In apply mode, also write LLM fixes (only after you've read them)\n --max-llm N Cap paid LLM proposals per run (default 25; spend guard)\n --llm-provider anthropic | openai-compatible (default: anthropic)\n --llm-model Model id for the BYOK provider\n -h, --help Show this help\n -v, --version Show version\n\nEvery fix must clear the verify_fix gate (target finding gone, no new finding) and\nthe patch policy (only files with findings + dependency manifests). Codemod fixes\nare deterministic; LLM fixes are **crypto-verified, not security-reviewed** \u2014 the\ngate proves the crypto is gone, not that the rewrite is safe, and the pipeline\nrejects any LLM patch that adds a network/exec sink or rewrites too much. Review\nLLM diffs before applying. Never merges.\n";
|
|
72
|
+
export {};
|
|
74
73
|
//# sourceMappingURL=remediate-cli.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"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,
|
|
1
|
+
{"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"}
|
package/dist/remediate-cli.js
CHANGED
|
@@ -15,7 +15,7 @@ import { scan, codemodFor, remediateFindings, isManifestFile, fingerprintFinding
|
|
|
15
15
|
const exec = promisify(execFile);
|
|
16
16
|
export const REMEDIATE_EXIT = { OK: 0, CHANGES: 0, ERROR: 2 };
|
|
17
17
|
/** Default per-run cap on paid LLM fix proposals (spend/DoS guard; override with --max-llm). */
|
|
18
|
-
|
|
18
|
+
const DEFAULT_MAX_LLM = 25;
|
|
19
19
|
function envKey(provider) {
|
|
20
20
|
return (process.env.QK_LLM_API_KEY ??
|
|
21
21
|
(provider === "anthropic" ? process.env.ANTHROPIC_API_KEY : process.env.OPENAI_API_KEY));
|