@quantakrypto/core 0.4.3 → 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/cbom-merge.d.ts +9 -0
- package/dist/cbom-merge.d.ts.map +1 -0
- package/dist/cbom-merge.js +99 -0
- package/dist/cbom-merge.js.map +1 -0
- package/dist/comments.d.ts +4 -3
- package/dist/comments.d.ts.map +1 -1
- package/dist/comments.js +53 -33
- package/dist/comments.js.map +1 -1
- package/dist/dependencies.d.ts.map +1 -1
- package/dist/dependencies.js +1 -1
- package/dist/dependencies.js.map +1 -1
- package/dist/detect-utils.d.ts +5 -1
- package/dist/detect-utils.d.ts.map +1 -1
- package/dist/detect-utils.js +11 -2
- package/dist/detect-utils.js.map +1 -1
- package/dist/detectors/c.d.ts +4 -2
- package/dist/detectors/c.d.ts.map +1 -1
- package/dist/detectors/c.js +192 -1
- package/dist/detectors/c.js.map +1 -1
- package/dist/detectors/cicd.d.ts +27 -0
- package/dist/detectors/cicd.d.ts.map +1 -0
- package/dist/detectors/cicd.js +116 -0
- package/dist/detectors/cicd.js.map +1 -0
- package/dist/detectors/cloud-kms.d.ts +21 -0
- package/dist/detectors/cloud-kms.d.ts.map +1 -0
- package/dist/detectors/cloud-kms.js +57 -0
- package/dist/detectors/cloud-kms.js.map +1 -0
- package/dist/detectors/cloudformation.d.ts +30 -0
- package/dist/detectors/cloudformation.d.ts.map +1 -0
- package/dist/detectors/cloudformation.js +170 -0
- package/dist/detectors/cloudformation.js.map +1 -0
- package/dist/detectors/database.d.ts +17 -0
- package/dist/detectors/database.d.ts.map +1 -0
- package/dist/detectors/database.js +60 -0
- package/dist/detectors/database.js.map +1 -0
- package/dist/detectors/dnssec.d.ts +55 -0
- package/dist/detectors/dnssec.d.ts.map +1 -0
- package/dist/detectors/dnssec.js +107 -0
- package/dist/detectors/dnssec.js.map +1 -0
- package/dist/detectors/elixir.d.ts +20 -0
- package/dist/detectors/elixir.d.ts.map +1 -0
- package/dist/detectors/elixir.js +195 -0
- package/dist/detectors/elixir.js.map +1 -0
- package/dist/detectors/java.d.ts.map +1 -1
- package/dist/detectors/java.js +1 -1
- package/dist/detectors/java.js.map +1 -1
- package/dist/detectors/jose.d.ts +20 -0
- package/dist/detectors/jose.d.ts.map +1 -0
- package/dist/detectors/jose.js +62 -0
- package/dist/detectors/jose.js.map +1 -0
- package/dist/detectors/jwk.d.ts +25 -0
- package/dist/detectors/jwk.d.ts.map +1 -0
- package/dist/detectors/jwk.js +95 -0
- package/dist/detectors/jwk.js.map +1 -0
- package/dist/detectors/k8s.d.ts +17 -0
- package/dist/detectors/k8s.d.ts.map +1 -0
- package/dist/detectors/k8s.js +85 -0
- package/dist/detectors/k8s.js.map +1 -0
- package/dist/detectors/mesh.d.ts +35 -0
- package/dist/detectors/mesh.d.ts.map +1 -0
- package/dist/detectors/mesh.js +109 -0
- package/dist/detectors/mesh.js.map +1 -0
- package/dist/detectors/messaging.d.ts +16 -0
- package/dist/detectors/messaging.d.ts.map +1 -0
- package/dist/detectors/messaging.js +72 -0
- package/dist/detectors/messaging.js.map +1 -0
- package/dist/detectors/pem.js +1 -1
- package/dist/detectors/pem.js.map +1 -1
- package/dist/detectors/php.d.ts +24 -0
- package/dist/detectors/php.d.ts.map +1 -0
- package/dist/detectors/php.js +167 -0
- package/dist/detectors/php.js.map +1 -0
- package/dist/detectors/python.d.ts.map +1 -1
- package/dist/detectors/python.js +70 -0
- package/dist/detectors/python.js.map +1 -1
- package/dist/detectors/rust.d.ts.map +1 -1
- package/dist/detectors/rust.js +68 -0
- package/dist/detectors/rust.js.map +1 -1
- package/dist/detectors/secrets.d.ts +21 -0
- package/dist/detectors/secrets.d.ts.map +1 -0
- package/dist/detectors/secrets.js +79 -0
- package/dist/detectors/secrets.js.map +1 -0
- package/dist/detectors/source.d.ts.map +1 -1
- package/dist/detectors/source.js +190 -47
- package/dist/detectors/source.js.map +1 -1
- package/dist/detectors/stateful-hbs.d.ts.map +1 -1
- package/dist/detectors/stateful-hbs.js +9 -6
- package/dist/detectors/stateful-hbs.js.map +1 -1
- package/dist/detectors/terraform.d.ts +21 -0
- package/dist/detectors/terraform.d.ts.map +1 -0
- package/dist/detectors/terraform.js +123 -0
- package/dist/detectors/terraform.js.map +1 -0
- package/dist/detectors/vpn.d.ts +22 -0
- package/dist/detectors/vpn.d.ts.map +1 -0
- package/dist/detectors/vpn.js +99 -0
- package/dist/detectors/vpn.js.map +1 -0
- package/dist/evidence.d.ts +5 -0
- package/dist/evidence.d.ts.map +1 -1
- package/dist/evidence.js +5 -0
- package/dist/evidence.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/policy.d.ts +67 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +115 -0
- package/dist/policy.js.map +1 -0
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +30 -0
- package/dist/registry.js.map +1 -1
- package/dist/remediate-pipeline.d.ts.map +1 -1
- package/dist/remediate-pipeline.js +22 -1
- package/dist/remediate-pipeline.js.map +1 -1
- package/dist/report.d.ts.map +1 -1
- package/dist/report.js +7 -0
- package/dist/report.js.map +1 -1
- package/dist/standards.d.ts +96 -0
- package/dist/standards.d.ts.map +1 -0
- package/dist/standards.js +107 -0
- package/dist/standards.js.map +1 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
- package/src/cbom-merge.ts +116 -0
- package/src/comments.ts +59 -33
- package/src/dependencies.ts +2 -1
- package/src/detect-utils.ts +14 -2
- package/src/detectors/c.ts +209 -3
- package/src/detectors/cicd.ts +158 -0
- package/src/detectors/cloud-kms.ts +87 -0
- package/src/detectors/cloudformation.ts +215 -0
- package/src/detectors/database.ts +88 -0
- package/src/detectors/dnssec.ts +186 -0
- package/src/detectors/elixir.ts +237 -0
- package/src/detectors/java.ts +2 -1
- package/src/detectors/jose.ts +94 -0
- package/src/detectors/jwk.ts +128 -0
- package/src/detectors/k8s.ts +112 -0
- package/src/detectors/mesh.ts +154 -0
- package/src/detectors/messaging.ts +98 -0
- package/src/detectors/pem.ts +1 -1
- package/src/detectors/php.ts +225 -0
- package/src/detectors/python.ts +71 -0
- package/src/detectors/rust.ts +71 -0
- package/src/detectors/secrets.ts +114 -0
- package/src/detectors/source.ts +235 -68
- package/src/detectors/stateful-hbs.ts +9 -6
- package/src/detectors/terraform.ts +154 -0
- package/src/detectors/vpn.ts +131 -0
- package/src/evidence.ts +11 -0
- package/src/index.ts +10 -0
- package/src/policy.ts +180 -0
- package/src/registry.ts +30 -0
- package/src/remediate-pipeline.ts +25 -2
- package/src/report.ts +7 -0
- package/src/standards.ts +186 -0
- package/src/types.ts +2 -0
- package/src/version.ts +1 -1
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CycloneDxBom } from "./cbom.js";
|
|
2
|
+
/**
|
|
3
|
+
* Merge CBOMs into one. Components with the same `bom-ref` (same algorithm +
|
|
4
|
+
* primitive) are combined; their occurrence evidence is unioned and their
|
|
5
|
+
* harvest-now-decrypt-later flag is OR-ed. Tool and root metadata from every input
|
|
6
|
+
* is preserved.
|
|
7
|
+
*/
|
|
8
|
+
export declare function mergeCboms(boms: readonly CycloneDxBom[]): CycloneDxBom;
|
|
9
|
+
//# sourceMappingURL=cbom-merge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cbom-merge.d.ts","sourceRoot":"","sources":["../src/cbom-merge.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,YAAY,EAAiB,MAAM,WAAW,CAAC;AA6B7D;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,SAAS,YAAY,EAAE,GAAG,YAAY,CAkEtE"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Merge multiple CycloneDX 1.6 CBOMs into one combined cryptographic bill of
|
|
3
|
+
* materials. This is what turns the "code + infra + live-endpoint" story into a
|
|
4
|
+
* single artifact: a qScan CBOM (code + infrastructure-as-code/config) and a
|
|
5
|
+
* qProbe CBOM (live endpoints) both emit `cryptographic-asset` components keyed by
|
|
6
|
+
* a deterministic `bom-ref` per (algorithm, primitive), so merging is a union with
|
|
7
|
+
* dedup — the same crypto seen by two planes collapses to one component whose
|
|
8
|
+
* occurrence evidence spans both.
|
|
9
|
+
*
|
|
10
|
+
* Output is deterministic (components sorted by bom-ref, occurrences sorted by
|
|
11
|
+
* location, a stable serial derived from the merged content), so re-merging the
|
|
12
|
+
* same inputs yields byte-identical output.
|
|
13
|
+
*/
|
|
14
|
+
import { createHash } from "node:crypto";
|
|
15
|
+
function occurrencesOf(c) {
|
|
16
|
+
const ev = c.evidence;
|
|
17
|
+
const occ = ev?.occurrences;
|
|
18
|
+
return Array.isArray(occ) ? occ : [];
|
|
19
|
+
}
|
|
20
|
+
function hndlOf(c) {
|
|
21
|
+
return (c.cryptoProperties.harvestNowDecryptLater === true);
|
|
22
|
+
}
|
|
23
|
+
/** Union + dedup occurrences by `location`, sorted deterministically. */
|
|
24
|
+
function mergeOccurrences(a, b) {
|
|
25
|
+
const byLoc = new Map();
|
|
26
|
+
for (const o of [...a, ...b])
|
|
27
|
+
if (!byLoc.has(o.location))
|
|
28
|
+
byLoc.set(o.location, o);
|
|
29
|
+
return [...byLoc.values()].sort((x, y) => x.location < y.location ? -1 : x.location > y.location ? 1 : 0);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Merge CBOMs into one. Components with the same `bom-ref` (same algorithm +
|
|
33
|
+
* primitive) are combined; their occurrence evidence is unioned and their
|
|
34
|
+
* harvest-now-decrypt-later flag is OR-ed. Tool and root metadata from every input
|
|
35
|
+
* is preserved.
|
|
36
|
+
*/
|
|
37
|
+
export function mergeCboms(boms) {
|
|
38
|
+
const byRef = new Map();
|
|
39
|
+
const toolComponents = [];
|
|
40
|
+
const toolSeen = new Set();
|
|
41
|
+
const roots = [];
|
|
42
|
+
for (const bom of boms) {
|
|
43
|
+
// Collect tool + root metadata (dedup tools by name@version).
|
|
44
|
+
const tools = bom.metadata?.tools?.components;
|
|
45
|
+
if (Array.isArray(tools)) {
|
|
46
|
+
for (const t of tools) {
|
|
47
|
+
const tc = t;
|
|
48
|
+
const key = `${tc.name}@${tc.version}`;
|
|
49
|
+
if (!toolSeen.has(key)) {
|
|
50
|
+
toolSeen.add(key);
|
|
51
|
+
toolComponents.push(t);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const root = bom.metadata?.component?.name;
|
|
56
|
+
if (typeof root === "string" && root && !roots.includes(root))
|
|
57
|
+
roots.push(root);
|
|
58
|
+
for (const c of bom.components) {
|
|
59
|
+
const ref = c["bom-ref"];
|
|
60
|
+
const existing = byRef.get(ref);
|
|
61
|
+
if (!existing) {
|
|
62
|
+
// Deep-ish copy so we can mutate evidence/hndl without touching the input.
|
|
63
|
+
byRef.set(ref, {
|
|
64
|
+
...c,
|
|
65
|
+
cryptoProperties: { ...c.cryptoProperties },
|
|
66
|
+
evidence: { occurrences: occurrencesOf(c) },
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
const merged = mergeOccurrences(occurrencesOf(existing), occurrencesOf(c));
|
|
71
|
+
existing.evidence = { occurrences: merged };
|
|
72
|
+
existing.cryptoProperties = {
|
|
73
|
+
...existing.cryptoProperties,
|
|
74
|
+
harvestNowDecryptLater: hndlOf(existing) || hndlOf(c),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const components = [...byRef.values()].sort((a, b) => a["bom-ref"] < b["bom-ref"] ? -1 : a["bom-ref"] > b["bom-ref"] ? 1 : 0);
|
|
80
|
+
const serialSeed = components.map((c) => c["bom-ref"]).join("|") + `|${roots.join(",")}`;
|
|
81
|
+
const h = createHash("sha256").update(serialSeed, "utf8").digest("hex");
|
|
82
|
+
const serial = `${h.slice(0, 8)}-${h.slice(8, 12)}-4${h.slice(13, 16)}-8${h.slice(17, 20)}-${h.slice(20, 32)}`;
|
|
83
|
+
return {
|
|
84
|
+
bomFormat: "CycloneDX",
|
|
85
|
+
specVersion: "1.6",
|
|
86
|
+
serialNumber: `urn:uuid:${serial}`,
|
|
87
|
+
version: 1,
|
|
88
|
+
metadata: {
|
|
89
|
+
tools: { components: toolComponents },
|
|
90
|
+
component: {
|
|
91
|
+
type: "application",
|
|
92
|
+
"bom-ref": "root",
|
|
93
|
+
name: roots.length ? `combined: ${roots.join(" + ")}` : "combined",
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
components,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=cbom-merge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cbom-merge.js","sourceRoot":"","sources":["../src/cbom-merge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AASzC,SAAS,aAAa,CAAC,CAAgB;IACrC,MAAM,EAAE,GAAG,CAAC,CAAC,QAAiD,CAAC;IAC/D,MAAM,GAAG,GAAG,EAAE,EAAE,WAAW,CAAC;IAC5B,OAAO,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAE,GAAoB,CAAC,CAAC,CAAC,EAAE,CAAC;AACzD,CAAC;AAED,SAAS,MAAM,CAAC,CAAgB;IAC9B,OAAO,CACJ,CAAC,CAAC,gBAAyD,CAAC,sBAAsB,KAAK,IAAI,CAC7F,CAAC;AACJ,CAAC;AAED,yEAAyE;AACzE,SAAS,gBAAgB,CAAC,CAAe,EAAE,CAAe;IACxD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAsB,CAAC;IAC5C,KAAK,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC;QAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC;YAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IACnF,OAAO,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACvC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAC/D,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,IAA6B;IACtD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAyB,CAAC;IAC/C,MAAM,cAAc,GAAc,EAAE,CAAC;IACrC,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;IACnC,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,8DAA8D;QAC9D,MAAM,KAAK,GAAI,GAAG,CAAC,QAAmD,EAAE,KAAK,EAAE,UAAU,CAAC;QAC1F,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACzB,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;gBACtB,MAAM,EAAE,GAAG,CAAwC,CAAC;gBACpD,MAAM,GAAG,GAAG,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,OAAO,EAAE,CAAC;gBACvC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;oBACvB,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;oBAClB,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;gBACzB,CAAC;YACH,CAAC;QACH,CAAC;QACD,MAAM,IAAI,GAAI,GAAG,CAAC,QAA8C,EAAE,SAAS,EAAE,IAAI,CAAC;QAClF,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAEhF,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC;YAC/B,MAAM,GAAG,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC;YACzB,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAChC,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,2EAA2E;gBAC3E,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE;oBACb,GAAG,CAAC;oBACJ,gBAAgB,EAAE,EAAE,GAAG,CAAC,CAAC,gBAAgB,EAAE;oBAC3C,QAAQ,EAAE,EAAE,WAAW,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE;iBAC5C,CAAC,CAAC;YACL,CAAC;iBAAM,CAAC;gBACN,MAAM,MAAM,GAAG,gBAAgB,CAAC,aAAa,CAAC,QAAQ,CAAC,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC3E,QAAQ,CAAC,QAAQ,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC;gBAC5C,QAAQ,CAAC,gBAAgB,GAAG;oBAC1B,GAAG,QAAQ,CAAC,gBAAgB;oBAC5B,sBAAsB,EAAE,MAAM,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC;iBACtD,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,UAAU,GAAG,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACnD,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CACvE,CAAC;IAEF,MAAM,UAAU,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;IACzF,MAAM,CAAC,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACxE,MAAM,MAAM,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC;IAE/G,OAAO;QACL,SAAS,EAAE,WAAW;QACtB,WAAW,EAAE,KAAK;QAClB,YAAY,EAAE,YAAY,MAAM,EAAE;QAClC,OAAO,EAAE,CAAC;QACV,QAAQ,EAAE;YACR,KAAK,EAAE,EAAE,UAAU,EAAE,cAAc,EAAE;YACrC,SAAS,EAAE;gBACT,IAAI,EAAE,aAAa;gBACnB,SAAS,EAAE,MAAM;gBACjB,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,aAAa,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU;aACnE;SACF;QACD,UAAU;KACX,CAAC;AACJ,CAAC","sourcesContent":["/**\n * Merge multiple CycloneDX 1.6 CBOMs into one combined cryptographic bill of\n * materials. This is what turns the \"code + infra + live-endpoint\" story into a\n * single artifact: a qScan CBOM (code + infrastructure-as-code/config) and a\n * qProbe CBOM (live endpoints) both emit `cryptographic-asset` components keyed by\n * a deterministic `bom-ref` per (algorithm, primitive), so merging is a union with\n * dedup — the same crypto seen by two planes collapses to one component whose\n * occurrence evidence spans both.\n *\n * Output is deterministic (components sorted by bom-ref, occurrences sorted by\n * location, a stable serial derived from the merged content), so re-merging the\n * same inputs yields byte-identical output.\n */\nimport { createHash } from \"node:crypto\";\nimport type { CycloneDxBom, CbomComponent } from \"./cbom.js\";\n\n/** Occurrence record shape inside a component's `evidence.occurrences`. */\ninterface Occurrence {\n location: string;\n additionalContext?: string;\n}\n\nfunction occurrencesOf(c: CbomComponent): Occurrence[] {\n const ev = c.evidence as { occurrences?: unknown } | undefined;\n const occ = ev?.occurrences;\n return Array.isArray(occ) ? (occ as Occurrence[]) : [];\n}\n\nfunction hndlOf(c: CbomComponent): boolean {\n return (\n (c.cryptoProperties as { harvestNowDecryptLater?: unknown }).harvestNowDecryptLater === true\n );\n}\n\n/** Union + dedup occurrences by `location`, sorted deterministically. */\nfunction mergeOccurrences(a: Occurrence[], b: Occurrence[]): Occurrence[] {\n const byLoc = new Map<string, Occurrence>();\n for (const o of [...a, ...b]) if (!byLoc.has(o.location)) byLoc.set(o.location, o);\n return [...byLoc.values()].sort((x, y) =>\n x.location < y.location ? -1 : x.location > y.location ? 1 : 0,\n );\n}\n\n/**\n * Merge CBOMs into one. Components with the same `bom-ref` (same algorithm +\n * primitive) are combined; their occurrence evidence is unioned and their\n * harvest-now-decrypt-later flag is OR-ed. Tool and root metadata from every input\n * is preserved.\n */\nexport function mergeCboms(boms: readonly CycloneDxBom[]): CycloneDxBom {\n const byRef = new Map<string, CbomComponent>();\n const toolComponents: unknown[] = [];\n const toolSeen = new Set<string>();\n const roots: string[] = [];\n\n for (const bom of boms) {\n // Collect tool + root metadata (dedup tools by name@version).\n const tools = (bom.metadata as { tools?: { components?: unknown[] } })?.tools?.components;\n if (Array.isArray(tools)) {\n for (const t of tools) {\n const tc = t as { name?: string; version?: string };\n const key = `${tc.name}@${tc.version}`;\n if (!toolSeen.has(key)) {\n toolSeen.add(key);\n toolComponents.push(t);\n }\n }\n }\n const root = (bom.metadata as { component?: { name?: string } })?.component?.name;\n if (typeof root === \"string\" && root && !roots.includes(root)) roots.push(root);\n\n for (const c of bom.components) {\n const ref = c[\"bom-ref\"];\n const existing = byRef.get(ref);\n if (!existing) {\n // Deep-ish copy so we can mutate evidence/hndl without touching the input.\n byRef.set(ref, {\n ...c,\n cryptoProperties: { ...c.cryptoProperties },\n evidence: { occurrences: occurrencesOf(c) },\n });\n } else {\n const merged = mergeOccurrences(occurrencesOf(existing), occurrencesOf(c));\n existing.evidence = { occurrences: merged };\n existing.cryptoProperties = {\n ...existing.cryptoProperties,\n harvestNowDecryptLater: hndlOf(existing) || hndlOf(c),\n };\n }\n }\n }\n\n const components = [...byRef.values()].sort((a, b) =>\n a[\"bom-ref\"] < b[\"bom-ref\"] ? -1 : a[\"bom-ref\"] > b[\"bom-ref\"] ? 1 : 0,\n );\n\n const serialSeed = components.map((c) => c[\"bom-ref\"]).join(\"|\") + `|${roots.join(\",\")}`;\n const h = createHash(\"sha256\").update(serialSeed, \"utf8\").digest(\"hex\");\n const serial = `${h.slice(0, 8)}-${h.slice(8, 12)}-4${h.slice(13, 16)}-8${h.slice(17, 20)}-${h.slice(20, 32)}`;\n\n return {\n bomFormat: \"CycloneDX\",\n specVersion: \"1.6\",\n serialNumber: `urn:uuid:${serial}`,\n version: 1,\n metadata: {\n tools: { components: toolComponents },\n component: {\n type: \"application\",\n \"bom-ref\": \"root\",\n name: roots.length ? `combined: ${roots.join(\" + \")}` : \"combined\",\n },\n },\n components,\n };\n}\n"]}
|
package/dist/comments.d.ts
CHANGED
|
@@ -21,9 +21,10 @@ export declare function commentStyleForFile(file: string): CommentStyle | null;
|
|
|
21
21
|
/**
|
|
22
22
|
* Compute the comment spans (`[start, end)` offsets) of `content`, skipping over
|
|
23
23
|
* string literals so a comment marker inside a string is not treated as a
|
|
24
|
-
* comment. Spans are returned sorted and non-overlapping.
|
|
24
|
+
* comment. Spans are returned sorted and non-overlapping. `rawBacktick` (Go)
|
|
25
|
+
* makes backtick strings raw (no escapes).
|
|
25
26
|
*/
|
|
26
|
-
export declare function commentSpans(content: string, style: CommentStyle): Array<[number, number]>;
|
|
27
|
+
export declare function commentSpans(content: string, style: CommentStyle, rawBacktick?: boolean): Array<[number, number]>;
|
|
27
28
|
/**
|
|
28
29
|
* Compute Python triple-quoted string spans (`"""…"""` / `'''…'''`). These are
|
|
29
30
|
* docstrings / prose in practice, so a crypto *name* mentioned inside one
|
|
@@ -57,7 +58,7 @@ export declare function stripCommentFindings(findings: Finding[], content: strin
|
|
|
57
58
|
* suppress findings from IDENTIFIER-only rules (e.g. a Go `SigningMethodRS256`
|
|
58
59
|
* mentioned inside an error-message string) — the mirror of {@link commentSpans}.
|
|
59
60
|
*/
|
|
60
|
-
export declare function stringSpans(content: string, style: CommentStyle): Array<[number, number]>;
|
|
61
|
+
export declare function stringSpans(content: string, style: CommentStyle, rawBacktick?: boolean): Array<[number, number]>;
|
|
61
62
|
/**
|
|
62
63
|
* Drop findings of "code-only" rules (`ruleIds`) whose match starts inside a
|
|
63
64
|
* string literal. Some rules — an identifier-form JWT signing method, a Go
|
package/dist/comments.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"comments.d.ts","sourceRoot":"","sources":["../src/comments.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C,MAAM,MAAM,YAAY,GAAG,GAAG,GAAG,MAAM,CAAC;AA8BxC,sFAAsF;AACtF,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAKrE;
|
|
1
|
+
{"version":3,"file":"comments.d.ts","sourceRoot":"","sources":["../src/comments.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C,MAAM,MAAM,YAAY,GAAG,GAAG,GAAG,MAAM,CAAC;AA8BxC,sFAAsF;AACtF,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAKrE;AAyCD;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,YAAY,EACnB,WAAW,UAAQ,GAClB,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAoCzB;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAsC7E;AAED,gFAAgF;AAChF,wBAAgB,aAAa,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAW7F;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CASzD;AAED,+EAA+E;AAC/E,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,EAAE,CAKpF;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,OAAO,EAAE,EACnB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,GACX,OAAO,EAAE,CAyBX;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,YAAY,EACnB,WAAW,UAAQ,GAClB,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAiCzB;AAED;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,OAAO,EAAE,EACnB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,GAC3B,OAAO,EAAE,CAkBX"}
|
package/dist/comments.js
CHANGED
|
@@ -33,32 +33,61 @@ export function commentStyleForFile(file) {
|
|
|
33
33
|
return "hash";
|
|
34
34
|
return null;
|
|
35
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Advance past a string / char literal starting at `content[i]` (a quote char);
|
|
38
|
+
* returns the index just after it. Two correctness rules a naive escape-scan got
|
|
39
|
+
* wrong (both verified as real bugs):
|
|
40
|
+
* - a `'` char literal must close within a short window (`'\u{10FFFF}'` is ~11
|
|
41
|
+
* chars); otherwise it is a Rust lifetime (`'a`) or a stray apostrophe, NOT a
|
|
42
|
+
* literal — return `i + 1` so the scan continues instead of consuming the
|
|
43
|
+
* rest of the file.
|
|
44
|
+
* - Go raw strings (backtick, when `rawBacktick`) do NOT process escapes, so a
|
|
45
|
+
* trailing backslash (`` `C:\` ``) must not swallow the closing delimiter.
|
|
46
|
+
*/
|
|
47
|
+
function skipQuoted(content, i, n, rawBacktick) {
|
|
48
|
+
const quote = content[i];
|
|
49
|
+
if (quote === "'") {
|
|
50
|
+
const limit = Math.min(n, i + 1 + 12);
|
|
51
|
+
let j = i + 1;
|
|
52
|
+
while (j < limit) {
|
|
53
|
+
if (content[j] === "\\") {
|
|
54
|
+
j += 2;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (content[j] === "'")
|
|
58
|
+
return j + 1;
|
|
59
|
+
j++;
|
|
60
|
+
}
|
|
61
|
+
return i + 1; // a lifetime / stray apostrophe, not a char literal
|
|
62
|
+
}
|
|
63
|
+
const escapes = !(quote === "`" && rawBacktick);
|
|
64
|
+
let j = i + 1;
|
|
65
|
+
while (j < n) {
|
|
66
|
+
if (escapes && content[j] === "\\") {
|
|
67
|
+
j += 2;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (content[j] === quote)
|
|
71
|
+
return j + 1;
|
|
72
|
+
j++;
|
|
73
|
+
}
|
|
74
|
+
return n;
|
|
75
|
+
}
|
|
36
76
|
/**
|
|
37
77
|
* Compute the comment spans (`[start, end)` offsets) of `content`, skipping over
|
|
38
78
|
* string literals so a comment marker inside a string is not treated as a
|
|
39
|
-
* comment. Spans are returned sorted and non-overlapping.
|
|
79
|
+
* comment. Spans are returned sorted and non-overlapping. `rawBacktick` (Go)
|
|
80
|
+
* makes backtick strings raw (no escapes).
|
|
40
81
|
*/
|
|
41
|
-
export function commentSpans(content, style) {
|
|
82
|
+
export function commentSpans(content, style, rawBacktick = false) {
|
|
42
83
|
const spans = [];
|
|
43
84
|
const n = content.length;
|
|
44
85
|
let i = 0;
|
|
45
86
|
while (i < n) {
|
|
46
87
|
const c = content[i];
|
|
47
|
-
// String /
|
|
88
|
+
// String / char literal: skip past it (lexically correct for lifetimes + raw strings).
|
|
48
89
|
if (c === '"' || c === "'" || c === "`") {
|
|
49
|
-
|
|
50
|
-
i++;
|
|
51
|
-
while (i < n) {
|
|
52
|
-
if (content[i] === "\\") {
|
|
53
|
-
i += 2;
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
if (content[i] === quote) {
|
|
57
|
-
i++;
|
|
58
|
-
break;
|
|
59
|
-
}
|
|
60
|
-
i++;
|
|
61
|
-
}
|
|
90
|
+
i = skipQuoted(content, i, n, rawBacktick);
|
|
62
91
|
continue;
|
|
63
92
|
}
|
|
64
93
|
if (style === "c" && c === "/" && content[i + 1] === "/") {
|
|
@@ -194,7 +223,7 @@ export function stripCommentFindings(findings, content, file) {
|
|
|
194
223
|
const style = commentStyleForFile(file);
|
|
195
224
|
if (!style)
|
|
196
225
|
return findings;
|
|
197
|
-
const spans = commentSpans(content, style);
|
|
226
|
+
const spans = commentSpans(content, style, /\.go$/i.test(file));
|
|
198
227
|
// Python docstrings suppress prose *token* rules but keep real PEM material.
|
|
199
228
|
const docSpans = style === "hash" ? pythonDocstringSpans(content) : [];
|
|
200
229
|
if (spans.length === 0 && docSpans.length === 0)
|
|
@@ -223,7 +252,7 @@ export function stripCommentFindings(findings, content, file) {
|
|
|
223
252
|
* suppress findings from IDENTIFIER-only rules (e.g. a Go `SigningMethodRS256`
|
|
224
253
|
* mentioned inside an error-message string) — the mirror of {@link commentSpans}.
|
|
225
254
|
*/
|
|
226
|
-
export function stringSpans(content, style) {
|
|
255
|
+
export function stringSpans(content, style, rawBacktick = false) {
|
|
227
256
|
const spans = [];
|
|
228
257
|
const n = content.length;
|
|
229
258
|
let i = 0;
|
|
@@ -249,21 +278,12 @@ export function stringSpans(content, style) {
|
|
|
249
278
|
continue;
|
|
250
279
|
}
|
|
251
280
|
if (c === '"' || c === "'" || c === "`") {
|
|
252
|
-
const quote = c;
|
|
253
281
|
const start = i;
|
|
254
|
-
i
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
}
|
|
260
|
-
if (content[i] === quote) {
|
|
261
|
-
i++;
|
|
262
|
-
break;
|
|
263
|
-
}
|
|
264
|
-
i++;
|
|
265
|
-
}
|
|
266
|
-
spans.push([start, i]);
|
|
282
|
+
const end = skipQuoted(content, i, n, rawBacktick);
|
|
283
|
+
// A lone `'` (Rust lifetime, end === start+1) is not a string; skip it.
|
|
284
|
+
if (c !== "'" || end > start + 1)
|
|
285
|
+
spans.push([start, end]);
|
|
286
|
+
i = end;
|
|
267
287
|
continue;
|
|
268
288
|
}
|
|
269
289
|
i++;
|
|
@@ -285,7 +305,7 @@ export function stripStringLiteralFindings(findings, content, file, ruleIds) {
|
|
|
285
305
|
const style = commentStyleForFile(file);
|
|
286
306
|
if (!style)
|
|
287
307
|
return findings;
|
|
288
|
-
const spans = stringSpans(content, style);
|
|
308
|
+
const spans = stringSpans(content, style, /\.go$/i.test(file));
|
|
289
309
|
if (spans.length === 0)
|
|
290
310
|
return findings;
|
|
291
311
|
const lineStarts = [0];
|
package/dist/comments.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"comments.js","sourceRoot":"","sources":["../src/comments.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Comment-aware false-positive suppression.\n *\n * The detectors are lexical (regex over source text), so a crypto API name\n * written in a COMMENT — `// migrated off createECDH()` — fires a finding just\n * like real code. This module computes the comment spans of a file (respecting\n * string literals, so a `//` inside a string is not a comment) and drops any\n * finding whose match starts inside one.\n *\n * The lexer is deliberately conservative: it only ever ENTERS a comment on a\n * literal line comment, block comment, or `#` in code position (all of which ARE\n * comments), so it never mis-classifies code as a comment and can only reduce\n * false positives, not recall. The one inherent lexical ambiguity — a regex\n * literal that looks like a block-comment open — is rare and guarded by the\n * detection benchmark's recall gate.\n */\nimport type { Finding } from \"./types.js\";\n\nexport type CommentStyle = \"c\" | \"hash\";\n\n/** C-style (line + block) comment languages, by extension. */\nconst C_LIKE: readonly string[] = [\n \".js\",\n \".jsx\",\n \".ts\",\n \".tsx\",\n \".mjs\",\n \".cjs\",\n \".vue\",\n \".svelte\",\n \".go\",\n \".java\",\n \".kt\",\n \".kts\",\n \".cs\",\n \".rs\",\n \".c\",\n \".h\",\n \".cc\",\n \".cpp\",\n \".cxx\",\n \".hpp\",\n \".hh\",\n];\n\n/** Hash-style (`#`) comment languages, by extension. */\nconst HASH_LIKE: readonly string[] = [\".py\", \".pyi\", \".pyw\", \".rb\"];\n\n/** The comment style for a file path, or null when we don't strip comments for it. */\nexport function commentStyleForFile(file: string): CommentStyle | null {\n const lower = file.toLowerCase();\n if (C_LIKE.some((e) => lower.endsWith(e))) return \"c\";\n if (HASH_LIKE.some((e) => lower.endsWith(e))) return \"hash\";\n return null;\n}\n\n/**\n * Compute the comment spans (`[start, end)` offsets) of `content`, skipping over\n * string literals so a comment marker inside a string is not treated as a\n * comment. Spans are returned sorted and non-overlapping.\n */\nexport function commentSpans(content: string, style: CommentStyle): Array<[number, number]> {\n const spans: Array<[number, number]> = [];\n const n = content.length;\n let i = 0;\n while (i < n) {\n const c = content[i];\n // String / template literal: skip to the matching unescaped delimiter.\n if (c === '\"' || c === \"'\" || c === \"`\") {\n const quote = c;\n i++;\n while (i < n) {\n if (content[i] === \"\\\\\") {\n i += 2;\n continue;\n }\n if (content[i] === quote) {\n i++;\n break;\n }\n i++;\n }\n continue;\n }\n if (style === \"c\" && c === \"/\" && content[i + 1] === \"/\") {\n const start = i;\n i += 2;\n while (i < n && content[i] !== \"\\n\") i++;\n spans.push([start, i]);\n continue;\n }\n if (style === \"c\" && c === \"/\" && content[i + 1] === \"*\") {\n const start = i;\n i += 2;\n while (i < n && !(content[i] === \"*\" && content[i + 1] === \"/\")) i++;\n i = Math.min(n, i + 2);\n spans.push([start, i]);\n continue;\n }\n if (style === \"hash\" && c === \"#\") {\n const start = i;\n i++;\n while (i < n && content[i] !== \"\\n\") i++;\n spans.push([start, i]);\n continue;\n }\n i++;\n }\n return spans;\n}\n\n/**\n * Compute Python triple-quoted string spans (`\"\"\"…\"\"\"` / `'''…'''`). These are\n * docstrings / prose in practice, so a crypto *name* mentioned inside one\n * (`:param key_type: eg \"ssh-ed25519\"`) should not fire a token finding — but a\n * PEM key pasted into one is still real material, so {@link stripCommentFindings}\n * exempts `pem-*` rules. Comments and normal strings are skipped so a `\"\"\"`\n * delimiter inside them is not mis-detected.\n */\nexport function pythonDocstringSpans(content: string): Array<[number, number]> {\n const spans: Array<[number, number]> = [];\n const n = content.length;\n let i = 0;\n while (i < n) {\n const c = content[i];\n if (c === \"#\") {\n i++;\n while (i < n && content[i] !== \"\\n\") i++;\n continue;\n }\n if ((c === '\"' || c === \"'\") && content[i + 1] === c && content[i + 2] === c) {\n const start = i;\n i += 3;\n while (i < n && !(content[i] === c && content[i + 1] === c && content[i + 2] === c)) i++;\n i = Math.min(n, i + 3);\n spans.push([start, i]);\n continue;\n }\n if (c === '\"' || c === \"'\") {\n const q = c;\n i++;\n while (i < n) {\n if (content[i] === \"\\\\\") {\n i += 2;\n continue;\n }\n if (content[i] === q) {\n i++;\n break;\n }\n i++;\n }\n continue;\n }\n i++;\n }\n return spans;\n}\n\n/** True if `offset` falls inside one of the (sorted, non-overlapping) spans. */\nexport function offsetInSpans(spans: ReadonlyArray<[number, number]>, offset: number): boolean {\n let lo = 0;\n let hi = spans.length - 1;\n while (lo <= hi) {\n const mid = (lo + hi) >>> 1;\n const [s, e] = spans[mid];\n if (offset < s) hi = mid - 1;\n else if (offset >= e) lo = mid + 1;\n else return true;\n }\n return false;\n}\n\n/**\n * Line numbers (1-based) suppressed by an inline ignore directive:\n * - `qscan-ignore-line` on a line suppresses findings on THAT line.\n * - `qscan-ignore-next-line` on a line suppresses findings on the NEXT line.\n * The directive text is matched anywhere on the line (usually in a comment), so\n * it is language-agnostic. `qscan-ignore-line` is not a substring of\n * `qscan-ignore-next-line`, so the two never collide.\n */\nexport function ignoredLines(content: string): Set<number> {\n const ignored = new Set<number>();\n const lines = content.split(\"\\n\");\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n if (line.includes(\"qscan-ignore-next-line\")) ignored.add(i + 2);\n else if (line.includes(\"qscan-ignore-line\")) ignored.add(i + 1);\n }\n return ignored;\n}\n\n/** Drop findings on lines suppressed by an inline `qscan-ignore` directive. */\nexport function stripIgnoredFindings(findings: Finding[], content: string): Finding[] {\n if (findings.length === 0 || !content.includes(\"qscan-ignore\")) return findings;\n const ignored = ignoredLines(content);\n if (ignored.size === 0) return findings;\n return findings.filter((f) => !ignored.has(f.location.line));\n}\n\n/**\n * Drop findings whose match starts inside a comment. No-op when the file's\n * language has no comment style we handle, or when it has no comments.\n */\nexport function stripCommentFindings(\n findings: Finding[],\n content: string,\n file: string,\n): Finding[] {\n if (findings.length === 0) return findings;\n const style = commentStyleForFile(file);\n if (!style) return findings;\n const spans = commentSpans(content, style);\n // Python docstrings suppress prose *token* rules but keep real PEM material.\n const docSpans = style === \"hash\" ? pythonDocstringSpans(content) : [];\n if (spans.length === 0 && docSpans.length === 0) return findings;\n\n // 1-based line → start offset, to turn a finding's (line, column) back into an\n // absolute offset (column is 1-based: offset = lineStart + column - 1).\n const lineStarts: number[] = [0];\n for (let i = 0; i < content.length; i++) {\n if (content[i] === \"\\n\") lineStarts.push(i + 1);\n }\n\n return findings.filter((f) => {\n const start = lineStarts[f.location.line - 1] ?? 0;\n const offset = start + ((f.location.column ?? 1) - 1);\n if (offsetInSpans(spans, offset)) return false;\n if (docSpans.length > 0 && !f.ruleId.startsWith(\"pem-\") && offsetInSpans(docSpans, offset)) {\n return false;\n }\n return true;\n });\n}\n\n/**\n * Compute the string-literal spans (`[start, end)`) of `content`, skipping over\n * comments so a quote inside a comment is not treated as a string. Used to\n * suppress findings from IDENTIFIER-only rules (e.g. a Go `SigningMethodRS256`\n * mentioned inside an error-message string) — the mirror of {@link commentSpans}.\n */\nexport function stringSpans(content: string, style: CommentStyle): Array<[number, number]> {\n const spans: Array<[number, number]> = [];\n const n = content.length;\n let i = 0;\n while (i < n) {\n const c = content[i];\n if (style === \"c\" && c === \"/\" && content[i + 1] === \"/\") {\n i += 2;\n while (i < n && content[i] !== \"\\n\") i++;\n continue;\n }\n if (style === \"c\" && c === \"/\" && content[i + 1] === \"*\") {\n i += 2;\n while (i < n && !(content[i] === \"*\" && content[i + 1] === \"/\")) i++;\n i = Math.min(n, i + 2);\n continue;\n }\n if (style === \"hash\" && c === \"#\") {\n i++;\n while (i < n && content[i] !== \"\\n\") i++;\n continue;\n }\n if (c === '\"' || c === \"'\" || c === \"`\") {\n const quote = c;\n const start = i;\n i++;\n while (i < n) {\n if (content[i] === \"\\\\\") {\n i += 2;\n continue;\n }\n if (content[i] === quote) {\n i++;\n break;\n }\n i++;\n }\n spans.push([start, i]);\n continue;\n }\n i++;\n }\n return spans;\n}\n\n/**\n * Drop findings of \"code-only\" rules (`ruleIds`) whose match starts inside a\n * string literal. Some rules — an identifier-form JWT signing method, a Go\n * `SigningMethodRS256` — are only meaningful as code; when the same token appears\n * inside a string (a test's `t.Error(\"SigningMethodPS256 …\")`) it is prose, not a\n * usage. Rules that legitimately match inside strings (quoted `\"RS256\"` alg\n * tokens, cipher-suite strings, ssh-key tokens) are NOT in `ruleIds` and pass\n * through untouched.\n */\nexport function stripStringLiteralFindings(\n findings: Finding[],\n content: string,\n file: string,\n ruleIds: ReadonlySet<string>,\n): Finding[] {\n if (findings.length === 0 || !findings.some((f) => ruleIds.has(f.ruleId))) return findings;\n const style = commentStyleForFile(file);\n if (!style) return findings;\n const spans = stringSpans(content, style);\n if (spans.length === 0) return findings;\n\n const lineStarts: number[] = [0];\n for (let i = 0; i < content.length; i++) {\n if (content[i] === \"\\n\") lineStarts.push(i + 1);\n }\n\n return findings.filter((f) => {\n if (!ruleIds.has(f.ruleId)) return true;\n const start = lineStarts[f.location.line - 1] ?? 0;\n const offset = start + ((f.location.column ?? 1) - 1);\n return !offsetInSpans(spans, offset);\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"comments.js","sourceRoot":"","sources":["../src/comments.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Comment-aware false-positive suppression.\n *\n * The detectors are lexical (regex over source text), so a crypto API name\n * written in a COMMENT — `// migrated off createECDH()` — fires a finding just\n * like real code. This module computes the comment spans of a file (respecting\n * string literals, so a `//` inside a string is not a comment) and drops any\n * finding whose match starts inside one.\n *\n * The lexer is deliberately conservative: it only ever ENTERS a comment on a\n * literal line comment, block comment, or `#` in code position (all of which ARE\n * comments), so it never mis-classifies code as a comment and can only reduce\n * false positives, not recall. The one inherent lexical ambiguity — a regex\n * literal that looks like a block-comment open — is rare and guarded by the\n * detection benchmark's recall gate.\n */\nimport type { Finding } from \"./types.js\";\n\nexport type CommentStyle = \"c\" | \"hash\";\n\n/** C-style (line + block) comment languages, by extension. */\nconst C_LIKE: readonly string[] = [\n \".js\",\n \".jsx\",\n \".ts\",\n \".tsx\",\n \".mjs\",\n \".cjs\",\n \".vue\",\n \".svelte\",\n \".go\",\n \".java\",\n \".kt\",\n \".kts\",\n \".cs\",\n \".rs\",\n \".c\",\n \".h\",\n \".cc\",\n \".cpp\",\n \".cxx\",\n \".hpp\",\n \".hh\",\n];\n\n/** Hash-style (`#`) comment languages, by extension. */\nconst HASH_LIKE: readonly string[] = [\".py\", \".pyi\", \".pyw\", \".rb\"];\n\n/** The comment style for a file path, or null when we don't strip comments for it. */\nexport function commentStyleForFile(file: string): CommentStyle | null {\n const lower = file.toLowerCase();\n if (C_LIKE.some((e) => lower.endsWith(e))) return \"c\";\n if (HASH_LIKE.some((e) => lower.endsWith(e))) return \"hash\";\n return null;\n}\n\n/**\n * Advance past a string / char literal starting at `content[i]` (a quote char);\n * returns the index just after it. Two correctness rules a naive escape-scan got\n * wrong (both verified as real bugs):\n * - a `'` char literal must close within a short window (`'\\u{10FFFF}'` is ~11\n * chars); otherwise it is a Rust lifetime (`'a`) or a stray apostrophe, NOT a\n * literal — return `i + 1` so the scan continues instead of consuming the\n * rest of the file.\n * - Go raw strings (backtick, when `rawBacktick`) do NOT process escapes, so a\n * trailing backslash (`` `C:\\` ``) must not swallow the closing delimiter.\n */\nfunction skipQuoted(content: string, i: number, n: number, rawBacktick: boolean): number {\n const quote = content[i];\n if (quote === \"'\") {\n const limit = Math.min(n, i + 1 + 12);\n let j = i + 1;\n while (j < limit) {\n if (content[j] === \"\\\\\") {\n j += 2;\n continue;\n }\n if (content[j] === \"'\") return j + 1;\n j++;\n }\n return i + 1; // a lifetime / stray apostrophe, not a char literal\n }\n const escapes = !(quote === \"`\" && rawBacktick);\n let j = i + 1;\n while (j < n) {\n if (escapes && content[j] === \"\\\\\") {\n j += 2;\n continue;\n }\n if (content[j] === quote) return j + 1;\n j++;\n }\n return n;\n}\n\n/**\n * Compute the comment spans (`[start, end)` offsets) of `content`, skipping over\n * string literals so a comment marker inside a string is not treated as a\n * comment. Spans are returned sorted and non-overlapping. `rawBacktick` (Go)\n * makes backtick strings raw (no escapes).\n */\nexport function commentSpans(\n content: string,\n style: CommentStyle,\n rawBacktick = false,\n): Array<[number, number]> {\n const spans: Array<[number, number]> = [];\n const n = content.length;\n let i = 0;\n while (i < n) {\n const c = content[i];\n // String / char literal: skip past it (lexically correct for lifetimes + raw strings).\n if (c === '\"' || c === \"'\" || c === \"`\") {\n i = skipQuoted(content, i, n, rawBacktick);\n continue;\n }\n if (style === \"c\" && c === \"/\" && content[i + 1] === \"/\") {\n const start = i;\n i += 2;\n while (i < n && content[i] !== \"\\n\") i++;\n spans.push([start, i]);\n continue;\n }\n if (style === \"c\" && c === \"/\" && content[i + 1] === \"*\") {\n const start = i;\n i += 2;\n while (i < n && !(content[i] === \"*\" && content[i + 1] === \"/\")) i++;\n i = Math.min(n, i + 2);\n spans.push([start, i]);\n continue;\n }\n if (style === \"hash\" && c === \"#\") {\n const start = i;\n i++;\n while (i < n && content[i] !== \"\\n\") i++;\n spans.push([start, i]);\n continue;\n }\n i++;\n }\n return spans;\n}\n\n/**\n * Compute Python triple-quoted string spans (`\"\"\"…\"\"\"` / `'''…'''`). These are\n * docstrings / prose in practice, so a crypto *name* mentioned inside one\n * (`:param key_type: eg \"ssh-ed25519\"`) should not fire a token finding — but a\n * PEM key pasted into one is still real material, so {@link stripCommentFindings}\n * exempts `pem-*` rules. Comments and normal strings are skipped so a `\"\"\"`\n * delimiter inside them is not mis-detected.\n */\nexport function pythonDocstringSpans(content: string): Array<[number, number]> {\n const spans: Array<[number, number]> = [];\n const n = content.length;\n let i = 0;\n while (i < n) {\n const c = content[i];\n if (c === \"#\") {\n i++;\n while (i < n && content[i] !== \"\\n\") i++;\n continue;\n }\n if ((c === '\"' || c === \"'\") && content[i + 1] === c && content[i + 2] === c) {\n const start = i;\n i += 3;\n while (i < n && !(content[i] === c && content[i + 1] === c && content[i + 2] === c)) i++;\n i = Math.min(n, i + 3);\n spans.push([start, i]);\n continue;\n }\n if (c === '\"' || c === \"'\") {\n const q = c;\n i++;\n while (i < n) {\n if (content[i] === \"\\\\\") {\n i += 2;\n continue;\n }\n if (content[i] === q) {\n i++;\n break;\n }\n i++;\n }\n continue;\n }\n i++;\n }\n return spans;\n}\n\n/** True if `offset` falls inside one of the (sorted, non-overlapping) spans. */\nexport function offsetInSpans(spans: ReadonlyArray<[number, number]>, offset: number): boolean {\n let lo = 0;\n let hi = spans.length - 1;\n while (lo <= hi) {\n const mid = (lo + hi) >>> 1;\n const [s, e] = spans[mid];\n if (offset < s) hi = mid - 1;\n else if (offset >= e) lo = mid + 1;\n else return true;\n }\n return false;\n}\n\n/**\n * Line numbers (1-based) suppressed by an inline ignore directive:\n * - `qscan-ignore-line` on a line suppresses findings on THAT line.\n * - `qscan-ignore-next-line` on a line suppresses findings on the NEXT line.\n * The directive text is matched anywhere on the line (usually in a comment), so\n * it is language-agnostic. `qscan-ignore-line` is not a substring of\n * `qscan-ignore-next-line`, so the two never collide.\n */\nexport function ignoredLines(content: string): Set<number> {\n const ignored = new Set<number>();\n const lines = content.split(\"\\n\");\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n if (line.includes(\"qscan-ignore-next-line\")) ignored.add(i + 2);\n else if (line.includes(\"qscan-ignore-line\")) ignored.add(i + 1);\n }\n return ignored;\n}\n\n/** Drop findings on lines suppressed by an inline `qscan-ignore` directive. */\nexport function stripIgnoredFindings(findings: Finding[], content: string): Finding[] {\n if (findings.length === 0 || !content.includes(\"qscan-ignore\")) return findings;\n const ignored = ignoredLines(content);\n if (ignored.size === 0) return findings;\n return findings.filter((f) => !ignored.has(f.location.line));\n}\n\n/**\n * Drop findings whose match starts inside a comment. No-op when the file's\n * language has no comment style we handle, or when it has no comments.\n */\nexport function stripCommentFindings(\n findings: Finding[],\n content: string,\n file: string,\n): Finding[] {\n if (findings.length === 0) return findings;\n const style = commentStyleForFile(file);\n if (!style) return findings;\n const spans = commentSpans(content, style, /\\.go$/i.test(file));\n // Python docstrings suppress prose *token* rules but keep real PEM material.\n const docSpans = style === \"hash\" ? pythonDocstringSpans(content) : [];\n if (spans.length === 0 && docSpans.length === 0) return findings;\n\n // 1-based line → start offset, to turn a finding's (line, column) back into an\n // absolute offset (column is 1-based: offset = lineStart + column - 1).\n const lineStarts: number[] = [0];\n for (let i = 0; i < content.length; i++) {\n if (content[i] === \"\\n\") lineStarts.push(i + 1);\n }\n\n return findings.filter((f) => {\n const start = lineStarts[f.location.line - 1] ?? 0;\n const offset = start + ((f.location.column ?? 1) - 1);\n if (offsetInSpans(spans, offset)) return false;\n if (docSpans.length > 0 && !f.ruleId.startsWith(\"pem-\") && offsetInSpans(docSpans, offset)) {\n return false;\n }\n return true;\n });\n}\n\n/**\n * Compute the string-literal spans (`[start, end)`) of `content`, skipping over\n * comments so a quote inside a comment is not treated as a string. Used to\n * suppress findings from IDENTIFIER-only rules (e.g. a Go `SigningMethodRS256`\n * mentioned inside an error-message string) — the mirror of {@link commentSpans}.\n */\nexport function stringSpans(\n content: string,\n style: CommentStyle,\n rawBacktick = false,\n): Array<[number, number]> {\n const spans: Array<[number, number]> = [];\n const n = content.length;\n let i = 0;\n while (i < n) {\n const c = content[i];\n if (style === \"c\" && c === \"/\" && content[i + 1] === \"/\") {\n i += 2;\n while (i < n && content[i] !== \"\\n\") i++;\n continue;\n }\n if (style === \"c\" && c === \"/\" && content[i + 1] === \"*\") {\n i += 2;\n while (i < n && !(content[i] === \"*\" && content[i + 1] === \"/\")) i++;\n i = Math.min(n, i + 2);\n continue;\n }\n if (style === \"hash\" && c === \"#\") {\n i++;\n while (i < n && content[i] !== \"\\n\") i++;\n continue;\n }\n if (c === '\"' || c === \"'\" || c === \"`\") {\n const start = i;\n const end = skipQuoted(content, i, n, rawBacktick);\n // A lone `'` (Rust lifetime, end === start+1) is not a string; skip it.\n if (c !== \"'\" || end > start + 1) spans.push([start, end]);\n i = end;\n continue;\n }\n i++;\n }\n return spans;\n}\n\n/**\n * Drop findings of \"code-only\" rules (`ruleIds`) whose match starts inside a\n * string literal. Some rules — an identifier-form JWT signing method, a Go\n * `SigningMethodRS256` — are only meaningful as code; when the same token appears\n * inside a string (a test's `t.Error(\"SigningMethodPS256 …\")`) it is prose, not a\n * usage. Rules that legitimately match inside strings (quoted `\"RS256\"` alg\n * tokens, cipher-suite strings, ssh-key tokens) are NOT in `ruleIds` and pass\n * through untouched.\n */\nexport function stripStringLiteralFindings(\n findings: Finding[],\n content: string,\n file: string,\n ruleIds: ReadonlySet<string>,\n): Finding[] {\n if (findings.length === 0 || !findings.some((f) => ruleIds.has(f.ruleId))) return findings;\n const style = commentStyleForFile(file);\n if (!style) return findings;\n const spans = stringSpans(content, style, /\\.go$/i.test(file));\n if (spans.length === 0) return findings;\n\n const lineStarts: number[] = [0];\n for (let i = 0; i < content.length; i++) {\n if (content[i] === \"\\n\") lineStarts.push(i + 1);\n }\n\n return findings.filter((f) => {\n if (!ruleIds.has(f.ruleId)) return true;\n const start = lineStarts[f.location.line - 1] ?? 0;\n const offset = start + ((f.location.column ?? 1) - 1);\n return !offsetInSpans(spans, offset);\n });\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dependencies.d.ts","sourceRoot":"","sources":["../src/dependencies.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAEV,mBAAmB,EACnB,OAAO,EACP,QAAQ,EACR,oBAAoB,EACrB,MAAM,YAAY,CAAC;AAKpB;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,EAAE,QAkBjC,CAAC;AAEF,iDAAiD;AACjD,eAAO,MAAM,sBAAsB,EAAE,oBAAoB,
|
|
1
|
+
{"version":3,"file":"dependencies.d.ts","sourceRoot":"","sources":["../src/dependencies.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAEV,mBAAmB,EACnB,OAAO,EACP,QAAQ,EACR,oBAAoB,EACrB,MAAM,YAAY,CAAC;AAKpB;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,EAAE,QAkBjC,CAAC;AAEF,iDAAiD;AACjD,eAAO,MAAM,sBAAsB,EAAE,oBAAoB,EA0jBxD,CAAC;AAuEF;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,mBAAmB,GAAG,IAAI,CAoB1E;AAED,+EAA+E;AAC/E,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpD;AAuID;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,EAAE,CAyBrE"}
|
package/dist/dependencies.js
CHANGED
|
@@ -471,7 +471,7 @@ export const vulnerableDependencies = [
|
|
|
471
471
|
{
|
|
472
472
|
name: "github.com/cloudflare/circl",
|
|
473
473
|
ecosystem: "go",
|
|
474
|
-
reason: "Cloudflare CIRCL — classical ECDH/EdDSA curves (X25519, X448, Ed25519, P-256).",
|
|
474
|
+
reason: "Cloudflare CIRCL — classical ECDH/EdDSA curves (X25519, X448, Ed25519, P-256); also ships PQC (ML-KEM/ML-DSA + hybrids), so migrate the classical *usage*, not the package.",
|
|
475
475
|
algorithms: ["ECDH", "EdDSA"],
|
|
476
476
|
severity: "medium",
|
|
477
477
|
},
|