blitzstrike 1.0.13 → 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.
- package/dist/index.js +390 -23
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { createRequire } from "node:module";
|
|
3
2
|
var __create = Object.create;
|
|
4
3
|
var __getProtoOf = Object.getPrototypeOf;
|
|
5
4
|
var __defProp = Object.defineProperty;
|
|
@@ -44,7 +43,6 @@ var __esm = (fn, res, err) => () => {
|
|
|
44
43
|
throw err[0];
|
|
45
44
|
return res;
|
|
46
45
|
};
|
|
47
|
-
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
48
46
|
|
|
49
47
|
// node_modules/ajv/dist/compile/codegen/code.js
|
|
50
48
|
var require_code = __commonJS(function(exports) {
|
|
@@ -7156,7 +7154,12 @@ var init_scanner = __esm(() => {
|
|
|
7156
7154
|
"call_user_func(": "Dynamic dispatch (attacker-controlled callback)",
|
|
7157
7155
|
"call_user_func_array(": "Dynamic dispatch",
|
|
7158
7156
|
"create_function(": "RCE (deprecated eval wrapper)",
|
|
7159
|
-
"preg_replace(": "RCE (if /e modifier or code in pattern)"
|
|
7157
|
+
"preg_replace(": "RCE (if /e modifier or code in pattern)",
|
|
7158
|
+
"echo ": "XSS (reflected output)",
|
|
7159
|
+
"print ": "XSS (reflected output)",
|
|
7160
|
+
"printf(": "XSS (reflected output)",
|
|
7161
|
+
"header(": "Open redirect / header injection",
|
|
7162
|
+
"wp_redirect(": "Open redirect"
|
|
7160
7163
|
};
|
|
7161
7164
|
AUTH_GATES = [
|
|
7162
7165
|
"check_ajax_referer",
|
|
@@ -7189,7 +7192,7 @@ var init_scanner = __esm(() => {
|
|
|
7189
7192
|
});
|
|
7190
7193
|
|
|
7191
7194
|
// src/sync.ts
|
|
7192
|
-
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync2, readFileSync as
|
|
7195
|
+
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync2, readFileSync as readFileSync10, readdirSync as readdirSync4, statSync as statSync3, rmSync } from "node:fs";
|
|
7193
7196
|
import { join as join8 } from "node:path";
|
|
7194
7197
|
import { homedir as homedir4 } from "node:os";
|
|
7195
7198
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
@@ -7285,7 +7288,7 @@ function manualCopy(src, dst) {
|
|
|
7285
7288
|
if (e.isDirectory())
|
|
7286
7289
|
manualCopy(s, d);
|
|
7287
7290
|
else
|
|
7288
|
-
writeFileSync2(d,
|
|
7291
|
+
writeFileSync2(d, readFileSync10(s));
|
|
7289
7292
|
}
|
|
7290
7293
|
}
|
|
7291
7294
|
var ROOT5, DATA_ROOT2, GITHUB_REPO = "https://github.com/shinthink/blitzstrike.git";
|
|
@@ -23157,6 +23160,7 @@ async function activeScan(target, scope = "", mode = "bug-bounty", allowed = und
|
|
|
23157
23160
|
|
|
23158
23161
|
// src/dataflow.ts
|
|
23159
23162
|
init_scanner();
|
|
23163
|
+
import { readFileSync as readFileSync7 } from "node:fs";
|
|
23160
23164
|
var SOURCES = {
|
|
23161
23165
|
$_GET: { id: "http_get", label: "HTTP GET parameter", attacker_controlled: true },
|
|
23162
23166
|
$_POST: { id: "http_post", label: "HTTP POST body", attacker_controlled: true },
|
|
@@ -23238,23 +23242,37 @@ function traceDataFlow(path) {
|
|
|
23238
23242
|
return result;
|
|
23239
23243
|
let text;
|
|
23240
23244
|
try {
|
|
23241
|
-
|
|
23242
|
-
text = readFileSync(path, "utf8");
|
|
23245
|
+
text = readFileSync7(path, "utf8");
|
|
23243
23246
|
} catch {
|
|
23244
23247
|
return result;
|
|
23245
23248
|
}
|
|
23246
23249
|
const lines = text.split(`
|
|
23247
23250
|
`);
|
|
23248
|
-
|
|
23251
|
+
const seen = new Set;
|
|
23252
|
+
const sinks = scan.sinks.filter((s) => {
|
|
23253
|
+
const cls = classifySink2(s.sink);
|
|
23254
|
+
if (!cls)
|
|
23255
|
+
return false;
|
|
23256
|
+
const key = `${cls.id}|${s.line}`;
|
|
23257
|
+
if (seen.has(key))
|
|
23258
|
+
return false;
|
|
23259
|
+
seen.add(key);
|
|
23260
|
+
return true;
|
|
23261
|
+
});
|
|
23262
|
+
for (const s of sinks) {
|
|
23249
23263
|
const sinkClass = classifySink2(s.sink);
|
|
23250
|
-
|
|
23251
|
-
|
|
23252
|
-
|
|
23253
|
-
const
|
|
23264
|
+
const wideStart = Math.max(0, s.line - 15);
|
|
23265
|
+
const wideWindow = lines.slice(wideStart, s.line).join(`
|
|
23266
|
+
`);
|
|
23267
|
+
const sanitizerWindow = lines.slice(Math.max(0, s.line - 3), s.line + 1).join(`
|
|
23254
23268
|
`);
|
|
23255
|
-
const
|
|
23256
|
-
const
|
|
23257
|
-
const
|
|
23269
|
+
const sinkLine = lines[s.line - 1] ?? "";
|
|
23270
|
+
const prevLine = lines[s.line - 2] ?? "";
|
|
23271
|
+
const authContext = sinkLine + `
|
|
23272
|
+
` + prevLine;
|
|
23273
|
+
const source = classifySource(wideWindow);
|
|
23274
|
+
const sanitizers = findSanitizers(sanitizerWindow);
|
|
23275
|
+
const authGates = AUTH_GATES.filter((g) => authContext.includes(g));
|
|
23258
23276
|
const edge = {
|
|
23259
23277
|
source,
|
|
23260
23278
|
sanitizers,
|
|
@@ -23267,7 +23285,7 @@ function traceDataFlow(path) {
|
|
|
23267
23285
|
result.authorized.push(edge);
|
|
23268
23286
|
continue;
|
|
23269
23287
|
}
|
|
23270
|
-
if (isSanitized(sanitizers, sinkClass.
|
|
23288
|
+
if (isSanitized(sanitizers, sinkClass.id)) {
|
|
23271
23289
|
result.sanitized.push(edge);
|
|
23272
23290
|
continue;
|
|
23273
23291
|
}
|
|
@@ -23300,6 +23318,326 @@ function groupVariants(results) {
|
|
|
23300
23318
|
return [...groups.values()].sort((a, b) => b.occurrences - a.occurrences);
|
|
23301
23319
|
}
|
|
23302
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
|
+
|
|
23303
23641
|
// src/server.ts
|
|
23304
23642
|
function createServer() {
|
|
23305
23643
|
const server = new McpServer({
|
|
@@ -23848,6 +24186,35 @@ function createServer() {
|
|
|
23848
24186
|
}]
|
|
23849
24187
|
};
|
|
23850
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
|
+
});
|
|
23851
24218
|
return server;
|
|
23852
24219
|
}
|
|
23853
24220
|
async function serve() {
|
|
@@ -23857,7 +24224,7 @@ async function serve() {
|
|
|
23857
24224
|
}
|
|
23858
24225
|
|
|
23859
24226
|
// src/cli.ts
|
|
23860
|
-
import { readFileSync as
|
|
24227
|
+
import { readFileSync as readFileSync9, existsSync as existsSync6, writeFileSync, mkdirSync as mkdirSync2 } from "node:fs";
|
|
23861
24228
|
import { join as join7, dirname } from "node:path";
|
|
23862
24229
|
import { homedir as homedir3 } from "node:os";
|
|
23863
24230
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
@@ -23939,7 +24306,7 @@ function resolveCommand() {
|
|
|
23939
24306
|
}
|
|
23940
24307
|
function readJson(p) {
|
|
23941
24308
|
try {
|
|
23942
|
-
return JSON.parse(
|
|
24309
|
+
return JSON.parse(readFileSync9(p, "utf8"));
|
|
23943
24310
|
} catch {
|
|
23944
24311
|
return null;
|
|
23945
24312
|
}
|
|
@@ -23982,7 +24349,7 @@ function copyOpenCodeAgents() {
|
|
|
23982
24349
|
const src = join7(srcDir, f);
|
|
23983
24350
|
const dst = join7(dstDir, f);
|
|
23984
24351
|
if (existsSync6(src)) {
|
|
23985
|
-
writeFileSync(dst,
|
|
24352
|
+
writeFileSync(dst, readFileSync9(src, "utf8"));
|
|
23986
24353
|
}
|
|
23987
24354
|
}
|
|
23988
24355
|
}
|
|
@@ -23991,7 +24358,7 @@ function codexWrite(p) {
|
|
|
23991
24358
|
const dir = dirname(p);
|
|
23992
24359
|
if (!existsSync6(dir))
|
|
23993
24360
|
mkdirSync2(dir, { recursive: true });
|
|
23994
|
-
let existing = existsSync6(p) ?
|
|
24361
|
+
let existing = existsSync6(p) ? readFileSync9(p, "utf8") : "";
|
|
23995
24362
|
if (!existing.trimEnd().endsWith(`
|
|
23996
24363
|
`))
|
|
23997
24364
|
existing += `
|
|
@@ -24010,7 +24377,7 @@ function hermesWrite(p) {
|
|
|
24010
24377
|
const dir = dirname(p);
|
|
24011
24378
|
if (!existsSync6(dir))
|
|
24012
24379
|
mkdirSync2(dir, { recursive: true });
|
|
24013
|
-
let existing = existsSync6(p) ?
|
|
24380
|
+
let existing = existsSync6(p) ? readFileSync9(p, "utf8") : "";
|
|
24014
24381
|
existing = existing.replace(/^ blitzstrike:\n(?: .*\n?)*/m, "");
|
|
24015
24382
|
if (!existing.trimEnd().endsWith(`
|
|
24016
24383
|
`))
|
|
@@ -24148,7 +24515,7 @@ Registered with ${ok}/${installed.length} agent(s).`);
|
|
|
24148
24515
|
}
|
|
24149
24516
|
|
|
24150
24517
|
// src/index.ts
|
|
24151
|
-
import { readFileSync as
|
|
24518
|
+
import { readFileSync as readFileSync11, existsSync as existsSync8 } from "node:fs";
|
|
24152
24519
|
import { join as join9 } from "node:path";
|
|
24153
24520
|
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
24154
24521
|
var ROOT6 = join9(fileURLToPath7(new URL(".", import.meta.url)), "..");
|
|
@@ -24156,7 +24523,7 @@ function readVersion() {
|
|
|
24156
24523
|
try {
|
|
24157
24524
|
const p = join9(ROOT6, "package.json");
|
|
24158
24525
|
if (existsSync8(p))
|
|
24159
|
-
return JSON.parse(
|
|
24526
|
+
return JSON.parse(readFileSync11(p, "utf8")).version ?? "1.0.0";
|
|
24160
24527
|
} catch {}
|
|
24161
24528
|
return "1.0.0";
|
|
24162
24529
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blitzstrike",
|
|
3
|
-
"version": "1.0.
|
|
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": {
|