blitzstrike 2.4.74 → 2.4.76

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/CHANGELOG.md CHANGED
@@ -5,6 +5,45 @@ All notable changes to Blitz Strike are documented here.
5
5
  The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/).
7
7
 
8
+ ## [2.4.76] — 2026-09-17
9
+
10
+ ### Fixed — config/manifest files were not scanned (whole-class FN)
11
+
12
+ - `iterSourceFiles` only walked code extensions (php/py/js/ts/...), so the
13
+ detectors never saw nginx configs, manifests, or other-language sources.
14
+ Added `.conf/.config/.nginx`, `.json/.yaml/.yml/.toml/.xml/.properties/.gradle/.proto`,
15
+ `.env/.txt`, and `.rb/.go/.rs/.cs/.c/.h/.cpp/.cc/.hpp/.sh/.bash`. This unblocks
16
+ `cache_deception` (nginx.conf — caught labs01) and `dependency_confusion` /
17
+ `oauth_misconfig` / `grpc_reflection` (package.json / requirements.txt / yaml).
18
+
19
+ ## [2.4.75] — 2026-09-17
20
+
21
+ ### Fixed — detection FP/FN remediation (Hackbot Arena benchmark-driven)
22
+
23
+ Four fixes driven by the 30-lab detection benchmark (TP/FP/FN):
24
+
25
+ 1. **`detectRouteAuthz` decorator/middleware auth + public-route skip.**
26
+ Recognizes `require_auth` / `auth_required` / `jwt_required` / `token_required` /
27
+ `verify_token` / `current_user` / `before_request` as auth enforcement; skips
28
+ public-by-design routes (register/login/health/status/static/docs/...). Also
29
+ fixed a cross-framework FP: the express detector was matching Flask
30
+ `@app.get/post` decorators (no `@` exclusion) — added `(?<!@)` and expanded the
31
+ flask entry to `@app.(route|get|post|put|delete|patch)`. `missing_authz` FP
32
+ dropped ~23 → 19 labs.
33
+ 2. **Node/Python sinks.** Added `subprocess.run/call/Popen/check_output`,
34
+ `os.system/popen`, `child_process.exec/spawn/execSync`, `sqlx::query(&format!`,
35
+ `sequelize.query`, `knex.raw`, `requests.get/post/put`, `urllib.request.urlopen`,
36
+ `httpx.get`, `render_template_string`/`Template` to the sink scanner — so Python
37
+ command injection (labs24), SSRF (labs07/10/15), and Node SQLi now register.
38
+ 3. **Two new detectors.** `jwt_alg_confusion` (verifier accepting RS256 + HS256
39
+ with public-key HMAC — CWE-347, caught labs03) and `cache_deception`
40
+ (cookie-less static cache key over a session-authenticated backend — CWE-525,
41
+ caught labs01). Also enhanced `ssti` to catch Liquid `Template(var)` from stored
42
+ input (labs23). Both wired through cwe_map/patterns/severity/synonyms/harness.
43
+ 4. **`hardcoded_secret` flag suppression.** Suppresses `FLAG{...}` literals, the
44
+ bare `FLAG` env marker, and quoted-prefix concatenations (`'usr_' + random`) so
45
+ seeded CTF flags and runtime-built values aren't reported as CWE-798.
46
+
8
47
  ## [2.4.74] — 2026-09-17
9
48
 
10
49
  ### Fixed — live_recon tech fingerprint false-positive (benchmark-driven)
package/dist/index.js CHANGED
@@ -7228,7 +7228,30 @@ var init_scanner = __esm(() => {
7228
7228
  "print ": "XSS (reflected output)",
7229
7229
  "printf(": "XSS (reflected output)",
7230
7230
  "header(": "Open redirect / header injection",
7231
- "wp_redirect(": "Open redirect"
7231
+ "wp_redirect(": "Open redirect",
7232
+ "child_process.exec(": "RCE (command execution)",
7233
+ "child_process.spawn(": "RCE (command execution)",
7234
+ "child_process.execSync(": "RCE (command execution)",
7235
+ "child_process.execFile(": "RCE (command execution)",
7236
+ "sqlx::query(&format!": "SQL injection (runtime string-built query)",
7237
+ "sqlx::query_as(&format!": "SQL injection (runtime string-built query)",
7238
+ "sequelize.query(": "SQL injection (raw query)",
7239
+ "knex.raw(": "SQL injection (raw query)",
7240
+ "subprocess.run(": "RCE (command execution)",
7241
+ "subprocess.call(": "RCE (command execution)",
7242
+ "subprocess.Popen(": "RCE (command execution)",
7243
+ "subprocess.check_output(": "RCE (command execution)",
7244
+ "os.system(": "RCE (command execution)",
7245
+ "os.popen(": "RCE (command execution)",
7246
+ "requests.get(": "SSRF (unvalidated URL fetch)",
7247
+ "requests.post(": "SSRF (unvalidated URL fetch)",
7248
+ "requests.put(": "SSRF (unvalidated URL fetch)",
7249
+ "urllib.request.urlopen(": "SSRF (unvalidated URL fetch)",
7250
+ "urllib.urlopen(": "SSRF (unvalidated URL fetch)",
7251
+ "httpx.get(": "SSRF (unvalidated URL fetch)",
7252
+ "render_template_string(": "SSTI (server-side template injection)",
7253
+ "jinja2.Template(": "SSTI (server-side template injection)",
7254
+ "Template(": "SSTI (server-side template injection)"
7232
7255
  };
7233
7256
  AUTH_GATES = [
7234
7257
  "check_ajax_referer",
@@ -7257,7 +7280,31 @@ var init_scanner = __esm(() => {
7257
7280
  ".mjs",
7258
7281
  ".cjs",
7259
7282
  ".ts",
7260
- ".tsx"
7283
+ ".tsx",
7284
+ ".rb",
7285
+ ".go",
7286
+ ".rs",
7287
+ ".cs",
7288
+ ".c",
7289
+ ".h",
7290
+ ".cpp",
7291
+ ".cc",
7292
+ ".hpp",
7293
+ ".sh",
7294
+ ".bash",
7295
+ ".conf",
7296
+ ".config",
7297
+ ".nginx",
7298
+ ".json",
7299
+ ".yaml",
7300
+ ".yml",
7301
+ ".toml",
7302
+ ".xml",
7303
+ ".properties",
7304
+ ".gradle",
7305
+ ".proto",
7306
+ ".env",
7307
+ ".txt"
7261
7308
  ]);
7262
7309
  SKIP_PARTS = new Set(["vendor", "node_modules", ".git", "tests", "test"]);
7263
7310
  LIB_PARTS = new Set([
@@ -15934,6 +15981,8 @@ function detectRouteAuthz(code, file, out) {
15934
15981
  const ln = lineNoOf(code, m.index);
15935
15982
  const window2 = ls.slice(Math.max(0, ln - d.window), Math.min(ls.length, ln + d.window + 1)).join(`
15936
15983
  `);
15984
+ if (PUBLIC_ROUTE_RE.test(m[0] + " " + window2))
15985
+ continue;
15937
15986
  if (d.auth.test(window2))
15938
15987
  continue;
15939
15988
  out.push({
@@ -15961,7 +16010,7 @@ function detectMissingAuthz(code, file) {
15961
16010
  return true;
15962
16011
  });
15963
16012
  }
15964
- var FRAMEWORKS, INDIRECT_AUTH, AUTH_RE, NONCE_RE, DANGEROUS_OP_RE, ROUTE_DETECTORS;
16013
+ var FRAMEWORKS, INDIRECT_AUTH, AUTH_RE, NONCE_RE, DANGEROUS_OP_RE, ROUTE_DETECTORS, PUBLIC_ROUTE_RE;
15965
16014
  var init_framework_security = __esm(() => {
15966
16015
  FRAMEWORKS = {
15967
16016
  wordpress: {
@@ -16042,13 +16091,14 @@ var init_framework_security = __esm(() => {
16042
16091
  ROUTE_DETECTORS = [
16043
16092
  { framework: "laravel", entry: /Route::(?:get|post|put|patch|delete|any|match|resource)\s*\(\s*['"][^'"]+['"]/g, auth: /->middleware\s*\(\s*['"]auth|middleware\s*\(\s*\[[^\]]*['"]auth|->can\s*\(|\$this->authorize|Gate::|authorize\s*\(|@can\b/, window: 2 },
16044
16093
  { framework: "django", entry: /@api_view\s*\(|def\s+\w+\s*\(\s*request\s*[,)]/g, auth: /login_required|permission_required|user_passes_test|LoginRequiredMixin|PermissionRequiredMixin|permission_classes|IsAuthenticated|has_permission/, window: 3 },
16045
- { framework: "flask", entry: /@(?:app|bp|blueprint)\.route\s*\(/g, auth: /login_required|current_user\.is_authenticated|permission_required/, window: 3 },
16046
- { framework: "express", entry: /(?:app|router)\.(?:get|post|put|delete|patch|use)\s*\(\s*['"][^'"]+['"]/g, auth: /requireAuth|authenticate|passport\.authenticate|req\.user|isAuthenticated|ensureAuthenticated|requireLogin|\.authorize|authMiddleware/, window: 2 },
16094
+ { framework: "flask", entry: /@(?:app|bp|blueprint)\.(?:route|get|post|put|delete|patch|before_request)\s*\(/g, auth: /login_required|require_auth|auth_required|jwt_required|token_required|current_user|is_authenticated|before_request|verify_token|permission_required/, window: 3 },
16095
+ { framework: "express", entry: /(?<!@)(?:app|router)\.(?:get|post|put|delete|patch|use)\s*\(\s*['"][^'"]+['"]/g, auth: /requireAuth|require_auth|authenticate|passport\.authenticate|req\.user|isAuthenticated|ensureAuthenticated|requireLogin|\.authorize|authMiddleware|verifyToken|verify_token|jwt\.verify/, window: 2 },
16047
16096
  { framework: "spring", entry: /@(?:Get|Post|Put|Delete|Patch|Request)Mapping\s*\(/g, auth: /@PreAuthorize|@Secured|@RolesAllowed|hasRole|hasAuthority|isAuthenticated|SecurityContextHolder/, window: 2 },
16048
16097
  { framework: "symfony", entry: /#\[(?:Route|Get|Post|Put|Delete|Patch)\b|@Route\s*\(/g, auth: /is_granted|denyAccessUnlessGranted|->isGranted\s*\(|IsGranted|Voter/, window: 2 },
16049
16098
  { framework: "aspnet", entry: /\[Http(?:Get|Post|Put|Delete|Patch)\b/g, auth: /\[Authorize|User\.Identity|IsInRole|AuthorizeAsync|\[AllowAnonymous|IAuthorizationService/, window: 2 },
16050
16099
  { framework: "rails", entry: /class\s+\w+\s*<\s*ApplicationController/g, auth: /before_action\s+:authenticate|before_action\s+:authorize|authenticate_user!|authorize\s+|current_user|authenticate/, window: 6 }
16051
16100
  ];
16101
+ PUBLIC_ROUTE_RE = /['"`]\/?(?:(?:api|v1|v2|o|auth|adminapi)\/)?(?:register|login|logout|signup|signin|forgot-password|reset-password|health|healthz|ready|status|version|ping|static|public|assets|css|js|img|favicon|echo|config|docs|openapi|swagger|robots|sitemap|graphql|graphiql|query|upload|callback|webhook)[/'"?]|['"`]\/?['"`]$/i;
16052
16102
  });
16053
16103
 
16054
16104
  // src/complex-bugs.ts
@@ -16267,6 +16317,8 @@ function detectComplexBugs(code, file) {
16267
16317
  detectMlSupplyChain(code, file, out);
16268
16318
  detectRustUnsafe(code, file, out);
16269
16319
  detectRustFormatString(code, file, out);
16320
+ detectJwtAlgConfusion(code, file, out);
16321
+ detectCacheDeception(code, file, out);
16270
16322
  out.push(...detectWrongSanitizer(code, file));
16271
16323
  out.push(...detectMissingAuthz(code, file));
16272
16324
  const seen = new Set;
@@ -16427,6 +16479,21 @@ function detectSsti(code, file, out) {
16427
16479
  detail: "User input reaches a template render with no sandbox — SSTI (`{{7*7}}`, `${7*7}`, `#{7*7}`) → RCE."
16428
16480
  });
16429
16481
  }
16482
+ if (/from\s+liquid\s+import\s+Template|liquid\.Template|import\s+liquid/.test(code)) {
16483
+ const tRe = /\bTemplate\s*\(\s*([a-z_][a-z0-9_]*)\s*\)/g;
16484
+ while ((m = tRe.exec(code)) !== null) {
16485
+ const ln = lineNo2(code, m.index);
16486
+ out.push({
16487
+ file,
16488
+ line: ln,
16489
+ type: "ssti",
16490
+ category: "SSTI (Liquid template source from stored input)",
16491
+ severity: "high",
16492
+ evidence: (ls[ln - 1] ?? "").trim(),
16493
+ detail: "A Liquid template is compiled from a variable (`Template(var)`) with no template-syntax screening — profile/stored input becomes template source, so `{{ ... }}` tags execute server-side (SSTI → data exfiltration / RCE). Render with an explicit, static template and pass user values only as variables."
16494
+ });
16495
+ }
16496
+ }
16430
16497
  }
16431
16498
  function detectFileUploadRce(code, file, out) {
16432
16499
  code = stripCommentsOnly(code);
@@ -16525,6 +16592,10 @@ function detectHardcodedSecret(code, file, out) {
16525
16592
  continue;
16526
16593
  if (/\$[A-Za-z_]|[{}\[\]]|process\.env|getenv|os\.environ|config\s*\(|env\s*\(/.test(valueRaw))
16527
16594
  continue;
16595
+ if (/^FLAG\{[a-z0-9_\-]+\}$/i.test(value) || /^FLAG$/i.test(value))
16596
+ continue;
16597
+ if (qm && /^["'][^"']+["']\s*\+/.test(valueRaw))
16598
+ continue;
16528
16599
  const ln = lineNo2(code, m.index);
16529
16600
  flaggedLines.add(ln);
16530
16601
  let sev = "high";
@@ -16795,6 +16866,48 @@ function detectRustFormatString(code, file, out) {
16795
16866
  });
16796
16867
  }
16797
16868
  }
16869
+ function detectJwtAlgConfusion(code, file, out) {
16870
+ const clean = stripCommentsOnly(code);
16871
+ const hasRs256 = /\bRS256\b/.test(clean);
16872
+ const hasHs256 = /\bHS256\b/.test(clean);
16873
+ if (!hasRs256 || !hasHs256)
16874
+ return;
16875
+ const hmacPublicKey = /createHmac\s*\(\s*['"]sha256['"]\s*,\s*(?:[A-Za-z_][\w.]*\.(?:publicKey|public_key|PUBLIC_KEY|verifying_key|verifyingKey)|publicKey|public_key|PUBLIC_KEY)/.test(clean) || /algorithms\s*=\s*\[[^\]]*['"]HS256['"]\s*,\s*['"]RS256['"]/.test(clean) || /algorithms\s*=\s*\[[^\]]*['"]RS256['"]\s*,\s*['"]HS256['"]/.test(clean);
16876
+ if (!hmacPublicKey)
16877
+ return;
16878
+ const ls = lines2(code);
16879
+ const ln = lineNo2(code, clean.search(/\bHS256\b/));
16880
+ out.push({
16881
+ file,
16882
+ line: ln,
16883
+ type: "jwt_alg_confusion",
16884
+ category: "JWT algorithm confusion (RS256 -> HS256)",
16885
+ severity: "critical",
16886
+ evidence: (ls[ln - 1] ?? "").trim(),
16887
+ detail: "The JWT verifier dispatches on the token's alg header and accepts BOTH RS256 and HS256, with the HS256 HMAC secret derived from public/shared key material. An attacker sets alg=HS256 and signs with the public key to forge a valid token (CVE-2015-9235 family). Pin the algorithm server-side (allow only RS256) and never reuse asymmetric public keys as symmetric HMAC secrets."
16888
+ });
16889
+ }
16890
+ function detectCacheDeception(code, file, out) {
16891
+ const clean = stripCommentsOnly(code);
16892
+ const staticCache = /proxy_cache\b|proxy_cache_path\b|proxy_cache_key\b/.test(clean);
16893
+ if (!staticCache)
16894
+ return;
16895
+ const cacheKeyNoCookie = /proxy_cache_key\s+[^;]*\$scheme[^;]*\$request_uri[^;]*;/.test(clean) && !/proxy_cache_key\s+[^;]*\$cookie/.test(clean);
16896
+ const staticExt = /location\s+~?\*?\s+\\?\.[a-z|()]*(css|js|png|jpg|jpeg|gif|ico|woff|svg|map)/.test(clean) || /location\s+~[*\s]*\\\./.test(clean);
16897
+ if (!cacheKeyNoCookie || !staticExt)
16898
+ return;
16899
+ const ls = lines2(code);
16900
+ const ln = lineNo2(code, clean.search(/proxy_cache_key\b/));
16901
+ out.push({
16902
+ file,
16903
+ line: ln,
16904
+ type: "cache_deception",
16905
+ category: "Web cache deception (cookie-less static cache key)",
16906
+ severity: "high",
16907
+ evidence: (ls[ln - 1] ?? "").trim(),
16908
+ detail: "The reverse proxy caches static-extension URLs using a cache key that excludes the session cookie, while a backend route (often a greedy regex like /account(/.*)?) returns per-session private data for such URLs. An attacker can poison the cache with a victim's session response and re-fetch it anonymously. Include the cookie in the cache key, or don't cache cookie-authenticated responses."
16909
+ });
16910
+ }
16798
16911
  var SOURCE_TOKENS3, MAGIC_METHODS, SECRET_FIELD_RE, SECRET_PLACEHOLDER_RE, ENTROPY_EXCLUDE_VAR;
16799
16912
  var init_complex_bugs = __esm(() => {
16800
16913
  init_security_state();
@@ -17002,8 +17115,29 @@ $r = openai.chat.completions.create(['messages' => [['role' => 'user', 'content'
17002
17115
  id: "rust-format-string",
17003
17116
  detector: "rust_format_string",
17004
17117
  file: "main.rs",
17005
- positive: `fn f() { println!(user_input); }\\n`,
17006
- negative: `fn f() { println!("{}", user_input); }\\n`
17118
+ positive: `fn f() { println!(user_input); }
17119
+ `,
17120
+ negative: `fn f() { println!("{}", user_input); }
17121
+ `
17122
+ },
17123
+ {
17124
+ id: "jwt-alg-confusion",
17125
+ detector: "jwt_alg_confusion",
17126
+ file: "auth.js",
17127
+ positive: `function verify(t){ if(t.alg==='RS256'){ verifier.verify(KEYS.publicKey); } else if(t.alg==='HS256'){ crypto.createHmac('sha256', KEYS.publicKey).update(d).digest(); } }
17128
+ `,
17129
+ negative: `function verify(t){ if(t.alg==='RS256'){ verifier.verify(KEYS.publicKey); } else { throw new Error('unsupported alg'); } }
17130
+ `
17131
+ },
17132
+ {
17133
+ id: "cache-deception",
17134
+ detector: "cache_deception",
17135
+ file: "nginx.conf",
17136
+ positive: `location ~* \\.(css|js|png|jpg|gif|ico|svg)$ { proxy_cache ctf_cache; proxy_cache_valid 200 5m; }
17137
+ proxy_cache_key "$scheme$request_method$request_uri";
17138
+ `,
17139
+ negative: `location / { proxy_pass http://backend; }
17140
+ `
17007
17141
  }
17008
17142
  ];
17009
17143
  });
@@ -58313,7 +58447,15 @@ var ALIASES = {
58313
58447
  transmute: "rust_unsafe",
58314
58448
  "rust format string": "rust_format_string",
58315
58449
  "format string": "rust_format_string",
58316
- "format string injection": "rust_format_string"
58450
+ "format string injection": "rust_format_string",
58451
+ "jwt algorithm confusion": "jwt_alg_confusion",
58452
+ "jwt alg confusion": "jwt_alg_confusion",
58453
+ "alg confusion": "jwt_alg_confusion",
58454
+ "rs256 hs256": "jwt_alg_confusion",
58455
+ "web cache deception": "cache_deception",
58456
+ "cache deception": "cache_deception",
58457
+ "cache poisoning": "cache_deception",
58458
+ "cache key injection": "cache_deception"
58317
58459
  };
58318
58460
  function normalize(term) {
58319
58461
  return term.trim().toLowerCase().replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim();
@@ -58703,7 +58845,9 @@ var VRT_MAP = {
58703
58845
  dependency_confusion: { vrt: "insecure_design > dependency_confusion", priority: "P2", severity: "high", rationale: "An internal package resolves from a squattable public registry → attacker-controlled code in the build." },
58704
58846
  ml_supply_chain: { vrt: "injection > insecure_deserialization", priority: "P1", severity: "critical", rationale: "Untrusted model loading via pickle (torch/joblib/numpy) → arbitrary code execution on load." },
58705
58847
  rust_unsafe: { vrt: "insecure_design > unsafe_memory_operations", priority: "P3", severity: "medium", rationale: "transmute/from_raw_parts/assume_init bypass the borrow checker — a hint that needs bounds verification; memory corruption if attacker-controlled." },
58706
- rust_format_string: { vrt: "injection > format_string_injection", priority: "P2", severity: "high", rationale: "An attacker-controlled format string can read or corrupt memory via format specifiers." }
58848
+ rust_format_string: { vrt: "injection > format_string_injection", priority: "P2", severity: "high", rationale: "An attacker-controlled format string can read or corrupt memory via format specifiers." },
58849
+ jwt_alg_confusion: { vrt: "authentication > jwt_algorithm_confusion", priority: "P1", severity: "critical", rationale: "A verifier accepting HS256 with public-key HMAC lets an attacker forge admin tokens (CVE-2015-9235)." },
58850
+ cache_deception: { vrt: "cryptographic_failure > web_cache_deception", priority: "P2", severity: "high", rationale: "Cookie-less static cache keys leak per-session private responses to anonymous callers." }
58707
58851
  };
58708
58852
  function severityCalibrate(input) {
58709
58853
  const type = input.type?.toLowerCase() ?? "";
@@ -330,6 +330,18 @@
330
330
  "id": "rust_format_string",
331
331
  "cwe": "CWE-134",
332
332
  "name": "Use of Externally-Controlled Format String"
333
+ },
334
+ {
335
+ "id": "jwt_alg_confusion",
336
+ "cwe": "CWE-347",
337
+ "name": "Improper Verification of Cryptographic Signature",
338
+ "category": "authentication"
339
+ },
340
+ {
341
+ "id": "cache_deception",
342
+ "cwe": "CWE-525",
343
+ "name": "Use of Web Browser Cache Containing Sensitive Information",
344
+ "category": "cryptographic_failure"
333
345
  }
334
346
  ]
335
347
  }
@@ -691,6 +691,74 @@
691
691
  "any panic path that formats attacker input",
692
692
  "logging/telemetry that echoes request data"
693
693
  ]
694
+ },
695
+ {
696
+ "id": "jwt_alg_confusion",
697
+ "name": "JWT Algorithm Confusion (RS256 -> HS256)",
698
+ "cwe": "CWE-347",
699
+ "severity": "critical",
700
+ "signature": "alg-dispatch verifier accepting RS256 AND HS256 with public/shared HMAC key",
701
+ "chains": [
702
+ "jwt"
703
+ ],
704
+ "references": [
705
+ "CVE-2015-9235",
706
+ "OWASP ASVS V3.5.2"
707
+ ],
708
+ "real_world": "A hand-written JWT verifier dispatches on the token's alg header and accepts BOTH RS256 (asymmetric) and HS256 (symmetric HMAC), with the HS256 secret derived from the RSA public key. Setting alg=HS256 and signing with the public key forges a valid token.",
709
+ "detection_signatures": [
710
+ "alg-dispatch verifier accepting RS256 AND HS256",
711
+ "createHmac('sha256', <publicKey>) alongside createVerify('RSA-SHA256')",
712
+ "pyjwt decode(..., algorithms=['HS256','RS256'])"
713
+ ],
714
+ "chain_templates": [
715
+ "jwt_alg_confusion_forgery",
716
+ "jwt_algorithm_confusion"
717
+ ],
718
+ "bypass_techniques": [
719
+ "public-key-to-HMAC confusion",
720
+ "kid/jku header injection to point the verifier at attacker keys",
721
+ "alg:none / empty signature when the verifier also accepts it"
722
+ ],
723
+ "crown_jewels": [
724
+ "the RSA public key (published at /.well-known/jwks.json)",
725
+ "admin role claims overridden by token claims",
726
+ "treasury/admin endpoints gated only by a role claim"
727
+ ]
728
+ },
729
+ {
730
+ "id": "cache_deception",
731
+ "name": "Web Cache Deception",
732
+ "cwe": "CWE-525",
733
+ "severity": "high",
734
+ "signature": "reverse-proxy caches static-extension URLs with a cookie-less cache key over a session-authenticated backend",
735
+ "chains": [
736
+ "cache_deception"
737
+ ],
738
+ "references": [
739
+ "CWE-525",
740
+ "Omer Gil — Web Cache Deception Attack",
741
+ "OWASP WSTG-ATHN-03"
742
+ ],
743
+ "real_world": "A reverse proxy caches static-extension URLs (.css/.js/.png) with a cache key that omits the session cookie, while a backend route (often a greedy regex like /account(/.*)?) serves per-session private data for those URLs. An attacker submits a victim-visited URL, the bot's private response is cached, then re-fetched anonymously.",
744
+ "detection_signatures": [
745
+ "proxy_cache_key without $cookie over a session-authenticated backend",
746
+ "location ~* \\.(css|js|png|...) with proxy_cache",
747
+ "greedy backend route regex + static-extension passthrough"
748
+ ],
749
+ "chain_templates": [
750
+ "cache_poisoning_xss"
751
+ ],
752
+ "bypass_techniques": [
753
+ "append a static extension to an authenticated path",
754
+ "path delimiters (; / ..) that still match the route regex",
755
+ "cache-key normalization differences (trailing slash, percent-encoding)"
756
+ ],
757
+ "crown_jewels": [
758
+ "per-session private profile/account JSON",
759
+ "api_key / session tokens in the cached body",
760
+ "any victim-session data cached under a static-extension URL"
761
+ ]
694
762
  }
695
763
  ]
696
764
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blitzstrike",
3
- "version": "2.4.74",
3
+ "version": "2.4.76",
4
4
  "description": "Blitz Strike — a universal MCP security-audit toolbelt. BLITZ sweeps the attack surface, EAGLE-EYE traces source-to-sink, STRIKE verifies live. 57 attack chains, 130-tool catalog, intelligence data layer. One server, every agent.",
5
5
  "type": "module",
6
6
  "bin": {