blitzstrike 2.4.62 → 2.4.64

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/dist/index.js CHANGED
@@ -7409,18 +7409,53 @@ function redactSecrets(text) {
7409
7409
  var SECRET_FIELDS = "api[_-]?key|apikey|api[_-]?secret|access[_-]?key|access[_-]?token|secret[_-]?key|secret[_-]?access[_-]?key|auth[_-]?token|client[_-]?secret|consumer[_-]?secret|consumer[_-]?key|private[_-]?key|db[_-]?password|database[_-]?password|db[_-]?pass|passwd|password|jwt[_-]?secret|encryption[_-]?key|encryption[_-]?password|github[_-]?token|gitlab[_-]?token|stripe[_-]?key|sendgrid[_-]?api[_-]?key|datadog[_-]?api[_-]?key|cloudflare[_-]?api[_-]?key|slack[_-]?token|aws[_-]?access[_-]?key[_-]?id|aws[_-]?secret[_-]?access[_-]?key|aws[_-]?session[_-]?token|bot[_-]?token", SECRET_FORMATS, REDACTION_PATTERNS;
7410
7410
  var init_secrets = __esm(() => {
7411
7411
  SECRET_FORMATS = [
7412
- { name: "JWT", re: /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{4,}/g },
7413
- { name: "AWS access key", re: /\bAKIA[0-9A-Z]{16}\b/g },
7414
- { name: "GitHub PAT (classic)", re: /\bghp_[A-Za-z0-9]{20,}\b/g },
7415
- { name: "GitHub PAT (fine-grained)", re: /\bgithub_pat_[A-Za-z0-9_]{20,}\b/g },
7416
- { name: "OpenAI/Stripe key", re: /\bsk-[A-Za-z0-9]{20,}\b/g },
7417
- { name: "Stripe live/test key", re: /\b(?:sk|pk|rk)_(?:live|test)_[0-9a-zA-Z]{10,}\b/g },
7418
- { name: "Slack token", re: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g },
7419
- { name: "Google API key", re: /\bAIza[0-9A-Za-z_-]{35}\b/g },
7420
- { name: "Twilio account SID", re: /\bAC[0-9a-f]{32}\b/g },
7421
- { name: "SendGrid API key", re: /\bSG\.[A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{16,}\b/g },
7422
- { name: "Mailgun API key", re: /\bkey-[0-9a-zA-Z]{32}\b/g },
7423
- { name: "PEM private key", re: /-----BEGIN [A-Z ]*PRIVATE KEY-----/g }
7412
+ { name: "AWS access key ID", re: /\bAKIA[0-9A-Z]{16}\b/g, severity: "critical", category: "aws" },
7413
+ { name: "AWS session token (temporary)", re: /\bASIA[0-9A-Z]{16}\b/g, severity: "critical", category: "aws" },
7414
+ { name: "AWS secret access key", re: /aws[_-]?secret[_-]?access[_-]?key['"\s:=]+([A-Za-z0-9/+=]{40})/gi, severity: "critical", category: "aws" },
7415
+ { name: "GCP service account key", re: /"type"\s*:\s*"service_account"/g, severity: "critical", category: "gcp" },
7416
+ { name: "Google API key", re: /\bAIza[0-9A-Za-z_-]{35}\b/g, severity: "high", category: "gcp" },
7417
+ { name: "GitHub PAT (classic)", re: /\bghp_[A-Za-z0-9]{20,}\b/g, severity: "critical", category: "github" },
7418
+ { name: "GitHub PAT (fine-grained)", re: /\bgithub_pat_[A-Za-z0-9_]{20,}\b/g, severity: "critical", category: "github" },
7419
+ { name: "GitHub OAuth token", re: /\bgho_[A-Za-z0-9]{20,}\b/g, severity: "high", category: "github" },
7420
+ { name: "GitHub server-to-server token", re: /\bgh[usr]_[A-Za-z0-9]{20,}\b/g, severity: "high", category: "github" },
7421
+ { name: "Stripe live key", re: /\bsk_live_[0-9A-Za-z]{16,}\b/g, severity: "critical", category: "stripe" },
7422
+ { name: "Stripe test key", re: /\bsk_test_[0-9A-Za-z]{16,}\b/g, severity: "low", category: "stripe" },
7423
+ { name: "Stripe publishable key", re: /\bpk_(?:live|test)_[0-9A-Za-z]{16,}\b/g, severity: "low", category: "stripe" },
7424
+ { name: "Slack token", re: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g, severity: "high", category: "slack" },
7425
+ { name: "Slack webhook URL", re: /https:\/\/hooks\.slack\.com\/services\/T[A-Z0-9]+\/B[A-Z0-9]+\/[A-Za-z0-9]+/g, severity: "medium", category: "slack" },
7426
+ { name: "SendGrid API key", re: /\bSG\.[A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{16,}\b/g, severity: "high", category: "email_svc" },
7427
+ { name: "Mailgun API key", re: /\bkey-[0-9a-zA-Z]{32}\b/g, severity: "high", category: "email_svc" },
7428
+ { name: "Twilio API key", re: /\bSK[0-9a-fA-F]{32}\b/g, severity: "high", category: "twilio" },
7429
+ { name: "Twilio account SID", re: /\bAC[0-9a-f]{32}\b/g, severity: "medium", category: "twilio" },
7430
+ { name: "Twilio auth token", re: /twilio(.{0,20})?(auth|token)['"=: ]+([a-f0-9]{32})/gi, severity: "high", category: "twilio" },
7431
+ { name: "Heroku API key", re: /heroku(.{0,20})?api['"=: ]+([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/gi, severity: "medium", category: "paas" },
7432
+ { name: "Firebase URL", re: /\bhttps?:\/\/[a-z0-9-]+\.firebaseio\.com\b/g, severity: "low", category: "firebase" },
7433
+ { name: "JWT", re: /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{4,}/g, severity: "medium", category: "jwt" },
7434
+ { name: "Bearer token", re: /authorization['"=: ]+bearer\s+[A-Za-z0-9._-]{20,}/gi, severity: "medium", category: "bearer" },
7435
+ { name: "Basic auth URL", re: /https?:\/\/[^/\s:@]+:[^/\s:@]+@[^/\s]+/g, severity: "medium", category: "basic_auth" },
7436
+ { name: "RSA private key", re: /-----BEGIN RSA PRIVATE KEY-----/g, severity: "critical", category: "private_key" },
7437
+ { name: "EC private key", re: /-----BEGIN EC PRIVATE KEY-----/g, severity: "critical", category: "private_key" },
7438
+ { name: "OpenSSH private key", re: /-----BEGIN OPENSSH PRIVATE KEY-----/g, severity: "critical", category: "private_key" },
7439
+ { name: "Generic private key", re: /-----BEGIN (DSA |PGP |)PRIVATE KEY-----/g, severity: "critical", category: "private_key" },
7440
+ { name: "Generic API key / token", re: /(?:api[_-]?key|apikey|api_secret|access_token|secret[_-]?token)['"\s:=]+["']([A-Za-z0-9+/=_-]{24,})["']/gi, severity: "medium", category: "generic" },
7441
+ { name: "Anthropic API key", re: /\bsk-ant-(?:api03|admin01)-[A-Za-z0-9_-]{20,}\b/g, severity: "critical", category: "ai_api" },
7442
+ { name: "OpenAI API key (legacy)", re: /\bsk-[A-Za-z0-9]{20}T3BlbkFJ[A-Za-z0-9]{20}\b/g, severity: "critical", category: "ai_api" },
7443
+ { name: "OpenAI project key", re: /\bsk-proj-[A-Za-z0-9_-]{20,}T3BlbkFJ[A-Za-z0-9_-]{20,}\b/g, severity: "critical", category: "ai_api" },
7444
+ { name: "OpenAI session token", re: /\bsess-[A-Za-z0-9]{40}\b/g, severity: "high", category: "ai_api" },
7445
+ { name: "Hugging Face token", re: /\bhf_[A-Za-z0-9]{30,}\b/g, severity: "high", category: "ai_api" },
7446
+ { name: "Cloudflare API key", re: /cf[_-]?api[_-]?key['"\s:=]+([a-f0-9]{37})/gi, severity: "critical", category: "infra_api" },
7447
+ { name: "DigitalOcean token", re: /\bdop_v1_[a-f0-9]{64}\b/g, severity: "high", category: "infra_api" },
7448
+ { name: "npm token", re: /\bnpm_[A-Za-z0-9]{36}\b/g, severity: "high", category: "package_registry" },
7449
+ { name: "PyPI token", re: /\bpypi-AgENdGV[A-Za-z0-9_-]+\b/g, severity: "high", category: "package_registry" },
7450
+ { name: "Docker Hub PAT", re: /\bdckr_pat_[A-Za-z0-9_-]{27,}\b/g, severity: "high", category: "package_registry" },
7451
+ { name: "Atlassian API token", re: /\bATATT3xFfGF0[A-Za-z0-9_-]{20,}\b/g, severity: "high", category: "saas_api" },
7452
+ { name: "Linear API key", re: /\blin_api_[A-Za-z0-9]{40}\b/g, severity: "medium", category: "saas_api" },
7453
+ { name: "New Relic license key", re: /\b(?:NRAA|NRAK|NRBR)-[A-F0-9]{27}\b/g, severity: "medium", category: "observability" },
7454
+ { name: "Datadog API key", re: /dd[_-]?api[_-]?key['"\s:=]+([a-f0-9]{32})/gi, severity: "high", category: "observability" },
7455
+ { name: "Sentry DSN", re: /https:\/\/[a-f0-9]+@o[0-9]+\.ingest\.sentry\.io\/[0-9]+/g, severity: "low", category: "observability" },
7456
+ { name: "ngrok auth token", re: /\b[12][A-Za-z0-9]{26}_[A-Za-z0-9]{32,}\b/g, severity: "medium", category: "tunneling" },
7457
+ { name: "Discord bot token", re: /\b[MN][A-Za-z\d]{23}\.[\w-]{6}\.[\w-]{27}\b/g, severity: "high", category: "bot_token" },
7458
+ { name: "Telegram bot token", re: /\b\d{8,10}:[A-Za-z0-9_-]{35}\b/g, severity: "high", category: "bot_token" }
7424
7459
  ];
7425
7460
  REDACTION_PATTERNS = [
7426
7461
  [/(authorization\s*[:=]\s*)(bearer\s+)?[A-Za-z0-9._~+/=-]{8,}/gi, "$1$2[REDACTED]"],
@@ -7433,17 +7468,34 @@ var init_secrets = __esm(() => {
7433
7468
  [/(session\s*[:=]\s*)["']?[A-Za-z0-9._-]{12,}["']?/gi, "$1[REDACTED]"],
7434
7469
  [/(cookie\s*[:=]\s*)["']?[A-Za-z0-9._-]{12,}["']?/gi, "$1[REDACTED]"],
7435
7470
  [/(set-cookie\s*:\s*)[^\r\n]+/gi, "$1[REDACTED]"],
7436
- [/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{4,}/g, "[REDACTED]"],
7437
- [/AKIA[0-9A-Z]{16}/g, "[REDACTED]"],
7438
- [/ghp_[A-Za-z0-9]{20,}/g, "[REDACTED]"],
7439
- [/github_pat_[A-Za-z0-9_]{20,}/g, "[REDACTED]"],
7440
- [/sk-[A-Za-z0-9]{20,}/g, "[REDACTED]"],
7441
- [/\b(?:sk|pk|rk)_(?:live|test)_[0-9a-zA-Z]{10,}\b/g, "[REDACTED]"],
7471
+ [/\bAKIA[0-9A-Z]{16}\b/g, "[REDACTED]"],
7472
+ [/\bASIA[0-9A-Z]{16}\b/g, "[REDACTED]"],
7473
+ [/\bghp_[A-Za-z0-9]{20,}\b/g, "[REDACTED]"],
7474
+ [/\bgithub_pat_[A-Za-z0-9_]{20,}\b/g, "[REDACTED]"],
7475
+ [/\bgho_[A-Za-z0-9]{20,}\b/g, "[REDACTED]"],
7476
+ [/\bgh[usr]_[A-Za-z0-9]{20,}\b/g, "[REDACTED]"],
7477
+ [/\bsk_live_[0-9A-Za-z]{16,}\b/g, "[REDACTED]"],
7478
+ [/\bsk_test_[0-9A-Za-z]{16,}\b/g, "[REDACTED]"],
7479
+ [/\bpk_(?:live|test)_[0-9A-Za-z]{16,}\b/g, "[REDACTED]"],
7442
7480
  [/\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g, "[REDACTED]"],
7443
7481
  [/\bAIza[0-9A-Za-z_-]{35}\b/g, "[REDACTED]"],
7444
7482
  [/\bAC[0-9a-f]{32}\b/g, "[REDACTED]"],
7483
+ [/\bSK[0-9a-fA-F]{32}\b/g, "[REDACTED]"],
7445
7484
  [/\bSG\.[A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{16,}\b/g, "[REDACTED]"],
7446
7485
  [/\bkey-[0-9a-zA-Z]{32}\b/g, "[REDACTED]"],
7486
+ [/\bsk-ant-(?:api03|admin01)-[A-Za-z0-9_-]{20,}\b/g, "[REDACTED]"],
7487
+ [/\bsk-proj-[A-Za-z0-9_-]{20,}T3BlbkFJ[A-Za-z0-9_-]{20,}\b/g, "[REDACTED]"],
7488
+ [/\bsess-[A-Za-z0-9]{40}\b/g, "[REDACTED]"],
7489
+ [/\bhf_[A-Za-z0-9]{30,}\b/g, "[REDACTED]"],
7490
+ [/\bdop_v1_[a-f0-9]{64}\b/g, "[REDACTED]"],
7491
+ [/\bnpm_[A-Za-z0-9]{36}\b/g, "[REDACTED]"],
7492
+ [/\bpypi-AgENdGV[A-Za-z0-9_-]+\b/g, "[REDACTED]"],
7493
+ [/\bdckr_pat_[A-Za-z0-9_-]{27,}\b/g, "[REDACTED]"],
7494
+ [/\bATATT3xFfGF0[A-Za-z0-9_-]{20,}\b/g, "[REDACTED]"],
7495
+ [/\blin_api_[A-Za-z0-9]{40}\b/g, "[REDACTED]"],
7496
+ [/\b(?:NRAA|NRAK|NRBR)-[A-F0-9]{27}\b/g, "[REDACTED]"],
7497
+ [/\b\d{8,10}:[A-Za-z0-9_-]{35}\b/g, "[REDACTED]"],
7498
+ [/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{4,}/g, "[REDACTED]"],
7447
7499
  [/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g, "[REDACTED]"]
7448
7500
  ];
7449
7501
  });
@@ -16054,13 +16106,32 @@ function stripCommentsOnly(code) {
16054
16106
  return out.join("");
16055
16107
  }
16056
16108
  function detectDeserialization(code, file, out) {
16057
- if (!MAGIC_METHODS.test(code))
16058
- return;
16059
16109
  const ls = lines2(code);
16060
- const re = /\b(unserialize|maybe_unserialize)\s*\(/g;
16061
- let m;
16062
- while ((m = re.exec(code)) !== null) {
16063
- const ln = lineNo2(code, m.index);
16110
+ if (MAGIC_METHODS.test(code)) {
16111
+ const re = /\b(unserialize|maybe_unserialize)\s*\(/g;
16112
+ let m;
16113
+ while ((m = re.exec(code)) !== null) {
16114
+ const ln = lineNo2(code, m.index);
16115
+ const l = ls[ln - 1] ?? "";
16116
+ const scope = ls.slice(Math.max(0, ln - 4), ln).join(`
16117
+ `);
16118
+ if (!SOURCE_TOKENS3.test(l) && !SOURCE_TOKENS3.test(scope))
16119
+ continue;
16120
+ out.push({
16121
+ file,
16122
+ line: ln,
16123
+ type: "deserialization",
16124
+ category: "Deserialization → POP gadget chain",
16125
+ severity: "high",
16126
+ evidence: l.trim(),
16127
+ detail: `\`${m[1]}()\` of attacker-controlled data AND a magic method is present — object injection can pivot to RCE via the gadget chain.`
16128
+ });
16129
+ }
16130
+ }
16131
+ const unsafeRe = /\b(pickle\.loads?|yaml\.load|yaml\.unsafe_load|ObjectInputStream|\.readObject)\s*\(/g;
16132
+ let u;
16133
+ while ((u = unsafeRe.exec(code)) !== null) {
16134
+ const ln = lineNo2(code, u.index);
16064
16135
  const l = ls[ln - 1] ?? "";
16065
16136
  const scope = ls.slice(Math.max(0, ln - 4), ln).join(`
16066
16137
  `);
@@ -16070,10 +16141,10 @@ function detectDeserialization(code, file, out) {
16070
16141
  file,
16071
16142
  line: ln,
16072
16143
  type: "deserialization",
16073
- category: "Deserialization → POP gadget chain",
16144
+ category: "Unsafe deserialization",
16074
16145
  severity: "high",
16075
16146
  evidence: l.trim(),
16076
- detail: `\`${m[1]}()\` of attacker-controlled data AND a magic method is present — object injection can pivot to RCE via the gadget chain.`
16147
+ detail: `\`${u[1]}\` of attacker-controlled data — unsafe deserialization can execute arbitrary code directly (pickle/yaml/Java object streams).`
16077
16148
  });
16078
16149
  }
16079
16150
  }
@@ -16087,7 +16158,7 @@ function detectTypeJuggling(code, file, out) {
16087
16158
  const scope = ls.slice(Math.max(0, ln - 4), ln).join(`
16088
16159
  `);
16089
16160
  const hasSource = SOURCE_TOKENS3.test(l) || SOURCE_TOKENS3.test(scope);
16090
- const hasHash = /\b(md5|sha1|sha256|hash|crc32|crypt|password_verify|strcmp|hash_equals|secret|token|nonce)\s*\(?/i.test(l) || /\b(secret|token|nonce|hash)\b/i.test(l);
16161
+ const hasHash = /\b(md5|sha1|sha256|hash|crc32|crypt|password_verify|strcmp|hash_equals|secret|token|nonce)\s*\(?/i.test(l) || /(secret|token|nonce|hash)\b/i.test(l);
16091
16162
  if (!hasSource || !hasHash)
16092
16163
  continue;
16093
16164
  out.push({
@@ -16138,6 +16209,34 @@ function detectMassAssignment(code, file, out) {
16138
16209
  detail: "`parse_str()` of user input into the symbol table — variable injection."
16139
16210
  });
16140
16211
  }
16212
+ const bindRe = /foreach\s*\(\s*\$_(POST|GET|REQUEST|COOKIE)\b[^)]*\)\s*\{[^}]*\$this->\$[A-Za-z_]\w*\s*=/g;
16213
+ while ((m = bindRe.exec(code)) !== null) {
16214
+ const ln = lineNo2(code, m.index);
16215
+ const l = ls[ln - 1] ?? "";
16216
+ out.push({
16217
+ file,
16218
+ line: ln,
16219
+ type: "mass_assignment",
16220
+ category: "Mass assignment (dynamic field binding)",
16221
+ severity: "high",
16222
+ evidence: l.trim(),
16223
+ detail: "Request keys are bound to object properties without an allowlist — attacker can set privileged fields (role, is_admin)."
16224
+ });
16225
+ }
16226
+ const createRe = /\.create\s*\(\s*(req\.body|req\.query|request\.body|request\.data|\$_(POST|REQUEST))\b/g;
16227
+ while ((m = createRe.exec(code)) !== null) {
16228
+ const ln = lineNo2(code, m.index);
16229
+ const l = ls[ln - 1] ?? "";
16230
+ out.push({
16231
+ file,
16232
+ line: ln,
16233
+ type: "mass_assignment",
16234
+ category: "Mass assignment (unrestricted model create)",
16235
+ severity: "high",
16236
+ evidence: l.trim(),
16237
+ detail: "A model is created directly from request data without an allowlist — attacker can set privileged fields."
16238
+ });
16239
+ }
16141
16240
  }
16142
16241
  function detectComplexBugs(code, file) {
16143
16242
  const out = [];
@@ -16171,7 +16270,7 @@ function detectComplexBugs(code, file) {
16171
16270
  }
16172
16271
  function detectPrototypePollution(code, file, out) {
16173
16272
  const ls = lines2(code);
16174
- const mergeRe = /\b(Object\.assign|_\.merge|_\.defaultsDeep|\.extend|\.merge|\.defaultsDeep|mergeDeep|deepMerge|_.set)\s*\(/g;
16273
+ const mergeRe = /(Object\.assign|_\.merge|_\.defaultsDeep|\.extend|\.merge|\.defaultsDeep|mergeDeep|deepMerge|_\.set)\s*\(/g;
16175
16274
  let m;
16176
16275
  while ((m = mergeRe.exec(code)) !== null) {
16177
16276
  const ln = lineNo2(code, m.index);
@@ -16193,7 +16292,7 @@ function detectPrototypePollution(code, file, out) {
16193
16292
  }
16194
16293
  function detectCrlfInjection(code, file, out) {
16195
16294
  const ls = lines2(code);
16196
- const headerRe = /\b(header|setHeader|addHeader|writeHead|appendHeader)\s*\(/g;
16295
+ const headerRe = /\b(header|setHeader|addHeader|writeHead|appendHeader|setcookie|setCookie|set_cookie)\s*\(/g;
16197
16296
  let m;
16198
16297
  while ((m = headerRe.exec(code)) !== null) {
16199
16298
  const ln = lineNo2(code, m.index);
@@ -16246,7 +16345,7 @@ function detectPathConfusion(code, file, out) {
16246
16345
  }
16247
16346
  function detectSsrF(code, file, out) {
16248
16347
  const ls = lines2(code);
16249
- const re = /\b(file_get_contents|fopen|file|readfile|curl_setopt|curl_init|fetch|axios|requests\.(get|post|put)|urllib\.request|http_get|wp_remote_(get|post)|new\s+URL\s*\(|got\(|http\.(get|request))\s*\(/g;
16348
+ const re = /\b(file_get_contents|fopen|file|readfile|curl_setopt|curl_init|fetch|axios\.(get|post|put|request|delete|head)|requests\.(get|post|put)|urllib\.request|http_get|wp_remote_(get|post)|new\s+URL\s*\(|got\(|http\.(get|request))\s*\(/g;
16250
16349
  let m;
16251
16350
  while ((m = re.exec(code)) !== null) {
16252
16351
  const ln = lineNo2(code, m.index);
@@ -16274,7 +16373,7 @@ function detectSsrF(code, file, out) {
16274
16373
  }
16275
16374
  function detectXxe(code, file, out) {
16276
16375
  const ls = lines2(code);
16277
- const re = /\b(simplexml_load_string|simplexml_load_file|new\s+SimpleXMLElement|->loadXML\s*\(|->load\s*\(|XMLReader\s*->\s*XML\s*\(|libxml_parse)\s*\(?/g;
16376
+ const re = /\b(simplexml_load_string|simplexml_load_file|new\s+SimpleXMLElement|->loadXML\s*\(|->load\s*\(|XMLReader\s*->\s*XML\s*\(|libxml_parse|DocumentBuilderFactory|SAXParserFactory|SAXReader)\s*\(?/g;
16278
16377
  let m;
16279
16378
  while ((m = re.exec(code)) !== null) {
16280
16379
  const ln = lineNo2(code, m.index);
@@ -16299,7 +16398,7 @@ function detectXxe(code, file, out) {
16299
16398
  }
16300
16399
  function detectSsti(code, file, out) {
16301
16400
  const ls = lines2(code);
16302
- const re = /\b(render_template_string|Template\s*\(|res\.render\s*\(|ejs\.(render|compile)\s*\(|_\.template\s*\(|render_string|twig\.render|Jinja2\(|jinja2\.Template)\s*\(?/g;
16401
+ const re = /(render_template_string|Template\s*\(|res\.render\s*\(|ejs\.(render|compile)\s*\(|_\.template\s*\(|render_string|twig->render|createTemplate\s*\(|Jinja2\(|jinja2\.Template|jinja2\.from_string|jinja2\.Environment)\s*\(?/g;
16303
16402
  let m;
16304
16403
  while ((m = re.exec(code)) !== null) {
16305
16404
  const ln = lineNo2(code, m.index);
@@ -16322,14 +16421,14 @@ function detectSsti(code, file, out) {
16322
16421
  function detectFileUploadRce(code, file, out) {
16323
16422
  code = stripCommentsOnly(code);
16324
16423
  const ls = lines2(code);
16325
- const re = /\b(move_uploaded_file|wp_upload_bits|->move\s*\(|storeAs\s*\(|->storeAs\s*\(|putFile\s*\()\s*\(/g;
16424
+ const re = /\b(move_uploaded_file|wp_upload_bits|->move|storeAs|->storeAs|putFile|\.save|uploadFile)\s*\(/g;
16326
16425
  let m;
16327
16426
  while ((m = re.exec(code)) !== null) {
16328
16427
  const ln = lineNo2(code, m.index);
16329
16428
  const l = ls[ln - 1] ?? "";
16330
16429
  const scope = ls.slice(Math.max(0, ln - 30), ln + 3).join(`
16331
16430
  `);
16332
- if (!/\$_FILES\b|tmp_name|getUploadedFile|uploadedFile|\['name'\]/i.test(scope))
16431
+ if (!/\$_FILES\b|tmp_name|getUploadedFile|uploadedFile|req\.files|\['name'\]|\.name/i.test(scope))
16333
16432
  continue;
16334
16433
  if (/(wp_check_filetype|PATHINFO_EXTENSION|wp_get_mime_types|allowed_?(types|extensions|exts)|mime_?(types|extensions)|in_array\s*\([^)]*\$(?:allowed|whitelist|ext)|wp_handle_upload\s*\(\s*[^)]*test_form\s*=>\s*true)/i.test(scope))
16335
16434
  continue;
@@ -16367,7 +16466,7 @@ function detectUnrestrictedUpload(code, file, out) {
16367
16466
  }
16368
16467
  function detectSqlInjection(code, file, out) {
16369
16468
  const ls = lines2(code);
16370
- const sinkRe = /\$wpdb->(get_results|get_col|get_var|get_row|query)\s*\(|->query\s*\(|DB::unprepared\s*\(|->whereRaw\s*\(|->selectRaw\s*\(/g;
16469
+ const sinkRe = /\$wpdb->(get_results|get_col|get_var|get_row|query)\s*\(|->query\s*\(|->execute\s*\(|mysqli_query\s*\(|pg_query\s*\(|DB::unprepared\s*\(|[->.]whereRaw\s*\(|[->.]selectRaw\s*\(/g;
16371
16470
  let m;
16372
16471
  while ((m = sinkRe.exec(code)) !== null) {
16373
16472
  const ln = lineNo2(code, m.index);
@@ -16378,7 +16477,7 @@ function detectSqlInjection(code, file, out) {
16378
16477
  continue;
16379
16478
  if (SOURCE_TOKENS3.test(scope))
16380
16479
  continue;
16381
- const inlineConcat = /\.\s*\$[A-Za-z_]\w*/.test(l);
16480
+ const inlineConcat = /(\.\s*\$[A-Za-z_]\w*|\+\s*[A-Za-z_$]\w*)/.test(l);
16382
16481
  const assignConcat = /(?:^|\n)\s*\$[A-Za-z_]\w*\s*(?:\.=|=)\s*[^;\n]*["'][^"']*["']\s*\.\s*\$[A-Za-z_]\w*/.test(scope);
16383
16482
  if (!inlineConcat && !assignConcat)
16384
16483
  continue;
@@ -16440,10 +16539,10 @@ function detectHardcodedSecret(code, file, out) {
16440
16539
  file,
16441
16540
  line: ln,
16442
16541
  type: "hardcoded_secret",
16443
- category: "Hardcoded secret (known format)",
16444
- severity: "high",
16542
+ category: `Hardcoded secret (${fmt.category}: ${fmt.name})`,
16543
+ severity: fmt.severity,
16445
16544
  evidence: (ls[ln - 1] ?? "").trim(),
16446
- detail: `A ${fmt.name} was found hardcoded in source — the credential is exposed to anyone with code access (CWE-798). Rotate it and load from the environment instead.`
16545
+ detail: `${fmt.name} was found hardcoded in source — the credential is exposed to anyone with code access (CWE-798, severity ${fmt.severity}). Rotate it and load from the environment instead.`
16447
16546
  });
16448
16547
  }
16449
16548
  }
@@ -16547,7 +16646,7 @@ function detectOauthMisconfig(code, file, out) {
16547
16646
  function detectGrpcReflection(code, file, out) {
16548
16647
  const ls = lines2(code);
16549
16648
  const clean = stripCommentsOnly(code);
16550
- const re = /\b(enable_server_reflection|RegisterReflectionService|reflection\.enable|grpc_reflection|reflection\.Register)\b/gi;
16649
+ const re = /grpc_reflection|enable_server_reflection|RegisterReflectionService|reflection\.enable|ServerReflection|ReflectionServicer|reflection\.Register/gi;
16551
16650
  let m;
16552
16651
  while ((m = re.exec(clean)) !== null) {
16553
16652
  const ln = lineNo2(code, m.index);
@@ -16567,7 +16666,7 @@ var init_complex_bugs = __esm(() => {
16567
16666
  init_security_state();
16568
16667
  init_framework_security();
16569
16668
  init_secrets();
16570
- SOURCE_TOKENS3 = /\$_(GET|POST|REQUEST|COOKIE|FILES|SERVER)\b|\$request->|\$req->|request\.(get|input|query|post|data|body|headers|cookies|params)|getParameter\(|req\.(query|body|params|headers|cookies)/;
16669
+ SOURCE_TOKENS3 = /\$_(GET|POST|REQUEST|COOKIE|FILES|SERVER)\b|\$request->|\$req->|request\.(get|input|query|post|data|body|headers|cookies|params|args|form|values|json)|getParameter\(|req\.(query|body|params|headers|cookies|args|form)|php:\/\/input/;
16571
16670
  MAGIC_METHODS = /\b(__destruct|__wakeup|__toString|__invoke|__call|__get|__set|__isset|__unset|__sleep)\s*\(/;
16572
16671
  SECRET_FIELD_RE = new RegExp(`\\b(${SECRET_FIELDS})\\s*(?:::|=>|=|:)\\s*`, "gi");
16573
16672
  SECRET_PLACEHOLDER_RE = /^(null|true|false|undefined|your_|xxx+|changeme|example|placeholder|test|secret|password|token|key|api|smtp|localhost|root|admin|pass|change|me|none|na|foo|bar|dummy)$/i;
@@ -16747,11 +16846,75 @@ $r = openai.chat.completions.create(['messages' => [['role' => 'user', 'content'
16747
16846
  ];
16748
16847
  });
16749
16848
 
16849
+ // src/variants.ts
16850
+ import { readFileSync as readFileSync19 } from "node:fs";
16851
+ import { join as join18, dirname as dirname4 } from "node:path";
16852
+ import { fileURLToPath as fileURLToPath10 } from "node:url";
16853
+ function loadVariants() {
16854
+ try {
16855
+ const doc = JSON.parse(readFileSync19(join18(__dirname4, "..", "intelligence", "variants.json"), "utf8"));
16856
+ return doc.variants ?? [];
16857
+ } catch {
16858
+ return [];
16859
+ }
16860
+ }
16861
+ function scanVariants() {
16862
+ const variants = loadVariants().filter((v) => !CROSS_FILE_ONLY.has(v.class));
16863
+ const results = variants.map((v, i) => {
16864
+ const findings = detectComplexBugs(v.code, `variant-${i}.php`).filter((f) => f.type === v.class);
16865
+ return { id: `variant-${i}`, class: v.class, fired: findings.length > 0, note: v.note };
16866
+ });
16867
+ const per_class = {};
16868
+ for (const r of results) {
16869
+ const c = per_class[r.class] ??= { total: 0, fired: 0, missed: 0, coverage: 0 };
16870
+ c.total++;
16871
+ if (r.fired)
16872
+ c.fired++;
16873
+ else
16874
+ c.missed++;
16875
+ c.coverage = c.fired / c.total;
16876
+ }
16877
+ const fired = results.filter((r) => r.fired).length;
16878
+ return {
16879
+ total: results.length,
16880
+ fired,
16881
+ missed: results.length - fired,
16882
+ coverage: results.length > 0 ? fired / results.length : 0,
16883
+ per_class,
16884
+ results
16885
+ };
16886
+ }
16887
+ function variantCoverageText() {
16888
+ const r = scanVariants();
16889
+ const lines = [];
16890
+ lines.push(`Blitz Strike variant corpus`);
16891
+ lines.push(` ${r.fired}/${r.total} shapes detected (${(r.coverage * 100).toFixed(0)}%) · ${r.missed} missed`);
16892
+ lines.push(``);
16893
+ for (const [cls, c] of Object.entries(r.per_class)) {
16894
+ const mark = c.missed === 0 ? "OK " : "GAP";
16895
+ lines.push(` [${mark}] ${cls.padEnd(20)} ${c.fired}/${c.total}`);
16896
+ }
16897
+ if (r.missed > 0) {
16898
+ lines.push(``);
16899
+ for (const res of r.results.filter((x) => !x.fired)) {
16900
+ lines.push(` missed: ${res.class} — ${res.note}`);
16901
+ }
16902
+ }
16903
+ return lines.join(`
16904
+ `);
16905
+ }
16906
+ var __dirname4, CROSS_FILE_ONLY;
16907
+ var init_variants = __esm(() => {
16908
+ init_complex_bugs();
16909
+ __dirname4 = dirname4(fileURLToPath10(import.meta.url));
16910
+ CROSS_FILE_ONLY = new Set(["priv_esc"]);
16911
+ });
16912
+
16750
16913
  // src/sync.ts
16751
- import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync8, readFileSync as readFileSync24, readdirSync as readdirSync7, statSync as statSync4, rmSync as rmSync4 } from "node:fs";
16752
- import { join as join21 } from "node:path";
16914
+ import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync8, readFileSync as readFileSync25, readdirSync as readdirSync7, statSync as statSync4, rmSync as rmSync4 } from "node:fs";
16915
+ import { join as join22 } from "node:path";
16753
16916
  import { homedir as homedir10 } from "node:os";
16754
- import { fileURLToPath as fileURLToPath11 } from "node:url";
16917
+ import { fileURLToPath as fileURLToPath12 } from "node:url";
16755
16918
  import { spawnSync as spawnSync4 } from "node:child_process";
16756
16919
  function dirExists(p) {
16757
16920
  try {
@@ -16766,7 +16929,7 @@ function countFiles(dir) {
16766
16929
  const walk = (d) => {
16767
16930
  for (const e of readdirSync7(d, { withFileTypes: true })) {
16768
16931
  if (e.isDirectory())
16769
- walk(join21(d, e.name));
16932
+ walk(join22(d, e.name));
16770
16933
  else
16771
16934
  n++;
16772
16935
  }
@@ -16780,8 +16943,8 @@ function countFiles(dir) {
16780
16943
  function syncData() {
16781
16944
  if (!dirExists(DATA_ROOT2))
16782
16945
  mkdirSync9(DATA_ROOT2, { recursive: true });
16783
- const payloadsPath = join21(DATA_ROOT2, "payloads");
16784
- const templatesPath = join21(DATA_ROOT2, "templates");
16946
+ const payloadsPath = join22(DATA_ROOT2, "payloads");
16947
+ const templatesPath = join22(DATA_ROOT2, "templates");
16785
16948
  const hasPayloads = dirExists(payloadsPath) && countFiles(payloadsPath) > 0;
16786
16949
  const hasTemplates = dirExists(templatesPath) && countFiles(templatesPath) > 0;
16787
16950
  if (hasPayloads && hasTemplates) {
@@ -16791,8 +16954,8 @@ function syncData() {
16791
16954
  data_root: DATA_ROOT2
16792
16955
  };
16793
16956
  }
16794
- const localPayloads = join21(ROOT7, "payloads");
16795
- const localTemplates = join21(ROOT7, "templates");
16957
+ const localPayloads = join22(ROOT7, "payloads");
16958
+ const localTemplates = join22(ROOT7, "templates");
16796
16959
  let payloadsSynced = false;
16797
16960
  let templatesSynced = false;
16798
16961
  if (!hasPayloads && dirExists(localPayloads)) {
@@ -16805,7 +16968,7 @@ function syncData() {
16805
16968
  }
16806
16969
  const gitOk = spawnSync4("git", ["--version"]).status === 0;
16807
16970
  if (gitOk && (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0 || !dirExists(templatesPath) || countFiles(templatesPath) === 0)) {
16808
- const tmp = join21(DATA_ROOT2, ".tmp-clone");
16971
+ const tmp = join22(DATA_ROOT2, ".tmp-clone");
16809
16972
  if (dirExists(tmp))
16810
16973
  rmSync4(tmp, { recursive: true, force: true });
16811
16974
  const r = spawnSync4("git", ["clone", "--depth", "1", "--filter=blob:none", "--sparse", GITHUB_REPO, tmp], { timeout: 120000 });
@@ -16813,11 +16976,11 @@ function syncData() {
16813
16976
  const sparse = spawnSync4("git", ["-C", tmp, "sparse-checkout", "set", "payloads", "templates"], { timeout: 120000 });
16814
16977
  if (sparse.status === 0) {
16815
16978
  if (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0) {
16816
- copyDir(join21(tmp, "payloads"), payloadsPath);
16979
+ copyDir(join22(tmp, "payloads"), payloadsPath);
16817
16980
  payloadsSynced = true;
16818
16981
  }
16819
16982
  if (!dirExists(templatesPath) || countFiles(templatesPath) === 0) {
16820
- copyDir(join21(tmp, "templates"), templatesPath);
16983
+ copyDir(join22(tmp, "templates"), templatesPath);
16821
16984
  templatesSynced = true;
16822
16985
  }
16823
16986
  }
@@ -16839,18 +17002,18 @@ function copyDir(src, dst) {
16839
17002
  function manualCopy(src, dst) {
16840
17003
  mkdirSync9(dst, { recursive: true });
16841
17004
  for (const e of readdirSync7(src, { withFileTypes: true })) {
16842
- const s = join21(src, e.name);
16843
- const d = join21(dst, e.name);
17005
+ const s = join22(src, e.name);
17006
+ const d = join22(dst, e.name);
16844
17007
  if (e.isDirectory())
16845
17008
  manualCopy(s, d);
16846
17009
  else
16847
- writeFileSync8(d, readFileSync24(s));
17010
+ writeFileSync8(d, readFileSync25(s));
16848
17011
  }
16849
17012
  }
16850
17013
  var ROOT7, DATA_ROOT2, GITHUB_REPO = "https://github.com/shinthink/blitzstrike.git";
16851
17014
  var init_sync = __esm(() => {
16852
- ROOT7 = join21(fileURLToPath11(new URL(".", import.meta.url)), "..");
16853
- DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ?? join21(homedir10(), ".blitzstrike", "data");
17015
+ ROOT7 = join22(fileURLToPath12(new URL(".", import.meta.url)), "..");
17016
+ DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ?? join22(homedir10(), ".blitzstrike", "data");
16854
17017
  });
16855
17018
 
16856
17019
  // src/update.ts
@@ -16895,15 +17058,15 @@ var init_update = __esm(() => {
16895
17058
  });
16896
17059
 
16897
17060
  // src/release.ts
16898
- import { readFileSync as readFileSync26, existsSync as existsSync12 } from "node:fs";
16899
- import { join as join22 } from "node:path";
16900
- import { fileURLToPath as fileURLToPath12 } from "node:url";
17061
+ import { readFileSync as readFileSync27, existsSync as existsSync12 } from "node:fs";
17062
+ import { join as join23 } from "node:path";
17063
+ import { fileURLToPath as fileURLToPath13 } from "node:url";
16901
17064
  import { execSync as execSync2 } from "node:child_process";
16902
17065
  function validateRelease() {
16903
17066
  const checks = [];
16904
17067
  checks.push({ name: "package.json version", ok: true, detail: VERSION });
16905
17068
  try {
16906
- const changelog = readFileSync26(join22(ROOT8, "CHANGELOG.md"), "utf8");
17069
+ const changelog = readFileSync27(join23(ROOT8, "CHANGELOG.md"), "utf8");
16907
17070
  const m = changelog.match(/^##\s+\[([0-9]+\.[0-9]+\.[0-9]+)\]/m);
16908
17071
  const changelogVersion = m?.[1] ?? null;
16909
17072
  checks.push({
@@ -16935,22 +17098,22 @@ function validateRelease() {
16935
17098
  return { version: VERSION, checks, consistent: checks.filter((c) => !c.soft).every((c) => c.ok) };
16936
17099
  }
16937
17100
  function lockfilePresent() {
16938
- return existsSync12(join22(ROOT8, "package-lock.json")) || existsSync12(join22(ROOT8, "bun.lock")) || existsSync12(join22(ROOT8, "bun.lockb"));
17101
+ return existsSync12(join23(ROOT8, "package-lock.json")) || existsSync12(join23(ROOT8, "bun.lock")) || existsSync12(join23(ROOT8, "bun.lockb"));
16939
17102
  }
16940
17103
  var ROOT8;
16941
17104
  var init_release = __esm(() => {
16942
17105
  init_version();
16943
- ROOT8 = join22(fileURLToPath12(new URL(".", import.meta.url)), "..");
17106
+ ROOT8 = join23(fileURLToPath13(new URL(".", import.meta.url)), "..");
16944
17107
  });
16945
17108
 
16946
17109
  // src/dependency.ts
16947
- import { readFileSync as readFileSync27 } from "node:fs";
16948
- import { join as join23 } from "node:path";
16949
- import { fileURLToPath as fileURLToPath13 } from "node:url";
17110
+ import { readFileSync as readFileSync28 } from "node:fs";
17111
+ import { join as join24 } from "node:path";
17112
+ import { fileURLToPath as fileURLToPath14 } from "node:url";
16950
17113
  function checkDependencies() {
16951
17114
  let pkg = {};
16952
17115
  try {
16953
- pkg = JSON.parse(readFileSync27(join23(ROOT9, "package.json"), "utf8"));
17116
+ pkg = JSON.parse(readFileSync28(join24(ROOT9, "package.json"), "utf8"));
16954
17117
  } catch {
16955
17118
  return { install_scripts: [], git_dependencies: [], total_dependencies: 0, safe: false };
16956
17119
  }
@@ -16973,14 +17136,14 @@ function checkDependencies() {
16973
17136
  }
16974
17137
  var ROOT9;
16975
17138
  var init_dependency = __esm(() => {
16976
- ROOT9 = join23(fileURLToPath13(new URL(".", import.meta.url)), "..");
17139
+ ROOT9 = join24(fileURLToPath14(new URL(".", import.meta.url)), "..");
16977
17140
  });
16978
17141
 
16979
17142
  // src/cli.ts
16980
- import { readFileSync as readFileSync28, existsSync as existsSync13, writeFileSync as writeFileSync9, mkdirSync as mkdirSync10 } from "node:fs";
16981
- import { join as join24, dirname as dirname4 } from "node:path";
17143
+ import { readFileSync as readFileSync29, existsSync as existsSync13, writeFileSync as writeFileSync9, mkdirSync as mkdirSync10 } from "node:fs";
17144
+ import { join as join25, dirname as dirname5 } from "node:path";
16982
17145
  import { homedir as homedir11 } from "node:os";
16983
- import { fileURLToPath as fileURLToPath14 } from "node:url";
17146
+ import { fileURLToPath as fileURLToPath15 } from "node:url";
16984
17147
  import { spawnSync as spawnSync5 } from "node:child_process";
16985
17148
  function which(cmd) {
16986
17149
  return spawnSync5("sh", ["-c", 'command -v "$1" >/dev/null 2>&1', "sh", cmd]).status === 0;
@@ -17023,7 +17186,7 @@ async function runDoctor() {
17023
17186
  detail: h1 ? `X-HackerOne-Research: ${h1}` : "H1_USERNAME not set",
17024
17187
  fix: h1 ? undefined : "export H1_USERNAME=<your-hackerone-username> (adds X-HackerOne-Research to outbound requests)"
17025
17188
  });
17026
- const dataOk = existsSync13(join24(ROOT10, "chains.json")) && existsSync13(join24(ROOT10, "tools-catalog.json"));
17189
+ const dataOk = existsSync13(join25(ROOT10, "chains.json")) && existsSync13(join25(ROOT10, "tools-catalog.json"));
17027
17190
  issues.push({
17028
17191
  name: "Data layers (chains + tools-catalog)",
17029
17192
  status: dataOk ? "ok" : "fail",
@@ -17074,28 +17237,28 @@ BlitzStrike — ${verdict}`);
17074
17237
  function resolveCommand() {
17075
17238
  if (which("blitzstrike"))
17076
17239
  return { command: "blitzstrike", args: ["serve", "--mcp"] };
17077
- const src = join24(ROOT10, "src", "index.ts");
17240
+ const src = join25(ROOT10, "src", "index.ts");
17078
17241
  if (which("bun") && existsSync13(src))
17079
17242
  return { command: "bun", args: ["run", src, "serve", "--mcp"] };
17080
17243
  if (which("npx"))
17081
17244
  return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
17082
17245
  if (which("bunx"))
17083
17246
  return { command: "bunx", args: ["blitzstrike", "serve", "--mcp"] };
17084
- const dist = join24(ROOT10, "dist", "index.js");
17247
+ const dist = join25(ROOT10, "dist", "index.js");
17085
17248
  if (existsSync13(dist))
17086
17249
  return { command: "bun", args: [dist, "serve", "--mcp"] };
17087
17250
  return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
17088
17251
  }
17089
17252
  function readJson(p) {
17090
17253
  try {
17091
- return JSON.parse(readFileSync28(p, "utf8"));
17254
+ return JSON.parse(readFileSync29(p, "utf8"));
17092
17255
  } catch {
17093
17256
  return null;
17094
17257
  }
17095
17258
  }
17096
17259
  function jsonMcpServersWrite(p) {
17097
17260
  return (command, args) => {
17098
- const dir = dirname4(p);
17261
+ const dir = dirname5(p);
17099
17262
  if (!existsSync13(dir))
17100
17263
  mkdirSync10(dir, { recursive: true });
17101
17264
  const existing = readJson(p) ?? {};
@@ -17108,7 +17271,7 @@ function jsonMcpServersWrite(p) {
17108
17271
  }
17109
17272
  function opencodeWrite(p) {
17110
17273
  return (command, args) => {
17111
- const dir = dirname4(p);
17274
+ const dir = dirname5(p);
17112
17275
  if (!existsSync13(dir))
17113
17276
  mkdirSync10(dir, { recursive: true });
17114
17277
  const existing = readJson(p) ?? {};
@@ -17121,26 +17284,26 @@ function opencodeWrite(p) {
17121
17284
  };
17122
17285
  }
17123
17286
  function copyOpenCodeAgents() {
17124
- const srcDir = join24(ROOT10, "opencode-agents");
17125
- const dstDir = join24(homedir11(), ".config", "opencode", "agents");
17287
+ const srcDir = join25(ROOT10, "opencode-agents");
17288
+ const dstDir = join25(homedir11(), ".config", "opencode", "agents");
17126
17289
  if (!existsSync13(srcDir))
17127
17290
  return;
17128
17291
  if (!existsSync13(dstDir))
17129
17292
  mkdirSync10(dstDir, { recursive: true });
17130
17293
  for (const f of ["Blitz Strike.md", "Blitz.md", "Eagle Eye.md", "Strike.md"]) {
17131
- const src = join24(srcDir, f);
17132
- const dst = join24(dstDir, f);
17294
+ const src = join25(srcDir, f);
17295
+ const dst = join25(dstDir, f);
17133
17296
  if (existsSync13(src)) {
17134
- writeFileSync9(dst, readFileSync28(src, "utf8"));
17297
+ writeFileSync9(dst, readFileSync29(src, "utf8"));
17135
17298
  }
17136
17299
  }
17137
17300
  }
17138
17301
  function codexWrite(p) {
17139
17302
  return (command, args) => {
17140
- const dir = dirname4(p);
17303
+ const dir = dirname5(p);
17141
17304
  if (!existsSync13(dir))
17142
17305
  mkdirSync10(dir, { recursive: true });
17143
- let existing = existsSync13(p) ? readFileSync28(p, "utf8") : "";
17306
+ let existing = existsSync13(p) ? readFileSync29(p, "utf8") : "";
17144
17307
  if (!existing.trimEnd().endsWith(`
17145
17308
  `))
17146
17309
  existing += `
@@ -17156,10 +17319,10 @@ args = ${JSON.stringify(args)}
17156
17319
  }
17157
17320
  function hermesWrite(p) {
17158
17321
  return (command, args) => {
17159
- const dir = dirname4(p);
17322
+ const dir = dirname5(p);
17160
17323
  if (!existsSync13(dir))
17161
17324
  mkdirSync10(dir, { recursive: true });
17162
- let existing = existsSync13(p) ? readFileSync28(p, "utf8") : "";
17325
+ let existing = existsSync13(p) ? readFileSync29(p, "utf8") : "";
17163
17326
  existing = existing.replace(/^ blitzstrike:\n(?: .*\n?)*/m, "");
17164
17327
  if (!existing.trimEnd().endsWith(`
17165
17328
  `))
@@ -17186,74 +17349,74 @@ mcp_servers:
17186
17349
  function detectAgents() {
17187
17350
  const home = homedir11();
17188
17351
  const agents = [];
17189
- const claudeJson = join24(home, ".claude.json");
17352
+ const claudeJson = join25(home, ".claude.json");
17190
17353
  agents.push({
17191
17354
  name: "Claude Code",
17192
17355
  detectPath: claudeJson,
17193
17356
  installed: () => existsSync13(claudeJson) || which("claude"),
17194
17357
  write: jsonMcpServersWrite(claudeJson)
17195
17358
  });
17196
- const claudeDesktop = join24(home, ".config", "Claude", "claude_desktop_config.json");
17359
+ const claudeDesktop = join25(home, ".config", "Claude", "claude_desktop_config.json");
17197
17360
  agents.push({
17198
17361
  name: "Claude Desktop",
17199
17362
  detectPath: claudeDesktop,
17200
17363
  installed: () => existsSync13(claudeDesktop),
17201
17364
  write: jsonMcpServersWrite(claudeDesktop)
17202
17365
  });
17203
- const cursor = join24(home, ".cursor", "mcp.json");
17366
+ const cursor = join25(home, ".cursor", "mcp.json");
17204
17367
  agents.push({
17205
17368
  name: "Cursor",
17206
17369
  detectPath: cursor,
17207
- installed: () => existsSync13(join24(home, ".cursor")) || which("cursor"),
17370
+ installed: () => existsSync13(join25(home, ".cursor")) || which("cursor"),
17208
17371
  write: jsonMcpServersWrite(cursor)
17209
17372
  });
17210
- const opencode = join24(home, ".config", "opencode", "opencode.jsonc");
17373
+ const opencode = join25(home, ".config", "opencode", "opencode.jsonc");
17211
17374
  agents.push({
17212
17375
  name: "OpenCode",
17213
17376
  detectPath: opencode,
17214
- installed: () => existsSync13(join24(home, ".config", "opencode")) || which("opencode"),
17377
+ installed: () => existsSync13(join25(home, ".config", "opencode")) || which("opencode"),
17215
17378
  write: opencodeWrite(opencode)
17216
17379
  });
17217
- const codex = join24(home, ".codex", "config.toml");
17380
+ const codex = join25(home, ".codex", "config.toml");
17218
17381
  agents.push({
17219
17382
  name: "Codex",
17220
17383
  detectPath: codex,
17221
- installed: () => existsSync13(join24(home, ".codex")) || which("codex"),
17384
+ installed: () => existsSync13(join25(home, ".codex")) || which("codex"),
17222
17385
  write: codexWrite(codex)
17223
17386
  });
17224
- const hermes = join24(home, ".hermes", "config.yaml");
17387
+ const hermes = join25(home, ".hermes", "config.yaml");
17225
17388
  agents.push({
17226
17389
  name: "Hermes",
17227
17390
  detectPath: hermes,
17228
- installed: () => existsSync13(join24(home, ".hermes")) || which("hermes"),
17391
+ installed: () => existsSync13(join25(home, ".hermes")) || which("hermes"),
17229
17392
  write: hermesWrite(hermes)
17230
17393
  });
17231
- const gemini = join24(home, ".gemini", "settings.json");
17394
+ const gemini = join25(home, ".gemini", "settings.json");
17232
17395
  agents.push({
17233
17396
  name: "Gemini CLI",
17234
17397
  detectPath: gemini,
17235
- installed: () => existsSync13(join24(home, ".gemini")) || which("gemini"),
17398
+ installed: () => existsSync13(join25(home, ".gemini")) || which("gemini"),
17236
17399
  write: jsonMcpServersWrite(gemini)
17237
17400
  });
17238
- const windsurf = join24(home, ".codeium", "windsurf", "mcp_config.json");
17401
+ const windsurf = join25(home, ".codeium", "windsurf", "mcp_config.json");
17239
17402
  agents.push({
17240
17403
  name: "Windsurf",
17241
17404
  detectPath: windsurf,
17242
- installed: () => existsSync13(join24(home, ".codeium")) || which("windsurf"),
17405
+ installed: () => existsSync13(join25(home, ".codeium")) || which("windsurf"),
17243
17406
  write: jsonMcpServersWrite(windsurf)
17244
17407
  });
17245
- const copilot = join24(home, ".copilot", "mcp.json");
17408
+ const copilot = join25(home, ".copilot", "mcp.json");
17246
17409
  agents.push({
17247
17410
  name: "Copilot",
17248
17411
  detectPath: copilot,
17249
- installed: () => existsSync13(join24(home, ".copilot")),
17412
+ installed: () => existsSync13(join25(home, ".copilot")),
17250
17413
  write: jsonMcpServersWrite(copilot)
17251
17414
  });
17252
- const cline = join24(home, ".cline", "mcp_settings.json");
17415
+ const cline = join25(home, ".cline", "mcp_settings.json");
17253
17416
  agents.push({
17254
17417
  name: "Cline",
17255
17418
  detectPath: cline,
17256
- installed: () => existsSync13(join24(home, ".cline")),
17419
+ installed: () => existsSync13(join25(home, ".cline")),
17257
17420
  write: jsonMcpServersWrite(cline)
17258
17421
  });
17259
17422
  return agents;
@@ -17371,7 +17534,7 @@ var init_cli = __esm(() => {
17371
17534
  init_release();
17372
17535
  init_dependency();
17373
17536
  init_mcp_status();
17374
- ROOT10 = join24(fileURLToPath14(new URL(".", import.meta.url)), "..");
17537
+ ROOT10 = join25(fileURLToPath15(new URL(".", import.meta.url)), "..");
17375
17538
  });
17376
17539
 
17377
17540
  // node_modules/zod/v3/helpers/util.js
@@ -32009,7 +32172,7 @@ class StdioServerTransport {
32009
32172
 
32010
32173
  // src/server.ts
32011
32174
  init_scanner();
32012
- import { readFileSync as readFileSync25 } from "node:fs";
32175
+ import { readFileSync as readFileSync26 } from "node:fs";
32013
32176
 
32014
32177
  // src/recon.ts
32015
32178
  var TIMEOUT_MS = 25000;
@@ -57675,20 +57838,21 @@ function dataRefresh() {
57675
57838
 
57676
57839
  // src/server.ts
57677
57840
  init_verify_harness();
57841
+ init_variants();
57678
57842
 
57679
57843
  // src/benchmark.ts
57680
- import { readFileSync as readFileSync19, readdirSync as readdirSync5, writeFileSync as writeFileSync6, rmSync as rmSync3, mkdirSync as mkdirSync7 } from "node:fs";
57681
- import { join as join18 } from "node:path";
57682
- import { fileURLToPath as fileURLToPath10 } from "node:url";
57844
+ import { readFileSync as readFileSync20, readdirSync as readdirSync5, writeFileSync as writeFileSync6, rmSync as rmSync3, mkdirSync as mkdirSync7 } from "node:fs";
57845
+ import { join as join19 } from "node:path";
57846
+ import { fileURLToPath as fileURLToPath11 } from "node:url";
57683
57847
  import { tmpdir as tmpdir3, homedir as homedir8 } from "node:os";
57684
57848
  init_complex_bugs();
57685
- var ROOT5 = join18(fileURLToPath10(new URL(".", import.meta.url)), "..");
57686
- var CORPUS = join18(ROOT5, "bench", "corpus");
57687
- var CAPTURED_CORPUS_DIR = process.env.BLITZSTRIKE_CORPUS_DIR ?? join18(homedir8(), ".blitzstrike", "corpus");
57849
+ var ROOT5 = join19(fileURLToPath11(new URL(".", import.meta.url)), "..");
57850
+ var CORPUS = join19(ROOT5, "bench", "corpus");
57851
+ var CAPTURED_CORPUS_DIR = process.env.BLITZSTRIKE_CORPUS_DIR ?? join19(homedir8(), ".blitzstrike", "corpus");
57688
57852
  function parseCorpusFile(path) {
57689
57853
  let text;
57690
57854
  try {
57691
- text = readFileSync19(path, "utf8");
57855
+ text = readFileSync20(path, "utf8");
57692
57856
  } catch {
57693
57857
  return null;
57694
57858
  }
@@ -57744,7 +57908,7 @@ function loadDir(dir) {
57744
57908
  for (const f of readdirSync5(dir)) {
57745
57909
  if (!f.endsWith(".md"))
57746
57910
  continue;
57747
- const e = parseCorpusFile(join18(dir, f));
57911
+ const e = parseCorpusFile(join19(dir, f));
57748
57912
  if (e)
57749
57913
  entries.push(e);
57750
57914
  }
@@ -57771,7 +57935,7 @@ function captureFalsePositive(input) {
57771
57935
  input.code,
57772
57936
  "```"
57773
57937
  ].filter((l) => l !== null);
57774
- const path = join18(CAPTURED_CORPUS_DIR, `${id}.md`);
57938
+ const path = join19(CAPTURED_CORPUS_DIR, `${id}.md`);
57775
57939
  writeFileSync6(path, lines.join(`
57776
57940
  `) + `
57777
57941
  `, "utf8");
@@ -57793,7 +57957,7 @@ function detect(entry) {
57793
57957
  }
57794
57958
  const lang = entry.language;
57795
57959
  if (lang === "php") {
57796
- const tmp = join18(tmpdir3(), `blitzstrike-bench-${entry.id}-${process.pid}.php`);
57960
+ const tmp = join19(tmpdir3(), `blitzstrike-bench-${entry.id}-${process.pid}.php`);
57797
57961
  writeFileSync6(tmp, entry.code);
57798
57962
  const r = analyzeDataFlow2(tmp);
57799
57963
  rmSync3(tmp, { force: true });
@@ -57862,12 +58026,12 @@ function runBenchmark() {
57862
58026
  }
57863
58027
 
57864
58028
  // src/obligations.ts
57865
- import { readFileSync as readFileSync20, appendFileSync as appendFileSync4, mkdirSync as mkdirSync8, existsSync as existsSync10, writeFileSync as writeFileSync7 } from "node:fs";
57866
- import { join as join19 } from "node:path";
58029
+ import { readFileSync as readFileSync21, appendFileSync as appendFileSync4, mkdirSync as mkdirSync8, existsSync as existsSync10, writeFileSync as writeFileSync7 } from "node:fs";
58030
+ import { join as join20 } from "node:path";
57867
58031
  import { createHash as createHash6 } from "node:crypto";
57868
58032
  import { homedir as homedir9 } from "node:os";
57869
- var HOME_DIR4 = process.env.BLITZSTRIKE_HOME ?? join19(homedir9(), ".blitzstrike");
57870
- var OBLIGATION_PATH = join19(HOME_DIR4, "obligations.jsonl");
58033
+ var HOME_DIR4 = process.env.BLITZSTRIKE_HOME ?? join20(homedir9(), ".blitzstrike");
58034
+ var OBLIGATION_PATH = join20(HOME_DIR4, "obligations.jsonl");
57871
58035
  function ensureDir3() {
57872
58036
  if (!existsSync10(HOME_DIR4))
57873
58037
  mkdirSync8(HOME_DIR4, { recursive: true });
@@ -57880,7 +58044,7 @@ function loadObligations() {
57880
58044
  return [];
57881
58045
  try {
57882
58046
  const out = [];
57883
- for (const line of readFileSync20(OBLIGATION_PATH, "utf8").split(`
58047
+ for (const line of readFileSync21(OBLIGATION_PATH, "utf8").split(`
57884
58048
  `)) {
57885
58049
  const t = line.trim();
57886
58050
  if (!t)
@@ -57984,11 +58148,11 @@ init_framework_security();
57984
58148
  // src/variant-scan.ts
57985
58149
  init_scanner();
57986
58150
  init_complex_bugs();
57987
- import { readFileSync as readFileSync22 } from "node:fs";
58151
+ import { readFileSync as readFileSync23 } from "node:fs";
57988
58152
 
57989
58153
  // src/cross-file.ts
57990
58154
  init_scanner();
57991
- import { readFileSync as readFileSync21 } from "node:fs";
58155
+ import { readFileSync as readFileSync22 } from "node:fs";
57992
58156
  var PUBLIC_HOOK_RE = /(?:wp_ajax_nopriv_\w+|admin_post_nopriv_\w+|init|template_redirect|wp_loaded|parse_request|user_register|um_user_register|register_new_user|authenticate)/;
57993
58157
  var PRIV_SINK_RE = /\b(set_role|add_role|remove_role|set_user_role|wp_set_current_user|wp_set_auth_cookie|wp_insert_user|wp_create_user|wp_update_user)\s*\(/;
57994
58158
  var ROLE_META_RE = /update_user_meta\s*\(\s*[^,]+,\s*['"](wp_capabilities|role|roles|capabilities|wp_user_level)['"]/;
@@ -58040,7 +58204,7 @@ function detectCrossFilePrivesc(root) {
58040
58204
  for (const file of files) {
58041
58205
  let code;
58042
58206
  try {
58043
- code = stripComments(readFileSync21(file, "utf8"));
58207
+ code = stripComments(readFileSync22(file, "utf8"));
58044
58208
  } catch {
58045
58209
  continue;
58046
58210
  }
@@ -58081,7 +58245,7 @@ function detectCrossFilePrivesc(root) {
58081
58245
  for (const file of files) {
58082
58246
  let code;
58083
58247
  try {
58084
- code = stripComments(readFileSync21(file, "utf8"));
58248
+ code = stripComments(readFileSync22(file, "utf8"));
58085
58249
  } catch {
58086
58250
  continue;
58087
58251
  }
@@ -58104,7 +58268,7 @@ function detectCrossFilePrivesc(root) {
58104
58268
  for (const file of files) {
58105
58269
  let code;
58106
58270
  try {
58107
- code = stripComments(readFileSync21(file, "utf8"));
58271
+ code = stripComments(readFileSync22(file, "utf8"));
58108
58272
  } catch {
58109
58273
  continue;
58110
58274
  }
@@ -58219,7 +58383,7 @@ function variantScan(root, opts = {}) {
58219
58383
  for (const file of files) {
58220
58384
  let code;
58221
58385
  try {
58222
- code = readFileSync22(file, "utf8");
58386
+ code = readFileSync23(file, "utf8");
58223
58387
  } catch {
58224
58388
  continue;
58225
58389
  }
@@ -58879,17 +59043,17 @@ function analyzeJwt(token, opts = {}) {
58879
59043
  }
58880
59044
 
58881
59045
  // src/enterprise-benchmark.ts
58882
- import { existsSync as existsSync11, readFileSync as readFileSync23, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
58883
- import { join as join20, resolve } from "node:path";
59046
+ import { existsSync as existsSync11, readFileSync as readFileSync24, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
59047
+ import { join as join21, resolve } from "node:path";
58884
59048
  init_complex_bugs();
58885
59049
  var ROOT6 = resolve(process.cwd(), "bench", "enterprise");
58886
59050
  var ENTERPRISE_FIXTURES = [
58887
- { id: "php-laravel", dir: join20(ROOT6, "php-laravel"), expected_vulns: 4 },
58888
- { id: "js-express", dir: join20(ROOT6, "js-express"), expected_vulns: 2 },
58889
- { id: "py-django", dir: join20(ROOT6, "py-django"), expected_vulns: 2 },
58890
- { id: "java-spring", dir: join20(ROOT6, "java-spring"), expected_vulns: 2 },
58891
- { id: "wp-batch-route", dir: join20(ROOT6, "wp-batch-route"), expected_vulns: 3 },
58892
- { id: "php-complex", dir: join20(ROOT6, "php-complex"), expected_vulns: 4 }
59051
+ { id: "php-laravel", dir: join21(ROOT6, "php-laravel"), expected_vulns: 4 },
59052
+ { id: "js-express", dir: join21(ROOT6, "js-express"), expected_vulns: 2 },
59053
+ { id: "py-django", dir: join21(ROOT6, "py-django"), expected_vulns: 2 },
59054
+ { id: "java-spring", dir: join21(ROOT6, "java-spring"), expected_vulns: 2 },
59055
+ { id: "wp-batch-route", dir: join21(ROOT6, "wp-batch-route"), expected_vulns: 3 },
59056
+ { id: "php-complex", dir: join21(ROOT6, "php-complex"), expected_vulns: 4 }
58893
59057
  ];
58894
59058
  function sourceFiles(root) {
58895
59059
  const out = [];
@@ -58901,7 +59065,7 @@ function sourceFiles(root) {
58901
59065
  return;
58902
59066
  }
58903
59067
  for (const e of entries) {
58904
- const p = join20(dir, e);
59068
+ const p = join21(dir, e);
58905
59069
  let st;
58906
59070
  try {
58907
59071
  st = statSync3(p);
@@ -58922,7 +59086,7 @@ function detectFixture(dir) {
58922
59086
  for (const f of sourceFiles(dir)) {
58923
59087
  let code;
58924
59088
  try {
58925
- code = readFileSync23(f, "utf8");
59089
+ code = readFileSync24(f, "utf8");
58926
59090
  } catch {
58927
59091
  continue;
58928
59092
  }
@@ -59542,6 +59706,13 @@ ${statusLine}` : statusLine },
59542
59706
  }, async () => {
59543
59707
  return { content: [{ type: "text", text: JSON.stringify(runVerificationHarness()) }] };
59544
59708
  });
59709
+ server.registerTool("scan_variants", {
59710
+ title: "Variant corpus coverage scan",
59711
+ description: "VERIFY: sweep every real-world code shape (intelligence/variants.json) against its class's detector and report per-class coverage. A shape that does not fire is a coverage gap (false negative) — use this to find detector blind spots, then fix the detector.",
59712
+ inputSchema: {}
59713
+ }, async () => {
59714
+ return { content: [{ type: "text", text: JSON.stringify(scanVariants()) }] };
59715
+ });
59545
59716
  server.registerTool("fofa_search", {
59546
59717
  title: "FOFA asset search",
59547
59718
  description: "STRIKE: search the FOFA asset index (needs FOFA_EMAIL + FOFA_KEY).",
@@ -59926,7 +60097,7 @@ ${statusLine}` : statusLine },
59926
60097
  let src = code;
59927
60098
  if (!src && path) {
59928
60099
  try {
59929
- src = readFileSync25(path, "utf8");
60100
+ src = readFileSync26(path, "utf8");
59930
60101
  } catch {
59931
60102
  return { content: [{ type: "text", text: JSON.stringify({ error: "unreadable path" }) }] };
59932
60103
  }
@@ -60544,7 +60715,7 @@ ${statusLine}` : statusLine },
60544
60715
  description: "Route confusion (separate class from source→sink taint): detect attacker-controlled dispatch (call_user_func/forward/dispatch), dynamic method calls ($obj->$m()), dynamic include (LFI), batch/proxy forwarding without per-route auth re-check, and route matching without path normalization.",
60545
60716
  inputSchema: { path: string2().describe("Source file path (PHP)") }
60546
60717
  }, async ({ path }) => {
60547
- const code = readFileSync25(path, "utf8");
60718
+ const code = readFileSync26(path, "utf8");
60548
60719
  const findings = detectRouteConfusion(code, path);
60549
60720
  return {
60550
60721
  content: [{
@@ -60558,7 +60729,7 @@ ${statusLine}` : statusLine },
60558
60729
  description: "Complex-bug detection beyond taint: deserialization→POP gadget chain (unserialize + magic method), type juggling (loose ==/!= vs hash/secret), mass assignment (extract/parse_str without a safe flag), prototype pollution (unsafe merge of request data), CRLF/header injection (user input into a header), path confusion (user input into a file path without canonicalization), SSRF (user-controlled URL with no host allowlist), XXE (user XML parsed without disabling entities), SSTI (user input into a template render with no sandbox), wrong-context sanitization (sanitize_text_field() into a SQL query), and missing authorization/nonce on WordPress entry points (wp_ajax_* / admin_post_* / register_rest_route without current_user_can, nonce verification, or permission_callback).",
60559
60730
  inputSchema: { path: string2().describe("Source file path") }
60560
60731
  }, async ({ path }) => {
60561
- const code = readFileSync25(path, "utf8");
60732
+ const code = readFileSync26(path, "utf8");
60562
60733
  const findings = detectComplexBugs(code, path);
60563
60734
  return {
60564
60735
  content: [{
@@ -60644,7 +60815,7 @@ ${statusLine}` : statusLine },
60644
60815
  let lang = language;
60645
60816
  if (path && !code) {
60646
60817
  try {
60647
- src = readFileSync25(path, "utf8");
60818
+ src = readFileSync26(path, "utf8");
60648
60819
  } catch {
60649
60820
  return { content: [{ type: "text", text: JSON.stringify({ error: `cannot read ${path}` }) }] };
60650
60821
  }
@@ -60885,15 +61056,15 @@ async function serve() {
60885
61056
 
60886
61057
  // src/index.ts
60887
61058
  init_cli();
60888
- import { readFileSync as readFileSync29, existsSync as existsSync14 } from "node:fs";
60889
- import { join as join25 } from "node:path";
60890
- import { fileURLToPath as fileURLToPath15 } from "node:url";
60891
- var ROOT11 = join25(fileURLToPath15(new URL(".", import.meta.url)), "..");
61059
+ import { readFileSync as readFileSync30, existsSync as existsSync14 } from "node:fs";
61060
+ import { join as join26 } from "node:path";
61061
+ import { fileURLToPath as fileURLToPath16 } from "node:url";
61062
+ var ROOT11 = join26(fileURLToPath16(new URL(".", import.meta.url)), "..");
60892
61063
  function readVersion() {
60893
61064
  try {
60894
- const p = join25(ROOT11, "package.json");
61065
+ const p = join26(ROOT11, "package.json");
60895
61066
  if (existsSync14(p))
60896
- return JSON.parse(readFileSync29(p, "utf8")).version ?? "1.0.0";
61067
+ return JSON.parse(readFileSync30(p, "utf8")).version ?? "1.0.0";
60897
61068
  } catch {}
60898
61069
  return "1.0.0";
60899
61070
  }
@@ -60964,9 +61135,13 @@ async function main() {
60964
61135
  }
60965
61136
  if (args[0] === "verify") {
60966
61137
  await Promise.resolve().then(() => init_verify_harness());
61138
+ await Promise.resolve().then(() => init_variants());
60967
61139
  console.log(verificationReportText());
61140
+ console.log(`
61141
+ ` + variantCoverageText());
60968
61142
  const r = runVerificationHarness();
60969
- if (r.failed > 0)
61143
+ const v = scanVariants();
61144
+ if (r.failed > 0 || v.missed > 0)
60970
61145
  process.exit(1);
60971
61146
  return;
60972
61147
  }