@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
- const hasJwt = testCode(code, /jwt|jsonwebtoken|jose/gi);
236
- const hasJwtVerify = testCode(code, /jwt\.verify|jwtVerify|verifyToken|jose\.jwtVerify/gi);
237
- const hasJwtSign = testCode(code, /jwt\.sign|jwtSign|signToken/gi);
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(code, /jwt\.decode|jwtDecode|jose\.decodeJwt/gi);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kevinrabun/judges",
3
- "version": "3.124.0",
3
+ "version": "3.124.2",
4
4
  "description": "45 specialized judges that evaluate AI-generated code for security, cost, and quality.",
5
5
  "mcpName": "io.github.KevinRabun/judges",
6
6
  "type": "module",
package/server.json CHANGED
@@ -16,12 +16,12 @@
16
16
  "mimeType": "image/png"
17
17
  }
18
18
  ],
19
- "version": "3.124.0",
19
+ "version": "3.124.2",
20
20
  "packages": [
21
21
  {
22
22
  "registryType": "npm",
23
23
  "identifier": "@kevinrabun/judges",
24
- "version": "3.124.0",
24
+ "version": "3.124.2",
25
25
  "transport": {
26
26
  "type": "stdio"
27
27
  }