@kevinrabun/judges 3.124.0 → 3.124.2
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.
|
@@ -232,9 +232,13 @@ export function analyzeAuthentication(code, language, context) {
|
|
|
232
232
|
// JWT without verification
|
|
233
233
|
// This is absence-based in single-file mode: verification middleware often
|
|
234
234
|
// lives in a separate file from the login/sign endpoint.
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
const
|
|
235
|
+
// Skip ambient type declarations (declare namespace/const) — type stubs
|
|
236
|
+
// describe APIs, they don't implement auth flows.
|
|
237
|
+
const isDeclareStubFile = /^\s*declare\s+(?:namespace|const|module|function|class)\b/m.test(code);
|
|
238
|
+
const nonDeclareCode = isDeclareStubFile ? lines.filter((ln) => !/^\s*declare\b/.test(ln)).join("\n") : code;
|
|
239
|
+
const hasJwt = testCode(nonDeclareCode, /jwt|jsonwebtoken|jose/gi);
|
|
240
|
+
const hasJwtVerify = testCode(nonDeclareCode, /jwt\.verify|jwtVerify|verifyToken|jose\.jwtVerify/gi);
|
|
241
|
+
const hasJwtSign = testCode(nonDeclareCode, /jwt\.sign|jwtSign|signToken/gi);
|
|
238
242
|
if (hasJwt && hasJwtSign && !hasJwtVerify) {
|
|
239
243
|
findings.push({
|
|
240
244
|
ruleId: `${prefix}-${String(ruleNum++).padStart(3, "0")}`,
|
|
@@ -250,8 +254,8 @@ export function analyzeAuthentication(code, language, context) {
|
|
|
250
254
|
});
|
|
251
255
|
}
|
|
252
256
|
// JWT decode used for auth decisions without signature verification
|
|
253
|
-
const hasJwtDecode = testCode(
|
|
254
|
-
const jwtDecodeLines = getLineNumbers(code, /jwt\.decode\s*\(|jwtDecode\s*\(|jose\.decodeJwt\s*\(/gi);
|
|
257
|
+
const hasJwtDecode = testCode(nonDeclareCode, /jwt\.decode|jwtDecode|jose\.decodeJwt/gi);
|
|
258
|
+
const jwtDecodeLines = getLineNumbers(code, /jwt\.decode\s*\(|jwtDecode\s*\(|jose\.decodeJwt\s*\(/gi).filter((ln) => !/^\s*declare\b/.test(lines[ln - 1] || ""));
|
|
255
259
|
// Python PyJWT: jwt.decode(token, key, algorithms=[...]) DOES verify the signature
|
|
256
260
|
const hasPyJwtVerify = /jwt\.decode\s*\([^)]*,\s*\w+[^)]*algorithms/i.test(code);
|
|
257
261
|
if (hasJwtDecode && !hasJwtVerify && !hasPyJwtVerify) {
|
|
@@ -295,6 +299,39 @@ export function analyzeAuthentication(code, language, context) {
|
|
|
295
299
|
});
|
|
296
300
|
}
|
|
297
301
|
}
|
|
302
|
+
// JWT verify without explicit algorithm restriction
|
|
303
|
+
// Without an `algorithms` option, some JWT libraries accept any algorithm
|
|
304
|
+
// including "none", allowing attackers to forge tokens.
|
|
305
|
+
{
|
|
306
|
+
const verifyNoAlgoLines = [];
|
|
307
|
+
const verifyCallPattern = /jwt\.verify\s*\(/gi;
|
|
308
|
+
for (let i = 0; i < lines.length; i++) {
|
|
309
|
+
const line = lines[i];
|
|
310
|
+
if (!/^\s*declare\b/.test(line) && verifyCallPattern.test(line)) {
|
|
311
|
+
// Look at a window around the call for an algorithms option
|
|
312
|
+
const windowStart = Math.max(0, i - 1);
|
|
313
|
+
const windowEnd = Math.min(lines.length, i + 6);
|
|
314
|
+
const window = lines.slice(windowStart, windowEnd).join("\n");
|
|
315
|
+
if (!/algorithms\s*:\s*\[/i.test(window)) {
|
|
316
|
+
verifyNoAlgoLines.push(i + 1);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
verifyCallPattern.lastIndex = 0;
|
|
320
|
+
}
|
|
321
|
+
if (verifyNoAlgoLines.length > 0) {
|
|
322
|
+
findings.push({
|
|
323
|
+
ruleId: `${prefix}-${String(ruleNum++).padStart(3, "0")}`,
|
|
324
|
+
severity: "high",
|
|
325
|
+
title: "JWT verify without explicit algorithm restriction",
|
|
326
|
+
description: "jwt.verify() is called without an explicit `algorithms` option. Without algorithm pinning, the server may accept tokens signed with unexpected algorithms (including 'none'), enabling token forgery.",
|
|
327
|
+
lineNumbers: verifyNoAlgoLines,
|
|
328
|
+
recommendation: "Always pass an explicit algorithm whitelist: jwt.verify(token, secret, { algorithms: ['HS256'] }). This prevents algorithm confusion and 'none' algorithm attacks.",
|
|
329
|
+
reference: "CWE-757: Selection of Less-Secure Algorithm During Negotiation / OWASP JWT Cheat Sheet",
|
|
330
|
+
suggestedFix: "Add algorithm restriction: jwt.verify(token, secret, { algorithms: ['HS256'] })",
|
|
331
|
+
confidence: 0.85,
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
}
|
|
298
335
|
// Timing-unsafe comparison of secrets/tokens/signatures
|
|
299
336
|
const timingLines = [];
|
|
300
337
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -695,7 +695,8 @@ function getFpReason(finding, lines, isIaC, fileCategory, filePath) {
|
|
|
695
695
|
/^from\s/.test(trimmed) ||
|
|
696
696
|
/^export\s+(?:type|interface|abstract)\s/.test(trimmed) ||
|
|
697
697
|
/^(?:type|interface)\s+\w+/.test(trimmed) ||
|
|
698
|
-
/^using\s/.test(trimmed)
|
|
698
|
+
/^using\s/.test(trimmed) ||
|
|
699
|
+
/^declare\s+(?:namespace|const|function|class|module|var|let|type|interface|enum|abstract)\b/.test(trimmed));
|
|
699
700
|
});
|
|
700
701
|
if (allImportsOrTypes) {
|
|
701
702
|
return "Finding targets import/type declarations — no runtime behavior to evaluate.";
|
package/package.json
CHANGED
package/server.json
CHANGED
|
@@ -16,12 +16,12 @@
|
|
|
16
16
|
"mimeType": "image/png"
|
|
17
17
|
}
|
|
18
18
|
],
|
|
19
|
-
"version": "3.124.
|
|
19
|
+
"version": "3.124.2",
|
|
20
20
|
"packages": [
|
|
21
21
|
{
|
|
22
22
|
"registryType": "npm",
|
|
23
23
|
"identifier": "@kevinrabun/judges",
|
|
24
|
-
"version": "3.124.
|
|
24
|
+
"version": "3.124.2",
|
|
25
25
|
"transport": {
|
|
26
26
|
"type": "stdio"
|
|
27
27
|
}
|