cryptoserve 0.1.3 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Centralized crypto package registry — single source of truth for all ecosystems.
3
+ *
4
+ * Consolidates package metadata previously duplicated across scanner.mjs (npm only,
5
+ * uppercase algorithms) and scanner-manifests.mjs (all ecosystems, lowercase).
6
+ * Algorithms are stored in lowercase, matching algorithm-db.mjs canonical form.
7
+ * Zero dependencies.
8
+ */
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // Known crypto packages per ecosystem
12
+ // ---------------------------------------------------------------------------
13
+
14
+ export const CRYPTO_PACKAGES = {
15
+ // npm uses display-name algorithms for backward compat with scanner.mjs output.
16
+ // The manifest scanner skips npm (parser is null), so only lookupNpmPackage reads this.
17
+ npm: {
18
+ 'crypto-js': { category: 'symmetric', algorithms: ['AES', 'DES', '3DES', 'MD5', 'SHA-256', 'SHA-512', 'HMAC'], quantumRisk: 'low' },
19
+ 'jsonwebtoken': { category: 'token', algorithms: ['RS256', 'HS256', 'ES256'], quantumRisk: 'high' },
20
+ 'jose': { category: 'token', algorithms: ['RS256', 'ES256', 'EdDSA', 'AES-GCM'], quantumRisk: 'high' },
21
+ 'node-forge': { category: 'tls', algorithms: ['RSA', 'AES', 'SHA-256', 'HMAC', 'TLS'], quantumRisk: 'high' },
22
+ 'tweetnacl': { category: 'asymmetric', algorithms: ['X25519', 'Ed25519', 'XSalsa20'], quantumRisk: 'high' },
23
+ 'libsodium-wrappers': { category: 'asymmetric', algorithms: ['X25519', 'Ed25519', 'ChaCha20', 'AES-256-GCM'], quantumRisk: 'high' },
24
+ '@noble/curves': { category: 'asymmetric', algorithms: ['ECDSA', 'Ed25519', 'X25519'], quantumRisk: 'high' },
25
+ '@noble/hashes': { category: 'hash', algorithms: ['SHA-256', 'SHA-512', 'SHA3', 'Blake2'], quantumRisk: 'low' },
26
+ '@noble/post-quantum': { category: 'pqc', algorithms: ['ML-KEM', 'ML-DSA', 'SLH-DSA'], quantumRisk: 'none' },
27
+ 'openpgp': { category: 'asymmetric', algorithms: ['RSA', 'ECDSA', 'AES', 'SHA-256'], quantumRisk: 'high' },
28
+ 'elliptic': { category: 'asymmetric', algorithms: ['ECDSA', 'ECDHE', 'Ed25519'], quantumRisk: 'high' },
29
+ 'secp256k1': { category: 'asymmetric', algorithms: ['ECDSA'], quantumRisk: 'high' },
30
+ 'argon2': { category: 'kdf', algorithms: ['Argon2'], quantumRisk: 'none' },
31
+ 'scrypt': { category: 'kdf', algorithms: ['scrypt'], quantumRisk: 'none' },
32
+ 'pbkdf2': { category: 'kdf', algorithms: ['PBKDF2'], quantumRisk: 'none' },
33
+ 'bcrypt': { category: 'kdf', algorithms: ['bcrypt'], quantumRisk: 'none' },
34
+ 'bcryptjs': { category: 'kdf', algorithms: ['bcrypt'], quantumRisk: 'none' },
35
+ 'tls': { category: 'tls', algorithms: ['TLS', 'RSA', 'ECDSA'], quantumRisk: 'high' },
36
+ 'ssh2': { category: 'asymmetric', algorithms: ['RSA', 'Ed25519', 'ECDSA', 'AES'], quantumRisk: 'high' },
37
+ 'node-rsa': { category: 'asymmetric', algorithms: ['RSA'], quantumRisk: 'high' },
38
+ 'jsrsasign': { category: 'asymmetric', algorithms: ['RSA', 'ECDSA', 'SHA-256'], quantumRisk: 'high' },
39
+ 'ethers': { category: 'asymmetric', algorithms: ['ECDSA', 'SHA-256'], quantumRisk: 'high' },
40
+ },
41
+ go: {
42
+ 'crypto/aes': { category: 'encryption', algorithms: ['aes'], quantumRisk: 'none' },
43
+ 'crypto/des': { category: 'encryption', algorithms: ['des', '3des'], quantumRisk: 'critical', isDeprecated: true },
44
+ 'crypto/rsa': { category: 'encryption', algorithms: ['rsa'], quantumRisk: 'high' },
45
+ 'crypto/ecdsa': { category: 'signing', algorithms: ['ecdsa'], quantumRisk: 'high' },
46
+ 'crypto/ed25519': { category: 'signing', algorithms: ['ed25519'], quantumRisk: 'high' },
47
+ 'crypto/ecdh': { category: 'key_exchange', algorithms: ['ecdh', 'x25519'], quantumRisk: 'high' },
48
+ 'crypto/sha256': { category: 'hashing', algorithms: ['sha256'], quantumRisk: 'low' },
49
+ 'crypto/sha512': { category: 'hashing', algorithms: ['sha512'], quantumRisk: 'low' },
50
+ 'crypto/sha1': { category: 'hashing', algorithms: ['sha1'], quantumRisk: 'critical', isDeprecated: true },
51
+ 'crypto/md5': { category: 'hashing', algorithms: ['md5'], quantumRisk: 'critical', isDeprecated: true },
52
+ 'crypto/hmac': { category: 'mac', algorithms: ['hmac'], quantumRisk: 'low' },
53
+ 'crypto/tls': { category: 'protocol', algorithms: ['tls'], quantumRisk: 'high' },
54
+ 'golang.org/x/crypto': { category: 'general', algorithms: ['chacha20-poly1305', 'argon2', 'bcrypt', 'scrypt', 'hkdf'], quantumRisk: 'none' },
55
+ 'golang.org/x/crypto/chacha20poly1305': { category: 'encryption', algorithms: ['chacha20-poly1305'], quantumRisk: 'none' },
56
+ 'golang.org/x/crypto/argon2': { category: 'kdf', algorithms: ['argon2'], quantumRisk: 'none' },
57
+ 'golang.org/x/crypto/bcrypt': { category: 'kdf', algorithms: ['bcrypt'], quantumRisk: 'none' },
58
+ 'golang.org/x/crypto/scrypt': { category: 'kdf', algorithms: ['scrypt'], quantumRisk: 'none' },
59
+ 'github.com/cloudflare/circl': { category: 'pqc', algorithms: ['kyber', 'dilithium', 'x25519'], quantumRisk: 'none' },
60
+ },
61
+ pypi: {
62
+ 'cryptography': { category: 'general', algorithms: ['aes', 'rsa', 'ecdsa', 'ed25519', 'x25519', 'sha256'], quantumRisk: 'high' },
63
+ 'pycryptodome': { category: 'general', algorithms: ['aes', 'des', 'rsa', 'sha256', 'md5'], quantumRisk: 'high' },
64
+ 'pycryptodomex': { category: 'general', algorithms: ['aes', 'des', 'rsa', 'sha256', 'md5'], quantumRisk: 'high' },
65
+ 'bcrypt': { category: 'kdf', algorithms: ['bcrypt'], quantumRisk: 'none' },
66
+ 'argon2-cffi': { category: 'kdf', algorithms: ['argon2'], quantumRisk: 'none' },
67
+ 'pynacl': { category: 'general', algorithms: ['xchacha20', 'ed25519', 'x25519'], quantumRisk: 'high' },
68
+ 'pyopenssl': { category: 'general', algorithms: ['rsa', 'aes', 'sha256', 'tls'], quantumRisk: 'high' },
69
+ 'paramiko': { category: 'asymmetric', algorithms: ['rsa', 'ed25519', 'ecdsa', 'aes'], quantumRisk: 'high' },
70
+ 'pyjwt': { category: 'signing', algorithms: ['rsa', 'ecdsa', 'hmac'], quantumRisk: 'high' },
71
+ 'hashlib': { category: 'hashing', algorithms: ['sha256', 'sha512', 'md5', 'sha1'], quantumRisk: 'low' },
72
+ 'hmac': { category: 'mac', algorithms: ['hmac'], quantumRisk: 'low' },
73
+ 'passlib': { category: 'kdf', algorithms: ['bcrypt', 'argon2', 'pbkdf2', 'scrypt'], quantumRisk: 'none' },
74
+ 'scrypt': { category: 'kdf', algorithms: ['scrypt'], quantumRisk: 'none' },
75
+ 'ecdsa': { category: 'signing', algorithms: ['ecdsa'], quantumRisk: 'high' },
76
+ 'rsa': { category: 'asymmetric', algorithms: ['rsa'], quantumRisk: 'high' },
77
+ 'liboqs-python': { category: 'pqc', algorithms: ['kyber', 'dilithium', 'sphincs'], quantumRisk: 'none' },
78
+ 'pqcrypto': { category: 'pqc', algorithms: ['kyber', 'dilithium'], quantumRisk: 'none' },
79
+ 'pyca-cryptography': { category: 'general', algorithms: ['aes', 'rsa', 'ecdsa'], quantumRisk: 'high' },
80
+ },
81
+ cargo: {
82
+ 'aes-gcm': { category: 'encryption', algorithms: ['aes-gcm'], quantumRisk: 'none' },
83
+ 'aes': { category: 'encryption', algorithms: ['aes'], quantumRisk: 'none' },
84
+ 'chacha20poly1305': { category: 'encryption', algorithms: ['chacha20-poly1305'], quantumRisk: 'none' },
85
+ 'chacha20': { category: 'encryption', algorithms: ['chacha20'], quantumRisk: 'none' },
86
+ 'rsa': { category: 'encryption', algorithms: ['rsa'], quantumRisk: 'high' },
87
+ 'ed25519-dalek': { category: 'signing', algorithms: ['ed25519'], quantumRisk: 'high' },
88
+ 'ed25519': { category: 'signing', algorithms: ['ed25519'], quantumRisk: 'high' },
89
+ 'ring': { category: 'general', algorithms: ['aes-gcm', 'sha256', 'ecdsa', 'ed25519'], quantumRisk: 'high' },
90
+ 'pqcrypto': { category: 'pqc', algorithms: ['kyber', 'dilithium', 'sphincs'], quantumRisk: 'none' },
91
+ 'sha2': { category: 'hashing', algorithms: ['sha256', 'sha512'], quantumRisk: 'low' },
92
+ 'sha1': { category: 'hashing', algorithms: ['sha1'], quantumRisk: 'critical', isDeprecated: true },
93
+ 'md-5': { category: 'hashing', algorithms: ['md5'], quantumRisk: 'critical', isDeprecated: true },
94
+ 'blake2': { category: 'hashing', algorithms: ['blake2b'], quantumRisk: 'low' },
95
+ 'argon2': { category: 'kdf', algorithms: ['argon2'], quantumRisk: 'none' },
96
+ 'bcrypt': { category: 'kdf', algorithms: ['bcrypt'], quantumRisk: 'none' },
97
+ 'scrypt': { category: 'kdf', algorithms: ['scrypt'], quantumRisk: 'none' },
98
+ 'pbkdf2': { category: 'kdf', algorithms: ['pbkdf2'], quantumRisk: 'none' },
99
+ 'x25519-dalek': { category: 'key_exchange', algorithms: ['x25519'], quantumRisk: 'high' },
100
+ 'p256': { category: 'signing', algorithms: ['ecdsa'], quantumRisk: 'high' },
101
+ 'hmac': { category: 'mac', algorithms: ['hmac'], quantumRisk: 'low' },
102
+ 'hkdf': { category: 'kdf', algorithms: ['hkdf'], quantumRisk: 'none' },
103
+ 'rustls': { category: 'protocol', algorithms: ['tls', 'aes-gcm', 'chacha20-poly1305'], quantumRisk: 'high' },
104
+ },
105
+ maven: {
106
+ 'org.bouncycastle': { category: 'general', algorithms: ['aes', 'rsa', 'ecdsa', 'ed25519', 'sha256'], quantumRisk: 'high' },
107
+ 'com.google.crypto.tink': { category: 'general', algorithms: ['aes-gcm', 'ecdsa', 'ed25519'], quantumRisk: 'high' },
108
+ 'io.jsonwebtoken': { category: 'signing', algorithms: ['rsa', 'ecdsa', 'hmac'], quantumRisk: 'high' },
109
+ 'com.nimbusds': { category: 'signing', algorithms: ['rsa', 'ecdsa', 'ed25519'], quantumRisk: 'high' },
110
+ 'org.signal': { category: 'asymmetric', algorithms: ['x25519', 'ed25519', 'aes-gcm'], quantumRisk: 'high' },
111
+ },
112
+ };
113
+
114
+ // ---------------------------------------------------------------------------
115
+ // Lookup helpers
116
+ // ---------------------------------------------------------------------------
117
+
118
+ /**
119
+ * Look up a dependency in the crypto packages database.
120
+ * For Go imports, also checks subpackage paths.
121
+ * For Maven, checks groupId prefix.
122
+ */
123
+ export function lookupPackage(name, ecosystem) {
124
+ const db = CRYPTO_PACKAGES[ecosystem];
125
+ if (!db) return null;
126
+
127
+ // Direct match
128
+ if (db[name]) return { ...db[name], name };
129
+
130
+ // For Go, check if it's a subpackage of a known package
131
+ if (ecosystem === 'go') {
132
+ for (const [pkgName, info] of Object.entries(db)) {
133
+ if (name.startsWith(pkgName + '/') || name === pkgName) {
134
+ return { ...info, name: pkgName };
135
+ }
136
+ }
137
+ }
138
+
139
+ // For Maven, check groupId
140
+ if (ecosystem === 'maven') {
141
+ const groupId = name.split(':')[0];
142
+ for (const [pkgName, info] of Object.entries(db)) {
143
+ if (groupId.startsWith(pkgName)) {
144
+ return { ...info, name: pkgName };
145
+ }
146
+ }
147
+ }
148
+
149
+ return null;
150
+ }
151
+
152
+ /**
153
+ * Shorthand for npm package lookup.
154
+ * Returns the package info directly — npm section already uses display-name
155
+ * algorithms and scanner.mjs-compatible categories.
156
+ */
157
+ export function lookupNpmPackage(name) {
158
+ const pkg = CRYPTO_PACKAGES.npm[name];
159
+ if (!pkg) return null;
160
+ return { algorithms: pkg.algorithms, quantumRisk: pkg.quantumRisk, category: pkg.category };
161
+ }
package/lib/init.mjs CHANGED
@@ -142,13 +142,13 @@ function protectAider(projectDir, result) {
142
142
  }
143
143
 
144
144
  function addInstructions(filePath, result) {
145
- let content = existsSync(filePath) ? readFileSync(filePath, 'utf-8') : '';
145
+ const existed = existsSync(filePath);
146
+ let content = existed ? readFileSync(filePath, 'utf-8') : '';
146
147
  if (content.includes(MARKER)) return;
147
148
 
148
149
  content += buildSecretlessInstructions();
149
150
  writeFileSync(filePath, content);
150
151
 
151
- const existed = existsSync(filePath);
152
152
  if (existed) {
153
153
  result.filesModified.push(filePath);
154
154
  } else {
@@ -5,6 +5,8 @@
5
5
  * Provides air-gapped quantum readiness analysis with zero dependencies.
6
6
  */
7
7
 
8
+ import { lookupAlgorithm, classifyFromDb } from './algorithm-db.mjs';
9
+
8
10
  // ---------------------------------------------------------------------------
9
11
  // Embedded intelligence data
10
12
  // ---------------------------------------------------------------------------
@@ -170,63 +172,101 @@ export const COMPLIANCE_FRAMEWORKS = {
170
172
  };
171
173
 
172
174
  // ---------------------------------------------------------------------------
173
- // Algorithm classification rules
175
+ // Algorithm classification
174
176
  // ---------------------------------------------------------------------------
175
177
 
176
- // [pattern, timelineKey, category] — first match wins
177
- const ALGO_CLASSIFICATION_RULES = [
178
- // PQC (safe)
179
- ['Kyber', 'pqc', 'pqc'],
180
- ['ML-KEM', 'pqc', 'pqc'],
181
- ['Dilithium', 'pqc', 'pqc'],
182
- ['ML-DSA', 'pqc', 'pqc'],
183
- ['Falcon', 'pqc', 'pqc'],
184
- ['SPHINCS', 'pqc', 'pqc'],
185
- ['SLH-DSA', 'pqc', 'pqc'],
186
- // Asymmetric (quantum-vulnerable via Shor's)
187
- ['RSA', 'rsa_2048', 'asymmetric'],
188
- ['ECDSA', 'ecdsa_p256', 'asymmetric'],
189
- ['ECDHE', 'ecdsa_p256', 'asymmetric'],
190
- ['ECC', 'ecdsa_p256', 'asymmetric'],
191
- ['Ed25519', 'ed25519', 'asymmetric'],
192
- ['EdDSA', 'ed25519', 'asymmetric'],
193
- ['Curve25519','x25519', 'asymmetric'],
194
- ['X25519', 'x25519', 'asymmetric'],
195
- ['DH', 'dh_2048', 'asymmetric'],
196
- // Symmetric (Grover's — key-doubling sufficient)
197
- ['AES', 'aes_256', 'symmetric'],
198
- ['ChaCha20', 'chacha20', 'symmetric'],
199
- ['3DES', 'aes_128', 'symmetric'],
200
- ['DES', 'aes_128', 'symmetric'],
201
- ['XSalsa20', 'chacha20', 'symmetric'],
178
+ // Timeline mapping: [pattern, timelineKey] — first match wins.
179
+ // Category is now derived from algorithm-db.mjs's classifyFromDb().
180
+ const TIMELINE_RULES = [
181
+ // PQC
182
+ ['Kyber', 'pqc'],
183
+ ['ML-KEM', 'pqc'],
184
+ ['Dilithium', 'pqc'],
185
+ ['ML-DSA', 'pqc'],
186
+ ['Falcon', 'pqc'],
187
+ ['SPHINCS', 'pqc'],
188
+ ['SLH-DSA', 'pqc'],
189
+ // Asymmetric
190
+ ['RSA', 'rsa_2048'],
191
+ ['ECDSA', 'ecdsa_p256'],
192
+ ['ECDHE', 'ecdsa_p256'],
193
+ ['ECC', 'ecdsa_p256'],
194
+ ['Ed25519', 'ed25519'],
195
+ ['EdDSA', 'ed25519'],
196
+ ['Curve25519','x25519'],
197
+ ['X25519', 'x25519'],
198
+ ['DH', 'dh_2048'],
199
+ // Symmetric
200
+ ['AES', 'aes_256'],
201
+ ['ChaCha20', 'chacha20'],
202
+ ['3DES', 'aes_128'],
203
+ ['DES', 'aes_128'],
204
+ ['XSalsa20', 'chacha20'],
202
205
  // Hashing
203
- ['SHA-256', 'sha_256', 'hash'],
204
- ['SHA-512', 'sha_256', 'hash'],
205
- ['SHA-1', 'sha_256', 'hash'],
206
- ['SHA3', 'sha_256', 'hash'],
207
- ['Blake2', 'sha_256', 'hash'],
208
- ['MD5', 'sha_256', 'hash'],
209
- // KDF / MAC / CSPRNG
210
- ['HMAC', 'sha_256', 'hash'],
211
- ['bcrypt', null, 'kdf'],
212
- ['Argon2', null, 'kdf'],
213
- ['PBKDF2', null, 'kdf'],
214
- ['scrypt', null, 'kdf'],
215
- ['CSPRNG', null, 'random'],
216
- ['Poly1305', null, 'mac'],
206
+ ['SHA-256', 'sha_256'],
207
+ ['SHA-512', 'sha_256'],
208
+ ['SHA-1', 'sha_256'],
209
+ ['SHA3', 'sha_256'],
210
+ ['Blake2', 'sha_256'],
211
+ ['MD5', 'sha_256'],
212
+ // MAC / KDF
213
+ ['HMAC', 'sha_256'],
214
+ ['bcrypt', null],
215
+ ['Argon2', null],
216
+ ['PBKDF2', null],
217
+ ['scrypt', null],
218
+ ['CSPRNG', null],
219
+ ['Poly1305', null],
217
220
  // Token / TLS wrappers
218
- ['TLS', 'rsa_2048', 'asymmetric'],
219
- ['JWS', 'rsa_2048', 'asymmetric'],
220
- ['JWE', 'rsa_2048', 'asymmetric'],
221
- ['JWK', 'rsa_2048', 'asymmetric'],
222
- ['RS256', 'rsa_2048', 'asymmetric'],
223
- ['ES256', 'ecdsa_p256', 'asymmetric'],
224
- ['HS256', 'sha_256', 'hash'],
221
+ ['TLS', 'rsa_2048'],
222
+ ['JWS', 'rsa_2048'],
223
+ ['JWE', 'rsa_2048'],
224
+ ['JWK', 'rsa_2048'],
225
+ ['RS256', 'rsa_2048'],
226
+ ['ES256', 'ecdsa_p256'],
227
+ ['HS256', 'sha_256'],
225
228
  ];
226
229
 
227
- // ---------------------------------------------------------------------------
228
- // Classification
229
- // ---------------------------------------------------------------------------
230
+ /**
231
+ * Map algorithm-db category + quantumRisk to PQC engine classification category.
232
+ * This derives the quantum vulnerability class from algorithm-db metadata.
233
+ */
234
+ function dbCategoryToPqcCategory(dbEntry) {
235
+ if (!dbEntry) return null;
236
+ const { category, quantumRisk } = dbEntry;
237
+
238
+ // PQC algorithms: signing/key_exchange with no quantum risk
239
+ // (classical asymmetric has 'high' risk, PQC has 'none')
240
+ if (quantumRisk === 'none' && (category === 'signing' || category === 'key_exchange')) {
241
+ return 'pqc';
242
+ }
243
+
244
+ // Signing and key exchange → asymmetric (quantum-vulnerable via Shor's)
245
+ if (category === 'signing' || category === 'key_exchange') return 'asymmetric';
246
+
247
+ // Encryption with high quantum risk → asymmetric (RSA encryption)
248
+ if (category === 'encryption' && quantumRisk === 'high') return 'asymmetric';
249
+
250
+ // Encryption with none/low risk → symmetric
251
+ if (category === 'encryption') return 'symmetric';
252
+
253
+ // Hashing
254
+ if (category === 'hashing') return 'hash';
255
+
256
+ // MAC
257
+ if (category === 'mac') return 'hash';
258
+
259
+ // KDF
260
+ if (category === 'kdf') return 'kdf';
261
+
262
+ // Protocol (TLS) → asymmetric (involves key exchange)
263
+ if (category === 'protocol') return 'asymmetric';
264
+
265
+ // Random
266
+ if (category === 'random') return 'random';
267
+
268
+ return null;
269
+ }
230
270
 
231
271
  function classifyAlgorithms(libraries) {
232
272
  const seen = new Set();
@@ -237,20 +277,33 @@ function classifyAlgorithms(libraries) {
237
277
  if (seen.has(algoName)) continue;
238
278
  seen.add(algoName);
239
279
 
240
- let matched = false;
280
+ // Look up timeline key via pattern matching
281
+ let timelineKey = null;
241
282
  const upper = algoName.toUpperCase();
242
-
243
- for (const [pattern, timelineKey, category] of ALGO_CLASSIFICATION_RULES) {
283
+ for (const [pattern, tKey] of TIMELINE_RULES) {
244
284
  if (upper.includes(pattern.toUpperCase())) {
245
- results.push({ algo: algoName, timelineKey, category });
246
- matched = true;
285
+ timelineKey = tKey;
247
286
  break;
248
287
  }
249
288
  }
250
289
 
251
- if (!matched) {
252
- results.push({ algo: algoName, timelineKey: null, category: 'unknown' });
290
+ // Derive category from algorithm-db
291
+ const dbEntry = classifyFromDb(algoName);
292
+ let category = dbEntry ? dbCategoryToPqcCategory(dbEntry) : null;
293
+
294
+ // Fallback: if algorithm-db doesn't know this algo, infer from timeline
295
+ if (!category) {
296
+ if (timelineKey === 'pqc') category = 'pqc';
297
+ else if (timelineKey && timelineKey in QUANTUM_THREAT_TIMELINE) {
298
+ // Timeline keys like rsa_2048, ecdsa_p256 → asymmetric
299
+ const t = QUANTUM_THREAT_TIMELINE[timelineKey];
300
+ category = t.median <= 30 ? 'asymmetric' : 'symmetric';
301
+ } else {
302
+ category = 'unknown';
303
+ }
253
304
  }
305
+
306
+ results.push({ algo: algoName, timelineKey, category });
254
307
  }
255
308
  }
256
309
 
@@ -478,7 +531,7 @@ function calculateQuantumScore(libraries, classifications) {
478
531
  // Score by individual algorithm classifications, not library count.
479
532
  // A project with 5 symmetric + 1 asymmetric algorithm is mostly ready, not 0%.
480
533
  const safe = classifications.filter(
481
- c => c.category !== 'asymmetric' || c.category === 'pqc'
534
+ c => c.category !== 'asymmetric'
482
535
  ).length;
483
536
  const vulnerable = classifications.filter(
484
537
  c => c.category === 'asymmetric'
@@ -605,7 +658,107 @@ function buildThreatTimelines(classifications) {
605
658
  // Main entry point
606
659
  // ---------------------------------------------------------------------------
607
660
 
608
- export function analyzeOffline(libraries, dataProfile = null) {
661
+ // ---------------------------------------------------------------------------
662
+ // Confidence calculation
663
+ // ---------------------------------------------------------------------------
664
+
665
+ function calculateConfidence(libraries, classifications, scanMeta = {}) {
666
+ const algorithmsFound = classifications.length;
667
+ const languagesScanned = scanMeta.languagesDetected?.length || 0;
668
+ const filesScanned = scanMeta.filesScanned || 0;
669
+ const manifestsFound = scanMeta.manifestsFound?.length || 0;
670
+
671
+ let score = 0;
672
+
673
+ // Algorithms contribute up to 40 points
674
+ if (algorithmsFound >= 10) score += 40;
675
+ else if (algorithmsFound >= 5) score += 30;
676
+ else if (algorithmsFound >= 3) score += 20;
677
+ else if (algorithmsFound >= 1) score += 10;
678
+
679
+ // Manifests contribute up to 25 points
680
+ if (manifestsFound >= 3) score += 25;
681
+ else if (manifestsFound >= 2) score += 20;
682
+ else if (manifestsFound >= 1) score += 15;
683
+
684
+ // Files scanned contribute up to 20 points
685
+ if (filesScanned >= 100) score += 20;
686
+ else if (filesScanned >= 50) score += 15;
687
+ else if (filesScanned >= 20) score += 10;
688
+ else if (filesScanned >= 5) score += 5;
689
+
690
+ // Languages contribute up to 15 points
691
+ if (languagesScanned >= 3) score += 15;
692
+ else if (languagesScanned >= 2) score += 10;
693
+ else if (languagesScanned >= 1) score += 5;
694
+
695
+ let level;
696
+ if (score >= 70) level = 'high';
697
+ else if (score >= 40) level = 'medium';
698
+ else level = 'low';
699
+
700
+ const parts = [];
701
+ if (algorithmsFound > 0) parts.push(`${algorithmsFound} algorithms found`);
702
+ if (languagesScanned > 0) parts.push(`${languagesScanned} languages`);
703
+ if (filesScanned > 0) parts.push(`${filesScanned} files`);
704
+ if (manifestsFound > 0) parts.push(`${manifestsFound} manifests`);
705
+
706
+ return {
707
+ level,
708
+ score,
709
+ reason: parts.join(', ') || 'no data scanned',
710
+ algorithmsFound,
711
+ languagesScanned,
712
+ filesScanned,
713
+ manifestsFound,
714
+ };
715
+ }
716
+
717
+ // ---------------------------------------------------------------------------
718
+ // Migration urgency mapping
719
+ // ---------------------------------------------------------------------------
720
+
721
+ function getMigrationUrgency(riskLevel) {
722
+ switch (riskLevel) {
723
+ case 'critical': return 'immediate';
724
+ case 'high': return 'high';
725
+ case 'medium': return 'medium';
726
+ case 'low': return 'low';
727
+ default: return 'none';
728
+ }
729
+ }
730
+
731
+ // ---------------------------------------------------------------------------
732
+ // Risk breakdown
733
+ // ---------------------------------------------------------------------------
734
+
735
+ function getRiskBreakdown(classifications) {
736
+ const breakdown = { critical: 0, high: 0, medium: 0, low: 0, none: 0 };
737
+
738
+ for (const c of classifications) {
739
+ // Use algorithm-db for per-algorithm risk if available
740
+ const dbEntry = lookupAlgorithm(c.algo);
741
+ if (dbEntry) {
742
+ const risk = dbEntry.quantumRisk || 'none';
743
+ if (risk in breakdown) breakdown[risk]++;
744
+ else breakdown.none++;
745
+ } else if (c.category === 'pqc') {
746
+ breakdown.none++;
747
+ } else if (c.category === 'asymmetric') {
748
+ breakdown.high++;
749
+ } else {
750
+ breakdown.low++;
751
+ }
752
+ }
753
+
754
+ return breakdown;
755
+ }
756
+
757
+ // ---------------------------------------------------------------------------
758
+ // Main entry point
759
+ // ---------------------------------------------------------------------------
760
+
761
+ export function analyzeOffline(libraries, dataProfile = null, scanMeta = {}) {
609
762
  const profileKey = dataProfile || 'general';
610
763
  const profile = DATA_PROFILES[profileKey] || DATA_PROFILES.general;
611
764
 
@@ -622,6 +775,11 @@ export function analyzeOffline(libraries, dataProfile = null) {
622
775
  const nextSteps = generateNextSteps(urgency, migrationPlan, sndl);
623
776
  const threatTimelines = buildThreatTimelines(classifications);
624
777
 
778
+ // New in v0.2.0
779
+ const confidence = calculateConfidence(libraries, classifications, scanMeta);
780
+ const migrationUrgency = getMigrationUrgency(urgency);
781
+ const riskBreakdown = getRiskBreakdown(classifications);
782
+
625
783
  return {
626
784
  generatedAt: new Date().toISOString(),
627
785
  analysisMode: 'offline',
@@ -630,7 +788,10 @@ export function analyzeOffline(libraries, dataProfile = null) {
630
788
  signatureRecommendations: sigRecs,
631
789
  migrationPlan,
632
790
  overallUrgency: urgency,
791
+ migrationUrgency,
633
792
  quantumReadinessScore: quantumScore,
793
+ confidence,
794
+ riskBreakdown,
634
795
  keyFindings: findings,
635
796
  nextSteps,
636
797
  complianceReferences: complianceRefs,