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.
- package/README.md +165 -14
- package/dist/commands/analyze.d.ts.map +1 -1
- package/dist/commands/analyze.js +15 -5
- package/dist/commands/analyze.js.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/output/json-code.d.ts.map +1 -1
- package/dist/output/json-code.js +12 -0
- package/dist/output/json-code.js.map +1 -1
- package/dist/output/sarif.d.ts.map +1 -1
- package/dist/output/sarif.js +27 -2
- package/dist/output/sarif.js.map +1 -1
- package/dist/output/terminal-code.d.ts +1 -0
- package/dist/output/terminal-code.d.ts.map +1 -1
- package/dist/output/terminal-code.js +66 -6
- package/dist/output/terminal-code.js.map +1 -1
- package/dist/scanner/classifier.js +1 -1
- package/dist/scanner/classifier.js.map +1 -1
- package/dist/scanner/code/grader.d.ts.map +1 -1
- package/dist/scanner/code/grader.js +75 -21
- package/dist/scanner/code/grader.js.map +1 -1
- package/dist/scanner/code/matcher.d.ts +11 -2
- package/dist/scanner/code/matcher.d.ts.map +1 -1
- package/dist/scanner/code/matcher.js +3 -2
- package/dist/scanner/code/matcher.js.map +1 -1
- package/dist/scanner/code/risk-assessor.d.ts +25 -0
- package/dist/scanner/code/risk-assessor.d.ts.map +1 -0
- package/dist/scanner/code/risk-assessor.js +412 -0
- package/dist/scanner/code/risk-assessor.js.map +1 -0
- package/dist/scanner/openssl.d.ts +25 -0
- package/dist/scanner/openssl.d.ts.map +1 -0
- package/dist/scanner/openssl.js +113 -0
- package/dist/scanner/openssl.js.map +1 -0
- package/dist/scanner/tls.d.ts.map +1 -1
- package/dist/scanner/tls.js +43 -1
- package/dist/scanner/tls.js.map +1 -1
- package/dist/types/index.d.ts +21 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +4 -1
- package/dist/types/index.js.map +1 -1
- 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":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AASrD,MAAM,kBAAkB,GAAG;IACzB,SAAS;IACT,yCAAyC;IACzC,sCAAsC;CACvC,CAAC;AAEF,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAE9B;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY;IAChC,KAAK,MAAM,SAAS,IAAI,kBAAkB,EAAE,CAAC;QAC3C,IAAI,CAAC;YACH,8CAA8C;YAC9C,IAAI,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC9B,MAAM,MAAM,CAAC,SAAS,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC;YAC1C,CAAC;YAED,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,SAAS,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;YACzD,IAAI,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;gBACrC,OAAO,SAAS,CAAC;YACnB,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,qCAAqC;QACvC,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,IAAY,EACZ,IAAY;IAEZ,MAAM,UAAU,GAAuB,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;IAE1E,IAAI,UAAyB,CAAC;IAC9B,IAAI,CAAC;QACH,UAAU,GAAG,MAAM,YAAY,EAAE,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,IAAI,MAAc,CAAC;IACnB,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,UAAU,CAAC,UAAU,EAAE;YACpC,UAAU;YACV,UAAU;YACV,GAAG,IAAI,IAAI,IAAI,EAAE;SAClB,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,OAAO,kBAAkB,CAAC,MAAM,CAAC,CAAC;AACpC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAc;IAC/C,MAAM,MAAM,GAAuB,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;IAEtE,mDAAmD;IACnD,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAC7B,sCAAsC,CACvC,CAAC;IACF,IAAI,UAAU,EAAE,CAAC;QACf,MAAM,CAAC,KAAK,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;IAC/B,CAAC;IAED,iDAAiD;IACjD,uDAAuD;IACvD,MAAM,YAAY,GAAG,MAAM,CAAC,KAAK,CAC/B,+DAA+D,CAChE,CAAC;IACF,IAAI,YAAY,EAAE,CAAC;QACjB,MAAM,CAAC,WAAW,GAAG;YACnB,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC;YACrB,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE;YAC5B,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;SACpC,CAAC;IACJ,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,UAAU,CAAC,GAAW,EAAE,IAAc;IAC7C,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,gBAAgB,CAAC,CAAC;QAErE,MAAM,KAAK,GAAG,QAAQ,CACpB,GAAG,EACH,IAAI,EACJ;YACE,OAAO,EAAE,gBAAgB;YACzB,MAAM,EAAE,UAAU,CAAC,MAAM;SAC1B,EACD,CAAC,KAAmB,EAAE,MAAuB,EAAE,MAAuB,EAAE,EAAE;YACxE,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,gEAAgE;YAChE,4DAA4D;YAC5D,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,GAAG,IAAI,GAAG,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;YACpE,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC/B,OAAO,CAAC,QAAQ,CAAC,CAAC;YACpB,CAAC;iBAAM,IAAI,KAAK,EAAE,CAAC;gBACjB,MAAM,CAAC,KAAK,CAAC,CAAC;YAChB,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,EAAE,CAAC,CAAC;YACd,CAAC;QACH,CAAC,CACF,CAAC;QAEF,oEAAoE;QACpE,IAAI,CAAC;YACH,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC;QACrB,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -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;
|
|
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"}
|
package/dist/scanner/tls.js
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
import tls from 'node:tls';
|
|
2
|
-
|
|
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,
|