blitzstrike 1.5.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/dist/index.js +427 -144
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,35 @@ All notable changes to Blitz Strike are documented here.
|
|
|
5
5
|
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
7
7
|
|
|
8
|
+
## [1.7.0] — 2026-09-13
|
|
9
|
+
|
|
10
|
+
### Added — advanced deterministic checks (live + source)
|
|
11
|
+
|
|
12
|
+
- Live pipeline: `run_engagement` now also runs advanced deterministic checks —
|
|
13
|
+
SSTI (`{{7*7}}` → `49`), SSRF (cloud metadata reflected through a URL param),
|
|
14
|
+
and error-based SQLi (SQL error signature) — on top of the standard
|
|
15
|
+
reflected/header/redirect/traversal/CORS sweep.
|
|
16
|
+
- Source pipeline: `complex_scan` grows from 6 to 9 detectors — added SSRF
|
|
17
|
+
(user-controlled URL with no host allowlist), XXE (user XML parsed without
|
|
18
|
+
disabling entities), and SSTI (user input into a template render), each with
|
|
19
|
+
a defense-suppression check (allowlist / libxml_disable_entity_loader /
|
|
20
|
+
basename).
|
|
21
|
+
|
|
22
|
+
## [1.6.0] — 2026-09-13
|
|
23
|
+
|
|
24
|
+
### Added — task checkpoint journal (per-workstream resume)
|
|
25
|
+
|
|
26
|
+
- Four new ORCHESTRATION tools for resilient delegation:
|
|
27
|
+
- `task_start` — register a workstream with an ordered plan (steps).
|
|
28
|
+
- `task_checkpoint` — record a checkpoint per step (in_progress / done /
|
|
29
|
+
failed / blocked) with an optional note + output hash.
|
|
30
|
+
- `task_status` — read the current step, progress (done/total), remaining
|
|
31
|
+
steps, and full checkpoint history.
|
|
32
|
+
- `task_resume` — compute the resume instruction: last completed checkpoint,
|
|
33
|
+
current step, remaining steps, and the exact re-dispatch prompt.
|
|
34
|
+
On-disk, so a failed workstream resumes from its last completed step instead
|
|
35
|
+
of restarting from scratch.
|
|
36
|
+
|
|
8
37
|
## [1.5.0] — 2026-09-13
|
|
9
38
|
|
|
10
39
|
### Added — run_autonomous (self-driving orchestrator with escalation)
|
package/dist/index.js
CHANGED
|
@@ -14898,10 +14898,10 @@ var require_src = __commonJS(function(exports, module) {
|
|
|
14898
14898
|
});
|
|
14899
14899
|
|
|
14900
14900
|
// src/sync.ts
|
|
14901
|
-
import { mkdirSync as
|
|
14902
|
-
import { join as
|
|
14901
|
+
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync6, readFileSync as readFileSync18, readdirSync as readdirSync6, statSync as statSync4, rmSync as rmSync4 } from "node:fs";
|
|
14902
|
+
import { join as join15 } from "node:path";
|
|
14903
14903
|
import { homedir as homedir4 } from "node:os";
|
|
14904
|
-
import { fileURLToPath as
|
|
14904
|
+
import { fileURLToPath as fileURLToPath9 } from "node:url";
|
|
14905
14905
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
14906
14906
|
function dirExists(p) {
|
|
14907
14907
|
try {
|
|
@@ -14916,7 +14916,7 @@ function countFiles(dir) {
|
|
|
14916
14916
|
const walk = (d) => {
|
|
14917
14917
|
for (const e of readdirSync6(d, { withFileTypes: true })) {
|
|
14918
14918
|
if (e.isDirectory())
|
|
14919
|
-
walk(
|
|
14919
|
+
walk(join15(d, e.name));
|
|
14920
14920
|
else
|
|
14921
14921
|
n++;
|
|
14922
14922
|
}
|
|
@@ -14929,9 +14929,9 @@ function countFiles(dir) {
|
|
|
14929
14929
|
}
|
|
14930
14930
|
function syncData() {
|
|
14931
14931
|
if (!dirExists(DATA_ROOT2))
|
|
14932
|
-
|
|
14933
|
-
const payloadsPath =
|
|
14934
|
-
const templatesPath =
|
|
14932
|
+
mkdirSync4(DATA_ROOT2, { recursive: true });
|
|
14933
|
+
const payloadsPath = join15(DATA_ROOT2, "payloads");
|
|
14934
|
+
const templatesPath = join15(DATA_ROOT2, "templates");
|
|
14935
14935
|
const hasPayloads = dirExists(payloadsPath) && countFiles(payloadsPath) > 0;
|
|
14936
14936
|
const hasTemplates = dirExists(templatesPath) && countFiles(templatesPath) > 0;
|
|
14937
14937
|
if (hasPayloads && hasTemplates) {
|
|
@@ -14941,8 +14941,8 @@ function syncData() {
|
|
|
14941
14941
|
data_root: DATA_ROOT2
|
|
14942
14942
|
};
|
|
14943
14943
|
}
|
|
14944
|
-
const localPayloads =
|
|
14945
|
-
const localTemplates =
|
|
14944
|
+
const localPayloads = join15(ROOT7, "payloads");
|
|
14945
|
+
const localTemplates = join15(ROOT7, "templates");
|
|
14946
14946
|
let payloadsSynced = false;
|
|
14947
14947
|
let templatesSynced = false;
|
|
14948
14948
|
if (!hasPayloads && dirExists(localPayloads)) {
|
|
@@ -14955,7 +14955,7 @@ function syncData() {
|
|
|
14955
14955
|
}
|
|
14956
14956
|
const gitOk = spawnSync2("git", ["--version"]).status === 0;
|
|
14957
14957
|
if (gitOk && (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0 || !dirExists(templatesPath) || countFiles(templatesPath) === 0)) {
|
|
14958
|
-
const tmp =
|
|
14958
|
+
const tmp = join15(DATA_ROOT2, ".tmp-clone");
|
|
14959
14959
|
if (dirExists(tmp))
|
|
14960
14960
|
rmSync4(tmp, { recursive: true, force: true });
|
|
14961
14961
|
const r = spawnSync2("git", ["clone", "--depth", "1", "--filter=blob:none", "--sparse", GITHUB_REPO, tmp], { timeout: 120000 });
|
|
@@ -14963,11 +14963,11 @@ function syncData() {
|
|
|
14963
14963
|
const sparse = spawnSync2("git", ["-C", tmp, "sparse-checkout", "set", "payloads", "templates"], { timeout: 120000 });
|
|
14964
14964
|
if (sparse.status === 0) {
|
|
14965
14965
|
if (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0) {
|
|
14966
|
-
copyDir(
|
|
14966
|
+
copyDir(join15(tmp, "payloads"), payloadsPath);
|
|
14967
14967
|
payloadsSynced = true;
|
|
14968
14968
|
}
|
|
14969
14969
|
if (!dirExists(templatesPath) || countFiles(templatesPath) === 0) {
|
|
14970
|
-
copyDir(
|
|
14970
|
+
copyDir(join15(tmp, "templates"), templatesPath);
|
|
14971
14971
|
templatesSynced = true;
|
|
14972
14972
|
}
|
|
14973
14973
|
}
|
|
@@ -14987,20 +14987,20 @@ function copyDir(src, dst) {
|
|
|
14987
14987
|
}
|
|
14988
14988
|
}
|
|
14989
14989
|
function manualCopy(src, dst) {
|
|
14990
|
-
|
|
14990
|
+
mkdirSync4(dst, { recursive: true });
|
|
14991
14991
|
for (const e of readdirSync6(src, { withFileTypes: true })) {
|
|
14992
|
-
const s =
|
|
14993
|
-
const d =
|
|
14992
|
+
const s = join15(src, e.name);
|
|
14993
|
+
const d = join15(dst, e.name);
|
|
14994
14994
|
if (e.isDirectory())
|
|
14995
14995
|
manualCopy(s, d);
|
|
14996
14996
|
else
|
|
14997
|
-
|
|
14997
|
+
writeFileSync6(d, readFileSync18(s));
|
|
14998
14998
|
}
|
|
14999
14999
|
}
|
|
15000
15000
|
var ROOT7, DATA_ROOT2, GITHUB_REPO = "https://github.com/shinthink/blitzstrike.git";
|
|
15001
15001
|
var init_sync = __esm(() => {
|
|
15002
|
-
ROOT7 =
|
|
15003
|
-
DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ??
|
|
15002
|
+
ROOT7 = join15(fileURLToPath9(new URL(".", import.meta.url)), "..");
|
|
15003
|
+
DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ?? join15(homedir4(), ".blitzstrike", "data");
|
|
15004
15004
|
});
|
|
15005
15005
|
|
|
15006
15006
|
// src/update.ts
|
|
@@ -29679,7 +29679,7 @@ class StdioServerTransport {
|
|
|
29679
29679
|
|
|
29680
29680
|
// src/server.ts
|
|
29681
29681
|
init_scanner();
|
|
29682
|
-
import { readFileSync as
|
|
29682
|
+
import { readFileSync as readFileSync19 } from "node:fs";
|
|
29683
29683
|
|
|
29684
29684
|
// src/recon.ts
|
|
29685
29685
|
var TIMEOUT_MS = 25000;
|
|
@@ -32207,6 +32207,74 @@ async function runDeterministicChecks(url) {
|
|
|
32207
32207
|
}
|
|
32208
32208
|
return findings;
|
|
32209
32209
|
}
|
|
32210
|
+
var SSTI_PROBE = "{{7*7}}";
|
|
32211
|
+
var SSTI_PARAMS = ["name", "template", "q", "search", "query", "message", "title", "content", "input", "data", "page", "id"];
|
|
32212
|
+
var SSRF_TARGET = "http://169.254.169.254/latest/meta-data/";
|
|
32213
|
+
var SSRF_MARKERS = ["ami-id", "instance-id", "security-credentials", "instance-type"];
|
|
32214
|
+
var SSRF_PARAMS = ["url", "uri", "path", "host", "domain", "webhook", "callback", "redirect", "proxy", "src", "dest", "link", "target", "endpoint", "image", "img"];
|
|
32215
|
+
var SQLI_PROBE = "'";
|
|
32216
|
+
var SQLI_MARKERS = ["sql syntax", "mysql", "mysqli", "postgresql", "sqlite", "ora-", "odbc", "syntax error", "unclosed quotation", "you have an error in your sql"];
|
|
32217
|
+
var SQLI_PARAMS = ["id", "user", "username", "email", "product", "item", "category", "page", "search", "q", "query", "order", "sort"];
|
|
32218
|
+
async function runAdvancedChecks(url) {
|
|
32219
|
+
const findings = [];
|
|
32220
|
+
const base = url.replace(/\/+$/, "");
|
|
32221
|
+
const sep = base.includes("?") ? "&" : "?";
|
|
32222
|
+
for (const p of SSTI_PARAMS) {
|
|
32223
|
+
const r = await httpGet2(`${base}${sep}${p}=${encodeURIComponent(SSTI_PROBE)}`);
|
|
32224
|
+
if (r && r.body.includes("49") && !r.body.includes(SSTI_PROBE)) {
|
|
32225
|
+
findings.push(makeFinding({
|
|
32226
|
+
title: `Server-side template injection via parameter '${p}'`,
|
|
32227
|
+
target: { type: "web", host: url, endpoint: `?${p}=` },
|
|
32228
|
+
severity: "critical",
|
|
32229
|
+
cwe: "CWE-1336",
|
|
32230
|
+
cweName: "Improper Neutralization of Special Elements Used in a Template Engine",
|
|
32231
|
+
source: { type: "request_parameter", name: p },
|
|
32232
|
+
sink: { type: "template_engine", symbol: p },
|
|
32233
|
+
chainId: "ssti",
|
|
32234
|
+
chainName: "Server-side template injection",
|
|
32235
|
+
status: "hypothesis"
|
|
32236
|
+
}));
|
|
32237
|
+
}
|
|
32238
|
+
}
|
|
32239
|
+
for (const p of SSRF_PARAMS) {
|
|
32240
|
+
const r = await httpGet2(`${base}${sep}${p}=${encodeURIComponent(SSRF_TARGET)}`);
|
|
32241
|
+
if (r && SSRF_MARKERS.some((m) => r.body.includes(m))) {
|
|
32242
|
+
findings.push(makeFinding({
|
|
32243
|
+
title: `Server-side request forgery via parameter '${p}'`,
|
|
32244
|
+
target: { type: "web", host: url, endpoint: `?${p}=` },
|
|
32245
|
+
severity: "critical",
|
|
32246
|
+
cwe: "CWE-918",
|
|
32247
|
+
cweName: "Server-Side Request Forgery (SSRF)",
|
|
32248
|
+
source: { type: "request_parameter", name: p },
|
|
32249
|
+
sink: { type: "ssrf", symbol: p },
|
|
32250
|
+
chainId: "ssrf",
|
|
32251
|
+
chainName: "Server-side request forgery",
|
|
32252
|
+
status: "hypothesis"
|
|
32253
|
+
}));
|
|
32254
|
+
}
|
|
32255
|
+
}
|
|
32256
|
+
for (const p of SQLI_PARAMS) {
|
|
32257
|
+
const r = await httpGet2(`${base}${sep}${p}=${encodeURIComponent(SQLI_PROBE)}`);
|
|
32258
|
+
if (r) {
|
|
32259
|
+
const body = r.body.toLowerCase();
|
|
32260
|
+
if (SQLI_MARKERS.some((m) => body.includes(m))) {
|
|
32261
|
+
findings.push(makeFinding({
|
|
32262
|
+
title: `SQL injection (error-based) via parameter '${p}'`,
|
|
32263
|
+
target: { type: "web", host: url, endpoint: `?${p}=` },
|
|
32264
|
+
severity: "critical",
|
|
32265
|
+
cwe: "CWE-89",
|
|
32266
|
+
cweName: "Improper Neutralization of Special Elements used in an SQL Command",
|
|
32267
|
+
source: { type: "request_parameter", name: p },
|
|
32268
|
+
sink: { type: "sql_query", symbol: p },
|
|
32269
|
+
chainId: "sql_injection",
|
|
32270
|
+
chainName: "SQL injection",
|
|
32271
|
+
status: "hypothesis"
|
|
32272
|
+
}));
|
|
32273
|
+
}
|
|
32274
|
+
}
|
|
32275
|
+
}
|
|
32276
|
+
return findings;
|
|
32277
|
+
}
|
|
32210
32278
|
async function runLiveEngagement(url, scope = "", mode = "bug-bounty") {
|
|
32211
32279
|
const report = { banner: engagementBanner(url, mode), target: url, mode, findings: [], scope: null, status: "" };
|
|
32212
32280
|
const sc = scopeCheck(url, scope, mode);
|
|
@@ -32245,6 +32313,7 @@ async function runLiveEngagement(url, scope = "", mode = "bug-bounty") {
|
|
|
32245
32313
|
findings.push(resolveFinding(finding, verdict));
|
|
32246
32314
|
}
|
|
32247
32315
|
findings.push(...await runDeterministicChecks(url));
|
|
32316
|
+
findings.push(...await runAdvancedChecks(url));
|
|
32248
32317
|
report.findings = findings.map((f) => ({
|
|
32249
32318
|
id: f.id,
|
|
32250
32319
|
status: f.status,
|
|
@@ -32409,19 +32478,116 @@ function compactState(phase, findings) {
|
|
|
32409
32478
|
};
|
|
32410
32479
|
}
|
|
32411
32480
|
|
|
32412
|
-
// src/
|
|
32413
|
-
import { readFileSync as readFileSync8,
|
|
32481
|
+
// src/tasks.ts
|
|
32482
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync8, writeFileSync as writeFileSync2 } from "node:fs";
|
|
32414
32483
|
import { join as join8 } from "node:path";
|
|
32415
32484
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
32485
|
+
function tasksDir() {
|
|
32486
|
+
return join8(fileURLToPath5(new URL(".", import.meta.url)), "..", ".blitzstrike", "tasks");
|
|
32487
|
+
}
|
|
32488
|
+
function taskPath(id) {
|
|
32489
|
+
return join8(tasksDir(), `${id}.json`);
|
|
32490
|
+
}
|
|
32491
|
+
function load(id) {
|
|
32492
|
+
try {
|
|
32493
|
+
return JSON.parse(readFileSync8(taskPath(id), "utf8"));
|
|
32494
|
+
} catch {
|
|
32495
|
+
return null;
|
|
32496
|
+
}
|
|
32497
|
+
}
|
|
32498
|
+
function save(t) {
|
|
32499
|
+
mkdirSync3(tasksDir(), { recursive: true });
|
|
32500
|
+
writeFileSync2(taskPath(t.task_id), JSON.stringify(t, null, 2));
|
|
32501
|
+
}
|
|
32502
|
+
function taskStart(id, objective, plan) {
|
|
32503
|
+
const now = Date.now();
|
|
32504
|
+
const t = {
|
|
32505
|
+
task_id: id,
|
|
32506
|
+
objective,
|
|
32507
|
+
plan,
|
|
32508
|
+
current_step: plan[0] ?? "complete",
|
|
32509
|
+
status: plan.length ? "in_progress" : "done",
|
|
32510
|
+
checkpoints: [],
|
|
32511
|
+
created_ts: now,
|
|
32512
|
+
updated_ts: now
|
|
32513
|
+
};
|
|
32514
|
+
save(t);
|
|
32515
|
+
return { ...t, progress: `0/${plan.length}`, next: t.current_step };
|
|
32516
|
+
}
|
|
32517
|
+
function taskCheckpoint(id, step, status, note, outputHash) {
|
|
32518
|
+
const t = load(id);
|
|
32519
|
+
if (!t)
|
|
32520
|
+
return { ok: false, error: `task '${id}' not found — call task_start first` };
|
|
32521
|
+
t.checkpoints.push({ step, status, output_hash: outputHash, note, ts: Date.now() });
|
|
32522
|
+
t.updated_ts = Date.now();
|
|
32523
|
+
const idx = t.plan.indexOf(step);
|
|
32524
|
+
if (status === "done") {
|
|
32525
|
+
t.current_step = idx >= 0 && idx + 1 < t.plan.length ? t.plan[idx + 1] : "complete";
|
|
32526
|
+
if (t.current_step === "complete")
|
|
32527
|
+
t.status = "done";
|
|
32528
|
+
} else if (status === "failed") {
|
|
32529
|
+
t.status = "failed";
|
|
32530
|
+
} else if (status === "blocked" && t.status !== "failed") {
|
|
32531
|
+
t.status = "blocked";
|
|
32532
|
+
}
|
|
32533
|
+
save(t);
|
|
32534
|
+
return {
|
|
32535
|
+
task_id: id,
|
|
32536
|
+
step,
|
|
32537
|
+
status,
|
|
32538
|
+
current_step: t.current_step,
|
|
32539
|
+
progress: `${t.checkpoints.filter((c) => c.status === "done").length}/${t.plan.length}`,
|
|
32540
|
+
next: t.current_step
|
|
32541
|
+
};
|
|
32542
|
+
}
|
|
32543
|
+
function taskStatus(id) {
|
|
32544
|
+
const t = load(id);
|
|
32545
|
+
if (!t)
|
|
32546
|
+
return { found: false, error: `task '${id}' not found` };
|
|
32547
|
+
const done = t.checkpoints.filter((c) => c.status === "done").length;
|
|
32548
|
+
return {
|
|
32549
|
+
found: true,
|
|
32550
|
+
task_id: t.task_id,
|
|
32551
|
+
objective: t.objective,
|
|
32552
|
+
status: t.status,
|
|
32553
|
+
current_step: t.current_step,
|
|
32554
|
+
progress: `${done}/${t.plan.length}`,
|
|
32555
|
+
remaining: t.plan.slice(t.plan.indexOf(t.current_step)),
|
|
32556
|
+
checkpoints: t.checkpoints
|
|
32557
|
+
};
|
|
32558
|
+
}
|
|
32559
|
+
function taskResume(id) {
|
|
32560
|
+
const t = load(id);
|
|
32561
|
+
if (!t)
|
|
32562
|
+
return { found: false, error: `task '${id}' not found` };
|
|
32563
|
+
const doneSteps = t.checkpoints.filter((c) => c.status === "done").map((c) => c.step);
|
|
32564
|
+
const remaining = t.plan.filter((s) => !doneSteps.includes(s));
|
|
32565
|
+
const last = t.checkpoints[t.checkpoints.length - 1];
|
|
32566
|
+
return {
|
|
32567
|
+
found: true,
|
|
32568
|
+
task_id: id,
|
|
32569
|
+
status: t.status,
|
|
32570
|
+
resume_from: t.current_step,
|
|
32571
|
+
already_done: doneSteps,
|
|
32572
|
+
remaining_steps: remaining,
|
|
32573
|
+
last_checkpoint: last ?? null,
|
|
32574
|
+
instruction: t.status === "done" ? "task complete — nothing to resume" : `resume '${id}' at step '${t.current_step}'. Completed: [${doneSteps.join(", ") || "none"}]. ` + `Remaining: [${remaining.join(", ") || "none"}]. Re-dispatch with the FAILED context (not fresh): ` + `'RESUME ${id}: you already completed [${doneSteps.join(", ")}]; continue from '${t.current_step}'.'`
|
|
32575
|
+
};
|
|
32576
|
+
}
|
|
32577
|
+
|
|
32578
|
+
// src/catalog.ts
|
|
32579
|
+
import { readFileSync as readFileSync9, readdirSync as readdirSync3 } from "node:fs";
|
|
32580
|
+
import { join as join9 } from "node:path";
|
|
32581
|
+
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
32416
32582
|
import { spawnSync } from "node:child_process";
|
|
32417
|
-
var ROOT3 =
|
|
32583
|
+
var ROOT3 = join9(fileURLToPath6(new URL(".", import.meta.url)), "..");
|
|
32418
32584
|
var _toolsCache = null;
|
|
32419
32585
|
var _skillsCache = null;
|
|
32420
32586
|
function loadTools() {
|
|
32421
32587
|
if (_toolsCache)
|
|
32422
32588
|
return _toolsCache;
|
|
32423
32589
|
try {
|
|
32424
|
-
const raw = JSON.parse(
|
|
32590
|
+
const raw = JSON.parse(readFileSync9(join9(ROOT3, "tools-catalog.json"), "utf8"));
|
|
32425
32591
|
_toolsCache = raw.tools ?? [];
|
|
32426
32592
|
} catch {
|
|
32427
32593
|
_toolsCache = [];
|
|
@@ -32431,14 +32597,14 @@ function loadTools() {
|
|
|
32431
32597
|
function loadSkills() {
|
|
32432
32598
|
if (_skillsCache)
|
|
32433
32599
|
return _skillsCache;
|
|
32434
|
-
const dir =
|
|
32600
|
+
const dir = join9(ROOT3, "skills");
|
|
32435
32601
|
const out = [];
|
|
32436
32602
|
try {
|
|
32437
32603
|
for (const f of readdirSync3(dir)) {
|
|
32438
32604
|
if (!f.endsWith(".md"))
|
|
32439
32605
|
continue;
|
|
32440
|
-
const p =
|
|
32441
|
-
const text =
|
|
32606
|
+
const p = join9(dir, f);
|
|
32607
|
+
const text = readFileSync9(p, "utf8");
|
|
32442
32608
|
const descMatch = text.match(/^description:\s*(.+)$/m);
|
|
32443
32609
|
out.push({
|
|
32444
32610
|
name: f.replace(/\.md$/, ""),
|
|
@@ -32511,7 +32677,7 @@ function readSkill(name) {
|
|
|
32511
32677
|
if (!s)
|
|
32512
32678
|
return { found: false, query: name };
|
|
32513
32679
|
try {
|
|
32514
|
-
const text =
|
|
32680
|
+
const text = readFileSync9(join9(ROOT3, "skills", s.file), "utf8");
|
|
32515
32681
|
return { found: true, name: s.name, content: text.slice(0, 12000) };
|
|
32516
32682
|
} catch {
|
|
32517
32683
|
return { found: false, query: name, error: "unreadable" };
|
|
@@ -32758,17 +32924,17 @@ function engagementStatus() {
|
|
|
32758
32924
|
|
|
32759
32925
|
// src/incremental.ts
|
|
32760
32926
|
import { execSync as execSync2 } from "node:child_process";
|
|
32761
|
-
import { writeFileSync as
|
|
32762
|
-
import { join as
|
|
32927
|
+
import { writeFileSync as writeFileSync3, mkdtempSync, rmSync } from "node:fs";
|
|
32928
|
+
import { join as join10 } from "node:path";
|
|
32763
32929
|
import { tmpdir } from "node:os";
|
|
32764
32930
|
|
|
32765
32931
|
// src/eagle2.ts
|
|
32766
32932
|
var import_php_parser2 = __toESM(require_src(), 1);
|
|
32767
|
-
import { readFileSync as
|
|
32933
|
+
import { readFileSync as readFileSync12 } from "node:fs";
|
|
32768
32934
|
|
|
32769
32935
|
// src/dataflow.ts
|
|
32770
32936
|
init_scanner();
|
|
32771
|
-
import { readFileSync as
|
|
32937
|
+
import { readFileSync as readFileSync10 } from "node:fs";
|
|
32772
32938
|
var SOURCES = {
|
|
32773
32939
|
$_GET: { id: "http_get", label: "HTTP GET parameter", attacker_controlled: true },
|
|
32774
32940
|
$_POST: { id: "http_post", label: "HTTP POST body", attacker_controlled: true },
|
|
@@ -32853,7 +33019,7 @@ function traceDataFlow(path) {
|
|
|
32853
33019
|
return result;
|
|
32854
33020
|
let text;
|
|
32855
33021
|
try {
|
|
32856
|
-
text =
|
|
33022
|
+
text = readFileSync10(path, "utf8");
|
|
32857
33023
|
} catch {
|
|
32858
33024
|
return result;
|
|
32859
33025
|
}
|
|
@@ -32934,7 +33100,7 @@ init_scanner();
|
|
|
32934
33100
|
|
|
32935
33101
|
// src/callgraph.ts
|
|
32936
33102
|
var import_php_parser = __toESM(require_src(), 1);
|
|
32937
|
-
import { readFileSync as
|
|
33103
|
+
import { readFileSync as readFileSync11 } from "node:fs";
|
|
32938
33104
|
var parser = new import_php_parser.Engine({
|
|
32939
33105
|
parser: { extractDoc: false, php7: true },
|
|
32940
33106
|
ast: { withPositions: true, withSource: false }
|
|
@@ -32995,7 +33161,7 @@ function buildCallGraph(path) {
|
|
|
32995
33161
|
const g = { nodes: new Map, edges: [] };
|
|
32996
33162
|
let code;
|
|
32997
33163
|
try {
|
|
32998
|
-
code =
|
|
33164
|
+
code = readFileSync11(path, "utf8");
|
|
32999
33165
|
} catch {
|
|
33000
33166
|
return g;
|
|
33001
33167
|
}
|
|
@@ -33656,7 +33822,7 @@ function resolveFuncId(name, summaries) {
|
|
|
33656
33822
|
function analyzeDataFlow2(path) {
|
|
33657
33823
|
let cacheCode = "";
|
|
33658
33824
|
try {
|
|
33659
|
-
cacheCode =
|
|
33825
|
+
cacheCode = readFileSync12(path, "utf8");
|
|
33660
33826
|
} catch {}
|
|
33661
33827
|
if (cacheCode) {
|
|
33662
33828
|
const cached = getCache("eagle2", cacheCode);
|
|
@@ -33678,7 +33844,7 @@ function analyzeDataFlow2(path) {
|
|
|
33678
33844
|
};
|
|
33679
33845
|
let code;
|
|
33680
33846
|
try {
|
|
33681
|
-
code = cacheCode ||
|
|
33847
|
+
code = cacheCode || readFileSync12(path, "utf8");
|
|
33682
33848
|
} catch {
|
|
33683
33849
|
return result;
|
|
33684
33850
|
}
|
|
@@ -52273,9 +52439,9 @@ function analyzeChangedFiles(repo, base = "HEAD~1", head = "HEAD") {
|
|
|
52273
52439
|
continue;
|
|
52274
52440
|
const lang = detectLanguage(file)?.language;
|
|
52275
52441
|
if (lang === "php") {
|
|
52276
|
-
const dir = mkdtempSync(
|
|
52277
|
-
const tmp =
|
|
52278
|
-
|
|
52442
|
+
const dir = mkdtempSync(join10(tmpdir(), "blitz-incr-"));
|
|
52443
|
+
const tmp = join10(dir, "changed.php");
|
|
52444
|
+
writeFileSync3(tmp, code);
|
|
52279
52445
|
try {
|
|
52280
52446
|
const r = analyzeDataFlow2(tmp);
|
|
52281
52447
|
for (const f of r.findings)
|
|
@@ -52295,7 +52461,7 @@ function analyzeChangedFiles(repo, base = "HEAD~1", head = "HEAD") {
|
|
|
52295
52461
|
// src/taint.ts
|
|
52296
52462
|
init_scanner();
|
|
52297
52463
|
var import_php_parser4 = __toESM(require_src(), 1);
|
|
52298
|
-
import { readFileSync as
|
|
52464
|
+
import { readFileSync as readFileSync13 } from "node:fs";
|
|
52299
52465
|
var parser4 = new import_php_parser4.Engine({
|
|
52300
52466
|
parser: { extractDoc: false, php7: true },
|
|
52301
52467
|
ast: { withPositions: true, withSource: false }
|
|
@@ -52593,7 +52759,7 @@ function analyzeTaint(path) {
|
|
|
52593
52759
|
const result = { file: path, findings: [], suppressed: 0 };
|
|
52594
52760
|
let code;
|
|
52595
52761
|
try {
|
|
52596
|
-
code =
|
|
52762
|
+
code = readFileSync13(path, "utf8");
|
|
52597
52763
|
} catch {
|
|
52598
52764
|
return result;
|
|
52599
52765
|
}
|
|
@@ -52687,16 +52853,16 @@ function taintTree(root, maxFiles = 2000) {
|
|
|
52687
52853
|
}
|
|
52688
52854
|
|
|
52689
52855
|
// src/differential.ts
|
|
52690
|
-
import { writeFileSync as
|
|
52691
|
-
import { join as
|
|
52856
|
+
import { writeFileSync as writeFileSync4, rmSync as rmSync2, mkdtempSync as mkdtempSync2, readFileSync as readFileSync14 } from "node:fs";
|
|
52857
|
+
import { join as join11 } from "node:path";
|
|
52692
52858
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
52693
52859
|
import { execSync as execSync3 } from "node:child_process";
|
|
52694
52860
|
init_scanner();
|
|
52695
52861
|
function runEngine(code, language, file) {
|
|
52696
52862
|
if (language === "php") {
|
|
52697
|
-
const dir = mkdtempSync2(
|
|
52698
|
-
const tmp =
|
|
52699
|
-
|
|
52863
|
+
const dir = mkdtempSync2(join11(tmpdir2(), "blitz-diff-"));
|
|
52864
|
+
const tmp = join11(dir, "old.php");
|
|
52865
|
+
writeFileSync4(tmp, code);
|
|
52700
52866
|
try {
|
|
52701
52867
|
const r = analyzeDataFlow2(tmp);
|
|
52702
52868
|
return r.findings.map((f) => ({ sink: f.sink, sink_line: f.sink_line, category: f.category, source: f.source, cwe: f.cwe }));
|
|
@@ -52849,8 +53015,8 @@ function languageOf(file) {
|
|
|
52849
53015
|
return adapter?.language ?? "php";
|
|
52850
53016
|
}
|
|
52851
53017
|
function analyzeDifferentialFiles(oldPath, newPath, language) {
|
|
52852
|
-
const oldCode =
|
|
52853
|
-
const newCode =
|
|
53018
|
+
const oldCode = readFileSync14(oldPath, "utf8");
|
|
53019
|
+
const newCode = readFileSync14(newPath, "utf8");
|
|
52854
53020
|
const lang = language ?? languageOf(newPath);
|
|
52855
53021
|
return analyzeDifferential(oldCode, newCode, lang, newPath);
|
|
52856
53022
|
}
|
|
@@ -52880,39 +53046,39 @@ function analyzeGitDiff(repo, base = "HEAD~1", head = "HEAD") {
|
|
|
52880
53046
|
}
|
|
52881
53047
|
|
|
52882
53048
|
// src/frameworks.ts
|
|
52883
|
-
import { readFileSync as
|
|
52884
|
-
import { join as
|
|
52885
|
-
import { fileURLToPath as
|
|
52886
|
-
var ROOT4 =
|
|
52887
|
-
var INTEL2 =
|
|
53049
|
+
import { readFileSync as readFileSync15, existsSync as existsSync6 } from "node:fs";
|
|
53050
|
+
import { join as join12 } from "node:path";
|
|
53051
|
+
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
53052
|
+
var ROOT4 = join12(fileURLToPath7(new URL(".", import.meta.url)), "..");
|
|
53053
|
+
var INTEL2 = join12(ROOT4, "intelligence");
|
|
52888
53054
|
var cache = null;
|
|
52889
|
-
function
|
|
53055
|
+
function load2() {
|
|
52890
53056
|
if (cache)
|
|
52891
53057
|
return cache;
|
|
52892
|
-
const p =
|
|
53058
|
+
const p = join12(INTEL2, "frameworks.json");
|
|
52893
53059
|
if (!existsSync6(p))
|
|
52894
53060
|
return null;
|
|
52895
53061
|
try {
|
|
52896
|
-
cache = JSON.parse(
|
|
53062
|
+
cache = JSON.parse(readFileSync15(p, "utf8"));
|
|
52897
53063
|
return cache;
|
|
52898
53064
|
} catch {
|
|
52899
53065
|
return null;
|
|
52900
53066
|
}
|
|
52901
53067
|
}
|
|
52902
53068
|
function listFrameworks() {
|
|
52903
|
-
const data =
|
|
53069
|
+
const data = load2();
|
|
52904
53070
|
if (!data)
|
|
52905
53071
|
return [];
|
|
52906
53072
|
return data.frameworks.map((f) => ({ id: f.id, name: f.name, language: f.language }));
|
|
52907
53073
|
}
|
|
52908
53074
|
function frameworkProfile(id) {
|
|
52909
|
-
const data =
|
|
53075
|
+
const data = load2();
|
|
52910
53076
|
if (!data)
|
|
52911
53077
|
return null;
|
|
52912
53078
|
return data.frameworks.find((f) => f.id === id) ?? null;
|
|
52913
53079
|
}
|
|
52914
53080
|
function detectFramework(code, language) {
|
|
52915
|
-
const data =
|
|
53081
|
+
const data = load2();
|
|
52916
53082
|
const hay = code.toLowerCase();
|
|
52917
53083
|
const candidates = [];
|
|
52918
53084
|
if (data) {
|
|
@@ -53589,6 +53755,9 @@ function detectComplexBugs(code, file) {
|
|
|
53589
53755
|
detectPrototypePollution(code, file, out);
|
|
53590
53756
|
detectCrlfInjection(code, file, out);
|
|
53591
53757
|
detectPathConfusion(code, file, out);
|
|
53758
|
+
detectSsrF(code, file, out);
|
|
53759
|
+
detectXxe(code, file, out);
|
|
53760
|
+
detectSsti(code, file, out);
|
|
53592
53761
|
const seen = new Set;
|
|
53593
53762
|
return out.filter((f) => {
|
|
53594
53763
|
const key = `${f.type}:${f.line}`;
|
|
@@ -53667,6 +53836,78 @@ function detectPathConfusion(code, file, out) {
|
|
|
53667
53836
|
});
|
|
53668
53837
|
}
|
|
53669
53838
|
}
|
|
53839
|
+
function detectSsrF(code, file, out) {
|
|
53840
|
+
const ls = lines2(code);
|
|
53841
|
+
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;
|
|
53842
|
+
let m;
|
|
53843
|
+
while ((m = re.exec(code)) !== null) {
|
|
53844
|
+
const ln = lineNo2(code, m.index);
|
|
53845
|
+
const l = ls[ln - 1] ?? "";
|
|
53846
|
+
const scope = ls.slice(Math.max(0, ln - 3), ln).join(`
|
|
53847
|
+
`);
|
|
53848
|
+
if (!SOURCE_TOKENS2.test(l) && !SOURCE_TOKENS2.test(scope))
|
|
53849
|
+
continue;
|
|
53850
|
+
if (/(allowlist|whitelist|in_array|preg_match|parse_url|startsWith\(["']https?|endsWith\(["']|basename|realpath|normalize)/i.test(scope + `
|
|
53851
|
+
` + l))
|
|
53852
|
+
continue;
|
|
53853
|
+
out.push({
|
|
53854
|
+
file,
|
|
53855
|
+
line: ln,
|
|
53856
|
+
type: "ssrf",
|
|
53857
|
+
category: "SSRF (user-controlled URL)",
|
|
53858
|
+
severity: "high",
|
|
53859
|
+
evidence: l.trim(),
|
|
53860
|
+
detail: "User input reaches a URL-accepting sink with no host allowlist — SSRF (cloud metadata, internal port scan, IAM credential theft)."
|
|
53861
|
+
});
|
|
53862
|
+
}
|
|
53863
|
+
}
|
|
53864
|
+
function detectXxe(code, file, out) {
|
|
53865
|
+
const ls = lines2(code);
|
|
53866
|
+
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;
|
|
53867
|
+
let m;
|
|
53868
|
+
while ((m = re.exec(code)) !== null) {
|
|
53869
|
+
const ln = lineNo2(code, m.index);
|
|
53870
|
+
const l = ls[ln - 1] ?? "";
|
|
53871
|
+
const scope = ls.slice(Math.max(0, ln - 4), ln).join(`
|
|
53872
|
+
`);
|
|
53873
|
+
if (!SOURCE_TOKENS2.test(l) && !SOURCE_TOKENS2.test(scope))
|
|
53874
|
+
continue;
|
|
53875
|
+
if (/libxml_disable_entity_loader\s*\(\s*true|LIBXML_NONET/.test(scope + `
|
|
53876
|
+
` + l))
|
|
53877
|
+
continue;
|
|
53878
|
+
out.push({
|
|
53879
|
+
file,
|
|
53880
|
+
line: ln,
|
|
53881
|
+
type: "xxe",
|
|
53882
|
+
category: "XXE (unprotected XML parse)",
|
|
53883
|
+
severity: "high",
|
|
53884
|
+
evidence: l.trim(),
|
|
53885
|
+
detail: "User-controlled XML parsed without disabling external entities — XXE (file read, SSRF, billion-laughs DoS)."
|
|
53886
|
+
});
|
|
53887
|
+
}
|
|
53888
|
+
}
|
|
53889
|
+
function detectSsti(code, file, out) {
|
|
53890
|
+
const ls = lines2(code);
|
|
53891
|
+
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;
|
|
53892
|
+
let m;
|
|
53893
|
+
while ((m = re.exec(code)) !== null) {
|
|
53894
|
+
const ln = lineNo2(code, m.index);
|
|
53895
|
+
const l = ls[ln - 1] ?? "";
|
|
53896
|
+
const scope = ls.slice(Math.max(0, ln - 3), ln).join(`
|
|
53897
|
+
`);
|
|
53898
|
+
if (!SOURCE_TOKENS2.test(l) && !SOURCE_TOKENS2.test(scope))
|
|
53899
|
+
continue;
|
|
53900
|
+
out.push({
|
|
53901
|
+
file,
|
|
53902
|
+
line: ln,
|
|
53903
|
+
type: "ssti",
|
|
53904
|
+
category: "SSTI (template render of user input)",
|
|
53905
|
+
severity: "high",
|
|
53906
|
+
evidence: l.trim(),
|
|
53907
|
+
detail: "User input reaches a template render with no sandbox — SSTI (`{{7*7}}`, `${7*7}`, `#{7*7}`) → RCE."
|
|
53908
|
+
});
|
|
53909
|
+
}
|
|
53910
|
+
}
|
|
53670
53911
|
|
|
53671
53912
|
// src/guidance.ts
|
|
53672
53913
|
var SINK_DIRECTIVES = {
|
|
@@ -53724,16 +53965,16 @@ function nextSteps(ctx) {
|
|
|
53724
53965
|
}
|
|
53725
53966
|
|
|
53726
53967
|
// src/benchmark.ts
|
|
53727
|
-
import { readFileSync as
|
|
53728
|
-
import { join as
|
|
53729
|
-
import { fileURLToPath as
|
|
53968
|
+
import { readFileSync as readFileSync16, readdirSync as readdirSync4, writeFileSync as writeFileSync5, rmSync as rmSync3 } from "node:fs";
|
|
53969
|
+
import { join as join13 } from "node:path";
|
|
53970
|
+
import { fileURLToPath as fileURLToPath8 } from "node:url";
|
|
53730
53971
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
53731
|
-
var ROOT5 =
|
|
53732
|
-
var CORPUS =
|
|
53972
|
+
var ROOT5 = join13(fileURLToPath8(new URL(".", import.meta.url)), "..");
|
|
53973
|
+
var CORPUS = join13(ROOT5, "bench", "corpus");
|
|
53733
53974
|
function parseCorpusFile(path) {
|
|
53734
53975
|
let text;
|
|
53735
53976
|
try {
|
|
53736
|
-
text =
|
|
53977
|
+
text = readFileSync16(path, "utf8");
|
|
53737
53978
|
} catch {
|
|
53738
53979
|
return null;
|
|
53739
53980
|
}
|
|
@@ -53783,7 +54024,7 @@ function loadCorpus() {
|
|
|
53783
54024
|
for (const f of readdirSync4(CORPUS)) {
|
|
53784
54025
|
if (!f.endsWith(".md"))
|
|
53785
54026
|
continue;
|
|
53786
|
-
const e = parseCorpusFile(
|
|
54027
|
+
const e = parseCorpusFile(join13(CORPUS, f));
|
|
53787
54028
|
if (e)
|
|
53788
54029
|
entries.push(e);
|
|
53789
54030
|
}
|
|
@@ -53795,8 +54036,8 @@ function loadCorpus() {
|
|
|
53795
54036
|
function detect(entry) {
|
|
53796
54037
|
const lang = entry.language;
|
|
53797
54038
|
if (lang === "php") {
|
|
53798
|
-
const tmp =
|
|
53799
|
-
|
|
54039
|
+
const tmp = join13(tmpdir3(), `blitzstrike-bench-${entry.id}-${process.pid}.php`);
|
|
54040
|
+
writeFileSync5(tmp, entry.code);
|
|
53800
54041
|
const r = analyzeDataFlow2(tmp);
|
|
53801
54042
|
rmSync3(tmp, { force: true });
|
|
53802
54043
|
if (r.findings.length > 0)
|
|
@@ -53838,16 +54079,16 @@ function runBenchmark() {
|
|
|
53838
54079
|
}
|
|
53839
54080
|
|
|
53840
54081
|
// src/enterprise-benchmark.ts
|
|
53841
|
-
import { existsSync as existsSync7, readFileSync as
|
|
53842
|
-
import { join as
|
|
54082
|
+
import { existsSync as existsSync7, readFileSync as readFileSync17, readdirSync as readdirSync5, statSync as statSync3 } from "node:fs";
|
|
54083
|
+
import { join as join14, resolve } from "node:path";
|
|
53843
54084
|
var ROOT6 = resolve(process.cwd(), "bench", "enterprise");
|
|
53844
54085
|
var ENTERPRISE_FIXTURES = [
|
|
53845
|
-
{ id: "php-laravel", dir:
|
|
53846
|
-
{ id: "js-express", dir:
|
|
53847
|
-
{ id: "py-django", dir:
|
|
53848
|
-
{ id: "java-spring", dir:
|
|
53849
|
-
{ id: "wp-batch-route", dir:
|
|
53850
|
-
{ id: "php-complex", dir:
|
|
54086
|
+
{ id: "php-laravel", dir: join14(ROOT6, "php-laravel"), expected_vulns: 4 },
|
|
54087
|
+
{ id: "js-express", dir: join14(ROOT6, "js-express"), expected_vulns: 2 },
|
|
54088
|
+
{ id: "py-django", dir: join14(ROOT6, "py-django"), expected_vulns: 2 },
|
|
54089
|
+
{ id: "java-spring", dir: join14(ROOT6, "java-spring"), expected_vulns: 2 },
|
|
54090
|
+
{ id: "wp-batch-route", dir: join14(ROOT6, "wp-batch-route"), expected_vulns: 1 },
|
|
54091
|
+
{ id: "php-complex", dir: join14(ROOT6, "php-complex"), expected_vulns: 4 }
|
|
53851
54092
|
];
|
|
53852
54093
|
function sourceFiles(root) {
|
|
53853
54094
|
const out = [];
|
|
@@ -53859,7 +54100,7 @@ function sourceFiles(root) {
|
|
|
53859
54100
|
return;
|
|
53860
54101
|
}
|
|
53861
54102
|
for (const e of entries) {
|
|
53862
|
-
const p =
|
|
54103
|
+
const p = join14(dir, e);
|
|
53863
54104
|
let st;
|
|
53864
54105
|
try {
|
|
53865
54106
|
st = statSync3(p);
|
|
@@ -53880,7 +54121,7 @@ function detectFixture(dir) {
|
|
|
53880
54121
|
for (const f of sourceFiles(dir)) {
|
|
53881
54122
|
let code;
|
|
53882
54123
|
try {
|
|
53883
|
-
code =
|
|
54124
|
+
code = readFileSync17(f, "utf8");
|
|
53884
54125
|
} catch {
|
|
53885
54126
|
continue;
|
|
53886
54127
|
}
|
|
@@ -54066,7 +54307,7 @@ function createServer() {
|
|
|
54066
54307
|
name: "blitzstrike",
|
|
54067
54308
|
version: "1.0.0"
|
|
54068
54309
|
}, {
|
|
54069
|
-
instructions: "Blitz Strike is a universal penetration-testing toolbelt with three tiers — " + "BLITZ (reconnaissance / attack-surface mapping), EAGLE-EYE (source-to-sink " + "analysis), STRIKE (live validation) — plus a canonical evidence-first finding " + "engine and a multi-language taint engine.\\n\\n" + "CORE MENTAL MODEL:\\n" + "- A scanner hit is a HYPOTHESIS, not a finding. Live verification (or data-flow " + " proof) is the verdict. Never report an unverified hit as a vulnerability.\\n" + "- Distinguish REACHABLE from mere PRESENT: a sink is only a finding when tainted " + " input reaches it AND it is not neutralized by a sanitizer and not guarded by an " + " auth gate.\\n\\n" + "HOW TO RUN AN AUDIT (start here):\\n" + "0. ROUTE BY TARGET. Classify automatically - no need to ask. A URL - with or " + " without http/https - is LIVE (live_recon + the " + " web-hunting doctrine + strike_verify). A filesystem path (exists on disk, " + " or a code extension) is SOURCE audit (source-audit doctrine: blitz_scan + " + " eagle_eye2 + complex_scan/route_scan). TWO DOCTRINES - never mix: live " + " web/API -> web-hunting skill; source tree -> source-audit skill. For a WHOLE " + " engagement prefer run_engagement (one call, no babysitting) FIRST; steps 1-5 " + " below are the manual path for targeted follow-up on a specific finding.\\n" + "1. RECORD SCOPE. scope_check classifies the target (intel for the report) - " + " it does NOT gate the engagement. No authorization gate: the repo disclaimer " + " places responsibility on the user. Hunt the target you are given.\\n" + "2. MAP THE SURFACE (BLITZ). For a source tree use blitz_scan (tree) or blitz_file " + " (single file) then enrich_scan to match sinks to escalation chains. For a live " + " URL use live_recon for a full pass (fingerprint/WAF/tech/version/crawler/params/" + " subdomains/API endpoints/intel correlation) — it returns next_steps. active_scan " + " is the lighter alternative. Both record scope internally.\\n" + "3. TRACE REACHABILITY (EAGLE-EYE). For every sink, determine whether attacker " + " input actually reaches it:\\n" + " - PHP/JS/TS/Python/Java source: taint_file (auto-detects language) or " + " taint_scan/taint_tree (PHP AST). list_languages shows what's supported.\\n" + " - Any language: trace_data_flow (window heuristic) and eagle_eye/eagle_grep " + " (function-scoped view of sinks + auth gates). Beyond taint: route_scan (route confusion / dispatch abuse / batch forwarding) and complex_scan (deserialization->POP, type juggling, mass assignment, prototype pollution, CRLF/header injection, path confusion) catch classes plain taint misses.\\n" + " A sink counts only if tainted input reaches it unsanitized and unguarded.\\n" + "4. VALIDATE (STRIKE). Confirm live with strike_verify using a MARKER plus a " + " NEGATIVE CONTROL plus a BASELINE. The verdict is deterministic: marker " + " reflected AND control inert = confirmed; both reflected = false_positive; " + " marker not reflected = unconfirmed. Feed the verdict to strike_resolve to " + " advance the finding lifecycle (hypothesis->validating->confirmed). Use " + " read_tool_manual for the exploit tool's manual, payload_lookup for payloads, " + " detect_waf/tech_correlation for context.\\n" + "5. RECORD + REPORT. Create a canonical finding with finding_create, advance it " + " through the lifecycle with finding_transition, attach confidence_score, and " + " redact any secrets before persisting. Only report findings that survived " + " verification.\\n\\n" + "SUPPORTING LAYERS:\\n" + "- run_engagement: ONE call does the whole audit (no babysitting). Source path -> " + " full static pipeline + inline report (markdown/JSON/SARIF). URL target -> " + " full autonomous live pipeline (live_recon recon + deterministic checks: reflected " + " input, security headers, open redirect, path traversal, CORS) + findings + report. " + " PREFER THIS FIRST for a whole engagement; drive " + " the steps above manually only for targeted follow-up on a specific finding.\\n" + "- run_autonomous: run_engagement + the escalation path pre-attached to every " + " finding (chain name/severity/prerequisites + ordered next_steps with tool_hint/" + " success_criteria/negative_control) + a deterministic termination reason. Use it " + " when you want the 'what to do next' already computed per finding.\\n" + "- list_chains: 57 escalation chains (source -> sink -> impact).\\n" + "- list_languages / taint_file: multi-language taint (PHP, JS/TS, Python, Java).\\n" + "- tool_lookup / ensure_tool / list_tools: 130-tool catalog, auto-install.\\n" + "- read_playbook / list_playbooks: 17 engagement playbooks (web-app, api-security, " + " source-code-audit, active-directory, mobile, external-attack-surface, ...).\\n" + "- skill_lookup / read_skill / list_skills: 39 skills (7 bs-* agent doctrine: " + " orchestrate, scope, source-audit, web-hunting, verify, review, report; + " + " technique playbooks: WP/PHP 0-day, AD, malware, mobile, cloud).\\n" + "- read_tool_manual / list_manuals: 317 tool manuals (sqlmap, nmap, commix, ...).\\n" + "- payload_lookup / read_payload: 66 payload collections (sqli, xss, ssti, xxe, lfi).\\n" + "- detect_waf / tech_correlation / cve_correlation / port_correlation: signature " + " + correlation intelligence.\\n" + "- nvd_lookup / fofa_search: CVE lookup + asset reconnaissance (FOFA needs env creds).\\n" + "- remember / memory_lookup / memory_list: persist + recall verified knowledge.\\n\\n" + "RESOURCE MAP (know what to reach for):\\n" + "- ORCHESTRATION = the engagement framework. orchestration() returns the " + " lifecycle (scope->recon->analyze->deep->verify->report, each with " + " entry/exit criteria) + sub-agent team + handoff contract + termination " + " criteria. Load it at engagement start. Autonomous driving aids: plan() " + " (deterministic phase plan + next action), delegate() (parallel-first " + " subagent batches + retry pattern), worktree() (isolate a workstream), " + " watchdog() (detect a stalled phase), context_prune() (compact state so " + " you can drop raw detail).\\n" + "- TOOL = acts (scan, taint, verify, record, correlate). Call it to DO something.\\n" + "- PLAYBOOK = campaign plan for a target TYPE. read_playbook(web-application) " + " BEFORE a full engagement of that type.\\n" + "- SKILL = procedure doctrine. Load the bs-* skill for the phase you are on " + " (orchestrate, scope, web-hunting for LIVE, source-audit for SOURCE, verify, " + " review, report); load a technique skill for the specific technique.\\n" + "- MANUAL = deep reference for ONE tool. read_tool_manual right before using it.\\n" + "- CATALOG = external tools (130: sqlmap, nmap, nuclei, ffuf, ysoserial, ...). " + " Blitz Strike's OWN tools find + verify; the catalog is for deeper EXTERNAL " + " exploitation/enumeration. After your own analysis confirms a finding, run " + " tool_lookup -> ensure_tool (auto-install) -> read_tool_manual to drive the " + " external tool that demonstrates impact.\\n" + "- PAYLOAD = attack payloads for a class. payload_lookup before crafting a probe.\\n" + "- INTELLIGENCE = knowledge. list_attack_vectors (34 categories) maps the " + " full surface during RECON; list_chains (57) turns a finding into impact; " + " chain_links composes chains for complex bugs; bypass_lookup when blocked; " + " payload_lookup when crafting a probe. technique_lookup(cls) returns the " + " DETAILED methodology + tricks for a vulnerability class (ssrf, ssti, " + " jwt, deserialization, ...) - load before testing a class. wstg_map maps " + " an OWASP category to coverage; taxonomy maps findings to standard IDs " + " (OWASP Top 10 / API Top 10 / CWE / ASVS); doctrine_map shows how everything links.\\n\\n" + "NO-BABYSITTING (while scanning + testing):\\n" + "- Scan the WHOLE surface before reporting - every file, entry point, and sink. " + " Do not stop after the first hit to ask; keep enumerating the full tree.\\n" + "- Test every hypothesis through its full verification cycle - vary probes on " + " failure, apply bypasses when blocked, keep going. Do not pause to ask.\\n" + "- Persist through partial results: a scan that surfaces few sinks is NOT done - " + " widen the surface, escalate detectors, dig into each reachable sink.\\n" + "- NEVER ask 'should I continue?' / 'proceed?' / approval-style questions. " + " Continue autonomously - the moment one step verifies, start the next. " + " Pause ONLY when: a genuinely destructive/DoS action needs consent, an " + " external dependency beyond your control blocks you (e.g. phone OTP), or " + " the engagement is COMPLETE (deliver the final report). Otherwise keep hunting.\\n\\n" + "PERSISTENCE (complex bugs - do not give up):\\n" + "- There is NO retry cap. A failed probe/sub-agent is diagnosed and resumed " + " until verified or definitively ruled out - never move on unverified.\\n" + "- On any failure (empty output, blocked probe, failed sub-agent/model), call " + " retry_guidance(signal) for concrete recovery actions before giving up.\\n" + "- On a model/sub-agent error (rate-limit, quota, timeout, 5xx, auth, " + " unavailable), model_fallback(signal) classifies it and returns the exact " + " recovery (retry+backoff / fall back / shrink scope / fix credentials / " + " abort). Do not fall back blindly - the class decides.\\n" + "- One failed probe != not vulnerable. Vary: alternative sinks, other params, " + " encoding/case/whitespace bypasses, different injection context.\\n" + "- Empty output != no findings. Re-run (server may be stale after an upgrade - " + " /reload-mcp), or escalate to route_scan / complex_scan for classes taint " + " misses (POP chains, type juggling, mass assignment, route confusion).\\n" + "- A 'sanitized' sink may not be safe. Challenge the sanitizer: loose == vs ===, " + " missing EXTR_SKIP, int cast truncation, weak regex.\\n" + "- An unconfirmed hypothesis is KEPT as hypothesis - try alternate verification, " + " then let adversarial review decide.\\n" + "- Complex chains (deserialization->POP, LFI->RCE) need the FULL chain traced, " + ` not just the first sink.\\n
|
|
54310
|
+
instructions: "Blitz Strike is a universal penetration-testing toolbelt with three tiers — " + "BLITZ (reconnaissance / attack-surface mapping), EAGLE-EYE (source-to-sink " + "analysis), STRIKE (live validation) — plus a canonical evidence-first finding " + "engine and a multi-language taint engine.\\n\\n" + "CORE MENTAL MODEL:\\n" + "- A scanner hit is a HYPOTHESIS, not a finding. Live verification (or data-flow " + " proof) is the verdict. Never report an unverified hit as a vulnerability.\\n" + "- Distinguish REACHABLE from mere PRESENT: a sink is only a finding when tainted " + " input reaches it AND it is not neutralized by a sanitizer and not guarded by an " + " auth gate.\\n\\n" + "HOW TO RUN AN AUDIT (start here):\\n" + "0. ROUTE BY TARGET. Classify automatically - no need to ask. A URL - with or " + " without http/https - is LIVE (live_recon + the " + " web-hunting doctrine + strike_verify). A filesystem path (exists on disk, " + " or a code extension) is SOURCE audit (source-audit doctrine: blitz_scan + " + " eagle_eye2 + complex_scan/route_scan). TWO DOCTRINES - never mix: live " + " web/API -> web-hunting skill; source tree -> source-audit skill. For a WHOLE " + " engagement prefer run_engagement (one call, no babysitting) FIRST; steps 1-5 " + " below are the manual path for targeted follow-up on a specific finding.\\n" + "1. RECORD SCOPE. scope_check classifies the target (intel for the report) - " + " it does NOT gate the engagement. No authorization gate: the repo disclaimer " + " places responsibility on the user. Hunt the target you are given.\\n" + "2. MAP THE SURFACE (BLITZ). For a source tree use blitz_scan (tree) or blitz_file " + " (single file) then enrich_scan to match sinks to escalation chains. For a live " + " URL use live_recon for a full pass (fingerprint/WAF/tech/version/crawler/params/" + " subdomains/API endpoints/intel correlation) — it returns next_steps. active_scan " + " is the lighter alternative. Both record scope internally.\\n" + "3. TRACE REACHABILITY (EAGLE-EYE). For every sink, determine whether attacker " + " input actually reaches it:\\n" + " - PHP/JS/TS/Python/Java source: taint_file (auto-detects language) or " + " taint_scan/taint_tree (PHP AST). list_languages shows what's supported.\\n" + " - Any language: trace_data_flow (window heuristic) and eagle_eye/eagle_grep " + " (function-scoped view of sinks + auth gates). Beyond taint: route_scan (route confusion / dispatch abuse / batch forwarding) and complex_scan (deserialization->POP, type juggling, mass assignment, prototype pollution, CRLF/header injection, path confusion, SSRF, XXE, SSTI) catch classes plain taint misses.\\n" + " A sink counts only if tainted input reaches it unsanitized and unguarded.\\n" + "4. VALIDATE (STRIKE). Confirm live with strike_verify using a MARKER plus a " + " NEGATIVE CONTROL plus a BASELINE. The verdict is deterministic: marker " + " reflected AND control inert = confirmed; both reflected = false_positive; " + " marker not reflected = unconfirmed. Feed the verdict to strike_resolve to " + " advance the finding lifecycle (hypothesis->validating->confirmed). Use " + " read_tool_manual for the exploit tool's manual, payload_lookup for payloads, " + " detect_waf/tech_correlation for context.\\n" + "5. RECORD + REPORT. Create a canonical finding with finding_create, advance it " + " through the lifecycle with finding_transition, attach confidence_score, and " + " redact any secrets before persisting. Only report findings that survived " + " verification.\\n\\n" + "SUPPORTING LAYERS:\\n" + "- run_engagement: ONE call does the whole audit (no babysitting). Source path -> " + " full static pipeline + inline report (markdown/JSON/SARIF). URL target -> " + " full autonomous live pipeline (live_recon recon + deterministic checks: reflected " + " input, security headers, open redirect, path traversal, CORS) + findings + report. " + " PREFER THIS FIRST for a whole engagement; drive " + " the steps above manually only for targeted follow-up on a specific finding.\\n" + "- run_autonomous: run_engagement + the escalation path pre-attached to every " + " finding (chain name/severity/prerequisites + ordered next_steps with tool_hint/" + " success_criteria/negative_control) + a deterministic termination reason. Use it " + " when you want the 'what to do next' already computed per finding.\\n" + "- list_chains: 57 escalation chains (source -> sink -> impact).\\n" + "- list_languages / taint_file: multi-language taint (PHP, JS/TS, Python, Java).\\n" + "- tool_lookup / ensure_tool / list_tools: 130-tool catalog, auto-install.\\n" + "- read_playbook / list_playbooks: 17 engagement playbooks (web-app, api-security, " + " source-code-audit, active-directory, mobile, external-attack-surface, ...).\\n" + "- skill_lookup / read_skill / list_skills: 39 skills (7 bs-* agent doctrine: " + " orchestrate, scope, source-audit, web-hunting, verify, review, report; + " + " technique playbooks: WP/PHP 0-day, AD, malware, mobile, cloud).\\n" + "- read_tool_manual / list_manuals: 317 tool manuals (sqlmap, nmap, commix, ...).\\n" + "- payload_lookup / read_payload: 66 payload collections (sqli, xss, ssti, xxe, lfi).\\n" + "- detect_waf / tech_correlation / cve_correlation / port_correlation: signature " + " + correlation intelligence.\\n" + "- nvd_lookup / fofa_search: CVE lookup + asset reconnaissance (FOFA needs env creds).\\n" + "- remember / memory_lookup / memory_list: persist + recall verified knowledge.\\n\\n" + "RESOURCE MAP (know what to reach for):\\n" + "- ORCHESTRATION = the engagement framework. orchestration() returns the " + " lifecycle (scope->recon->analyze->deep->verify->report, each with " + " entry/exit criteria) + sub-agent team + handoff contract + termination " + " criteria. Load it at engagement start. Autonomous driving aids: plan() " + " (deterministic phase plan + next action), delegate() (parallel-first " + " subagent batches + retry pattern), worktree() (isolate a workstream), " + " watchdog() (detect a stalled phase), context_prune() (compact state so " + " you can drop raw detail), and task_start/task_checkpoint/task_status/" + " task_resume (per-workstream checkpoint journal — resume a failed task " + " from its last completed step, never restart).\\n" + "- TOOL = acts (scan, taint, verify, record, correlate). Call it to DO something.\\n" + "- PLAYBOOK = campaign plan for a target TYPE. read_playbook(web-application) " + " BEFORE a full engagement of that type.\\n" + "- SKILL = procedure doctrine. Load the bs-* skill for the phase you are on " + " (orchestrate, scope, web-hunting for LIVE, source-audit for SOURCE, verify, " + " review, report); load a technique skill for the specific technique.\\n" + "- MANUAL = deep reference for ONE tool. read_tool_manual right before using it.\\n" + "- CATALOG = external tools (130: sqlmap, nmap, nuclei, ffuf, ysoserial, ...). " + " Blitz Strike's OWN tools find + verify; the catalog is for deeper EXTERNAL " + " exploitation/enumeration. After your own analysis confirms a finding, run " + " tool_lookup -> ensure_tool (auto-install) -> read_tool_manual to drive the " + " external tool that demonstrates impact.\\n" + "- PAYLOAD = attack payloads for a class. payload_lookup before crafting a probe.\\n" + "- INTELLIGENCE = knowledge. list_attack_vectors (34 categories) maps the " + " full surface during RECON; list_chains (57) turns a finding into impact; " + " chain_links composes chains for complex bugs; bypass_lookup when blocked; " + " payload_lookup when crafting a probe. technique_lookup(cls) returns the " + " DETAILED methodology + tricks for a vulnerability class (ssrf, ssti, " + " jwt, deserialization, ...) - load before testing a class. wstg_map maps " + " an OWASP category to coverage; taxonomy maps findings to standard IDs " + " (OWASP Top 10 / API Top 10 / CWE / ASVS); doctrine_map shows how everything links.\\n\\n" + "NO-BABYSITTING (while scanning + testing):\\n" + "- Scan the WHOLE surface before reporting - every file, entry point, and sink. " + " Do not stop after the first hit to ask; keep enumerating the full tree.\\n" + "- Test every hypothesis through its full verification cycle - vary probes on " + " failure, apply bypasses when blocked, keep going. Do not pause to ask.\\n" + "- Persist through partial results: a scan that surfaces few sinks is NOT done - " + " widen the surface, escalate detectors, dig into each reachable sink.\\n" + "- NEVER ask 'should I continue?' / 'proceed?' / approval-style questions. " + " Continue autonomously - the moment one step verifies, start the next. " + " Pause ONLY when: a genuinely destructive/DoS action needs consent, an " + " external dependency beyond your control blocks you (e.g. phone OTP), or " + " the engagement is COMPLETE (deliver the final report). Otherwise keep hunting.\\n\\n" + "PERSISTENCE (complex bugs - do not give up):\\n" + "- There is NO retry cap. A failed probe/sub-agent is diagnosed and resumed " + " until verified or definitively ruled out - never move on unverified.\\n" + "- On any failure (empty output, blocked probe, failed sub-agent/model), call " + " retry_guidance(signal) for concrete recovery actions before giving up.\\n" + "- On a model/sub-agent error (rate-limit, quota, timeout, 5xx, auth, " + " unavailable), model_fallback(signal) classifies it and returns the exact " + " recovery (retry+backoff / fall back / shrink scope / fix credentials / " + " abort). Do not fall back blindly - the class decides.\\n" + "- One failed probe != not vulnerable. Vary: alternative sinks, other params, " + " encoding/case/whitespace bypasses, different injection context.\\n" + "- Empty output != no findings. Re-run (server may be stale after an upgrade - " + " /reload-mcp), or escalate to route_scan / complex_scan for classes taint " + " misses (POP chains, type juggling, mass assignment, route confusion).\\n" + "- A 'sanitized' sink may not be safe. Challenge the sanitizer: loose == vs ===, " + " missing EXTR_SKIP, int cast truncation, weak regex.\\n" + "- An unconfirmed hypothesis is KEPT as hypothesis - try alternate verification, " + " then let adversarial review decide.\\n" + "- Complex chains (deserialization->POP, LFI->RCE) need the FULL chain traced, " + ` not just the first sink.\\n
|
|
54070
54311
|
` + "- When a probe is BLOCKED by a WAF/filter/sanitizer, call bypass_lookup(defense) " + " for concrete techniques (encoding, case, comment insertion, chunked, null byte, " + " double-encoding, verb tampering, header spoofing) and apply them - do not give up.\\n" + "- CHAIN findings: a low-severity finding may escalate — do not assume it is the " + " end of the story. list_chains / chain_links show whether it composes into a " + " multi-stage chain (e.g. SSRF->metadata, SQLi->RCE, LFI->RCE, upload->shell). " + " Check before concluding, and do not overclaim an escalation that is not there.\\n\\n" + "- Complex bugs chain ACROSS chains: after confirming one chain, chain_links " + " shows what follows (SSRF->metadata then default-creds->pivot via stolen IAM). " + " Trace the full composition, not just a single chain.\\n\\n" + "HYPOTHESIS EXPANSION (from each finding, enumerate + verify every follow-up):\\n" + "- A confirmed finding is the START, not the end. From each one, enumerate " + " the follow-ups: ESCALATE (list_chains + chain_links), CHAIN (chain_links " + " from->to), BYPASS (bypass_lookup), PIVOT (new endpoints/sinks now reachable).\\n" + "- Every follow-up (this might happen, this could chain, maybe bypassable) is " + " a HYPOTHESIS, not a finding. Verify EACH via strike_verify (marker + negative " + " control) before it enters the report.\\n" + "- Never report speculation: might / could / possibly live in the " + " hypothesis list, never in the report. Every report line is a verified fact.\\n\\n" + "PROFESSIONAL JUDGMENT (act like a senior tester, not a checklist runner):\\n" + "- PRIORITIZE by severity, computed deterministically with cvss_score (impact x " + " exploitability) and weighted by escalation potential (chain_links). Do NOT rank " + " by bug class - a critical stored XSS that yields account takeover outranks an " + " unreachable blind RCE. Weight by the engagement context (which asset/data is " + " most sensitive for THIS target).\\n" + "- Recognize dead ends: if a branch yields nothing after a few varied probes, " + " note it and move on. Do not tunnel-vision one sink at the expense of the rest.\\n" + "- Live-test with care: respect rate limits, do not lock accounts, do not " + " trigger DoS. Test non-destructively and prove impact with the smallest payload.\\n" + "- Know when done: stop when the termination criteria are met, not when you feel " + " tired. A thorough audit over a rushed one, every time.\\n\\n" + "IRON RULES:\\n" + "- No authorization gate - the repo disclaimer places responsibility on the " + " user. Hunt the target you are given (like nmap/sqlmap/Burp, which do not " + " ask). The one hard stop: malicious/third-party infra (cari pelaku) - hold/static.\\n" + " need finer control."
|
|
54071
54312
|
});
|
|
54072
54313
|
server.registerTool("blitz_scan", {
|
|
@@ -54254,7 +54495,7 @@ function createServer() {
|
|
|
54254
54495
|
});
|
|
54255
54496
|
server.registerTool("run_engagement", {
|
|
54256
54497
|
title: "STRIKE full engagement",
|
|
54257
|
-
description: "STRIKE: run a full audit in ONE call (no babysitting). Source path -> triage -> chain enrichment -> findings -> inline report (markdown+JSON+SARIF). URL target -> full autonomous live pipeline: live_recon (fingerprint/WAF/tech/crawl/subdomains/API) + deterministic security checks (reflected input
|
|
54498
|
+
description: "STRIKE: run a full audit in ONE call (no babysitting). Source path -> triage -> chain enrichment -> findings -> inline report (markdown+JSON+SARIF). URL target -> full autonomous live pipeline: live_recon (fingerprint/WAF/tech/crawl/subdomains/API) + deterministic security checks (reflected input, missing headers, open redirect, path traversal, CORS) + advanced checks (SSTI, SSRF, error-based SQLi) -> findings -> inline report. Prefer this over driving tools one-by-one when you want the whole engagement in a single deterministic call.",
|
|
54258
54499
|
inputSchema: {
|
|
54259
54500
|
target: string2().describe("Source path or URL"),
|
|
54260
54501
|
scope: string2().optional().describe("Scope (for URL targets)"),
|
|
@@ -54736,6 +54977,48 @@ ${statusLine}` : statusLine },
|
|
|
54736
54977
|
const { batches, total } = decomposeBatches(tasks);
|
|
54737
54978
|
return { content: [{ type: "text", text: JSON.stringify({ total, batches, retry: retryPattern("a workstream") }, null, 2) }] };
|
|
54738
54979
|
});
|
|
54980
|
+
server.registerTool("task_start", {
|
|
54981
|
+
title: "Register a task with a checkpointed plan",
|
|
54982
|
+
description: "ORCHESTRATION: register a delegated workstream with an ordered plan (steps). Every step records a checkpoint so a failed workstream can be RESUMED from its last completed step instead of restarting. On-disk, so checkpoints survive restarts.",
|
|
54983
|
+
inputSchema: {
|
|
54984
|
+
task_id: string2().describe("Unique task id (e.g. 'recon-api', 'audit-auth')"),
|
|
54985
|
+
objective: string2().describe("What the task must accomplish"),
|
|
54986
|
+
plan: array(string2()).describe("Ordered steps (e.g. ['recon','analyze','verify','report'])")
|
|
54987
|
+
}
|
|
54988
|
+
}, async ({ task_id, objective, plan }) => {
|
|
54989
|
+
return { content: [{ type: "text", text: JSON.stringify(taskStart(task_id, objective, plan), null, 2) }] };
|
|
54990
|
+
});
|
|
54991
|
+
server.registerTool("task_checkpoint", {
|
|
54992
|
+
title: "Record a task checkpoint",
|
|
54993
|
+
description: "ORCHESTRATION: record a checkpoint for a task step (in_progress / done / failed / blocked) with an optional note + output hash. Advancing 'done' moves current_step forward; a 'failed' checkpoint marks the task for resume.",
|
|
54994
|
+
inputSchema: {
|
|
54995
|
+
task_id: string2().describe("Task id"),
|
|
54996
|
+
step: string2().describe("Step name (must be in the plan)"),
|
|
54997
|
+
status: _enum(["in_progress", "done", "failed", "blocked"]).describe("Checkpoint status"),
|
|
54998
|
+
note: string2().optional().describe("What happened at this step"),
|
|
54999
|
+
output_hash: string2().optional().describe("Hash of the artifacts produced (for resume dedup)")
|
|
55000
|
+
}
|
|
55001
|
+
}, async ({ task_id, step, status, note, output_hash }) => {
|
|
55002
|
+
return { content: [{ type: "text", text: JSON.stringify(taskCheckpoint(task_id, step, status, note, output_hash), null, 2) }] };
|
|
55003
|
+
});
|
|
55004
|
+
server.registerTool("task_status", {
|
|
55005
|
+
title: "Read a task's checkpoint state",
|
|
55006
|
+
description: "ORCHESTRATION: read a task's current state — status, current step, progress (done/total), remaining steps, and full checkpoint history. The orchestrator's deterministic view of where each workstream is.",
|
|
55007
|
+
inputSchema: {
|
|
55008
|
+
task_id: string2().describe("Task id")
|
|
55009
|
+
}
|
|
55010
|
+
}, async ({ task_id }) => {
|
|
55011
|
+
return { content: [{ type: "text", text: JSON.stringify(taskStatus(task_id), null, 2) }] };
|
|
55012
|
+
});
|
|
55013
|
+
server.registerTool("task_resume", {
|
|
55014
|
+
title: "Resume a task from its last checkpoint",
|
|
55015
|
+
description: "ORCHESTRATION: compute the resume instruction for a failed/blocked task — the last completed checkpoint, the current step, the remaining steps, and the exact re-dispatch prompt (resume from where it stopped, not a fresh start).",
|
|
55016
|
+
inputSchema: {
|
|
55017
|
+
task_id: string2().describe("Task id")
|
|
55018
|
+
}
|
|
55019
|
+
}, async ({ task_id }) => {
|
|
55020
|
+
return { content: [{ type: "text", text: JSON.stringify(taskResume(task_id), null, 2) }] };
|
|
55021
|
+
});
|
|
54739
55022
|
server.registerTool("worktree", {
|
|
54740
55023
|
title: "Isolate a git worktree",
|
|
54741
55024
|
description: "ORCHESTRATION: create/list/remove an isolated git worktree so parallel workstreams don't conflict on the same files. add: `git worktree add -b <branch> <path>`; list: enumerate existing; remove: clean up after a workstream.",
|
|
@@ -54978,7 +55261,7 @@ ${statusLine}` : statusLine },
|
|
|
54978
55261
|
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.",
|
|
54979
55262
|
inputSchema: { path: string2().describe("Source file path (PHP)") }
|
|
54980
55263
|
}, async ({ path }) => {
|
|
54981
|
-
const code =
|
|
55264
|
+
const code = readFileSync19(path, "utf8");
|
|
54982
55265
|
const findings = detectRouteConfusion(code, path);
|
|
54983
55266
|
return {
|
|
54984
55267
|
content: [{
|
|
@@ -54988,11 +55271,11 @@ ${statusLine}` : statusLine },
|
|
|
54988
55271
|
};
|
|
54989
55272
|
});
|
|
54990
55273
|
server.registerTool("complex_scan", {
|
|
54991
|
-
title: "Complex-bug scan (deserialization / type juggling / mass assignment / prototype pollution / CRLF / path confusion)",
|
|
54992
|
-
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),
|
|
55274
|
+
title: "Complex-bug scan (deserialization / type juggling / mass assignment / prototype pollution / CRLF / path confusion / SSRF / XXE / SSTI)",
|
|
55275
|
+
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), and SSTI (user input into a template render with no sandbox).",
|
|
54993
55276
|
inputSchema: { path: string2().describe("Source file path") }
|
|
54994
55277
|
}, async ({ path }) => {
|
|
54995
|
-
const code =
|
|
55278
|
+
const code = readFileSync19(path, "utf8");
|
|
54996
55279
|
const findings = detectComplexBugs(code, path);
|
|
54997
55280
|
return {
|
|
54998
55281
|
content: [{
|
|
@@ -55059,7 +55342,7 @@ ${statusLine}` : statusLine },
|
|
|
55059
55342
|
let lang = language;
|
|
55060
55343
|
if (path && !code) {
|
|
55061
55344
|
try {
|
|
55062
|
-
src =
|
|
55345
|
+
src = readFileSync19(path, "utf8");
|
|
55063
55346
|
} catch {
|
|
55064
55347
|
return { content: [{ type: "text", text: JSON.stringify({ error: `cannot read ${path}` }) }] };
|
|
55065
55348
|
}
|
|
@@ -55290,23 +55573,23 @@ async function serve() {
|
|
|
55290
55573
|
}
|
|
55291
55574
|
|
|
55292
55575
|
// src/cli.ts
|
|
55293
|
-
import { readFileSync as
|
|
55294
|
-
import { join as
|
|
55576
|
+
import { readFileSync as readFileSync22, existsSync as existsSync9, writeFileSync as writeFileSync7, mkdirSync as mkdirSync5 } from "node:fs";
|
|
55577
|
+
import { join as join18, dirname as dirname2 } from "node:path";
|
|
55295
55578
|
import { homedir as homedir5 } from "node:os";
|
|
55296
|
-
import { fileURLToPath as
|
|
55579
|
+
import { fileURLToPath as fileURLToPath12 } from "node:url";
|
|
55297
55580
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
55298
55581
|
|
|
55299
55582
|
// src/release.ts
|
|
55300
|
-
import { readFileSync as
|
|
55301
|
-
import { join as
|
|
55302
|
-
import { fileURLToPath as
|
|
55583
|
+
import { readFileSync as readFileSync20, existsSync as existsSync8 } from "node:fs";
|
|
55584
|
+
import { join as join16 } from "node:path";
|
|
55585
|
+
import { fileURLToPath as fileURLToPath10 } from "node:url";
|
|
55303
55586
|
import { execSync as execSync4 } from "node:child_process";
|
|
55304
|
-
var ROOT8 =
|
|
55587
|
+
var ROOT8 = join16(fileURLToPath10(new URL(".", import.meta.url)), "..");
|
|
55305
55588
|
function validateRelease() {
|
|
55306
55589
|
const checks = [];
|
|
55307
55590
|
checks.push({ name: "package.json version", ok: true, detail: VERSION });
|
|
55308
55591
|
try {
|
|
55309
|
-
const changelog =
|
|
55592
|
+
const changelog = readFileSync20(join16(ROOT8, "CHANGELOG.md"), "utf8");
|
|
55310
55593
|
const m = changelog.match(/^##\s+\[([0-9]+\.[0-9]+\.[0-9]+)\]/m);
|
|
55311
55594
|
const changelogVersion = m?.[1] ?? null;
|
|
55312
55595
|
checks.push({
|
|
@@ -55338,18 +55621,18 @@ function validateRelease() {
|
|
|
55338
55621
|
return { version: VERSION, checks, consistent: checks.filter((c) => !c.soft).every((c) => c.ok) };
|
|
55339
55622
|
}
|
|
55340
55623
|
function lockfilePresent() {
|
|
55341
|
-
return existsSync8(
|
|
55624
|
+
return existsSync8(join16(ROOT8, "package-lock.json")) || existsSync8(join16(ROOT8, "bun.lock")) || existsSync8(join16(ROOT8, "bun.lockb"));
|
|
55342
55625
|
}
|
|
55343
55626
|
|
|
55344
55627
|
// src/dependency.ts
|
|
55345
|
-
import { readFileSync as
|
|
55346
|
-
import { join as
|
|
55347
|
-
import { fileURLToPath as
|
|
55348
|
-
var ROOT9 =
|
|
55628
|
+
import { readFileSync as readFileSync21 } from "node:fs";
|
|
55629
|
+
import { join as join17 } from "node:path";
|
|
55630
|
+
import { fileURLToPath as fileURLToPath11 } from "node:url";
|
|
55631
|
+
var ROOT9 = join17(fileURLToPath11(new URL(".", import.meta.url)), "..");
|
|
55349
55632
|
function checkDependencies() {
|
|
55350
55633
|
let pkg = {};
|
|
55351
55634
|
try {
|
|
55352
|
-
pkg = JSON.parse(
|
|
55635
|
+
pkg = JSON.parse(readFileSync21(join17(ROOT9, "package.json"), "utf8"));
|
|
55353
55636
|
} catch {
|
|
55354
55637
|
return { install_scripts: [], git_dependencies: [], total_dependencies: 0, safe: false };
|
|
55355
55638
|
}
|
|
@@ -55372,7 +55655,7 @@ function checkDependencies() {
|
|
|
55372
55655
|
}
|
|
55373
55656
|
|
|
55374
55657
|
// src/cli.ts
|
|
55375
|
-
var ROOT10 =
|
|
55658
|
+
var ROOT10 = join18(fileURLToPath12(new URL(".", import.meta.url)), "..");
|
|
55376
55659
|
function which(cmd) {
|
|
55377
55660
|
return spawnSync3(`command -v ${cmd}`, { shell: true }).status === 0;
|
|
55378
55661
|
}
|
|
@@ -55407,7 +55690,7 @@ function runDoctor() {
|
|
|
55407
55690
|
detail: fofa ? "set" : "FOFA_EMAIL/FOFA_KEY not set",
|
|
55408
55691
|
fix: fofa ? undefined : "export FOFA_EMAIL=... && export FOFA_KEY=... (enables fofa_search)"
|
|
55409
55692
|
});
|
|
55410
|
-
const dataOk = existsSync9(
|
|
55693
|
+
const dataOk = existsSync9(join18(ROOT10, "chains.json")) && existsSync9(join18(ROOT10, "tools-catalog.json"));
|
|
55411
55694
|
issues.push({
|
|
55412
55695
|
name: "Data layers (chains + tools-catalog)",
|
|
55413
55696
|
status: dataOk ? "ok" : "fail",
|
|
@@ -55449,21 +55732,21 @@ BlitzStrike — ${verdict}`);
|
|
|
55449
55732
|
function resolveCommand() {
|
|
55450
55733
|
if (which("blitzstrike"))
|
|
55451
55734
|
return { command: "blitzstrike", args: ["serve", "--mcp"] };
|
|
55452
|
-
const src =
|
|
55735
|
+
const src = join18(ROOT10, "src", "index.ts");
|
|
55453
55736
|
if (which("bun") && existsSync9(src))
|
|
55454
55737
|
return { command: "bun", args: ["run", src, "serve", "--mcp"] };
|
|
55455
55738
|
if (which("npx"))
|
|
55456
55739
|
return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
|
|
55457
55740
|
if (which("bunx"))
|
|
55458
55741
|
return { command: "bunx", args: ["blitzstrike", "serve", "--mcp"] };
|
|
55459
|
-
const dist =
|
|
55742
|
+
const dist = join18(ROOT10, "dist", "index.js");
|
|
55460
55743
|
if (existsSync9(dist))
|
|
55461
55744
|
return { command: "bun", args: [dist, "serve", "--mcp"] };
|
|
55462
55745
|
return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
|
|
55463
55746
|
}
|
|
55464
55747
|
function readJson(p) {
|
|
55465
55748
|
try {
|
|
55466
|
-
return JSON.parse(
|
|
55749
|
+
return JSON.parse(readFileSync22(p, "utf8"));
|
|
55467
55750
|
} catch {
|
|
55468
55751
|
return null;
|
|
55469
55752
|
}
|
|
@@ -55472,41 +55755,41 @@ function jsonMcpServersWrite(p) {
|
|
|
55472
55755
|
return (command, args) => {
|
|
55473
55756
|
const dir = dirname2(p);
|
|
55474
55757
|
if (!existsSync9(dir))
|
|
55475
|
-
|
|
55758
|
+
mkdirSync5(dir, { recursive: true });
|
|
55476
55759
|
const existing = readJson(p) ?? {};
|
|
55477
55760
|
existing.mcpServers = {
|
|
55478
55761
|
...existing.mcpServers ?? {},
|
|
55479
55762
|
blitzstrike: { command, args }
|
|
55480
55763
|
};
|
|
55481
|
-
|
|
55764
|
+
writeFileSync7(p, JSON.stringify(existing, null, 2));
|
|
55482
55765
|
};
|
|
55483
55766
|
}
|
|
55484
55767
|
function opencodeWrite(p) {
|
|
55485
55768
|
return (_command, _args) => {
|
|
55486
55769
|
const dir = dirname2(p);
|
|
55487
55770
|
if (!existsSync9(dir))
|
|
55488
|
-
|
|
55771
|
+
mkdirSync5(dir, { recursive: true });
|
|
55489
55772
|
const existing = readJson(p) ?? {};
|
|
55490
55773
|
existing.mcp = {
|
|
55491
55774
|
...existing.mcp ?? {},
|
|
55492
55775
|
blitzstrike: { type: "local", command: ["npx", "-y", "blitzstrike", "serve", "--mcp"], enabled: true }
|
|
55493
55776
|
};
|
|
55494
|
-
|
|
55777
|
+
writeFileSync7(p, JSON.stringify(existing, null, 2));
|
|
55495
55778
|
copyOpenCodeAgents();
|
|
55496
55779
|
};
|
|
55497
55780
|
}
|
|
55498
55781
|
function copyOpenCodeAgents() {
|
|
55499
|
-
const srcDir =
|
|
55500
|
-
const dstDir =
|
|
55782
|
+
const srcDir = join18(ROOT10, "opencode-agents");
|
|
55783
|
+
const dstDir = join18(homedir5(), ".config", "opencode", "agents");
|
|
55501
55784
|
if (!existsSync9(srcDir))
|
|
55502
55785
|
return;
|
|
55503
55786
|
if (!existsSync9(dstDir))
|
|
55504
|
-
|
|
55787
|
+
mkdirSync5(dstDir, { recursive: true });
|
|
55505
55788
|
for (const f of ["Blitz Strike.md", "Blitz.md", "Eagle Eye.md", "Strike.md"]) {
|
|
55506
|
-
const src =
|
|
55507
|
-
const dst =
|
|
55789
|
+
const src = join18(srcDir, f);
|
|
55790
|
+
const dst = join18(dstDir, f);
|
|
55508
55791
|
if (existsSync9(src)) {
|
|
55509
|
-
|
|
55792
|
+
writeFileSync7(dst, readFileSync22(src, "utf8"));
|
|
55510
55793
|
}
|
|
55511
55794
|
}
|
|
55512
55795
|
}
|
|
@@ -55514,8 +55797,8 @@ function codexWrite(p) {
|
|
|
55514
55797
|
return (command, args) => {
|
|
55515
55798
|
const dir = dirname2(p);
|
|
55516
55799
|
if (!existsSync9(dir))
|
|
55517
|
-
|
|
55518
|
-
let existing = existsSync9(p) ?
|
|
55800
|
+
mkdirSync5(dir, { recursive: true });
|
|
55801
|
+
let existing = existsSync9(p) ? readFileSync22(p, "utf8") : "";
|
|
55519
55802
|
if (!existing.trimEnd().endsWith(`
|
|
55520
55803
|
`))
|
|
55521
55804
|
existing += `
|
|
@@ -55526,15 +55809,15 @@ function codexWrite(p) {
|
|
|
55526
55809
|
command = "${command}"
|
|
55527
55810
|
args = ${JSON.stringify(args)}
|
|
55528
55811
|
`;
|
|
55529
|
-
|
|
55812
|
+
writeFileSync7(p, existing);
|
|
55530
55813
|
};
|
|
55531
55814
|
}
|
|
55532
55815
|
function hermesWrite(p) {
|
|
55533
55816
|
return (command, args) => {
|
|
55534
55817
|
const dir = dirname2(p);
|
|
55535
55818
|
if (!existsSync9(dir))
|
|
55536
|
-
|
|
55537
|
-
let existing = existsSync9(p) ?
|
|
55819
|
+
mkdirSync5(dir, { recursive: true });
|
|
55820
|
+
let existing = existsSync9(p) ? readFileSync22(p, "utf8") : "";
|
|
55538
55821
|
existing = existing.replace(/^ blitzstrike:\n(?: .*\n?)*/m, "");
|
|
55539
55822
|
if (!existing.trimEnd().endsWith(`
|
|
55540
55823
|
`))
|
|
@@ -55555,80 +55838,80 @@ mcp_servers:
|
|
|
55555
55838
|
args: ${JSON.stringify(args)}
|
|
55556
55839
|
`;
|
|
55557
55840
|
}
|
|
55558
|
-
|
|
55841
|
+
writeFileSync7(p, existing);
|
|
55559
55842
|
};
|
|
55560
55843
|
}
|
|
55561
55844
|
function detectAgents() {
|
|
55562
55845
|
const home = homedir5();
|
|
55563
55846
|
const agents = [];
|
|
55564
|
-
const claudeJson =
|
|
55847
|
+
const claudeJson = join18(home, ".claude.json");
|
|
55565
55848
|
agents.push({
|
|
55566
55849
|
name: "Claude Code",
|
|
55567
55850
|
detectPath: claudeJson,
|
|
55568
55851
|
installed: () => existsSync9(claudeJson) || which("claude"),
|
|
55569
55852
|
write: jsonMcpServersWrite(claudeJson)
|
|
55570
55853
|
});
|
|
55571
|
-
const claudeDesktop =
|
|
55854
|
+
const claudeDesktop = join18(home, ".config", "Claude", "claude_desktop_config.json");
|
|
55572
55855
|
agents.push({
|
|
55573
55856
|
name: "Claude Desktop",
|
|
55574
55857
|
detectPath: claudeDesktop,
|
|
55575
55858
|
installed: () => existsSync9(claudeDesktop),
|
|
55576
55859
|
write: jsonMcpServersWrite(claudeDesktop)
|
|
55577
55860
|
});
|
|
55578
|
-
const cursor =
|
|
55861
|
+
const cursor = join18(home, ".cursor", "mcp.json");
|
|
55579
55862
|
agents.push({
|
|
55580
55863
|
name: "Cursor",
|
|
55581
55864
|
detectPath: cursor,
|
|
55582
|
-
installed: () => existsSync9(
|
|
55865
|
+
installed: () => existsSync9(join18(home, ".cursor")) || which("cursor"),
|
|
55583
55866
|
write: jsonMcpServersWrite(cursor)
|
|
55584
55867
|
});
|
|
55585
|
-
const opencode =
|
|
55868
|
+
const opencode = join18(home, ".config", "opencode", "opencode.jsonc");
|
|
55586
55869
|
agents.push({
|
|
55587
55870
|
name: "OpenCode",
|
|
55588
55871
|
detectPath: opencode,
|
|
55589
|
-
installed: () => existsSync9(
|
|
55872
|
+
installed: () => existsSync9(join18(home, ".config", "opencode")) || which("opencode"),
|
|
55590
55873
|
write: opencodeWrite(opencode)
|
|
55591
55874
|
});
|
|
55592
|
-
const codex =
|
|
55875
|
+
const codex = join18(home, ".codex", "config.toml");
|
|
55593
55876
|
agents.push({
|
|
55594
55877
|
name: "Codex",
|
|
55595
55878
|
detectPath: codex,
|
|
55596
|
-
installed: () => existsSync9(
|
|
55879
|
+
installed: () => existsSync9(join18(home, ".codex")) || which("codex"),
|
|
55597
55880
|
write: codexWrite(codex)
|
|
55598
55881
|
});
|
|
55599
|
-
const hermes =
|
|
55882
|
+
const hermes = join18(home, ".hermes", "config.yaml");
|
|
55600
55883
|
agents.push({
|
|
55601
55884
|
name: "Hermes",
|
|
55602
55885
|
detectPath: hermes,
|
|
55603
|
-
installed: () => existsSync9(
|
|
55886
|
+
installed: () => existsSync9(join18(home, ".hermes")) || which("hermes"),
|
|
55604
55887
|
write: hermesWrite(hermes)
|
|
55605
55888
|
});
|
|
55606
|
-
const gemini =
|
|
55889
|
+
const gemini = join18(home, ".gemini", "settings.json");
|
|
55607
55890
|
agents.push({
|
|
55608
55891
|
name: "Gemini CLI",
|
|
55609
55892
|
detectPath: gemini,
|
|
55610
|
-
installed: () => existsSync9(
|
|
55893
|
+
installed: () => existsSync9(join18(home, ".gemini")) || which("gemini"),
|
|
55611
55894
|
write: jsonMcpServersWrite(gemini)
|
|
55612
55895
|
});
|
|
55613
|
-
const windsurf =
|
|
55896
|
+
const windsurf = join18(home, ".codeium", "windsurf", "mcp_config.json");
|
|
55614
55897
|
agents.push({
|
|
55615
55898
|
name: "Windsurf",
|
|
55616
55899
|
detectPath: windsurf,
|
|
55617
|
-
installed: () => existsSync9(
|
|
55900
|
+
installed: () => existsSync9(join18(home, ".codeium")) || which("windsurf"),
|
|
55618
55901
|
write: jsonMcpServersWrite(windsurf)
|
|
55619
55902
|
});
|
|
55620
|
-
const copilot =
|
|
55903
|
+
const copilot = join18(home, ".copilot", "mcp.json");
|
|
55621
55904
|
agents.push({
|
|
55622
55905
|
name: "Copilot",
|
|
55623
55906
|
detectPath: copilot,
|
|
55624
|
-
installed: () => existsSync9(
|
|
55907
|
+
installed: () => existsSync9(join18(home, ".copilot")),
|
|
55625
55908
|
write: jsonMcpServersWrite(copilot)
|
|
55626
55909
|
});
|
|
55627
|
-
const cline =
|
|
55910
|
+
const cline = join18(home, ".cline", "mcp_settings.json");
|
|
55628
55911
|
agents.push({
|
|
55629
55912
|
name: "Cline",
|
|
55630
55913
|
detectPath: cline,
|
|
55631
|
-
installed: () => existsSync9(
|
|
55914
|
+
installed: () => existsSync9(join18(home, ".cline")),
|
|
55632
55915
|
write: jsonMcpServersWrite(cline)
|
|
55633
55916
|
});
|
|
55634
55917
|
return agents;
|
|
@@ -55672,15 +55955,15 @@ Registered with ${ok}/${installed.length} agent(s).`);
|
|
|
55672
55955
|
}
|
|
55673
55956
|
|
|
55674
55957
|
// src/index.ts
|
|
55675
|
-
import { readFileSync as
|
|
55676
|
-
import { join as
|
|
55677
|
-
import { fileURLToPath as
|
|
55678
|
-
var ROOT11 =
|
|
55958
|
+
import { readFileSync as readFileSync23, existsSync as existsSync10 } from "node:fs";
|
|
55959
|
+
import { join as join19 } from "node:path";
|
|
55960
|
+
import { fileURLToPath as fileURLToPath13 } from "node:url";
|
|
55961
|
+
var ROOT11 = join19(fileURLToPath13(new URL(".", import.meta.url)), "..");
|
|
55679
55962
|
function readVersion() {
|
|
55680
55963
|
try {
|
|
55681
|
-
const p =
|
|
55964
|
+
const p = join19(ROOT11, "package.json");
|
|
55682
55965
|
if (existsSync10(p))
|
|
55683
|
-
return JSON.parse(
|
|
55966
|
+
return JSON.parse(readFileSync23(p, "utf8")).version ?? "1.0.0";
|
|
55684
55967
|
} catch {}
|
|
55685
55968
|
return "1.0.0";
|
|
55686
55969
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blitzstrike",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
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": {
|