@quantakrypto/core 0.5.0 → 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.
Files changed (46) hide show
  1. package/dist/crypto-agility.d.ts +158 -0
  2. package/dist/crypto-agility.d.ts.map +1 -0
  3. package/dist/crypto-agility.js +285 -0
  4. package/dist/crypto-agility.js.map +1 -0
  5. package/dist/dependencies.d.ts.map +1 -1
  6. package/dist/dependencies.js +180 -18
  7. package/dist/dependencies.js.map +1 -1
  8. package/dist/detectors/source.d.ts.map +1 -1
  9. package/dist/detectors/source.js +52 -0
  10. package/dist/detectors/source.js.map +1 -1
  11. package/dist/hndl.d.ts +241 -0
  12. package/dist/hndl.d.ts.map +1 -0
  13. package/dist/hndl.js +752 -0
  14. package/dist/hndl.js.map +1 -0
  15. package/dist/index.d.ts +4 -0
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +4 -0
  18. package/dist/index.js.map +1 -1
  19. package/dist/parallel.d.ts.map +1 -1
  20. package/dist/parallel.js +22 -23
  21. package/dist/parallel.js.map +1 -1
  22. package/dist/report.d.ts +8 -0
  23. package/dist/report.d.ts.map +1 -1
  24. package/dist/report.js +75 -19
  25. package/dist/report.js.map +1 -1
  26. package/dist/types.d.ts +1 -1
  27. package/dist/types.d.ts.map +1 -1
  28. package/dist/types.js.map +1 -1
  29. package/dist/version.d.ts +1 -1
  30. package/dist/version.js +1 -1
  31. package/dist/version.js.map +1 -1
  32. package/dist/walk.d.ts +31 -1
  33. package/dist/walk.d.ts.map +1 -1
  34. package/dist/walk.js +30 -9
  35. package/dist/walk.js.map +1 -1
  36. package/package.json +1 -1
  37. package/src/crypto-agility.ts +407 -0
  38. package/src/dependencies.ts +182 -16
  39. package/src/detectors/source.ts +62 -0
  40. package/src/hndl.ts +1012 -0
  41. package/src/index.ts +52 -0
  42. package/src/parallel.ts +21 -20
  43. package/src/report.ts +84 -19
  44. package/src/types.ts +9 -1
  45. package/src/version.ts +1 -1
  46. package/src/walk.ts +47 -10
@@ -0,0 +1,158 @@
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 type { CryptoPolicy } from "./policy.js";
22
+ import type { ScanResult, Severity } from "./types.js";
23
+ /**
24
+ * Schema version of the manifest this module emits and validates. Bumped only on a
25
+ * breaking change to the manifest shape; consumers key their parsing off it.
26
+ */
27
+ export declare const CRYPTO_AGILITY_MANIFEST_VERSION = 1;
28
+ /** The conventional path a manifest is served from, relative to the site origin. */
29
+ export declare const CRYPTO_AGILITY_WELL_KNOWN_PATH = "/.well-known/crypto-agility.json";
30
+ /** A single algorithm family in use, mirroring the CBOM's grouping. */
31
+ export interface CryptoAgilityFamily {
32
+ /** The algorithm family (e.g. `RSA`, `ECDH`, `ECDSA`), or `unknown`. */
33
+ family: string;
34
+ /** How many findings referenced this family. */
35
+ count: number;
36
+ /** Always true today: every detected family is classical (Shor-broken). */
37
+ quantumVulnerable: boolean;
38
+ }
39
+ /** The project's cryptographic posture, distilled from the scan inventory. */
40
+ export interface CryptoAgilityPosture {
41
+ /** qScan readiness score, 0–100 (100 = no classical asymmetric crypto found). */
42
+ readinessScore: number;
43
+ /**
44
+ * Whether the project negotiates hybrid post-quantum key exchange. qScan is a
45
+ * static scanner and cannot observe a negotiated TLS group, so this is `null`
46
+ * ("not determined by this generator") unless the operator asserts it
47
+ * (`--hybrid-kex` / `--no-hybrid-kex`). A live probe (qProbe) or the website can
48
+ * fill it authoritatively.
49
+ */
50
+ hybridKexInUse: boolean | null;
51
+ /** Count of quantum-vulnerable findings, total and broken down by severity. */
52
+ quantumVulnerable: {
53
+ total: number;
54
+ bySeverity: Record<Severity, number>;
55
+ };
56
+ /** How many findings are exposed to harvest-now-decrypt-later. */
57
+ hndlExposedCount: number;
58
+ }
59
+ /** A compact summary of the full CBOM, linking to it by serial number. */
60
+ export interface CryptoAgilityCbomSummary {
61
+ /** The CycloneDX `serialNumber` of the full CBOM this summary was derived from. */
62
+ serialNumber: string;
63
+ /** Number of distinct cryptographic-asset components in the full CBOM. */
64
+ assetCount: number;
65
+ /** Algorithm families in use, most-referenced first. */
66
+ algorithmFamilies: CryptoAgilityFamily[];
67
+ }
68
+ /** The migration policy the project declares it is measured against. */
69
+ export interface CryptoAgilityPolicy {
70
+ /** Human label for the policy source (a standard name, or `operator-declared`). */
71
+ source: string;
72
+ /** Year after which classical public-key crypto is deprecated. */
73
+ deprecateClassicalAfter: number;
74
+ /** Year after which classical public-key crypto is disallowed. */
75
+ disallowClassicalAfter: number;
76
+ /** Operator-declared migration deadline (ISO date / year), or `null`. */
77
+ transitionDeadline: string | null;
78
+ /** Citation for the deadlines (spec / publication). */
79
+ citation: string;
80
+ }
81
+ /** The full crypto-agility manifest. */
82
+ export interface CryptoAgilityManifest {
83
+ /** Manifest schema version ({@link CRYPTO_AGILITY_MANIFEST_VERSION}). */
84
+ version: number;
85
+ /** Discriminator so a consumer can tell this document apart from other JSON. */
86
+ manifestType: "crypto-agility";
87
+ /** ISO 8601 timestamp the manifest was generated (caller-supplied). */
88
+ generatedAt: string;
89
+ /** What produced the manifest. */
90
+ generator: {
91
+ name: string;
92
+ version: string;
93
+ };
94
+ /** What the manifest describes. */
95
+ subject: {
96
+ root: string;
97
+ repository: string | null;
98
+ commit: string | null;
99
+ };
100
+ /** The distilled cryptographic posture. */
101
+ posture: CryptoAgilityPosture;
102
+ /** A compact summary of the CBOM. */
103
+ cbomSummary: CryptoAgilityCbomSummary;
104
+ /** Optional link to a quantakrypto (or other) posture credential. */
105
+ attestation?: {
106
+ url: string;
107
+ };
108
+ /** The declared migration policy / deadlines. */
109
+ policy: CryptoAgilityPolicy;
110
+ }
111
+ /** Inputs the CLI/runtime supplies that are not derivable from the scan. */
112
+ export interface CryptoAgilityManifestOptions {
113
+ /**
114
+ * ISO 8601 generation timestamp. The caller supplies it (`Date.now()` is fine in
115
+ * the CLI runtime) so the builder itself stays pure and deterministic.
116
+ */
117
+ generatedAt: string;
118
+ /** Optional URL to a posture credential (recorded verbatim, never fetched here). */
119
+ attestationUrl?: string;
120
+ /**
121
+ * Operator assertion of hybrid-KEX use. `undefined` leaves the manifest's
122
+ * `hybridKexInUse` as `null` (not determined).
123
+ */
124
+ hybridKexInUse?: boolean;
125
+ /** Optional org crypto policy; its `transitionDeadline` overlays the defaults. */
126
+ policy?: CryptoPolicy;
127
+ /** Repository URL (e.g. `GITHUB_REPOSITORY`); omitted → null. */
128
+ repository?: string;
129
+ /** Commit SHA (e.g. `GITHUB_SHA`); omitted → null. */
130
+ commit?: string;
131
+ }
132
+ /**
133
+ * Build a crypto-agility manifest from a scan result. Pure: every runtime input
134
+ * (timestamp, attestation, hybrid-KEX assertion, policy) arrives via `opts`.
135
+ *
136
+ * The posture and CBOM summary are derived straight from the scan's inventory and
137
+ * its CycloneDX CBOM, so the manifest can never disagree with the full `--cbom` /
138
+ * `--format json` outputs of the same scan.
139
+ */
140
+ export declare function buildCryptoAgilityManifest(result: ScanResult, opts: CryptoAgilityManifestOptions): CryptoAgilityManifest;
141
+ /** Outcome of {@link validateCryptoAgilityManifest}. */
142
+ export interface ManifestValidation {
143
+ /** True when the value satisfies the manifest schema. */
144
+ valid: boolean;
145
+ /** One human-readable message per problem found (empty when `valid`). */
146
+ errors: string[];
147
+ }
148
+ /**
149
+ * Validate an untrusted value against the crypto-agility manifest schema.
150
+ *
151
+ * Checks structure, required fields, types, and the schema version. Purely local:
152
+ * it parses an already-in-memory value and NEVER performs I/O or network fetches;
153
+ * a fetch-and-validate of a remote manifest is a website-side concern, deliberately
154
+ * kept out of this offline-boundary tool. Returns every problem it finds rather than
155
+ * throwing on the first, so a caller can report them all at once.
156
+ */
157
+ export declare function validateCryptoAgilityManifest(value: unknown): ManifestValidation;
158
+ //# sourceMappingURL=crypto-agility.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto-agility.d.ts","sourceRoot":"","sources":["../src/crypto-agility.ts"],"names":[],"mappings":"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"}
@@ -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,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"}
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"}