blitzstrike 2.4.94 → 2.4.96

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,38 @@ 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.96] — 2026-09-17
9
+
10
+ ### Security — self-audit (command injection + path traversal fixed)
11
+
12
+ - `resolve_check(host)` interpolated the user-controlled host into a `curl` shell
13
+ string (`execSync`), allowing command injection (`host = "x.com; id"`). Now
14
+ validates the host against a strict hostname regex BEFORE any subprocess/DNS and
15
+ calls curl via `execFileSync` (array argv, no shell) — defense in depth.
16
+ - `foundry_run(contract_name=...)` used the name as a filename under the temp
17
+ project without validation, allowing path traversal (`"../../evil"`). Now
18
+ sanitizes to a valid Solidity identifier (`[A-Za-z_][A-Za-z0-9_]*`), defaulting
19
+ to `Vulnerable` otherwise.
20
+ - Added regression checks: `isValidHostname` rejects shell metacharacters
21
+ (`;`, backticks, `$()`, `|`, `../`, newlines); the identifier regex is shared.
22
+
23
+ ## [2.4.95] — 2026-09-17
24
+
25
+ ### Fixed — full interconnection audit (unreachable template + 2 ungrounded classes)
26
+
27
+ - `auto_poc` `open_redirect` template was UNREACHABLE: `canonicalId("open_redirect")`
28
+ resolves to `oauth_misconfig`, but the template key was `open_redirect`, so the
29
+ lookup fell back to the `missing_authz` recipe. Renamed the key to the canonical
30
+ `oauth_misconfig` + added an interconn invariant that every auto-poc key IS its
31
+ own canonical id.
32
+ - `nosql_injection` and `host_header` were referenced across intel/orchestrator/
33
+ writeup/technique-lookup but had no pattern, severity, or synonym (ungrounded).
34
+ Added full patterns (CWE-943 / CWE-644), severity VRT entries (P1 critical /
35
+ P3 medium), and synonyms (nosql/mongo injection; host-header poisoning /
36
+ password-reset poisoning). cwe_map already carried both CWEs.
37
+ - `SINK_TO_VECTOR` mapped the `code_execution` sink to a non-pattern vector name;
38
+ now maps to `command_injection` (consistent with the "code execution" synonym).
39
+
8
40
  ## [2.4.94] — 2026-09-17
9
41
 
10
42
  ### Added — Hunting intel (DNS sinkhole detection + CWE/CVE watchlist)
package/dist/index.js CHANGED
@@ -17499,7 +17499,7 @@ var init_update = __esm(() => {
17499
17499
  import { readFileSync as readFileSync29, existsSync as existsSync16 } from "node:fs";
17500
17500
  import { join as join27 } from "node:path";
17501
17501
  import { fileURLToPath as fileURLToPath14 } from "node:url";
17502
- import { execSync as execSync5 } from "node:child_process";
17502
+ import { execSync as execSync4 } from "node:child_process";
17503
17503
  function validateRelease() {
17504
17504
  const checks = [];
17505
17505
  checks.push({ name: "package.json version", ok: true, detail: VERSION });
@@ -17516,13 +17516,13 @@ function validateRelease() {
17516
17516
  checks.push({ name: "CHANGELOG version", ok: false, detail: "CHANGELOG.md not found" });
17517
17517
  }
17518
17518
  try {
17519
- const tag = execSync5("git describe --tags --abbrev=0", { cwd: ROOT8, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
17519
+ const tag = execSync4("git describe --tags --abbrev=0", { cwd: ROOT8, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
17520
17520
  checks.push({ name: "Git tag", ok: tag === `v${VERSION}`, detail: tag, soft: true });
17521
17521
  } catch {
17522
17522
  checks.push({ name: "Git tag", ok: true, detail: "unavailable (no git/tags)", soft: true });
17523
17523
  }
17524
17524
  try {
17525
- const published = execSync5("npm view blitzstrike version", { encoding: "utf8", timeout: 8000, stdio: ["ignore", "pipe", "ignore"] }).trim();
17525
+ const published = execSync4("npm view blitzstrike version", { encoding: "utf8", timeout: 8000, stdio: ["ignore", "pipe", "ignore"] }).trim();
17526
17526
  checks.push({ name: "npm registry version", ok: published === VERSION, detail: published, soft: true });
17527
17527
  } catch {
17528
17528
  checks.push({ name: "npm registry version", ok: true, detail: "unavailable (offline)", soft: true });
@@ -34262,7 +34262,7 @@ var SINK_TO_VECTOR = {
34262
34262
  sql_execution: "sql_injection",
34263
34263
  command_execution: "command_injection",
34264
34264
  html_render: "xss",
34265
- code_execution: "code_execution",
34265
+ code_execution: "command_injection",
34266
34266
  http_request: "ssrf",
34267
34267
  deserialization: "deserialization",
34268
34268
  file_operations: "path_traversal",
@@ -34545,7 +34545,16 @@ var ALIASES = {
34545
34545
  "subdomain takeover": "subdomain_takeover",
34546
34546
  takeover: "subdomain_takeover",
34547
34547
  "dangling dns": "subdomain_takeover",
34548
- "dangling cname": "subdomain_takeover"
34548
+ "dangling cname": "subdomain_takeover",
34549
+ "nosql injection": "nosql_injection",
34550
+ nosql: "nosql_injection",
34551
+ nosqli: "nosql_injection",
34552
+ "mongo injection": "nosql_injection",
34553
+ "mongodb injection": "nosql_injection",
34554
+ "host header": "host_header",
34555
+ "host header injection": "host_header",
34556
+ "host header poisoning": "host_header",
34557
+ "password reset poisoning": "host_header"
34549
34558
  };
34550
34559
  function normalize(term) {
34551
34560
  return term.trim().toLowerCase().replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim();
@@ -59037,6 +59046,8 @@ var VRT_MAP = {
59037
59046
  sql_injection: { vrt: "injection > sql_injection", priority: "P1", severity: "critical", rationale: "SQL injection reaches sensitive data or enables admin takeover." },
59038
59047
  ssti: { vrt: "injection > server_side_template_injection", priority: "P1", severity: "critical", rationale: "SSTI evaluates expressions server-side → code execution." },
59039
59048
  command_injection: { vrt: "injection > os_command_injection", priority: "P1", severity: "critical", rationale: "User input reaches a shell (system/exec/popen) → arbitrary command execution, full host takeover." },
59049
+ nosql_injection: { vrt: "injection > nosql_injection", priority: "P1", severity: "critical", rationale: "NoSQL operator injection reaches a database query → auth bypass or data exfiltration." },
59050
+ host_header: { vrt: "injection > host_header_injection", priority: "P3", severity: "medium", rationale: "A reflected/trusted Host header can poison password-reset links or enable cache poisoning → account takeover in the right flow." },
59040
59051
  deserialization: { vrt: "injection > insecure_deserialization", priority: "P1", severity: "critical", rationale: "Deserialization escalated via a gadget chain → code execution." },
59041
59052
  xxe: { vrt: "injection > xml_external_entity", priority: "P1", severity: "critical", rationale: "XXE reads local files or reaches internal services." },
59042
59053
  file_upload: { vrt: "insecure_design > unrestricted_file_upload", priority: "P1", severity: "critical", rationale: "A web-reachable uploaded file of an executable type → code execution." },
@@ -61257,7 +61268,7 @@ var POC_TEMPLATES = {
61257
61268
  expected: `The response contains an injected \`X-Injected: ${MK}\` header.`,
61258
61269
  fix: "Strip CR/LF from any value placed into a response header."
61259
61270
  },
61260
- open_redirect: {
61271
+ oauth_misconfig: {
61261
61272
  severity: "medium",
61262
61273
  marker: "https://attacker.example",
61263
61274
  request: (i) => qs(i, encodeURIComponent("https://attacker.example")),
@@ -61397,7 +61408,8 @@ function parseTestResults(out) {
61397
61408
  }
61398
61409
  async function foundryRun(input) {
61399
61410
  const forge = findForge();
61400
- const name = input.contract_name ?? "Vulnerable";
61411
+ const rawName = input.contract_name ?? "Vulnerable";
61412
+ const name = /^[A-Za-z_][A-Za-z0-9_]{0,63}$/.test(rawName) ? rawName : "Vulnerable";
61401
61413
  if (!forge) {
61402
61414
  return {
61403
61415
  forge: null,
@@ -61448,11 +61460,15 @@ solc = "0.8.20"
61448
61460
  }
61449
61461
 
61450
61462
  // src/hunting-intel.ts
61451
- import { execSync as execSync4 } from "node:child_process";
61463
+ import { execFileSync as execFileSync5 } from "node:child_process";
61452
61464
  import { resolve4 as resolve42, Resolver } from "node:dns/promises";
61453
61465
  import { readFileSync as readFileSync25 } from "node:fs";
61454
61466
  import { join as join24 } from "node:path";
61455
61467
  var BLOCKING_KEYWORDS = /internet positif|kominfo|\.go\.id\b|pemblokiran|ditutup|dilarang|akses dibatasi|blocked|restricted|illegal|advertencia|coljuegos|regulat|forbidden|this (site|website) has been (blocked|banned)/i;
61468
+ var HOSTNAME_RE = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$/i;
61469
+ function isValidHostname(host) {
61470
+ return host.length <= 253 && HOSTNAME_RE.test(host);
61471
+ }
61456
61472
  async function localResolve(host) {
61457
61473
  try {
61458
61474
  return await resolve42(host);
@@ -61473,7 +61489,7 @@ function probeBlockingPage(host, ip, timeoutMs = 8000) {
61473
61489
  for (const port of ["443", "80"]) {
61474
61490
  const scheme = port === "443" ? "https" : "http";
61475
61491
  try {
61476
- const out = execSync4(`curl -sk -m 6 --resolve ${host}:${port}:${ip} ${scheme}://${host}/ 2>/dev/null`, { encoding: "utf8", timeout: timeoutMs, stdio: ["ignore", "pipe", "ignore"] });
61492
+ const out = execFileSync5("curl", ["-sk", "-m", "6", "--resolve", `${host}:${port}:${ip}`, `${scheme}://${host}/`], { encoding: "utf8", timeout: timeoutMs, stdio: ["ignore", "pipe", "ignore"] });
61477
61493
  const preview = out.replace(/\s+/g, " ").trim().slice(0, 300);
61478
61494
  if (!preview)
61479
61495
  continue;
@@ -61484,6 +61500,9 @@ function probeBlockingPage(host, ip, timeoutMs = 8000) {
61484
61500
  }
61485
61501
  async function resolveCheck(host) {
61486
61502
  const clean = host.replace(/^https?:\/\//, "").split("/")[0];
61503
+ if (!isValidHostname(clean)) {
61504
+ return { host: clean, local_ips: [], public_ips: [], sinkhole: false, verdict: "invalid", evidence: "not a valid hostname — rejected before DNS/subprocess" };
61505
+ }
61487
61506
  const local = await localResolve(clean);
61488
61507
  const pub = await publicResolve(clean);
61489
61508
  if (local.length === 0 && pub.length === 0) {
@@ -988,6 +988,69 @@
988
988
  "payout_min": 1000,
989
989
  "payout_max": 15000,
990
990
  "typical_payout": "$1K\u2013$15K"
991
+ },
992
+ {
993
+ "id": "nosql_injection",
994
+ "name": "NoSQL Injection",
995
+ "cwe": "CWE-943",
996
+ "real_world": "User input reaches a NoSQL query (MongoDB $where/$gt/$ne, an unsanitized find/aggregate). Attackers inject operators to bypass authentication or dump collections.",
997
+ "detection_signatures": [
998
+ "unsanitized input into a NoSQL find/where/aggregate",
999
+ "operator injection ($ne/$gt/$where) from request input"
1000
+ ],
1001
+ "chain_templates": [
1002
+ "nosql_injection_to_data_theft"
1003
+ ],
1004
+ "references": [
1005
+ "CWE-943",
1006
+ "OWASP A03:2021 Injection"
1007
+ ],
1008
+ "bypass_techniques": [
1009
+ "operator injection ($ne/$gt/$where)",
1010
+ "JSON-body operator injection",
1011
+ "array/object type juggling to slip past input filters",
1012
+ "blind extraction via regex/timing"
1013
+ ],
1014
+ "crown_jewels": [
1015
+ "user/account collections",
1016
+ "password hashes",
1017
+ "admin documents"
1018
+ ],
1019
+ "severity": "critical",
1020
+ "payout_min": 1000,
1021
+ "payout_max": 15000,
1022
+ "typical_payout": "$1K\u2013$15K"
1023
+ },
1024
+ {
1025
+ "id": "host_header",
1026
+ "name": "Host Header Injection",
1027
+ "cwe": "CWE-644",
1028
+ "real_world": "The app trusts the Host header for link generation, password-reset URLs, or routing. An attacker injects a malicious host to poison password-reset links or bypass access controls.",
1029
+ "detection_signatures": [
1030
+ "Host header reflected into a generated URL / Location / Set-Cookie",
1031
+ "Host header used in password-reset link generation"
1032
+ ],
1033
+ "chain_templates": [
1034
+ "http_smuggling_to_auth_bypass"
1035
+ ],
1036
+ "references": [
1037
+ "CWE-644"
1038
+ ],
1039
+ "bypass_techniques": [
1040
+ "Host header override via X-Forwarded-Host",
1041
+ "absolute-URL host injection",
1042
+ "password-reset link poisoning",
1043
+ "web cache poisoning via Host"
1044
+ ],
1045
+ "crown_jewels": [
1046
+ "password-reset tokens",
1047
+ "account takeover via poisoned links",
1048
+ "cache poisoning"
1049
+ ],
1050
+ "severity": "medium",
1051
+ "payout_min": 500,
1052
+ "payout_max": 5000,
1053
+ "typical_payout": "$500\u2013$5K"
991
1054
  }
992
1055
  ]
993
1056
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blitzstrike",
3
- "version": "2.4.94",
3
+ "version": "2.4.96",
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": {