blitzstrike 1.0.14 → 1.0.15

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.
Files changed (2) hide show
  1. package/dist/index.js +358 -9
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7192,7 +7192,7 @@ var init_scanner = __esm(() => {
7192
7192
  });
7193
7193
 
7194
7194
  // src/sync.ts
7195
- import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync2, readFileSync as readFileSync9, readdirSync as readdirSync4, statSync as statSync3, rmSync } from "node:fs";
7195
+ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync2, readFileSync as readFileSync10, readdirSync as readdirSync4, statSync as statSync3, rmSync } from "node:fs";
7196
7196
  import { join as join8 } from "node:path";
7197
7197
  import { homedir as homedir4 } from "node:os";
7198
7198
  import { fileURLToPath as fileURLToPath6 } from "node:url";
@@ -7288,7 +7288,7 @@ function manualCopy(src, dst) {
7288
7288
  if (e.isDirectory())
7289
7289
  manualCopy(s, d);
7290
7290
  else
7291
- writeFileSync2(d, readFileSync9(s));
7291
+ writeFileSync2(d, readFileSync10(s));
7292
7292
  }
7293
7293
  }
7294
7294
  var ROOT5, DATA_ROOT2, GITHUB_REPO = "https://github.com/shinthink/blitzstrike.git";
@@ -23318,6 +23318,326 @@ function groupVariants(results) {
23318
23318
  return [...groups.values()].sort((a, b) => b.occurrences - a.occurrences);
23319
23319
  }
23320
23320
 
23321
+ // src/taint.ts
23322
+ init_scanner();
23323
+ import { readFileSync as readFileSync8 } from "node:fs";
23324
+ function lineIndexOf(text, pos) {
23325
+ let n = 1;
23326
+ for (let i = 0;i < pos; i++)
23327
+ if (text.charCodeAt(i) === 10)
23328
+ n++;
23329
+ return n;
23330
+ }
23331
+ function extractVars(s) {
23332
+ const out = [];
23333
+ const re = /\$([A-Za-z_][A-Za-z0-9_]*)/g;
23334
+ let m;
23335
+ while ((m = re.exec(s)) !== null) {
23336
+ if (!out.includes(m[1]))
23337
+ out.push(m[1]);
23338
+ }
23339
+ return out;
23340
+ }
23341
+ function parseFunctions(text, lines) {
23342
+ const scopes = [];
23343
+ const re = /function\s+&?([A-Za-z_][A-Za-z0-9_]*)\s*\(([^)]*)\)/g;
23344
+ let m;
23345
+ while ((m = re.exec(text)) !== null) {
23346
+ const name = m[1];
23347
+ const params = extractVars(m[2]);
23348
+ const brace = text.indexOf("{", m.index + m[0].length);
23349
+ if (brace === -1)
23350
+ continue;
23351
+ let depth = 0;
23352
+ let end = -1;
23353
+ for (let i = brace;i < text.length; i++) {
23354
+ if (text[i] === "{")
23355
+ depth++;
23356
+ else if (text[i] === "}") {
23357
+ depth--;
23358
+ if (depth === 0) {
23359
+ end = i;
23360
+ break;
23361
+ }
23362
+ }
23363
+ }
23364
+ if (end === -1)
23365
+ continue;
23366
+ const startLine = lineIndexOf(text, m.index) - 1;
23367
+ const endLine = lineIndexOf(text, end);
23368
+ scopes.push({ name, start: startLine, end: endLine, params, isGlobal: false });
23369
+ }
23370
+ return scopes;
23371
+ }
23372
+ function isSourceExpr(expr) {
23373
+ for (const token of Object.keys({ $_GET: 1, $_POST: 1, $_REQUEST: 1, $_COOKIE: 1, $_FILES: 1, $_SERVER: 1 })) {
23374
+ if (expr.includes(token))
23375
+ return token;
23376
+ }
23377
+ if (expr.includes("php://input") || expr.includes("file_get_contents('php://input')"))
23378
+ return "php://input";
23379
+ if (expr.includes("getallheaders"))
23380
+ return "getallheaders";
23381
+ return null;
23382
+ }
23383
+ function analyzeScope(text, lines, scope) {
23384
+ const vars = new Map;
23385
+ for (const p of scope.params)
23386
+ vars.set(p, { tainted: false, sanitizers: [] });
23387
+ const endLine = Math.min(scope.end, lines.length);
23388
+ for (let li = scope.start;li < endLine; li++) {
23389
+ const line = lines[li];
23390
+ const assignRe = /\$([A-Za-z_][A-Za-z0-9_]*)\s*(?:\.?=)\s*(.+)/;
23391
+ const am = assignRe.exec(line);
23392
+ if (!am)
23393
+ continue;
23394
+ const varName = am[1];
23395
+ const expr = am[2];
23396
+ const src = isSourceExpr(expr);
23397
+ if (src) {
23398
+ vars.set(varName, { tainted: true, sanitizers: [], source: src, sourceLine: li + 1 });
23399
+ continue;
23400
+ }
23401
+ const sanitizers = findSanitizers(expr);
23402
+ const innerVars = extractVars(expr);
23403
+ if (sanitizers.length > 0 && innerVars.length > 0) {
23404
+ const inner = vars.get(innerVars[0]);
23405
+ vars.set(varName, {
23406
+ tainted: inner?.tainted ?? false,
23407
+ sanitizers: [...inner?.sanitizers ?? [], ...sanitizers],
23408
+ source: inner?.source,
23409
+ sourceLine: inner?.sourceLine
23410
+ });
23411
+ continue;
23412
+ }
23413
+ let tainted = false;
23414
+ let mergedSanitizers = [];
23415
+ let source;
23416
+ let sourceLine;
23417
+ for (const v of innerVars) {
23418
+ const st = vars.get(v);
23419
+ if (st?.tainted) {
23420
+ tainted = true;
23421
+ mergedSanitizers = st.sanitizers;
23422
+ source = st.source;
23423
+ sourceLine = st.sourceLine;
23424
+ break;
23425
+ }
23426
+ }
23427
+ vars.set(varName, { tainted, sanitizers: mergedSanitizers, source, sourceLine });
23428
+ }
23429
+ return vars;
23430
+ }
23431
+ function buildSummaries(text, lines, scopes) {
23432
+ const summaries = new Map;
23433
+ for (const scope of scopes) {
23434
+ if (scope.isGlobal)
23435
+ continue;
23436
+ const sinkParams = new Map;
23437
+ const sanitizedParams = new Map;
23438
+ const vars = analyzeScope(text, lines, scope);
23439
+ const endLine = Math.min(scope.end, lines.length);
23440
+ for (let li = scope.start;li < endLine; li++) {
23441
+ const line = lines[li];
23442
+ const inlineSan = findSanitizers(line);
23443
+ if (inlineSan.length > 0) {
23444
+ for (const p of scope.params) {
23445
+ if (line.includes("$" + p)) {
23446
+ const existing = sanitizedParams.get(p) ?? [];
23447
+ sanitizedParams.set(p, [...existing, ...inlineSan]);
23448
+ }
23449
+ }
23450
+ }
23451
+ for (const sinkToken of Object.keys({
23452
+ "->query(": 1,
23453
+ "$wpdb->query": 1,
23454
+ "$wpdb->get_var": 1,
23455
+ "$wpdb->get_results": 1,
23456
+ "system(": 1,
23457
+ "exec(": 1,
23458
+ "shell_exec(": 1,
23459
+ "eval(": 1,
23460
+ "unserialize(": 1,
23461
+ "move_uploaded_file(": 1,
23462
+ "file_put_contents(": 1,
23463
+ include: 1,
23464
+ require: 1,
23465
+ "echo ": 1,
23466
+ "print ": 1,
23467
+ "header(": 1,
23468
+ "wp_redirect(": 1,
23469
+ "file_get_contents(": 1
23470
+ })) {
23471
+ if (!line.includes(sinkToken))
23472
+ continue;
23473
+ const varsInLine = extractVars(line);
23474
+ for (const v of varsInLine) {
23475
+ const st = vars.get(v);
23476
+ if (st?.tainted) {
23477
+ sinkParams.set(v, { sink: sinkToken, line: li + 1 });
23478
+ }
23479
+ }
23480
+ for (const p of scope.params) {
23481
+ if (line.includes("$" + p))
23482
+ sinkParams.set(p, { sink: sinkToken, line: li + 1 });
23483
+ }
23484
+ }
23485
+ }
23486
+ summaries.set(scope.name, { name: scope.name, sinkParams, sanitizedParams });
23487
+ }
23488
+ return summaries;
23489
+ }
23490
+ function analyzeTaint(path) {
23491
+ const result = { file: path, findings: [], suppressed: 0 };
23492
+ let text;
23493
+ try {
23494
+ text = readFileSync8(path, "utf8");
23495
+ } catch {
23496
+ return result;
23497
+ }
23498
+ const lines = text.split(`
23499
+ `);
23500
+ const scopes = parseFunctions(text, lines);
23501
+ const summaries = buildSummaries(text, lines, scopes);
23502
+ const globalScope = { name: "<global>", start: 0, end: lines.length, params: [], isGlobal: true };
23503
+ const allScopes = [...scopes, globalScope];
23504
+ for (const scope of allScopes) {
23505
+ const vars = analyzeScope(text, lines, scope);
23506
+ const endLine = Math.min(scope.end, lines.length);
23507
+ for (let li = scope.start;li < endLine; li++) {
23508
+ const line = lines[li];
23509
+ const calledFn = /([A-Za-z_][A-Za-z0-9_]*)\s*\(/.exec(line);
23510
+ if (calledFn && summaries.has(calledFn[1])) {
23511
+ const sum = summaries.get(calledFn[1]);
23512
+ const callVars = extractVars(line);
23513
+ const passedTainted = callVars.filter((v) => vars.get(v)?.tainted);
23514
+ if (passedTainted.length > 0 && sum.sinkParams.size > 0) {
23515
+ const firstSink = [...sum.sinkParams.values()][0];
23516
+ const cls = classifySink2(firstSink.sink);
23517
+ if (cls) {
23518
+ const st = vars.get(passedTainted[0]);
23519
+ const inline = findSanitizers(line);
23520
+ const helperSan = [...sum.sanitizedParams.values()].flat();
23521
+ const san = [...st.sanitizers, ...inline, ...helperSan];
23522
+ const authGated = AUTH_GATES.some((g) => line.includes(g) || (lines[li - 1] ?? "").includes(g));
23523
+ if (!isSanitized(san, cls.id) && !authGated) {
23524
+ result.findings.push({
23525
+ sink: firstSink.sink,
23526
+ sink_line: firstSink.line,
23527
+ category: cls.category,
23528
+ cwe: cls.cwe,
23529
+ variables: passedTainted.map((v) => vars.get(v).source ?? "var"),
23530
+ source: st.source,
23531
+ source_line: st.sourceLine,
23532
+ sanitized: false,
23533
+ sanitizers: san.map((s) => s.id),
23534
+ auth_gated: authGated,
23535
+ interprocedural: true
23536
+ });
23537
+ } else {
23538
+ result.suppressed += 1;
23539
+ }
23540
+ continue;
23541
+ }
23542
+ }
23543
+ }
23544
+ let sinkToken = null;
23545
+ let sinkClass = null;
23546
+ for (const tok of Object.keys({
23547
+ "->query(": 1,
23548
+ "$wpdb->query": 1,
23549
+ "$wpdb->get_var": 1,
23550
+ "$wpdb->get_results": 1,
23551
+ "->whereRaw": 1,
23552
+ "system(": 1,
23553
+ "exec(": 1,
23554
+ "shell_exec(": 1,
23555
+ "passthru(": 1,
23556
+ "popen(": 1,
23557
+ "proc_open(": 1,
23558
+ "eval(": 1,
23559
+ "assert(": 1,
23560
+ "unserialize(": 1,
23561
+ "maybe_unserialize(": 1,
23562
+ "move_uploaded_file(": 1,
23563
+ "file_put_contents(": 1,
23564
+ "fwrite(": 1,
23565
+ include: 1,
23566
+ require: 1,
23567
+ "echo ": 1,
23568
+ "print ": 1,
23569
+ "printf(": 1,
23570
+ "header(": 1,
23571
+ "wp_redirect(": 1,
23572
+ "file_get_contents(": 1,
23573
+ "wp_remote_get(": 1,
23574
+ "wp_remote_post(": 1
23575
+ })) {
23576
+ if (line.includes(tok)) {
23577
+ const cls = classifySink2(tok);
23578
+ if (cls) {
23579
+ sinkToken = tok;
23580
+ sinkClass = cls;
23581
+ break;
23582
+ }
23583
+ }
23584
+ }
23585
+ if (!sinkToken || !sinkClass)
23586
+ continue;
23587
+ const varsInLine = extractVars(line);
23588
+ const taintedVars = [];
23589
+ const allSanitizers = [];
23590
+ let source;
23591
+ let sourceLine;
23592
+ for (const v of varsInLine) {
23593
+ const st = vars.get(v);
23594
+ if (st?.tainted) {
23595
+ taintedVars.push(st);
23596
+ allSanitizers.push(...st.sanitizers);
23597
+ if (!source) {
23598
+ source = st.source;
23599
+ sourceLine = st.sourceLine;
23600
+ }
23601
+ }
23602
+ }
23603
+ const inlineSource = isSourceExpr(line);
23604
+ if (inlineSource && taintedVars.length === 0) {
23605
+ taintedVars.push({ tainted: true, sanitizers: [], source: inlineSource, sourceLine: li + 1 });
23606
+ source = inlineSource;
23607
+ sourceLine = li + 1;
23608
+ }
23609
+ if (taintedVars.length === 0)
23610
+ continue;
23611
+ const inlineSanitizers = findSanitizers(line);
23612
+ const sanitizers = [...allSanitizers, ...inlineSanitizers].filter((s, i, arr) => arr.indexOf(s) === i);
23613
+ const authGated = AUTH_GATES.some((g) => line.includes(g) || (lines[li - 1] ?? "").includes(g));
23614
+ const sanitized = isSanitized(sanitizers, sinkClass.id);
23615
+ if (sanitized || authGated) {
23616
+ result.suppressed += 1;
23617
+ continue;
23618
+ }
23619
+ result.findings.push({
23620
+ sink: sinkToken,
23621
+ sink_line: li + 1,
23622
+ category: sinkClass.category,
23623
+ cwe: sinkClass.cwe,
23624
+ variables: taintedVars.map((t) => t.source ?? "var").filter((v, i, a) => a.indexOf(v) === i),
23625
+ source,
23626
+ source_line: sourceLine,
23627
+ sanitized,
23628
+ sanitizers: sanitizers.map((s) => s.id),
23629
+ auth_gated: authGated,
23630
+ interprocedural: false
23631
+ });
23632
+ }
23633
+ }
23634
+ return result;
23635
+ }
23636
+ function taintTree(root, maxFiles = 2000) {
23637
+ const files = iterSourceFiles(root, maxFiles);
23638
+ return files.map((f) => analyzeTaint(f));
23639
+ }
23640
+
23321
23641
  // src/server.ts
23322
23642
  function createServer() {
23323
23643
  const server = new McpServer({
@@ -23866,6 +24186,35 @@ function createServer() {
23866
24186
  }]
23867
24187
  };
23868
24188
  });
24189
+ server.registerTool("taint_scan", {
24190
+ title: "Inter-procedural taint analysis",
24191
+ description: "EAGLE-EYE: real taint tracking — variable assignment, cross-function flow, and sanitizer awareness. Finds tainted data reaching sinks even across function boundaries.",
24192
+ inputSchema: { path: string2().describe("Source file path") }
24193
+ }, async ({ path }) => {
24194
+ const r = analyzeTaint(path);
24195
+ return { content: [{ type: "text", text: JSON.stringify(r) }] };
24196
+ });
24197
+ server.registerTool("taint_tree", {
24198
+ title: "Whole-tree taint scan",
24199
+ description: "EAGLE-EYE: run inter-procedural taint analysis across an entire source tree.",
24200
+ inputSchema: {
24201
+ path: string2().describe("Source directory path"),
24202
+ max_files: number2().int().optional().describe("Max files (default 2000)")
24203
+ }
24204
+ }, async ({ path, max_files }) => {
24205
+ const results = taintTree(path, max_files ?? 2000);
24206
+ return {
24207
+ content: [{
24208
+ type: "text",
24209
+ text: JSON.stringify({
24210
+ files_analyzed: results.length,
24211
+ total_findings: results.reduce((n, r) => n + r.findings.length, 0),
24212
+ total_suppressed: results.reduce((n, r) => n + r.suppressed, 0),
24213
+ findings: results.filter((r) => r.findings.length > 0).map((r) => ({ file: r.file, findings: r.findings }))
24214
+ })
24215
+ }]
24216
+ };
24217
+ });
23869
24218
  return server;
23870
24219
  }
23871
24220
  async function serve() {
@@ -23875,7 +24224,7 @@ async function serve() {
23875
24224
  }
23876
24225
 
23877
24226
  // src/cli.ts
23878
- import { readFileSync as readFileSync8, existsSync as existsSync6, writeFileSync, mkdirSync as mkdirSync2 } from "node:fs";
24227
+ import { readFileSync as readFileSync9, existsSync as existsSync6, writeFileSync, mkdirSync as mkdirSync2 } from "node:fs";
23879
24228
  import { join as join7, dirname } from "node:path";
23880
24229
  import { homedir as homedir3 } from "node:os";
23881
24230
  import { fileURLToPath as fileURLToPath5 } from "node:url";
@@ -23957,7 +24306,7 @@ function resolveCommand() {
23957
24306
  }
23958
24307
  function readJson(p) {
23959
24308
  try {
23960
- return JSON.parse(readFileSync8(p, "utf8"));
24309
+ return JSON.parse(readFileSync9(p, "utf8"));
23961
24310
  } catch {
23962
24311
  return null;
23963
24312
  }
@@ -24000,7 +24349,7 @@ function copyOpenCodeAgents() {
24000
24349
  const src = join7(srcDir, f);
24001
24350
  const dst = join7(dstDir, f);
24002
24351
  if (existsSync6(src)) {
24003
- writeFileSync(dst, readFileSync8(src, "utf8"));
24352
+ writeFileSync(dst, readFileSync9(src, "utf8"));
24004
24353
  }
24005
24354
  }
24006
24355
  }
@@ -24009,7 +24358,7 @@ function codexWrite(p) {
24009
24358
  const dir = dirname(p);
24010
24359
  if (!existsSync6(dir))
24011
24360
  mkdirSync2(dir, { recursive: true });
24012
- let existing = existsSync6(p) ? readFileSync8(p, "utf8") : "";
24361
+ let existing = existsSync6(p) ? readFileSync9(p, "utf8") : "";
24013
24362
  if (!existing.trimEnd().endsWith(`
24014
24363
  `))
24015
24364
  existing += `
@@ -24028,7 +24377,7 @@ function hermesWrite(p) {
24028
24377
  const dir = dirname(p);
24029
24378
  if (!existsSync6(dir))
24030
24379
  mkdirSync2(dir, { recursive: true });
24031
- let existing = existsSync6(p) ? readFileSync8(p, "utf8") : "";
24380
+ let existing = existsSync6(p) ? readFileSync9(p, "utf8") : "";
24032
24381
  existing = existing.replace(/^ blitzstrike:\n(?: .*\n?)*/m, "");
24033
24382
  if (!existing.trimEnd().endsWith(`
24034
24383
  `))
@@ -24166,7 +24515,7 @@ Registered with ${ok}/${installed.length} agent(s).`);
24166
24515
  }
24167
24516
 
24168
24517
  // src/index.ts
24169
- import { readFileSync as readFileSync10, existsSync as existsSync8 } from "node:fs";
24518
+ import { readFileSync as readFileSync11, existsSync as existsSync8 } from "node:fs";
24170
24519
  import { join as join9 } from "node:path";
24171
24520
  import { fileURLToPath as fileURLToPath7 } from "node:url";
24172
24521
  var ROOT6 = join9(fileURLToPath7(new URL(".", import.meta.url)), "..");
@@ -24174,7 +24523,7 @@ function readVersion() {
24174
24523
  try {
24175
24524
  const p = join9(ROOT6, "package.json");
24176
24525
  if (existsSync8(p))
24177
- return JSON.parse(readFileSync10(p, "utf8")).version ?? "1.0.0";
24526
+ return JSON.parse(readFileSync11(p, "utf8")).version ?? "1.0.0";
24178
24527
  } catch {}
24179
24528
  return "1.0.0";
24180
24529
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blitzstrike",
3
- "version": "1.0.14",
3
+ "version": "1.0.15",
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": {