@quantakrypto/core 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 +76 -27
- package/dist/cbom-merge.d.ts.map +1 -1
- package/dist/cbom-merge.js +30 -8
- package/dist/cbom-merge.js.map +1 -1
- package/dist/cbom.d.ts +5 -0
- package/dist/cbom.d.ts.map +1 -1
- package/dist/cbom.js +132 -37
- package/dist/cbom.js.map +1 -1
- package/dist/comments.d.ts +2 -23
- package/dist/comments.d.ts.map +1 -1
- package/dist/comments.js +24 -5
- package/dist/comments.js.map +1 -1
- package/dist/crypto-agility.d.ts +158 -0
- package/dist/crypto-agility.d.ts.map +1 -0
- package/dist/crypto-agility.js +285 -0
- package/dist/crypto-agility.js.map +1 -0
- package/dist/dependencies.d.ts.map +1 -1
- package/dist/dependencies.js +180 -18
- package/dist/dependencies.js.map +1 -1
- package/dist/detect-utils.d.ts +55 -21
- package/dist/detect-utils.d.ts.map +1 -1
- package/dist/detect-utils.js +133 -7
- package/dist/detect-utils.js.map +1 -1
- package/dist/detectors/age.d.ts +16 -0
- package/dist/detectors/age.d.ts.map +1 -0
- package/dist/detectors/age.js +40 -0
- package/dist/detectors/age.js.map +1 -0
- package/dist/detectors/ansible.d.ts +13 -0
- package/dist/detectors/ansible.d.ts.map +1 -0
- package/dist/detectors/ansible.js +108 -0
- package/dist/detectors/ansible.js.map +1 -0
- package/dist/detectors/bicep.d.ts +20 -0
- package/dist/detectors/bicep.d.ts.map +1 -0
- package/dist/detectors/bicep.js +74 -0
- package/dist/detectors/bicep.js.map +1 -0
- package/dist/detectors/c.d.ts.map +1 -1
- package/dist/detectors/c.js +30 -3
- package/dist/detectors/c.js.map +1 -1
- package/dist/detectors/cicd.d.ts.map +1 -1
- package/dist/detectors/cicd.js +18 -5
- package/dist/detectors/cicd.js.map +1 -1
- package/dist/detectors/cloud-kms.d.ts +8 -5
- package/dist/detectors/cloud-kms.d.ts.map +1 -1
- package/dist/detectors/cloud-kms.js +62 -20
- package/dist/detectors/cloud-kms.js.map +1 -1
- package/dist/detectors/cloudformation.d.ts +8 -0
- package/dist/detectors/cloudformation.d.ts.map +1 -1
- package/dist/detectors/cloudformation.js +41 -10
- package/dist/detectors/cloudformation.js.map +1 -1
- package/dist/detectors/codesign.d.ts +50 -0
- package/dist/detectors/codesign.d.ts.map +1 -0
- package/dist/detectors/codesign.js +143 -0
- package/dist/detectors/codesign.js.map +1 -0
- package/dist/detectors/csharp.d.ts.map +1 -1
- package/dist/detectors/csharp.js +13 -3
- package/dist/detectors/csharp.js.map +1 -1
- package/dist/detectors/dart.d.ts +35 -0
- package/dist/detectors/dart.d.ts.map +1 -0
- package/dist/detectors/dart.js +177 -0
- package/dist/detectors/dart.js.map +1 -0
- package/dist/detectors/database.d.ts +5 -3
- package/dist/detectors/database.d.ts.map +1 -1
- package/dist/detectors/database.js +45 -8
- package/dist/detectors/database.js.map +1 -1
- package/dist/detectors/dkim.d.ts +54 -0
- package/dist/detectors/dkim.d.ts.map +1 -0
- package/dist/detectors/dkim.js +92 -0
- package/dist/detectors/dkim.js.map +1 -0
- package/dist/detectors/dnssec.d.ts.map +1 -1
- package/dist/detectors/dnssec.js +19 -10
- package/dist/detectors/dnssec.js.map +1 -1
- package/dist/detectors/elixir.d.ts.map +1 -1
- package/dist/detectors/elixir.js +34 -2
- package/dist/detectors/elixir.js.map +1 -1
- package/dist/detectors/java.d.ts.map +1 -1
- package/dist/detectors/java.js +33 -3
- package/dist/detectors/java.js.map +1 -1
- package/dist/detectors/jose.d.ts.map +1 -1
- package/dist/detectors/jose.js +13 -3
- package/dist/detectors/jose.js.map +1 -1
- package/dist/detectors/jwk.d.ts +7 -7
- package/dist/detectors/jwk.d.ts.map +1 -1
- package/dist/detectors/jwk.js +44 -8
- package/dist/detectors/jwk.js.map +1 -1
- package/dist/detectors/k8s.d.ts.map +1 -1
- package/dist/detectors/k8s.js +5 -2
- package/dist/detectors/k8s.js.map +1 -1
- package/dist/detectors/keystore.d.ts +19 -0
- package/dist/detectors/keystore.d.ts.map +1 -0
- package/dist/detectors/keystore.js +76 -0
- package/dist/detectors/keystore.js.map +1 -0
- package/dist/detectors/mesh.d.ts +7 -8
- package/dist/detectors/mesh.d.ts.map +1 -1
- package/dist/detectors/mesh.js +17 -35
- package/dist/detectors/mesh.js.map +1 -1
- package/dist/detectors/messaging.d.ts +2 -2
- package/dist/detectors/messaging.d.ts.map +1 -1
- package/dist/detectors/messaging.js +19 -12
- package/dist/detectors/messaging.js.map +1 -1
- package/dist/detectors/objc.d.ts +41 -0
- package/dist/detectors/objc.d.ts.map +1 -0
- package/dist/detectors/objc.js +146 -0
- package/dist/detectors/objc.js.map +1 -0
- package/dist/detectors/openpgp.d.ts +20 -0
- package/dist/detectors/openpgp.d.ts.map +1 -0
- package/dist/detectors/openpgp.js +151 -0
- package/dist/detectors/openpgp.js.map +1 -0
- package/dist/detectors/pem.d.ts.map +1 -1
- package/dist/detectors/pem.js +32 -0
- package/dist/detectors/pem.js.map +1 -1
- package/dist/detectors/php.d.ts +2 -1
- package/dist/detectors/php.d.ts.map +1 -1
- package/dist/detectors/php.js +7 -3
- package/dist/detectors/php.js.map +1 -1
- package/dist/detectors/pkcs11.d.ts +17 -0
- package/dist/detectors/pkcs11.d.ts.map +1 -0
- package/dist/detectors/pkcs11.js +87 -0
- package/dist/detectors/pkcs11.js.map +1 -0
- package/dist/detectors/proxy.d.ts +52 -0
- package/dist/detectors/proxy.d.ts.map +1 -0
- package/dist/detectors/proxy.js +157 -0
- package/dist/detectors/proxy.js.map +1 -0
- package/dist/detectors/pulumi.d.ts +19 -0
- package/dist/detectors/pulumi.d.ts.map +1 -0
- package/dist/detectors/pulumi.js +80 -0
- package/dist/detectors/pulumi.js.map +1 -0
- package/dist/detectors/rust.d.ts.map +1 -1
- package/dist/detectors/rust.js +5 -0
- package/dist/detectors/rust.js.map +1 -1
- package/dist/detectors/secrets.d.ts +4 -1
- package/dist/detectors/secrets.d.ts.map +1 -1
- package/dist/detectors/secrets.js +5 -21
- package/dist/detectors/secrets.js.map +1 -1
- package/dist/detectors/solidity.d.ts +50 -0
- package/dist/detectors/solidity.d.ts.map +1 -0
- package/dist/detectors/solidity.js +108 -0
- package/dist/detectors/solidity.js.map +1 -0
- package/dist/detectors/source.d.ts.map +1 -1
- package/dist/detectors/source.js +105 -6
- package/dist/detectors/source.js.map +1 -1
- package/dist/detectors/spire.d.ts +49 -0
- package/dist/detectors/spire.d.ts.map +1 -0
- package/dist/detectors/spire.js +77 -0
- package/dist/detectors/spire.js.map +1 -0
- package/dist/detectors/ssh-ca.d.ts +72 -0
- package/dist/detectors/ssh-ca.d.ts.map +1 -0
- package/dist/detectors/ssh-ca.js +119 -0
- package/dist/detectors/ssh-ca.js.map +1 -0
- package/dist/detectors/supply-chain.d.ts +17 -0
- package/dist/detectors/supply-chain.d.ts.map +1 -0
- package/dist/detectors/supply-chain.js +89 -0
- package/dist/detectors/supply-chain.js.map +1 -0
- package/dist/detectors/swift.d.ts +22 -0
- package/dist/detectors/swift.d.ts.map +1 -0
- package/dist/detectors/swift.js +120 -0
- package/dist/detectors/swift.js.map +1 -0
- package/dist/detectors/terraform.d.ts.map +1 -1
- package/dist/detectors/terraform.js +35 -8
- package/dist/detectors/terraform.js.map +1 -1
- package/dist/detectors/vault.d.ts +15 -0
- package/dist/detectors/vault.d.ts.map +1 -0
- package/dist/detectors/vault.js +97 -0
- package/dist/detectors/vault.js.map +1 -0
- package/dist/detectors/vpn.d.ts +8 -8
- package/dist/detectors/vpn.d.ts.map +1 -1
- package/dist/detectors/vpn.js +12 -38
- package/dist/detectors/vpn.js.map +1 -1
- package/dist/detectors/weak-hash.d.ts +71 -0
- package/dist/detectors/weak-hash.d.ts.map +1 -0
- package/dist/detectors/weak-hash.js +116 -0
- package/dist/detectors/weak-hash.js.map +1 -0
- package/dist/detectors/webauthn.d.ts +61 -0
- package/dist/detectors/webauthn.d.ts.map +1 -0
- package/dist/detectors/webauthn.js +109 -0
- package/dist/detectors/webauthn.js.map +1 -0
- package/dist/detectors/xmldsig.d.ts +23 -0
- package/dist/detectors/xmldsig.d.ts.map +1 -0
- package/dist/detectors/xmldsig.js +84 -0
- package/dist/detectors/xmldsig.js.map +1 -0
- package/dist/evidence.d.ts +74 -4
- package/dist/evidence.d.ts.map +1 -1
- package/dist/evidence.js +71 -0
- package/dist/evidence.js.map +1 -1
- package/dist/hndl.d.ts +241 -0
- package/dist/hndl.d.ts.map +1 -0
- package/dist/hndl.js +752 -0
- package/dist/hndl.js.map +1 -0
- package/dist/index.d.ts +18 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -11
- package/dist/index.js.map +1 -1
- package/dist/inventory.d.ts +0 -2
- package/dist/inventory.d.ts.map +1 -1
- package/dist/inventory.js +15 -6
- package/dist/inventory.js.map +1 -1
- package/dist/parallel.d.ts.map +1 -1
- package/dist/parallel.js +22 -23
- package/dist/parallel.js.map +1 -1
- package/dist/registry.d.ts +9 -4
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +51 -4
- package/dist/registry.js.map +1 -1
- package/dist/remediate-pipeline.d.ts +0 -3
- package/dist/remediate-pipeline.d.ts.map +1 -1
- package/dist/remediate-pipeline.js +1 -1
- package/dist/remediate-pipeline.js.map +1 -1
- package/dist/remediation.d.ts +10 -0
- package/dist/remediation.d.ts.map +1 -1
- package/dist/remediation.js +32 -0
- package/dist/remediation.js.map +1 -1
- package/dist/report.d.ts +17 -0
- package/dist/report.d.ts.map +1 -1
- package/dist/report.js +106 -20
- package/dist/report.js.map +1 -1
- package/dist/scan-worker.js +5 -3
- package/dist/scan-worker.js.map +1 -1
- package/dist/scan.d.ts.map +1 -1
- package/dist/scan.js +35 -6
- package/dist/scan.js.map +1 -1
- package/dist/standards-profiles.d.ts +58 -0
- package/dist/standards-profiles.d.ts.map +1 -0
- package/dist/standards-profiles.js +100 -0
- package/dist/standards-profiles.js.map +1 -0
- package/dist/types.d.ts +7 -6
- 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/dist/vex.d.ts +39 -0
- package/dist/vex.d.ts.map +1 -0
- package/dist/vex.js +87 -0
- package/dist/vex.js.map +1 -0
- package/dist/walk.d.ts +39 -4
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +62 -10
- package/dist/walk.js.map +1 -1
- package/package.json +1 -1
- package/src/cbom-merge.ts +38 -8
- package/src/cbom.ts +153 -39
- package/src/comments.ts +25 -6
- package/src/crypto-agility.ts +407 -0
- package/src/dependencies.ts +182 -16
- package/src/detect-utils.ts +133 -11
- package/src/detectors/age.ts +60 -0
- package/src/detectors/ansible.ts +140 -0
- package/src/detectors/bicep.ts +107 -0
- package/src/detectors/c.ts +31 -3
- package/src/detectors/cicd.ts +18 -5
- package/src/detectors/cloud-kms.ts +79 -25
- package/src/detectors/cloudformation.ts +45 -10
- package/src/detectors/codesign.ts +214 -0
- package/src/detectors/csharp.ts +13 -3
- package/src/detectors/dart.ts +237 -0
- package/src/detectors/database.ts +64 -11
- package/src/detectors/dkim.ts +172 -0
- package/src/detectors/dnssec.ts +19 -10
- package/src/detectors/elixir.ts +33 -2
- package/src/detectors/java.ts +37 -3
- package/src/detectors/jose.ts +19 -3
- package/src/detectors/jwk.ts +59 -17
- package/src/detectors/k8s.ts +5 -2
- package/src/detectors/keystore.ts +109 -0
- package/src/detectors/mesh.ts +21 -45
- package/src/detectors/messaging.ts +22 -14
- package/src/detectors/objc.ts +209 -0
- package/src/detectors/openpgp.ts +194 -0
- package/src/detectors/pem.ts +30 -0
- package/src/detectors/php.ts +9 -4
- package/src/detectors/pkcs11.ts +113 -0
- package/src/detectors/proxy.ts +236 -0
- package/src/detectors/pulumi.ts +106 -0
- package/src/detectors/rust.ts +5 -0
- package/src/detectors/secrets.ts +15 -26
- package/src/detectors/solidity.ts +176 -0
- package/src/detectors/source.ts +110 -5
- package/src/detectors/spire.ts +143 -0
- package/src/detectors/ssh-ca.ts +215 -0
- package/src/detectors/supply-chain.ts +117 -0
- package/src/detectors/swift.ts +149 -0
- package/src/detectors/terraform.ts +42 -8
- package/src/detectors/vault.ts +120 -0
- package/src/detectors/vpn.ts +20 -48
- package/src/detectors/weak-hash.ts +217 -0
- package/src/detectors/webauthn.ts +205 -0
- package/src/detectors/xmldsig.ts +115 -0
- package/src/evidence.ts +127 -4
- package/src/hndl.ts +1012 -0
- package/src/index.ts +93 -13
- package/src/inventory.ts +15 -7
- package/src/parallel.ts +21 -20
- package/src/registry.ts +51 -4
- package/src/remediate-pipeline.ts +1 -1
- package/src/remediation.ts +39 -0
- package/src/report.ts +120 -20
- package/src/scan-worker.ts +5 -3
- package/src/scan.ts +41 -6
- package/src/standards-profiles.ts +136 -0
- package/src/types.ts +18 -5
- package/src/version.ts +1 -1
- package/src/vex.ts +122 -0
- package/src/walk.ts +80 -12
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Crypto-agility manifest: an agent-consumable crypto-posture document.
|
|
3
|
+
*
|
|
4
|
+
* A small, versioned JSON manifest a project publishes at a well-known URL
|
|
5
|
+
* (`/.well-known/crypto-agility.json`, see docs/CRYPTO-AGILITY-MANIFEST.md) so any
|
|
6
|
+
* agent, scanner, or CI bot can read the project's cryptographic posture the way it
|
|
7
|
+
* reads `security.txt` or `robots.txt`. It is a compact, decision-oriented summary
|
|
8
|
+
* DERIVED from a qScan result: the readiness score, the count of quantum-vulnerable
|
|
9
|
+
* findings by severity, the CBOM's algorithm families, an optional attestation URL,
|
|
10
|
+
* and the migration-policy deadlines.
|
|
11
|
+
*
|
|
12
|
+
* This module owns two pure operations and their shared schema:
|
|
13
|
+
* - {@link buildCryptoAgilityManifest}: derive a manifest from a {@link ScanResult}.
|
|
14
|
+
* - {@link validateCryptoAgilityManifest}: check an untrusted value against the
|
|
15
|
+
* schema, returning a list of problems (used by the local validator; a network
|
|
16
|
+
* fetch-and-validate lives on the website, never in this offline-boundary tool).
|
|
17
|
+
*
|
|
18
|
+
* Zero dependencies; hand-rolled JSON shape and validation so the package stays
|
|
19
|
+
* runtime-dependency-free.
|
|
20
|
+
*/
|
|
21
|
+
import { toCbom } from "./cbom.js";
|
|
22
|
+
import { PQC_STANDARDS } from "./standards.js";
|
|
23
|
+
import { SEVERITY_ORDER } from "./severity.js";
|
|
24
|
+
import { VERSION } from "./version.js";
|
|
25
|
+
/**
|
|
26
|
+
* Schema version of the manifest this module emits and validates. Bumped only on a
|
|
27
|
+
* breaking change to the manifest shape; consumers key their parsing off it.
|
|
28
|
+
*/
|
|
29
|
+
export const CRYPTO_AGILITY_MANIFEST_VERSION = 1;
|
|
30
|
+
/** The conventional path a manifest is served from, relative to the site origin. */
|
|
31
|
+
export const CRYPTO_AGILITY_WELL_KNOWN_PATH = "/.well-known/crypto-agility.json";
|
|
32
|
+
/**
|
|
33
|
+
* Build a crypto-agility manifest from a scan result. Pure: every runtime input
|
|
34
|
+
* (timestamp, attestation, hybrid-KEX assertion, policy) arrives via `opts`.
|
|
35
|
+
*
|
|
36
|
+
* The posture and CBOM summary are derived straight from the scan's inventory and
|
|
37
|
+
* its CycloneDX CBOM, so the manifest can never disagree with the full `--cbom` /
|
|
38
|
+
* `--format json` outputs of the same scan.
|
|
39
|
+
*/
|
|
40
|
+
export function buildCryptoAgilityManifest(result, opts) {
|
|
41
|
+
const cbom = toCbom(result);
|
|
42
|
+
const inv = result.inventory;
|
|
43
|
+
const algorithmFamilies = Object.entries(inv.byAlgorithm)
|
|
44
|
+
.filter(([, count]) => (count ?? 0) > 0)
|
|
45
|
+
.map(([family, count]) => ({
|
|
46
|
+
family,
|
|
47
|
+
count: count,
|
|
48
|
+
// Every family the detectors surface is classical, hence quantum-vulnerable
|
|
49
|
+
// by construction (mirrors cbom.ts `isQuantumVulnerable`).
|
|
50
|
+
quantumVulnerable: true,
|
|
51
|
+
}))
|
|
52
|
+
.sort((a, b) => b.count - a.count || a.family.localeCompare(b.family));
|
|
53
|
+
const bySeverity = { ...inv.bySeverity };
|
|
54
|
+
// Default policy: the NIST IR 8547 transition timeline. An operator crypto policy
|
|
55
|
+
// (`--policy`) does not override the standards deadlines but layers its own
|
|
56
|
+
// `transitionDeadline` on top and re-labels the source as operator-declared.
|
|
57
|
+
const timeline = PQC_STANDARDS.transitionTimeline;
|
|
58
|
+
const policy = {
|
|
59
|
+
source: opts.policy ? "operator-declared" : timeline.source,
|
|
60
|
+
deprecateClassicalAfter: timeline.deprecateAfter,
|
|
61
|
+
disallowClassicalAfter: timeline.disallowAfter,
|
|
62
|
+
transitionDeadline: opts.policy?.transitionDeadline ?? null,
|
|
63
|
+
citation: timeline.source,
|
|
64
|
+
};
|
|
65
|
+
const manifest = {
|
|
66
|
+
version: CRYPTO_AGILITY_MANIFEST_VERSION,
|
|
67
|
+
manifestType: "crypto-agility",
|
|
68
|
+
generatedAt: opts.generatedAt,
|
|
69
|
+
generator: { name: "qScan", version: result.toolVersion || VERSION },
|
|
70
|
+
subject: {
|
|
71
|
+
root: result.root,
|
|
72
|
+
repository: opts.repository ?? null,
|
|
73
|
+
commit: opts.commit ?? null,
|
|
74
|
+
},
|
|
75
|
+
posture: {
|
|
76
|
+
readinessScore: inv.readinessScore,
|
|
77
|
+
hybridKexInUse: opts.hybridKexInUse ?? null,
|
|
78
|
+
quantumVulnerable: {
|
|
79
|
+
total: result.findings.length,
|
|
80
|
+
bySeverity,
|
|
81
|
+
},
|
|
82
|
+
hndlExposedCount: inv.hndlCount,
|
|
83
|
+
},
|
|
84
|
+
cbomSummary: {
|
|
85
|
+
serialNumber: cbom.serialNumber,
|
|
86
|
+
assetCount: cbom.components.length,
|
|
87
|
+
algorithmFamilies,
|
|
88
|
+
},
|
|
89
|
+
...(opts.attestationUrl ? { attestation: { url: opts.attestationUrl } } : {}),
|
|
90
|
+
policy,
|
|
91
|
+
};
|
|
92
|
+
return manifest;
|
|
93
|
+
}
|
|
94
|
+
/** True for a plain (non-array, non-null) object. */
|
|
95
|
+
function isObject(v) {
|
|
96
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Validate an untrusted value against the crypto-agility manifest schema.
|
|
100
|
+
*
|
|
101
|
+
* Checks structure, required fields, types, and the schema version. Purely local:
|
|
102
|
+
* it parses an already-in-memory value and NEVER performs I/O or network fetches;
|
|
103
|
+
* a fetch-and-validate of a remote manifest is a website-side concern, deliberately
|
|
104
|
+
* kept out of this offline-boundary tool. Returns every problem it finds rather than
|
|
105
|
+
* throwing on the first, so a caller can report them all at once.
|
|
106
|
+
*/
|
|
107
|
+
export function validateCryptoAgilityManifest(value) {
|
|
108
|
+
const errors = [];
|
|
109
|
+
const err = (m) => void errors.push(m);
|
|
110
|
+
if (!isObject(value)) {
|
|
111
|
+
return { valid: false, errors: ["manifest must be a JSON object"] };
|
|
112
|
+
}
|
|
113
|
+
// version
|
|
114
|
+
if (value.version === undefined) {
|
|
115
|
+
err("missing required field: version");
|
|
116
|
+
}
|
|
117
|
+
else if (typeof value.version !== "number" || !Number.isInteger(value.version)) {
|
|
118
|
+
err("version must be an integer");
|
|
119
|
+
}
|
|
120
|
+
else if (value.version !== CRYPTO_AGILITY_MANIFEST_VERSION) {
|
|
121
|
+
err(`unsupported version ${value.version} (this validator understands version ${CRYPTO_AGILITY_MANIFEST_VERSION})`);
|
|
122
|
+
}
|
|
123
|
+
// manifestType
|
|
124
|
+
if (value.manifestType === undefined) {
|
|
125
|
+
err("missing required field: manifestType");
|
|
126
|
+
}
|
|
127
|
+
else if (value.manifestType !== "crypto-agility") {
|
|
128
|
+
err('manifestType must be the string "crypto-agility"');
|
|
129
|
+
}
|
|
130
|
+
// generatedAt
|
|
131
|
+
if (value.generatedAt === undefined) {
|
|
132
|
+
err("missing required field: generatedAt");
|
|
133
|
+
}
|
|
134
|
+
else if (typeof value.generatedAt !== "string" || value.generatedAt.length === 0) {
|
|
135
|
+
err("generatedAt must be a non-empty ISO 8601 string");
|
|
136
|
+
}
|
|
137
|
+
else if (Number.isNaN(Date.parse(value.generatedAt))) {
|
|
138
|
+
err("generatedAt must be a parseable ISO 8601 timestamp");
|
|
139
|
+
}
|
|
140
|
+
// generator
|
|
141
|
+
if (value.generator === undefined) {
|
|
142
|
+
err("missing required field: generator");
|
|
143
|
+
}
|
|
144
|
+
else if (!isObject(value.generator)) {
|
|
145
|
+
err("generator must be an object");
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
if (typeof value.generator.name !== "string")
|
|
149
|
+
err("generator.name must be a string");
|
|
150
|
+
if (typeof value.generator.version !== "string")
|
|
151
|
+
err("generator.version must be a string");
|
|
152
|
+
}
|
|
153
|
+
// subject
|
|
154
|
+
if (value.subject === undefined) {
|
|
155
|
+
err("missing required field: subject");
|
|
156
|
+
}
|
|
157
|
+
else if (!isObject(value.subject)) {
|
|
158
|
+
err("subject must be an object");
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
if (typeof value.subject.root !== "string")
|
|
162
|
+
err("subject.root must be a string");
|
|
163
|
+
if (value.subject.repository !== null && typeof value.subject.repository !== "string") {
|
|
164
|
+
err("subject.repository must be a string or null");
|
|
165
|
+
}
|
|
166
|
+
if (value.subject.commit !== null && typeof value.subject.commit !== "string") {
|
|
167
|
+
err("subject.commit must be a string or null");
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// posture
|
|
171
|
+
validatePosture(value.posture, err);
|
|
172
|
+
// cbomSummary
|
|
173
|
+
validateCbomSummary(value.cbomSummary, err);
|
|
174
|
+
// attestation (optional)
|
|
175
|
+
if (value.attestation !== undefined) {
|
|
176
|
+
if (!isObject(value.attestation)) {
|
|
177
|
+
err("attestation must be an object when present");
|
|
178
|
+
}
|
|
179
|
+
else if (typeof value.attestation.url !== "string" || value.attestation.url.length === 0) {
|
|
180
|
+
err("attestation.url must be a non-empty string");
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// policy
|
|
184
|
+
validatePolicy(value.policy, err);
|
|
185
|
+
return { valid: errors.length === 0, errors };
|
|
186
|
+
}
|
|
187
|
+
/** Validate the `posture` block. */
|
|
188
|
+
function validatePosture(posture, err) {
|
|
189
|
+
if (posture === undefined) {
|
|
190
|
+
err("missing required field: posture");
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (!isObject(posture)) {
|
|
194
|
+
err("posture must be an object");
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (typeof posture.readinessScore !== "number" ||
|
|
198
|
+
posture.readinessScore < 0 ||
|
|
199
|
+
posture.readinessScore > 100) {
|
|
200
|
+
err("posture.readinessScore must be a number between 0 and 100");
|
|
201
|
+
}
|
|
202
|
+
if (posture.hybridKexInUse !== null && typeof posture.hybridKexInUse !== "boolean") {
|
|
203
|
+
err("posture.hybridKexInUse must be a boolean or null");
|
|
204
|
+
}
|
|
205
|
+
if (typeof posture.hndlExposedCount !== "number" || posture.hndlExposedCount < 0) {
|
|
206
|
+
err("posture.hndlExposedCount must be a non-negative number");
|
|
207
|
+
}
|
|
208
|
+
const qv = posture.quantumVulnerable;
|
|
209
|
+
if (!isObject(qv)) {
|
|
210
|
+
err("posture.quantumVulnerable must be an object");
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
if (typeof qv.total !== "number" || qv.total < 0) {
|
|
214
|
+
err("posture.quantumVulnerable.total must be a non-negative number");
|
|
215
|
+
}
|
|
216
|
+
if (!isObject(qv.bySeverity)) {
|
|
217
|
+
err("posture.quantumVulnerable.bySeverity must be an object");
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
for (const sev of SEVERITY_ORDER) {
|
|
221
|
+
if (typeof qv.bySeverity[sev] !== "number") {
|
|
222
|
+
err(`posture.quantumVulnerable.bySeverity.${sev} must be a number`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
/** Validate the `cbomSummary` block. */
|
|
228
|
+
function validateCbomSummary(summary, err) {
|
|
229
|
+
if (summary === undefined) {
|
|
230
|
+
err("missing required field: cbomSummary");
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (!isObject(summary)) {
|
|
234
|
+
err("cbomSummary must be an object");
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
if (typeof summary.serialNumber !== "string")
|
|
238
|
+
err("cbomSummary.serialNumber must be a string");
|
|
239
|
+
if (typeof summary.assetCount !== "number" || summary.assetCount < 0) {
|
|
240
|
+
err("cbomSummary.assetCount must be a non-negative number");
|
|
241
|
+
}
|
|
242
|
+
if (!Array.isArray(summary.algorithmFamilies)) {
|
|
243
|
+
err("cbomSummary.algorithmFamilies must be an array");
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
summary.algorithmFamilies.forEach((fam, i) => {
|
|
247
|
+
if (!isObject(fam)) {
|
|
248
|
+
err(`cbomSummary.algorithmFamilies[${i}] must be an object`);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (typeof fam.family !== "string")
|
|
252
|
+
err(`cbomSummary.algorithmFamilies[${i}].family must be a string`);
|
|
253
|
+
if (typeof fam.count !== "number" || fam.count < 0) {
|
|
254
|
+
err(`cbomSummary.algorithmFamilies[${i}].count must be a non-negative number`);
|
|
255
|
+
}
|
|
256
|
+
if (typeof fam.quantumVulnerable !== "boolean") {
|
|
257
|
+
err(`cbomSummary.algorithmFamilies[${i}].quantumVulnerable must be a boolean`);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
/** Validate the `policy` block. */
|
|
262
|
+
function validatePolicy(policy, err) {
|
|
263
|
+
if (policy === undefined) {
|
|
264
|
+
err("missing required field: policy");
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (!isObject(policy)) {
|
|
268
|
+
err("policy must be an object");
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (typeof policy.source !== "string")
|
|
272
|
+
err("policy.source must be a string");
|
|
273
|
+
if (typeof policy.deprecateClassicalAfter !== "number") {
|
|
274
|
+
err("policy.deprecateClassicalAfter must be a number (year)");
|
|
275
|
+
}
|
|
276
|
+
if (typeof policy.disallowClassicalAfter !== "number") {
|
|
277
|
+
err("policy.disallowClassicalAfter must be a number (year)");
|
|
278
|
+
}
|
|
279
|
+
if (policy.transitionDeadline !== null && typeof policy.transitionDeadline !== "string") {
|
|
280
|
+
err("policy.transitionDeadline must be a string or null");
|
|
281
|
+
}
|
|
282
|
+
if (typeof policy.citation !== "string")
|
|
283
|
+
err("policy.citation must be a string");
|
|
284
|
+
}
|
|
285
|
+
//# sourceMappingURL=crypto-agility.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto-agility.js","sourceRoot":"","sources":["../src/crypto-agility.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Crypto-agility manifest: an agent-consumable crypto-posture document.\n *\n * A small, versioned JSON manifest a project publishes at a well-known URL\n * (`/.well-known/crypto-agility.json`, see docs/CRYPTO-AGILITY-MANIFEST.md) so any\n * agent, scanner, or CI bot can read the project's cryptographic posture the way it\n * reads `security.txt` or `robots.txt`. It is a compact, decision-oriented summary\n * DERIVED from a qScan result: the readiness score, the count of quantum-vulnerable\n * findings by severity, the CBOM's algorithm families, an optional attestation URL,\n * and the migration-policy deadlines.\n *\n * This module owns two pure operations and their shared schema:\n * - {@link buildCryptoAgilityManifest}: derive a manifest from a {@link ScanResult}.\n * - {@link validateCryptoAgilityManifest}: check an untrusted value against the\n * schema, returning a list of problems (used by the local validator; a network\n * fetch-and-validate lives on the website, never in this offline-boundary tool).\n *\n * Zero dependencies; hand-rolled JSON shape and validation so the package stays\n * runtime-dependency-free.\n */\n\nimport { toCbom } from \"./cbom.js\";\nimport { PQC_STANDARDS } from \"./standards.js\";\nimport { SEVERITY_ORDER } from \"./severity.js\";\nimport type { CryptoPolicy } from \"./policy.js\";\nimport type { ScanResult, Severity } from \"./types.js\";\nimport { VERSION } from \"./version.js\";\n\n/**\n * Schema version of the manifest this module emits and validates. Bumped only on a\n * breaking change to the manifest shape; consumers key their parsing off it.\n */\nexport const CRYPTO_AGILITY_MANIFEST_VERSION = 1;\n\n/** The conventional path a manifest is served from, relative to the site origin. */\nexport const CRYPTO_AGILITY_WELL_KNOWN_PATH = \"/.well-known/crypto-agility.json\";\n\n/** A single algorithm family in use, mirroring the CBOM's grouping. */\nexport interface CryptoAgilityFamily {\n /** The algorithm family (e.g. `RSA`, `ECDH`, `ECDSA`), or `unknown`. */\n family: string;\n /** How many findings referenced this family. */\n count: number;\n /** Always true today: every detected family is classical (Shor-broken). */\n quantumVulnerable: boolean;\n}\n\n/** The project's cryptographic posture, distilled from the scan inventory. */\nexport interface CryptoAgilityPosture {\n /** qScan readiness score, 0–100 (100 = no classical asymmetric crypto found). */\n readinessScore: number;\n /**\n * Whether the project negotiates hybrid post-quantum key exchange. qScan is a\n * static scanner and cannot observe a negotiated TLS group, so this is `null`\n * (\"not determined by this generator\") unless the operator asserts it\n * (`--hybrid-kex` / `--no-hybrid-kex`). A live probe (qProbe) or the website can\n * fill it authoritatively.\n */\n hybridKexInUse: boolean | null;\n /** Count of quantum-vulnerable findings, total and broken down by severity. */\n quantumVulnerable: {\n total: number;\n bySeverity: Record<Severity, number>;\n };\n /** How many findings are exposed to harvest-now-decrypt-later. */\n hndlExposedCount: number;\n}\n\n/** A compact summary of the full CBOM, linking to it by serial number. */\nexport interface CryptoAgilityCbomSummary {\n /** The CycloneDX `serialNumber` of the full CBOM this summary was derived from. */\n serialNumber: string;\n /** Number of distinct cryptographic-asset components in the full CBOM. */\n assetCount: number;\n /** Algorithm families in use, most-referenced first. */\n algorithmFamilies: CryptoAgilityFamily[];\n}\n\n/** The migration policy the project declares it is measured against. */\nexport interface CryptoAgilityPolicy {\n /** Human label for the policy source (a standard name, or `operator-declared`). */\n source: string;\n /** Year after which classical public-key crypto is deprecated. */\n deprecateClassicalAfter: number;\n /** Year after which classical public-key crypto is disallowed. */\n disallowClassicalAfter: number;\n /** Operator-declared migration deadline (ISO date / year), or `null`. */\n transitionDeadline: string | null;\n /** Citation for the deadlines (spec / publication). */\n citation: string;\n}\n\n/** The full crypto-agility manifest. */\nexport interface CryptoAgilityManifest {\n /** Manifest schema version ({@link CRYPTO_AGILITY_MANIFEST_VERSION}). */\n version: number;\n /** Discriminator so a consumer can tell this document apart from other JSON. */\n manifestType: \"crypto-agility\";\n /** ISO 8601 timestamp the manifest was generated (caller-supplied). */\n generatedAt: string;\n /** What produced the manifest. */\n generator: { name: string; version: string };\n /** What the manifest describes. */\n subject: { root: string; repository: string | null; commit: string | null };\n /** The distilled cryptographic posture. */\n posture: CryptoAgilityPosture;\n /** A compact summary of the CBOM. */\n cbomSummary: CryptoAgilityCbomSummary;\n /** Optional link to a quantakrypto (or other) posture credential. */\n attestation?: { url: string };\n /** The declared migration policy / deadlines. */\n policy: CryptoAgilityPolicy;\n}\n\n/** Inputs the CLI/runtime supplies that are not derivable from the scan. */\nexport interface CryptoAgilityManifestOptions {\n /**\n * ISO 8601 generation timestamp. The caller supplies it (`Date.now()` is fine in\n * the CLI runtime) so the builder itself stays pure and deterministic.\n */\n generatedAt: string;\n /** Optional URL to a posture credential (recorded verbatim, never fetched here). */\n attestationUrl?: string;\n /**\n * Operator assertion of hybrid-KEX use. `undefined` leaves the manifest's\n * `hybridKexInUse` as `null` (not determined).\n */\n hybridKexInUse?: boolean;\n /** Optional org crypto policy; its `transitionDeadline` overlays the defaults. */\n policy?: CryptoPolicy;\n /** Repository URL (e.g. `GITHUB_REPOSITORY`); omitted → null. */\n repository?: string;\n /** Commit SHA (e.g. `GITHUB_SHA`); omitted → null. */\n commit?: string;\n}\n\n/**\n * Build a crypto-agility manifest from a scan result. Pure: every runtime input\n * (timestamp, attestation, hybrid-KEX assertion, policy) arrives via `opts`.\n *\n * The posture and CBOM summary are derived straight from the scan's inventory and\n * its CycloneDX CBOM, so the manifest can never disagree with the full `--cbom` /\n * `--format json` outputs of the same scan.\n */\nexport function buildCryptoAgilityManifest(\n result: ScanResult,\n opts: CryptoAgilityManifestOptions,\n): CryptoAgilityManifest {\n const cbom = toCbom(result);\n const inv = result.inventory;\n\n const algorithmFamilies: CryptoAgilityFamily[] = Object.entries(inv.byAlgorithm)\n .filter(([, count]) => (count ?? 0) > 0)\n .map(([family, count]) => ({\n family,\n count: count as number,\n // Every family the detectors surface is classical, hence quantum-vulnerable\n // by construction (mirrors cbom.ts `isQuantumVulnerable`).\n quantumVulnerable: true,\n }))\n .sort((a, b) => b.count - a.count || a.family.localeCompare(b.family));\n\n const bySeverity = { ...inv.bySeverity };\n\n // Default policy: the NIST IR 8547 transition timeline. An operator crypto policy\n // (`--policy`) does not override the standards deadlines but layers its own\n // `transitionDeadline` on top and re-labels the source as operator-declared.\n const timeline = PQC_STANDARDS.transitionTimeline;\n const policy: CryptoAgilityPolicy = {\n source: opts.policy ? \"operator-declared\" : timeline.source,\n deprecateClassicalAfter: timeline.deprecateAfter,\n disallowClassicalAfter: timeline.disallowAfter,\n transitionDeadline: opts.policy?.transitionDeadline ?? null,\n citation: timeline.source,\n };\n\n const manifest: CryptoAgilityManifest = {\n version: CRYPTO_AGILITY_MANIFEST_VERSION,\n manifestType: \"crypto-agility\",\n generatedAt: opts.generatedAt,\n generator: { name: \"qScan\", version: result.toolVersion || VERSION },\n subject: {\n root: result.root,\n repository: opts.repository ?? null,\n commit: opts.commit ?? null,\n },\n posture: {\n readinessScore: inv.readinessScore,\n hybridKexInUse: opts.hybridKexInUse ?? null,\n quantumVulnerable: {\n total: result.findings.length,\n bySeverity,\n },\n hndlExposedCount: inv.hndlCount,\n },\n cbomSummary: {\n serialNumber: cbom.serialNumber,\n assetCount: cbom.components.length,\n algorithmFamilies,\n },\n ...(opts.attestationUrl ? { attestation: { url: opts.attestationUrl } } : {}),\n policy,\n };\n return manifest;\n}\n\n/** Outcome of {@link validateCryptoAgilityManifest}. */\nexport interface ManifestValidation {\n /** True when the value satisfies the manifest schema. */\n valid: boolean;\n /** One human-readable message per problem found (empty when `valid`). */\n errors: string[];\n}\n\n/** True for a plain (non-array, non-null) object. */\nfunction isObject(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n\n/**\n * Validate an untrusted value against the crypto-agility manifest schema.\n *\n * Checks structure, required fields, types, and the schema version. Purely local:\n * it parses an already-in-memory value and NEVER performs I/O or network fetches;\n * a fetch-and-validate of a remote manifest is a website-side concern, deliberately\n * kept out of this offline-boundary tool. Returns every problem it finds rather than\n * throwing on the first, so a caller can report them all at once.\n */\nexport function validateCryptoAgilityManifest(value: unknown): ManifestValidation {\n const errors: string[] = [];\n const err = (m: string): void => void errors.push(m);\n\n if (!isObject(value)) {\n return { valid: false, errors: [\"manifest must be a JSON object\"] };\n }\n\n // version\n if (value.version === undefined) {\n err(\"missing required field: version\");\n } else if (typeof value.version !== \"number\" || !Number.isInteger(value.version)) {\n err(\"version must be an integer\");\n } else if (value.version !== CRYPTO_AGILITY_MANIFEST_VERSION) {\n err(\n `unsupported version ${value.version} (this validator understands version ${CRYPTO_AGILITY_MANIFEST_VERSION})`,\n );\n }\n\n // manifestType\n if (value.manifestType === undefined) {\n err(\"missing required field: manifestType\");\n } else if (value.manifestType !== \"crypto-agility\") {\n err('manifestType must be the string \"crypto-agility\"');\n }\n\n // generatedAt\n if (value.generatedAt === undefined) {\n err(\"missing required field: generatedAt\");\n } else if (typeof value.generatedAt !== \"string\" || value.generatedAt.length === 0) {\n err(\"generatedAt must be a non-empty ISO 8601 string\");\n } else if (Number.isNaN(Date.parse(value.generatedAt))) {\n err(\"generatedAt must be a parseable ISO 8601 timestamp\");\n }\n\n // generator\n if (value.generator === undefined) {\n err(\"missing required field: generator\");\n } else if (!isObject(value.generator)) {\n err(\"generator must be an object\");\n } else {\n if (typeof value.generator.name !== \"string\") err(\"generator.name must be a string\");\n if (typeof value.generator.version !== \"string\") err(\"generator.version must be a string\");\n }\n\n // subject\n if (value.subject === undefined) {\n err(\"missing required field: subject\");\n } else if (!isObject(value.subject)) {\n err(\"subject must be an object\");\n } else {\n if (typeof value.subject.root !== \"string\") err(\"subject.root must be a string\");\n if (value.subject.repository !== null && typeof value.subject.repository !== \"string\") {\n err(\"subject.repository must be a string or null\");\n }\n if (value.subject.commit !== null && typeof value.subject.commit !== \"string\") {\n err(\"subject.commit must be a string or null\");\n }\n }\n\n // posture\n validatePosture(value.posture, err);\n\n // cbomSummary\n validateCbomSummary(value.cbomSummary, err);\n\n // attestation (optional)\n if (value.attestation !== undefined) {\n if (!isObject(value.attestation)) {\n err(\"attestation must be an object when present\");\n } else if (typeof value.attestation.url !== \"string\" || value.attestation.url.length === 0) {\n err(\"attestation.url must be a non-empty string\");\n }\n }\n\n // policy\n validatePolicy(value.policy, err);\n\n return { valid: errors.length === 0, errors };\n}\n\n/** Validate the `posture` block. */\nfunction validatePosture(posture: unknown, err: (m: string) => void): void {\n if (posture === undefined) {\n err(\"missing required field: posture\");\n return;\n }\n if (!isObject(posture)) {\n err(\"posture must be an object\");\n return;\n }\n if (\n typeof posture.readinessScore !== \"number\" ||\n posture.readinessScore < 0 ||\n posture.readinessScore > 100\n ) {\n err(\"posture.readinessScore must be a number between 0 and 100\");\n }\n if (posture.hybridKexInUse !== null && typeof posture.hybridKexInUse !== \"boolean\") {\n err(\"posture.hybridKexInUse must be a boolean or null\");\n }\n if (typeof posture.hndlExposedCount !== \"number\" || posture.hndlExposedCount < 0) {\n err(\"posture.hndlExposedCount must be a non-negative number\");\n }\n const qv = posture.quantumVulnerable;\n if (!isObject(qv)) {\n err(\"posture.quantumVulnerable must be an object\");\n return;\n }\n if (typeof qv.total !== \"number\" || qv.total < 0) {\n err(\"posture.quantumVulnerable.total must be a non-negative number\");\n }\n if (!isObject(qv.bySeverity)) {\n err(\"posture.quantumVulnerable.bySeverity must be an object\");\n } else {\n for (const sev of SEVERITY_ORDER) {\n if (typeof qv.bySeverity[sev] !== \"number\") {\n err(`posture.quantumVulnerable.bySeverity.${sev} must be a number`);\n }\n }\n }\n}\n\n/** Validate the `cbomSummary` block. */\nfunction validateCbomSummary(summary: unknown, err: (m: string) => void): void {\n if (summary === undefined) {\n err(\"missing required field: cbomSummary\");\n return;\n }\n if (!isObject(summary)) {\n err(\"cbomSummary must be an object\");\n return;\n }\n if (typeof summary.serialNumber !== \"string\") err(\"cbomSummary.serialNumber must be a string\");\n if (typeof summary.assetCount !== \"number\" || summary.assetCount < 0) {\n err(\"cbomSummary.assetCount must be a non-negative number\");\n }\n if (!Array.isArray(summary.algorithmFamilies)) {\n err(\"cbomSummary.algorithmFamilies must be an array\");\n return;\n }\n summary.algorithmFamilies.forEach((fam, i) => {\n if (!isObject(fam)) {\n err(`cbomSummary.algorithmFamilies[${i}] must be an object`);\n return;\n }\n if (typeof fam.family !== \"string\")\n err(`cbomSummary.algorithmFamilies[${i}].family must be a string`);\n if (typeof fam.count !== \"number\" || fam.count < 0) {\n err(`cbomSummary.algorithmFamilies[${i}].count must be a non-negative number`);\n }\n if (typeof fam.quantumVulnerable !== \"boolean\") {\n err(`cbomSummary.algorithmFamilies[${i}].quantumVulnerable must be a boolean`);\n }\n });\n}\n\n/** Validate the `policy` block. */\nfunction validatePolicy(policy: unknown, err: (m: string) => void): void {\n if (policy === undefined) {\n err(\"missing required field: policy\");\n return;\n }\n if (!isObject(policy)) {\n err(\"policy must be an object\");\n return;\n }\n if (typeof policy.source !== \"string\") err(\"policy.source must be a string\");\n if (typeof policy.deprecateClassicalAfter !== \"number\") {\n err(\"policy.deprecateClassicalAfter must be a number (year)\");\n }\n if (typeof policy.disallowClassicalAfter !== \"number\") {\n err(\"policy.disallowClassicalAfter must be a number (year)\");\n }\n if (policy.transitionDeadline !== null && typeof policy.transitionDeadline !== \"string\") {\n err(\"policy.transitionDeadline must be a string or null\");\n }\n if (typeof policy.citation !== \"string\") err(\"policy.citation must be a string\");\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,EA6pBxD,CAAC;AAwEF;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,mBAAmB,GAAG,IAAI,CAqB1E;AAED,+EAA+E;AAC/E,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpD;AAqJD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,EAAE,CAyBrE"}
|
package/dist/dependencies.js
CHANGED
|
@@ -373,6 +373,13 @@ export const vulnerableDependencies = [
|
|
|
373
373
|
algorithms: ["X25519", "EdDSA"],
|
|
374
374
|
severity: "low",
|
|
375
375
|
},
|
|
376
|
+
{
|
|
377
|
+
name: "tink",
|
|
378
|
+
ecosystem: "pypi",
|
|
379
|
+
reason: "Google Tink (Python) — classical RSA/ECDSA/Ed25519 signatures and ECIES hybrid encryption.",
|
|
380
|
+
algorithms: ["RSA", "ECDSA", "EdDSA", "ECIES"],
|
|
381
|
+
severity: "high",
|
|
382
|
+
},
|
|
376
383
|
// --- crates.io (Rust) ---
|
|
377
384
|
{
|
|
378
385
|
name: "rsa",
|
|
@@ -482,6 +489,20 @@ export const vulnerableDependencies = [
|
|
|
482
489
|
algorithms: ["ECDSA", "ECDH"],
|
|
483
490
|
severity: "medium",
|
|
484
491
|
},
|
|
492
|
+
{
|
|
493
|
+
name: "github.com/tink-crypto/tink-go/v2",
|
|
494
|
+
ecosystem: "go",
|
|
495
|
+
reason: "Google Tink (Go) — classical RSA/ECDSA/Ed25519 signatures and ECIES hybrid encryption.",
|
|
496
|
+
algorithms: ["RSA", "ECDSA", "EdDSA", "ECIES"],
|
|
497
|
+
severity: "high",
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
name: "github.com/google/tink/go",
|
|
501
|
+
ecosystem: "go",
|
|
502
|
+
reason: "Google Tink (legacy Go module) — classical RSA/ECDSA/Ed25519 and ECIES hybrid.",
|
|
503
|
+
algorithms: ["RSA", "ECDSA", "EdDSA", "ECIES"],
|
|
504
|
+
severity: "high",
|
|
505
|
+
},
|
|
485
506
|
// --- Maven (Java) ---
|
|
486
507
|
{
|
|
487
508
|
name: "bcprov-jdk18on",
|
|
@@ -527,6 +548,13 @@ export const vulnerableDependencies = [
|
|
|
527
548
|
severity: "medium",
|
|
528
549
|
hndl: false,
|
|
529
550
|
},
|
|
551
|
+
{
|
|
552
|
+
name: "tink",
|
|
553
|
+
ecosystem: "maven",
|
|
554
|
+
reason: "Google Tink (com.google.crypto.tink) — classical RSA/ECDSA/Ed25519 signatures and ECIES hybrid encryption.",
|
|
555
|
+
algorithms: ["RSA", "ECDSA", "EdDSA", "ECIES"],
|
|
556
|
+
severity: "high",
|
|
557
|
+
},
|
|
530
558
|
// --- RubyGems ---
|
|
531
559
|
{
|
|
532
560
|
name: "jwt",
|
|
@@ -588,13 +616,81 @@ export const vulnerableDependencies = [
|
|
|
588
616
|
severity: "medium",
|
|
589
617
|
hndl: false,
|
|
590
618
|
},
|
|
619
|
+
// --- Composer (PHP) ---
|
|
620
|
+
{
|
|
621
|
+
name: "phpseclib/phpseclib",
|
|
622
|
+
ecosystem: "composer",
|
|
623
|
+
reason: "Pure-PHP RSA / DSA / ECDSA / Diffie-Hellman public-key crypto and X.509.",
|
|
624
|
+
algorithms: ["RSA", "DSA", "ECDSA", "DH"],
|
|
625
|
+
severity: "high",
|
|
626
|
+
},
|
|
627
|
+
{
|
|
628
|
+
name: "paragonie/sodium_compat",
|
|
629
|
+
ecosystem: "composer",
|
|
630
|
+
reason: "Pure-PHP libsodium polyfill: X25519 key agreement and Ed25519 signatures.",
|
|
631
|
+
algorithms: ["X25519", "EdDSA"],
|
|
632
|
+
severity: "low",
|
|
633
|
+
},
|
|
634
|
+
{
|
|
635
|
+
name: "paragonie/halite",
|
|
636
|
+
ecosystem: "composer",
|
|
637
|
+
reason: "libsodium wrapper: X25519 key agreement and Ed25519 signatures.",
|
|
638
|
+
algorithms: ["X25519", "EdDSA"],
|
|
639
|
+
severity: "low",
|
|
640
|
+
},
|
|
641
|
+
{
|
|
642
|
+
name: "paragonie/paseto",
|
|
643
|
+
ecosystem: "composer",
|
|
644
|
+
reason: "PASETO public tokens signed with classical Ed25519 (v2/v4) or RSA.",
|
|
645
|
+
algorithms: ["EdDSA", "RSA"],
|
|
646
|
+
severity: "medium",
|
|
647
|
+
hndl: false,
|
|
648
|
+
},
|
|
649
|
+
{
|
|
650
|
+
name: "firebase/php-jwt",
|
|
651
|
+
ecosystem: "composer",
|
|
652
|
+
reason: "PHP JWT signing/verification with classical RS*/ES* algorithms.",
|
|
653
|
+
algorithms: ["RSA", "ECDSA"],
|
|
654
|
+
severity: "medium",
|
|
655
|
+
hndl: false,
|
|
656
|
+
},
|
|
657
|
+
{
|
|
658
|
+
name: "lcobucci/jwt",
|
|
659
|
+
ecosystem: "composer",
|
|
660
|
+
reason: "PHP JWT with classical RSA/ECDSA signature algorithms.",
|
|
661
|
+
algorithms: ["RSA", "ECDSA"],
|
|
662
|
+
severity: "medium",
|
|
663
|
+
hndl: false,
|
|
664
|
+
},
|
|
665
|
+
{
|
|
666
|
+
name: "web-token/jwt-framework",
|
|
667
|
+
ecosystem: "composer",
|
|
668
|
+
reason: "JOSE (JWS/JWE) framework: classical RSA/ECDSA signatures and ECDH-ES key agreement.",
|
|
669
|
+
algorithms: ["RSA", "ECDSA", "ECDH"],
|
|
670
|
+
severity: "medium",
|
|
671
|
+
},
|
|
672
|
+
{
|
|
673
|
+
name: "mdanter/ecc",
|
|
674
|
+
ecosystem: "composer",
|
|
675
|
+
reason: "Pure-PHP elliptic-curve ECDSA signatures and ECDH key agreement.",
|
|
676
|
+
algorithms: ["ECDSA", "ECDH"],
|
|
677
|
+
severity: "high",
|
|
678
|
+
},
|
|
679
|
+
{
|
|
680
|
+
name: "simplito/elliptic-php",
|
|
681
|
+
ecosystem: "composer",
|
|
682
|
+
reason: "secp256k1 / NIST-curve ECDSA and ECDH in pure PHP (blockchain keys).",
|
|
683
|
+
algorithms: ["ECDSA", "ECDH"],
|
|
684
|
+
severity: "high",
|
|
685
|
+
},
|
|
591
686
|
];
|
|
592
687
|
/**
|
|
593
688
|
* Normalise a package name for matching within its ecosystem. PyPI is
|
|
594
689
|
* case-insensitive and folds runs of `-_.` to a single `-` (PEP 503); cargo /
|
|
595
|
-
* maven / rubygems / nuget are effectively lower-case (NuGet ids are
|
|
596
|
-
* case-insensitively
|
|
597
|
-
* and go paths are
|
|
690
|
+
* maven / rubygems / nuget / composer are effectively lower-case (NuGet ids are
|
|
691
|
+
* matched case-insensitively; Composer `vendor/package` names are lower-cased by
|
|
692
|
+
* Packagist); npm and go module paths are matched verbatim (npm scopes and go
|
|
693
|
+
* paths are case-sensitive).
|
|
598
694
|
*/
|
|
599
695
|
function normalizeName(ecosystem, name) {
|
|
600
696
|
const n = name.trim();
|
|
@@ -687,6 +783,8 @@ export function manifestEcosystem(file) {
|
|
|
687
783
|
return "rubygems";
|
|
688
784
|
if (base === "packages.config" || base === "directory.packages.props" || base.endsWith(".csproj"))
|
|
689
785
|
return "nuget";
|
|
786
|
+
if (base === "composer.json" || base === "composer.lock")
|
|
787
|
+
return "composer";
|
|
690
788
|
return null;
|
|
691
789
|
}
|
|
692
790
|
/** True if a file path looks like a manifest we can parse for dependencies. */
|
|
@@ -771,6 +869,18 @@ function candidateNames(ecosystem, content) {
|
|
|
771
869
|
}
|
|
772
870
|
break;
|
|
773
871
|
}
|
|
872
|
+
case "composer": {
|
|
873
|
+
// composer.json `require`/`require-dev` maps are keyed by "vendor/package";
|
|
874
|
+
// composer.lock lists `{"name": "vendor/package", …}`. Both are JSON, so
|
|
875
|
+
// grab every quoted `vendor/package` token (a lower-case vendor + package
|
|
876
|
+
// separated by a single `/`). PHP platform requirements ("php", "ext-*")
|
|
877
|
+
// carry no slash and so are naturally excluded; the curated DB filters the
|
|
878
|
+
// rest, so over-capture is safe.
|
|
879
|
+
for (const m of content.matchAll(/["']([a-z0-9](?:[a-z0-9._-]*[a-z0-9])?\/[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?)["']/gi)) {
|
|
880
|
+
names.push(m[1]);
|
|
881
|
+
}
|
|
882
|
+
break;
|
|
883
|
+
}
|
|
774
884
|
case "npm":
|
|
775
885
|
break; // npm uses the JSON path, not this extractor.
|
|
776
886
|
}
|
|
@@ -858,10 +968,32 @@ export function scanManifest(file, content) {
|
|
|
858
968
|
}
|
|
859
969
|
return sortByTitle(findings);
|
|
860
970
|
}
|
|
971
|
+
/**
|
|
972
|
+
* Resolve an npm `npm:` alias spec to the real package it installs, or null when
|
|
973
|
+
* the spec is not an alias. An aliased dependency records the alias as the map
|
|
974
|
+
* KEY and `npm:<real-name>@<range>` as the VALUE (in package.json and in a
|
|
975
|
+
* lockfile entry's `version`), so the vulnerable package hides behind a benign
|
|
976
|
+
* alias name. Keeps a leading scope `@` (only a version `@` past index 0 is a
|
|
977
|
+
* separator): `npm:node-forge@^1` → `node-forge`, `npm:@scope/pkg@1` → `@scope/pkg`.
|
|
978
|
+
*/
|
|
979
|
+
function npmAliasTarget(spec) {
|
|
980
|
+
if (!spec.startsWith("npm:"))
|
|
981
|
+
return null;
|
|
982
|
+
const rest = spec.slice(4);
|
|
983
|
+
const at = rest.lastIndexOf("@");
|
|
984
|
+
const name = at > 0 ? rest.slice(0, at) : rest;
|
|
985
|
+
return name || null;
|
|
986
|
+
}
|
|
987
|
+
/** Guard against a pathological / hostile lockfile nesting the walk unbounded. */
|
|
988
|
+
const NPM_TREE_MAX_DEPTH = 64;
|
|
861
989
|
/**
|
|
862
990
|
* npm manifest scan.
|
|
863
|
-
* - `package.json`: dependencies / devDependencies / peerDependencies / optionalDependencies
|
|
864
|
-
*
|
|
991
|
+
* - `package.json`: dependencies / devDependencies / peerDependencies / optionalDependencies,
|
|
992
|
+
* including `npm:`-aliased entries (the alias key hides the real package name).
|
|
993
|
+
* - `package-lock.json` v1: the NESTED `dependencies` tree, where each entry may carry
|
|
994
|
+
* its own `dependencies` (transitive deps) and an `npm:` alias in `version`.
|
|
995
|
+
* - `package-lock.json` v2/v3: the flat `packages` map keys (node_modules/<name>),
|
|
996
|
+
* plus each entry's own `name` field (set for aliased installs).
|
|
865
997
|
*/
|
|
866
998
|
function scanNpmManifest(content, file, db) {
|
|
867
999
|
let json;
|
|
@@ -875,28 +1007,58 @@ function scanNpmManifest(content, file, db) {
|
|
|
875
1007
|
return [];
|
|
876
1008
|
const found = new Set();
|
|
877
1009
|
const obj = json;
|
|
878
|
-
const
|
|
879
|
-
if (
|
|
1010
|
+
const add = (name) => {
|
|
1011
|
+
if (db.has(name))
|
|
1012
|
+
found.add(name);
|
|
1013
|
+
};
|
|
1014
|
+
/**
|
|
1015
|
+
* Walk a dependency map that may be either a flat package.json map
|
|
1016
|
+
* (name → version-range string) or a legacy package-lock v1 tree (name →
|
|
1017
|
+
* `{ version, requires, dependencies }`, arbitrarily nested). Matches keys,
|
|
1018
|
+
* resolves `npm:` aliases from string values and entry `version` fields, and
|
|
1019
|
+
* recurses into a nested `dependencies` sub-tree.
|
|
1020
|
+
*/
|
|
1021
|
+
const walkDepTree = (deps, depth) => {
|
|
1022
|
+
if (depth > NPM_TREE_MAX_DEPTH || deps === null || typeof deps !== "object")
|
|
880
1023
|
return;
|
|
881
|
-
for (const
|
|
882
|
-
|
|
883
|
-
|
|
1024
|
+
for (const [name, node] of Object.entries(deps)) {
|
|
1025
|
+
add(name);
|
|
1026
|
+
if (typeof node === "string") {
|
|
1027
|
+
// package.json form: the value is a range, possibly `npm:<real>@<range>`.
|
|
1028
|
+
const aliased = npmAliasTarget(node);
|
|
1029
|
+
if (aliased)
|
|
1030
|
+
add(aliased);
|
|
1031
|
+
}
|
|
1032
|
+
else if (node !== null && typeof node === "object") {
|
|
1033
|
+
const rec = node;
|
|
1034
|
+
if (typeof rec.version === "string") {
|
|
1035
|
+
const aliased = npmAliasTarget(rec.version);
|
|
1036
|
+
if (aliased)
|
|
1037
|
+
add(aliased);
|
|
1038
|
+
}
|
|
1039
|
+
walkDepTree(rec.dependencies, depth + 1); // v1 nests transitive deps here
|
|
1040
|
+
}
|
|
884
1041
|
}
|
|
885
1042
|
};
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
1043
|
+
walkDepTree(obj.dependencies, 0);
|
|
1044
|
+
walkDepTree(obj.devDependencies, 0);
|
|
1045
|
+
walkDepTree(obj.peerDependencies, 0);
|
|
1046
|
+
walkDepTree(obj.optionalDependencies, 0);
|
|
890
1047
|
const packages = obj.packages;
|
|
891
1048
|
if (packages !== null && typeof packages === "object") {
|
|
892
|
-
for (const key of Object.
|
|
1049
|
+
for (const [key, entry] of Object.entries(packages)) {
|
|
893
1050
|
if (!key)
|
|
894
1051
|
continue; // root package entry
|
|
895
1052
|
const marker = "node_modules/";
|
|
896
1053
|
const idx = key.lastIndexOf(marker);
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
1054
|
+
add(idx >= 0 ? key.slice(idx + marker.length) : key);
|
|
1055
|
+
// Aliased install: the entry's own `name` field is the real package, which
|
|
1056
|
+
// differs from the alias embedded in the node_modules/<alias> key.
|
|
1057
|
+
if (entry !== null && typeof entry === "object") {
|
|
1058
|
+
const realName = entry.name;
|
|
1059
|
+
if (typeof realName === "string")
|
|
1060
|
+
add(realName);
|
|
1061
|
+
}
|
|
900
1062
|
}
|
|
901
1063
|
}
|
|
902
1064
|
const findings = [];
|