postquant 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +165 -14
  2. package/dist/commands/analyze.d.ts.map +1 -1
  3. package/dist/commands/analyze.js +15 -5
  4. package/dist/commands/analyze.js.map +1 -1
  5. package/dist/index.js +4 -0
  6. package/dist/index.js.map +1 -1
  7. package/dist/output/json-code.d.ts.map +1 -1
  8. package/dist/output/json-code.js +12 -0
  9. package/dist/output/json-code.js.map +1 -1
  10. package/dist/output/sarif.d.ts.map +1 -1
  11. package/dist/output/sarif.js +27 -2
  12. package/dist/output/sarif.js.map +1 -1
  13. package/dist/output/terminal-code.d.ts +1 -0
  14. package/dist/output/terminal-code.d.ts.map +1 -1
  15. package/dist/output/terminal-code.js +66 -6
  16. package/dist/output/terminal-code.js.map +1 -1
  17. package/dist/scanner/classifier.js +1 -1
  18. package/dist/scanner/classifier.js.map +1 -1
  19. package/dist/scanner/code/grader.d.ts.map +1 -1
  20. package/dist/scanner/code/grader.js +75 -21
  21. package/dist/scanner/code/grader.js.map +1 -1
  22. package/dist/scanner/code/matcher.d.ts +11 -2
  23. package/dist/scanner/code/matcher.d.ts.map +1 -1
  24. package/dist/scanner/code/matcher.js +3 -2
  25. package/dist/scanner/code/matcher.js.map +1 -1
  26. package/dist/scanner/code/risk-assessor.d.ts +25 -0
  27. package/dist/scanner/code/risk-assessor.d.ts.map +1 -0
  28. package/dist/scanner/code/risk-assessor.js +412 -0
  29. package/dist/scanner/code/risk-assessor.js.map +1 -0
  30. package/dist/scanner/openssl.d.ts +25 -0
  31. package/dist/scanner/openssl.d.ts.map +1 -0
  32. package/dist/scanner/openssl.js +113 -0
  33. package/dist/scanner/openssl.js.map +1 -0
  34. package/dist/scanner/tls.d.ts.map +1 -1
  35. package/dist/scanner/tls.js +43 -1
  36. package/dist/scanner/tls.js.map +1 -1
  37. package/dist/types/index.d.ts +21 -0
  38. package/dist/types/index.d.ts.map +1 -1
  39. package/dist/types/index.js +4 -1
  40. package/dist/types/index.js.map +1 -1
  41. package/package.json +4 -1
@@ -0,0 +1,412 @@
1
+ /**
2
+ * Risk Assessment Layer for PostQuant v0.3.0
3
+ *
4
+ * Analyzes the CONTEXT of each cryptographic finding to determine
5
+ * whether the algorithm usage is security-critical or benign.
6
+ * MD5 in password hashing is critical; MD5 in UUID v3 is informational.
7
+ */
8
+ // ── Context priority map ────────────────────────────────────────
9
+ const CONTEXT_PRIORITY = {
10
+ 'authentication': 10,
11
+ 'encryption': 9,
12
+ 'key-exchange': 9,
13
+ 'digital-signature': 8,
14
+ 'legacy-support': 5,
15
+ 'integrity-check': 4,
16
+ 'protocol-compliance': 3,
17
+ 'test-fixture': 2,
18
+ 'documentation': 1,
19
+ 'unknown': 0,
20
+ };
21
+ // ── Signal-to-context mapping ───────────────────────────────────
22
+ // Maps signal value keywords to their UsageContext.
23
+ function inferContextFromSignalValue(value) {
24
+ const v = value.toLowerCase();
25
+ // increases-risk contexts
26
+ if (/password|passwd|pwd|hmac|token|jwt|bearer|oauth/.test(v))
27
+ return 'authentication';
28
+ if (/encrypt|decrypt|cipher/.test(v))
29
+ return 'encryption';
30
+ if (/key_exchange|kex|handshake/.test(v))
31
+ return 'key-exchange';
32
+ if (/(?<![a-zA-Z])sign(?![a-zA-Z])|(?<![a-zA-Z])verify(?![a-zA-Z])|(?<![a-zA-Z])signature(?![a-zA-Z])/.test(v))
33
+ return 'digital-signature';
34
+ // decreases-risk contexts
35
+ if (/uuid|rfc4122|rfc-4122|content-md5|content_md5/.test(v))
36
+ return 'protocol-compliance';
37
+ if (/checksum|digest|fingerprint|etag|cache|dedup|lookup|index|md5sum|md5_checksum|file_hash|compute_hash|content_hash|cache_key|compute_etag/.test(v))
38
+ return 'integrity-check';
39
+ if (/legacy|compat|fallback|deprecated|vendor|node_modules|third_party|migrations/.test(v))
40
+ return 'legacy-support';
41
+ if (/test|mock|stub|fixture|assert|spec|__tests__/.test(v))
42
+ return 'test-fixture';
43
+ if (/docs|examples|readme/.test(v))
44
+ return 'documentation';
45
+ // Import-based contexts
46
+ if (/boto3|botocore|aws-sdk|@aws-sdk/.test(v))
47
+ return 'protocol-compliance';
48
+ if (/\bpg\b|postgres|psycopg|node-postgres|mysql|mysqlclient/.test(v))
49
+ return 'legacy-support';
50
+ if (/\bgit\b/.test(v))
51
+ return 'protocol-compliance';
52
+ if (/passlib|bcrypt|argon2|pyjwt|jsonwebtoken|jose|paramiko|ssh2|libssh/.test(v))
53
+ return 'authentication';
54
+ // Function-name increases-risk
55
+ if (/hash_password|check_password|verify_password/.test(v))
56
+ return 'authentication';
57
+ if (/generate_key|create_key|new_key/.test(v))
58
+ return 'encryption';
59
+ if (/sign_/.test(v))
60
+ return 'digital-signature';
61
+ if (/verify_/.test(v))
62
+ return 'digital-signature';
63
+ // File path contexts
64
+ if (/auth|authentication|session|login|password/.test(v))
65
+ return 'authentication';
66
+ if (/security|crypto|certs/.test(v))
67
+ return 'encryption';
68
+ return 'unknown';
69
+ }
70
+ const FILE_PATH_RULES = [
71
+ // increases-risk
72
+ { pattern: /(?:^|\/)(auth|authentication)\//, influence: 'increases-risk', label: 'auth/' },
73
+ { pattern: /(?:^|\/)(security|crypto|certs)\//, influence: 'increases-risk', label: 'security/' },
74
+ { pattern: /(?:^|\/)(session|login|password)\//, influence: 'increases-risk', label: 'session/' },
75
+ // decreases-risk: directories
76
+ { pattern: /(?:^|\/)(test|tests|__tests__|spec)\//, influence: 'decreases-risk', label: 'test/' },
77
+ { pattern: /(?:^|\/)(vendor|node_modules|third_party)\//, influence: 'decreases-risk', label: 'vendor/' },
78
+ { pattern: /(?:^|\/)(docs|examples)\//, influence: 'decreases-risk', label: 'docs/' },
79
+ { pattern: /README/, influence: 'decreases-risk', label: 'README' },
80
+ { pattern: /(?:^|\/)(migrations|compat|legacy)\//, influence: 'decreases-risk', label: 'legacy/' },
81
+ // decreases-risk: file suffixes
82
+ { pattern: /_test\.go$/, influence: 'decreases-risk', label: '_test.go' },
83
+ { pattern: /\.test\.[tj]s$/, influence: 'decreases-risk', label: '.test.ts/js' },
84
+ { pattern: /\.spec\.[tj]s$/, influence: 'decreases-risk', label: '.spec.ts/js' },
85
+ ];
86
+ export function detectFilePathSignals(filePath) {
87
+ const signals = [];
88
+ for (const rule of FILE_PATH_RULES) {
89
+ if (rule.pattern.test(filePath)) {
90
+ signals.push({
91
+ type: 'file-path',
92
+ value: rule.label,
93
+ influence: rule.influence,
94
+ });
95
+ }
96
+ }
97
+ return signals;
98
+ }
99
+ const NEARBY_CODE_RULES = [
100
+ // increases-risk
101
+ { pattern: /password|passwd|pwd/i, influence: 'increases-risk', label: 'password' },
102
+ { pattern: /(?<![a-zA-Z])sign(?![a-zA-Z])|(?<![a-zA-Z])verify(?![a-zA-Z])|(?<![a-zA-Z])signature(?![a-zA-Z])/i, influence: 'increases-risk', label: 'sign/verify' },
103
+ { pattern: /encrypt|decrypt|cipher/i, influence: 'increases-risk', label: 'encrypt' },
104
+ { pattern: /key_exchange|kex|handshake/i, influence: 'increases-risk', label: 'key_exchange' },
105
+ { pattern: /\bhmac\b|\bHMAC\b/, influence: 'increases-risk', label: 'hmac' },
106
+ { pattern: /\btoken\b|jwt|bearer|oauth/i, influence: 'increases-risk', label: 'token/jwt' },
107
+ // decreases-risk
108
+ { pattern: /checksum|digest|fingerprint|etag/i, influence: 'decreases-risk', label: 'checksum' },
109
+ { pattern: /\bcache\b|\bdedup\b|\blookup\b|\bindex\b/i, influence: 'decreases-risk', label: 'cache' },
110
+ { pattern: /\buuid\b|\bUUID\b|rfc4122|rfc-4122/, influence: 'decreases-risk', label: 'uuid' },
111
+ { pattern: /Content-MD5|content_md5/i, influence: 'decreases-risk', label: 'Content-MD5' },
112
+ { pattern: /legacy|compat|fallback|deprecated/i, influence: 'decreases-risk', label: 'legacy' },
113
+ { pattern: /\btest\b|\bmock\b|\bstub\b|\bfixture\b|\bassert\b/i, influence: 'decreases-risk', label: 'test' },
114
+ ];
115
+ export function detectNearbyCodeSignals(lines, lineNumber, windowSize = 5) {
116
+ // lineNumber is 1-indexed
117
+ const idx = lineNumber - 1;
118
+ const start = Math.max(0, idx - windowSize);
119
+ const end = Math.min(lines.length - 1, idx + windowSize);
120
+ const window = lines.slice(start, end + 1).join('\n');
121
+ const seen = new Set();
122
+ const signals = [];
123
+ for (const rule of NEARBY_CODE_RULES) {
124
+ if (rule.pattern.test(window) && !seen.has(rule.label)) {
125
+ seen.add(rule.label);
126
+ signals.push({
127
+ type: 'nearby-code',
128
+ value: rule.label,
129
+ influence: rule.influence,
130
+ });
131
+ }
132
+ }
133
+ return signals;
134
+ }
135
+ const IMPORT_RULES = [
136
+ // decreases-risk
137
+ { libraryPattern: /\buuid\b/i, influence: 'decreases-risk', label: 'uuid' },
138
+ { libraryPattern: /boto3|botocore|aws-sdk|@aws-sdk/, influence: 'decreases-risk', label: 'boto3/aws-sdk' },
139
+ { libraryPattern: /\bpg\b|postgres|psycopg|node-postgres/, influence: 'decreases-risk', label: 'pg/psycopg/postgres' },
140
+ { libraryPattern: /mysql2|mysql|mysqlclient/, influence: 'decreases-risk', label: 'mysql' },
141
+ { libraryPattern: /\bgit\b/, influence: 'decreases-risk', label: 'git' },
142
+ // increases-risk
143
+ { libraryPattern: /passlib|bcrypt|argon2/, influence: 'increases-risk', label: 'passlib/bcrypt/argon2' },
144
+ { libraryPattern: /pyjwt|jsonwebtoken|jose|\bjwt\b/, influence: 'increases-risk', label: 'pyjwt/jsonwebtoken/jose' },
145
+ { libraryPattern: /paramiko|ssh2|libssh/, influence: 'increases-risk', label: 'paramiko/ssh2/libssh' },
146
+ ];
147
+ // Language-specific import statement patterns
148
+ const IMPORT_LINE_PATTERNS = {
149
+ python: [
150
+ /^\s*import\s+(.+)/,
151
+ /^\s*from\s+(\S+)\s+import/,
152
+ ],
153
+ javascript: [
154
+ /^\s*import\s+.+\s+from\s+['"]([^'"]+)['"]/,
155
+ /^\s*import\s+['"]([^'"]+)['"]/,
156
+ /^\s*import\s*\{[^}]+\}\s*from\s*['"]([^'"]+)['"]/,
157
+ /(?:require|import)\s*\(\s*['"]([^'"]+)['"]\s*\)/,
158
+ ],
159
+ go: [
160
+ /^\s*"([^"]+)"/,
161
+ /^\s*\w+\s+"([^"]+)"/,
162
+ ],
163
+ java: [
164
+ /^\s*import\s+([\w.]+)/,
165
+ ],
166
+ };
167
+ export function detectImportSignals(content, language) {
168
+ const lines = content.split('\n').slice(0, 50);
169
+ const signals = [];
170
+ const seen = new Set();
171
+ const patterns = IMPORT_LINE_PATTERNS[language] || [];
172
+ for (const line of lines) {
173
+ for (const importPattern of patterns) {
174
+ const match = importPattern.exec(line);
175
+ if (!match)
176
+ continue;
177
+ const importedModule = match[1] || line;
178
+ for (const rule of IMPORT_RULES) {
179
+ if (rule.libraryPattern.test(importedModule) && !seen.has(rule.label)) {
180
+ seen.add(rule.label);
181
+ signals.push({
182
+ type: 'import-context',
183
+ value: rule.label,
184
+ influence: rule.influence,
185
+ });
186
+ }
187
+ }
188
+ }
189
+ }
190
+ return signals;
191
+ }
192
+ const FUNCTION_NAME_RULES = [
193
+ // increases-risk
194
+ { pattern: /hash_password|check_password|verify_password/, influence: 'increases-risk', label: 'hash_password' },
195
+ { pattern: /generate_key|create_key|new_key/, influence: 'increases-risk', label: 'generate_key' },
196
+ { pattern: /\bsign_\w+/, influence: 'increases-risk', label: 'sign_*' },
197
+ { pattern: /\bverify_\w+/, influence: 'increases-risk', label: 'verify_*' },
198
+ // decreases-risk
199
+ { pattern: /md5sum|md5_checksum|file_hash|compute_hash/, influence: 'decreases-risk', label: 'md5sum/file_hash' },
200
+ { pattern: /cache_key|compute_etag|content_hash/, influence: 'decreases-risk', label: 'cache_key/etag' },
201
+ { pattern: /\betag\b/, influence: 'decreases-risk', label: 'etag' },
202
+ ];
203
+ export function detectFunctionNameSignals(matchedLine) {
204
+ const signals = [];
205
+ const seen = new Set();
206
+ for (const rule of FUNCTION_NAME_RULES) {
207
+ if (rule.pattern.test(matchedLine) && !seen.has(rule.label)) {
208
+ seen.add(rule.label);
209
+ signals.push({
210
+ type: 'function-name',
211
+ value: rule.label,
212
+ influence: rule.influence,
213
+ });
214
+ }
215
+ }
216
+ return signals;
217
+ }
218
+ const PROTOCOL_RULES = [
219
+ {
220
+ algorithm: 'MD5',
221
+ contextPatterns: [/\buuid\b|\bUUID\b|uuid3|NAMESPACE|rfc4122/],
222
+ importHints: [/\buuid\b/],
223
+ protocolName: 'UUID v3 (RFC 4122)',
224
+ contextOverride: 'protocol-compliance',
225
+ },
226
+ {
227
+ algorithm: 'SHA-1',
228
+ contextPatterns: [/\buuid\b|\bUUID\b|uuid5|NAMESPACE|rfc4122/],
229
+ importHints: [/\buuid\b/],
230
+ protocolName: 'UUID v5 (RFC 4122)',
231
+ contextOverride: 'protocol-compliance',
232
+ },
233
+ {
234
+ algorithm: 'MD5',
235
+ contextPatterns: [/Content-MD5|content_md5|ContentMD5/],
236
+ importHints: [/boto3|aws|s3/],
237
+ protocolName: 'HTTP/S3 Content-MD5',
238
+ contextOverride: 'protocol-compliance',
239
+ },
240
+ {
241
+ algorithm: 'MD5',
242
+ contextPatterns: [/postgres|postgresql|pg_|md5.*password/i],
243
+ importHints: [/\bpg\b|postgres|psycopg/],
244
+ protocolName: 'PostgreSQL MD5 auth',
245
+ contextOverride: 'legacy-support',
246
+ },
247
+ {
248
+ algorithm: 'SHA-1',
249
+ contextPatterns: [/\bgit\b|object_hash|blob|commit|tree/],
250
+ importHints: [/\bgit\b/],
251
+ protocolName: 'Git object hashing',
252
+ contextOverride: 'protocol-compliance',
253
+ },
254
+ ];
255
+ /** Shared helper: compute a line window around a 1-indexed lineNumber. */
256
+ function computeWindow(lines, lineNumber, windowSize = 5) {
257
+ const idx = lineNumber - 1;
258
+ const start = Math.max(0, idx - windowSize);
259
+ const end = Math.min(lines.length - 1, idx + windowSize);
260
+ return lines.slice(start, end + 1);
261
+ }
262
+ export function detectProtocolPattern(finding, lines, lineNumber, imports) {
263
+ const nearbyLines = computeWindow(lines, lineNumber);
264
+ const nearbyText = nearbyLines.join('\n');
265
+ for (const rule of PROTOCOL_RULES) {
266
+ if (finding.algorithm !== rule.algorithm)
267
+ continue;
268
+ const contextMatch = rule.contextPatterns.some(p => p.test(nearbyText));
269
+ const importMatch = rule.importHints.some(p => p.test(imports));
270
+ if (contextMatch || importMatch) {
271
+ return {
272
+ signal: {
273
+ type: 'api-pattern',
274
+ value: `${rule.protocolName} (${rule.contextOverride})`,
275
+ influence: 'decreases-risk',
276
+ },
277
+ contextOverride: rule.contextOverride,
278
+ };
279
+ }
280
+ }
281
+ return null;
282
+ }
283
+ // ── 6. resolveContext ───────────────────────────────────────────
284
+ export function resolveContext(signals) {
285
+ if (signals.length === 0) {
286
+ return { context: 'unknown', influence: 'neutral' };
287
+ }
288
+ const increasesRisk = signals.filter(s => s.influence === 'increases-risk');
289
+ const decreasesRisk = signals.filter(s => s.influence === 'decreases-risk');
290
+ // increases-risk wins
291
+ if (increasesRisk.length > 0) {
292
+ const context = pickHighestPriority(increasesRisk);
293
+ return { context, influence: 'increases-risk' };
294
+ }
295
+ if (decreasesRisk.length > 0) {
296
+ const context = pickHighestPriority(decreasesRisk);
297
+ return { context, influence: 'decreases-risk' };
298
+ }
299
+ // All neutral
300
+ return { context: 'unknown', influence: 'neutral' };
301
+ }
302
+ function pickHighestPriority(signals) {
303
+ let bestContext = 'unknown';
304
+ let bestPriority = -1;
305
+ for (const signal of signals) {
306
+ const ctx = inferContextFromSignalValue(signal.value);
307
+ const priority = CONTEXT_PRIORITY[ctx];
308
+ if (priority > bestPriority) {
309
+ bestPriority = priority;
310
+ bestContext = ctx;
311
+ }
312
+ }
313
+ return bestContext;
314
+ }
315
+ // ── 7. computeAdjustedRisk ──────────────────────────────────────
316
+ const RISK_MATRIX = {
317
+ critical: {
318
+ 'authentication': 'critical',
319
+ 'encryption': 'critical',
320
+ 'key-exchange': 'critical',
321
+ 'digital-signature': 'critical',
322
+ 'integrity-check': 'low',
323
+ 'protocol-compliance': 'informational',
324
+ 'legacy-support': 'medium',
325
+ 'test-fixture': 'informational',
326
+ 'documentation': 'informational',
327
+ 'unknown': 'high',
328
+ },
329
+ moderate: {
330
+ 'authentication': 'medium',
331
+ 'encryption': 'medium',
332
+ 'key-exchange': 'medium',
333
+ 'digital-signature': 'medium',
334
+ 'integrity-check': 'low',
335
+ 'protocol-compliance': 'informational',
336
+ 'legacy-support': 'low',
337
+ 'test-fixture': 'informational',
338
+ 'documentation': 'informational',
339
+ 'unknown': 'medium',
340
+ },
341
+ safe: {
342
+ 'authentication': 'informational',
343
+ 'encryption': 'informational',
344
+ 'key-exchange': 'informational',
345
+ 'digital-signature': 'informational',
346
+ 'integrity-check': 'informational',
347
+ 'protocol-compliance': 'informational',
348
+ 'legacy-support': 'informational',
349
+ 'test-fixture': 'informational',
350
+ 'documentation': 'informational',
351
+ 'unknown': 'informational',
352
+ },
353
+ };
354
+ export function computeAdjustedRisk(originalRisk, context) {
355
+ return RISK_MATRIX[originalRisk][context];
356
+ }
357
+ // ── 8. assessFindings (main entry point) ────────────────────────
358
+ export function assessFindings(findings, fileContents) {
359
+ return findings.map(finding => assessSingleFinding(finding, fileContents));
360
+ }
361
+ function assessSingleFinding(finding, fileContents) {
362
+ const content = fileContents.get(finding.file) ?? '';
363
+ const lines = content.split('\n');
364
+ // 1. Collect all signals
365
+ const filePathSignals = detectFilePathSignals(finding.file);
366
+ const nearbyCodeSignals = detectNearbyCodeSignals(lines, finding.line);
367
+ const importSignals = detectImportSignals(content, finding.language);
368
+ const functionNameSignals = detectFunctionNameSignals(finding.matchedLine);
369
+ // 2. Check protocol pattern (handles its own windowing)
370
+ const importText = lines.slice(0, 50).join('\n');
371
+ const protocolResult = detectProtocolPattern(finding, lines, finding.line, importText);
372
+ // 3. Merge all signals
373
+ const allSignals = [
374
+ ...filePathSignals,
375
+ ...nearbyCodeSignals,
376
+ ...importSignals,
377
+ ...functionNameSignals,
378
+ ];
379
+ if (protocolResult) {
380
+ allSignals.push(protocolResult.signal);
381
+ }
382
+ // 4. Resolve context
383
+ let usageContext;
384
+ const hasIncreasesRisk = allSignals.some(s => s.influence === 'increases-risk');
385
+ if (protocolResult && !hasIncreasesRisk) {
386
+ // Protocol pattern detected with no security-increasing signals => use protocol's context
387
+ usageContext = protocolResult.contextOverride;
388
+ }
389
+ else {
390
+ usageContext = resolveContext(allSignals).context;
391
+ }
392
+ // 5. Compute adjusted risk
393
+ const adjustedRisk = computeAdjustedRisk(finding.risk, usageContext);
394
+ // 6. Build context evidence
395
+ const contextEvidence = allSignals.map(signal => {
396
+ const arrow = signal.influence === 'increases-risk' ? '\u2191' : signal.influence === 'decreases-risk' ? '\u2193' : '\u2022';
397
+ return `${arrow} ${signal.type}: ${signal.value}`;
398
+ });
399
+ // 7. Build AssessedFinding
400
+ const riskContext = {
401
+ usageContext,
402
+ adjustedRisk,
403
+ contextEvidence,
404
+ signals: allSignals,
405
+ };
406
+ return {
407
+ ...finding,
408
+ originalRisk: finding.risk,
409
+ riskContext,
410
+ };
411
+ }
412
+ //# sourceMappingURL=risk-assessor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"risk-assessor.js","sourceRoot":"","sources":["../../../src/scanner/code/risk-assessor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ export interface OpensslProbeResult {
2
+ /** Negotiated TLS 1.3 group (e.g., 'X25519MLKEM768') */
3
+ group: string | null;
4
+ /** Classical key exchange info from "Peer Temp Key" / "Server Temp Key" */
5
+ peerTempKey: {
6
+ type: string;
7
+ name: string;
8
+ size: number;
9
+ } | null;
10
+ }
11
+ /**
12
+ * Find an OpenSSL 3.x binary (not LibreSSL).
13
+ * Returns the path or null if none found.
14
+ */
15
+ export declare function findOpenssl3(): Promise<string | null>;
16
+ /**
17
+ * Probe a host via `openssl s_client` to detect the negotiated TLS group.
18
+ * Gracefully returns nulls on any failure.
19
+ */
20
+ export declare function probeWithOpenssl(host: string, port: number): Promise<OpensslProbeResult>;
21
+ /**
22
+ * Parse openssl s_client output for TLS group and key exchange info.
23
+ */
24
+ export declare function parseOpensslOutput(output: string): OpensslProbeResult;
25
+ //# sourceMappingURL=openssl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openssl.d.ts","sourceRoot":"","sources":["../../src/scanner/openssl.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,kBAAkB;IACjC,wDAAwD;IACxD,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,2EAA2E;IAC3E,WAAW,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CAClE;AAUD;;;GAGG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAiB3D;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,kBAAkB,CAAC,CA0B7B;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,kBAAkB,CAyBrE"}
@@ -0,0 +1,113 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { access, constants } from 'node:fs/promises';
3
+ const OPENSSL_CANDIDATES = [
4
+ 'openssl',
5
+ '/opt/homebrew/opt/openssl@3/bin/openssl',
6
+ '/usr/local/opt/openssl@3/bin/openssl',
7
+ ];
8
+ const PROBE_TIMEOUT_MS = 5000;
9
+ /**
10
+ * Find an OpenSSL 3.x binary (not LibreSSL).
11
+ * Returns the path or null if none found.
12
+ */
13
+ export async function findOpenssl3() {
14
+ for (const candidate of OPENSSL_CANDIDATES) {
15
+ try {
16
+ // For absolute paths, check file exists first
17
+ if (candidate.startsWith('/')) {
18
+ await access(candidate, constants.X_OK);
19
+ }
20
+ const version = await runCommand(candidate, ['version']);
21
+ if (version.startsWith('OpenSSL 3.')) {
22
+ return candidate;
23
+ }
24
+ }
25
+ catch {
26
+ // Not found or not usable — try next
27
+ }
28
+ }
29
+ return null;
30
+ }
31
+ /**
32
+ * Probe a host via `openssl s_client` to detect the negotiated TLS group.
33
+ * Gracefully returns nulls on any failure.
34
+ */
35
+ export async function probeWithOpenssl(host, port) {
36
+ const nullResult = { group: null, peerTempKey: null };
37
+ let opensslBin;
38
+ try {
39
+ opensslBin = await findOpenssl3();
40
+ }
41
+ catch {
42
+ return nullResult;
43
+ }
44
+ if (!opensslBin) {
45
+ return nullResult;
46
+ }
47
+ let output;
48
+ try {
49
+ output = await runCommand(opensslBin, [
50
+ 's_client',
51
+ '-connect',
52
+ `${host}:${port}`,
53
+ ]);
54
+ }
55
+ catch {
56
+ return nullResult;
57
+ }
58
+ return parseOpensslOutput(output);
59
+ }
60
+ /**
61
+ * Parse openssl s_client output for TLS group and key exchange info.
62
+ */
63
+ export function parseOpensslOutput(output) {
64
+ const result = { group: null, peerTempKey: null };
65
+ // Match: "Negotiated TLS1.3 group: X25519MLKEM768"
66
+ const groupMatch = output.match(/Negotiated TLS[\d.]+ group:\s*(\S+)/i);
67
+ if (groupMatch) {
68
+ result.group = groupMatch[1];
69
+ }
70
+ // Match: "Peer Temp Key: ECDH, X25519, 253 bits"
71
+ // or: "Server Temp Key: ECDH, prime256v1, 256 bits"
72
+ const tempKeyMatch = output.match(/(?:Peer|Server) Temp Key:\s*(\w+),\s*([^,]+),\s*(\d+)\s*bits/i);
73
+ if (tempKeyMatch) {
74
+ result.peerTempKey = {
75
+ type: tempKeyMatch[1],
76
+ name: tempKeyMatch[2].trim(),
77
+ size: parseInt(tempKeyMatch[3], 10),
78
+ };
79
+ }
80
+ return result;
81
+ }
82
+ function runCommand(bin, args) {
83
+ return new Promise((resolve, reject) => {
84
+ const controller = new AbortController();
85
+ const timer = setTimeout(() => controller.abort(), PROBE_TIMEOUT_MS);
86
+ const child = execFile(bin, args, {
87
+ timeout: PROBE_TIMEOUT_MS,
88
+ signal: controller.signal,
89
+ }, (error, stdout, stderr) => {
90
+ clearTimeout(timer);
91
+ // openssl s_client writes useful info to both stdout and stderr
92
+ // and may exit non-zero even on success (connection closed)
93
+ const combined = String(stdout ?? '') + '\n' + String(stderr ?? '');
94
+ if (combined.trim().length > 0) {
95
+ resolve(combined);
96
+ }
97
+ else if (error) {
98
+ reject(error);
99
+ }
100
+ else {
101
+ resolve('');
102
+ }
103
+ });
104
+ // Close stdin immediately so openssl doesn't hang waiting for input
105
+ try {
106
+ child.stdin?.end();
107
+ }
108
+ catch {
109
+ // ignore
110
+ }
111
+ });
112
+ }
113
+ //# sourceMappingURL=openssl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openssl.js","sourceRoot":"","sources":["../../src/scanner/openssl.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"tls.d.ts","sourceRoot":"","sources":["../../src/scanner/tls.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,wBAAgB,QAAQ,CACtB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,aAAa,CAAC,CA+BxB"}
1
+ {"version":3,"file":"tls.d.ts","sourceRoot":"","sources":["../../src/scanner/tls.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAGvD,wBAAsB,QAAQ,CAC5B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,aAAa,CAAC,CA0CxB"}
@@ -1,5 +1,47 @@
1
1
  import tls from 'node:tls';
2
- export function scanHost(host, port, timeout) {
2
+ import { probeWithOpenssl } from './openssl.js';
3
+ export async function scanHost(host, port, timeout) {
4
+ const result = await connectTls(host, port, timeout);
5
+ // Enrich with openssl probe for PQC detection
6
+ // Node.js returns {} for getEphemeralKeyInfo() on TLS 1.3
7
+ try {
8
+ const probe = await probeWithOpenssl(host, port);
9
+ if (probe.group) {
10
+ const groupUpper = probe.group.toUpperCase();
11
+ const isPqc = groupUpper.includes('KYBER') ||
12
+ groupUpper.includes('MLKEM') ||
13
+ groupUpper.includes('ML-KEM');
14
+ if (isPqc) {
15
+ result.ephemeralKeyInfo = {
16
+ type: 'KEM',
17
+ name: probe.group,
18
+ size: 0,
19
+ };
20
+ }
21
+ else if (!result.ephemeralKeyInfo) {
22
+ // Classical group detected by openssl, Node.js had nothing
23
+ result.ephemeralKeyInfo = {
24
+ type: 'ECDH',
25
+ name: probe.group,
26
+ size: 0,
27
+ };
28
+ }
29
+ }
30
+ else if (!result.ephemeralKeyInfo && probe.peerTempKey) {
31
+ // No negotiated group line but we got Peer Temp Key info
32
+ result.ephemeralKeyInfo = {
33
+ type: probe.peerTempKey.type,
34
+ name: probe.peerTempKey.name,
35
+ size: probe.peerTempKey.size,
36
+ };
37
+ }
38
+ }
39
+ catch {
40
+ // openssl probe failed — keep Node.js data as-is
41
+ }
42
+ return result;
43
+ }
44
+ function connectTls(host, port, timeout) {
3
45
  return new Promise((resolve, reject) => {
4
46
  const socket = tls.connect({
5
47
  host,