@quantakrypto/qscan 0.3.0 → 0.4.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.
@@ -0,0 +1,98 @@
1
+ /**
2
+ * `qscan --triage` glue (BYOK plane). Runs the LLM triage pass over a scan
3
+ * result and attaches an exposure annotation to each finding, then re-sorts by
4
+ * exposure. It NEVER drops a finding and NEVER touches the exit code — the CLI
5
+ * computes the exit code from raw severities before this ever runs.
6
+ *
7
+ * `@quantakrypto/agent` (the only networked package) is loaded via dynamic
8
+ * `import()` so a plain scan never pulls in the network client.
9
+ */
10
+ import { readFile as fsReadFile } from "node:fs/promises";
11
+ import path from "node:path";
12
+ import process from "node:process";
13
+ import { buildContext, compareFindings, fingerprintFinding, renderPreflight, } from "@quantakrypto/core";
14
+ const SEVERITY_RANK = {
15
+ critical: 0,
16
+ high: 1,
17
+ medium: 2,
18
+ low: 3,
19
+ info: 4,
20
+ };
21
+ function envKey(provider) {
22
+ return (process.env.QK_LLM_API_KEY ??
23
+ (provider === "anthropic" ? process.env.ANTHROPIC_API_KEY : process.env.OPENAI_API_KEY));
24
+ }
25
+ function defaultModel(provider) {
26
+ return provider === "anthropic" ? "claude-sonnet-5" : "gpt-4o-mini";
27
+ }
28
+ /**
29
+ * Annotate `result.findings` with triage verdicts (mutating `result`). Returns a
30
+ * preflight string when `--dry-run` is set (no provider is contacted). Failures
31
+ * degrade gracefully: the scan/report proceed without triage.
32
+ */
33
+ export async function runTriage(result, opts) {
34
+ const level = opts.level;
35
+ const floorRank = SEVERITY_RANK[opts.floor ?? "medium"];
36
+ const targets = result.findings.filter((f) => SEVERITY_RANK[f.severity] <= floorRank);
37
+ const stderr = opts.stderr ?? ((s) => void process.stderr.write(s));
38
+ const root = opts.root ?? result.root ?? ".";
39
+ const readFile = opts.readFile ?? ((rel) => fsReadFile(path.resolve(root, rel), "utf8"));
40
+ // --dry-run: show exactly what would be sent; contact nothing.
41
+ if (opts.dryRun) {
42
+ const contexts = [];
43
+ for (const f of targets) {
44
+ const content = level === "metadata" ? "" : await readFile(f.location.file).catch(() => "");
45
+ contexts.push(buildContext(f, level, content));
46
+ }
47
+ return {
48
+ preflight: contexts.length
49
+ ? renderPreflight(contexts)
50
+ : "qscan --triage --dry-run: no findings at or above the triage floor.",
51
+ };
52
+ }
53
+ const provider = opts.provider ?? "anthropic";
54
+ const key = opts.resolveKey ? opts.resolveKey() : envKey(provider);
55
+ // No key and no injected triage function → graceful degrade (never fatal).
56
+ if (!opts.triageFn && !key) {
57
+ stderr("qscan: --triage needs an API key (set QK_LLM_API_KEY, ANTHROPIC_API_KEY, or OPENAI_API_KEY). Skipping triage.\n");
58
+ return {};
59
+ }
60
+ const model = opts.model ?? defaultModel(provider);
61
+ const triageFn = opts.triageFn ??
62
+ (async (findings) => {
63
+ const agent = await import("@quantakrypto/agent");
64
+ const client = agent.resolveClient({ provider, model, apiKey: key });
65
+ return agent.triageFindings(findings, {
66
+ client,
67
+ level,
68
+ readFile,
69
+ fingerprint: fingerprintFinding,
70
+ floor: opts.floor,
71
+ cacheFile: opts.cacheFile,
72
+ model,
73
+ });
74
+ });
75
+ try {
76
+ const verdicts = await triageFn(result.findings);
77
+ for (const f of result.findings) {
78
+ const v = verdicts.get(fingerprintFinding(f));
79
+ if (v) {
80
+ f.triage = { exposureScore: v.exposureScore, priority: v.priority, rationale: v.rationale };
81
+ }
82
+ }
83
+ // Re-sort by exposure (desc), falling back to the stable finding order.
84
+ result.findings = [...result.findings].sort((a, b) => {
85
+ const ea = a.triage?.exposureScore ?? -1;
86
+ const eb = b.triage?.exposureScore ?? -1;
87
+ if (eb !== ea)
88
+ return eb - ea;
89
+ return compareFindings(a, b);
90
+ });
91
+ }
92
+ catch (err) {
93
+ const msg = err instanceof Error ? err.message : String(err);
94
+ stderr(`qscan: triage failed (${msg}); showing findings without triage.\n`);
95
+ }
96
+ return {};
97
+ }
98
+ //# sourceMappingURL=triage-run.js.map
@@ -0,0 +1 @@
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;AA0BF,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,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QACjD,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,EAAE,aAAa,EAAE,CAAC,CAAC,aAAa,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC;YAC9F,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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quantakrypto/qscan",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
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)",
@@ -15,7 +15,8 @@
15
15
  },
16
16
  "type": "module",
17
17
  "bin": {
18
- "qscan": "./dist/cli.js"
18
+ "qscan": "./dist/cli.js",
19
+ "qremediate": "./dist/remediate-bin.js"
19
20
  },
20
21
  "main": "./dist/index.js",
21
22
  "types": "./dist/index.d.ts",
@@ -36,7 +37,8 @@
36
37
  "access": "public"
37
38
  },
38
39
  "dependencies": {
39
- "@quantakrypto/core": "0.3.0"
40
+ "@quantakrypto/agent": "0.4.0",
41
+ "@quantakrypto/core": "0.4.0"
40
42
  },
41
43
  "scripts": {
42
44
  "build": "tsc -b",