blitzstrike 1.0.43 → 1.0.44
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 +16 -0
- package/dist/index.js +1193 -1069
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -30319,654 +30319,1115 @@ function computeGuidance(state, scopeAllowed) {
|
|
|
30319
30319
|
};
|
|
30320
30320
|
}
|
|
30321
30321
|
|
|
30322
|
-
// src/
|
|
30323
|
-
var
|
|
30324
|
-
|
|
30325
|
-
|
|
30326
|
-
|
|
30327
|
-
|
|
30328
|
-
|
|
30329
|
-
|
|
30330
|
-
|
|
30331
|
-
|
|
30332
|
-
|
|
30333
|
-
} catch {
|
|
30334
|
-
_chainsCache = [];
|
|
30335
|
-
return [];
|
|
30336
|
-
}
|
|
30322
|
+
// src/cvss.ts
|
|
30323
|
+
var AV_WEIGHT = { N: 0.85, A: 0.62, L: 0.55, P: 0.2 };
|
|
30324
|
+
var AC_WEIGHT = { L: 0.77, H: 0.44 };
|
|
30325
|
+
var PR_WEIGHT = {
|
|
30326
|
+
U: { N: 0.85, L: 0.62, H: 0.27 },
|
|
30327
|
+
C: { N: 0.85, L: 0.68, H: 0.5 }
|
|
30328
|
+
};
|
|
30329
|
+
var UI_WEIGHT = { N: 0.85, R: 0.62 };
|
|
30330
|
+
var CIA_WEIGHT = { H: 0.56, L: 0.22, N: 0 };
|
|
30331
|
+
function roundup(value) {
|
|
30332
|
+
return Math.ceil(value * 10) / 10;
|
|
30337
30333
|
}
|
|
30338
|
-
function
|
|
30339
|
-
|
|
30340
|
-
|
|
30341
|
-
|
|
30342
|
-
|
|
30343
|
-
|
|
30334
|
+
function cvssBaseScore(input) {
|
|
30335
|
+
const AV = input.AV ?? "N";
|
|
30336
|
+
const AC = input.AC ?? "L";
|
|
30337
|
+
const PR = input.PR ?? "N";
|
|
30338
|
+
const UI = input.UI ?? "N";
|
|
30339
|
+
const S = input.S ?? "U";
|
|
30340
|
+
const C = input.C ?? "N";
|
|
30341
|
+
const I = input.I ?? "N";
|
|
30342
|
+
const A = input.A ?? "N";
|
|
30343
|
+
const iscBase = 1 - (1 - CIA_WEIGHT[C]) * (1 - CIA_WEIGHT[I]) * (1 - CIA_WEIGHT[A]);
|
|
30344
|
+
let impact;
|
|
30345
|
+
if (S === "U") {
|
|
30346
|
+
impact = 6.42 * iscBase;
|
|
30344
30347
|
} else {
|
|
30345
|
-
|
|
30346
|
-
const outScope = [];
|
|
30347
|
-
for (const raw of scope.split(/[,\n]/)) {
|
|
30348
|
-
const line = raw.trim();
|
|
30349
|
-
if (!line)
|
|
30350
|
-
continue;
|
|
30351
|
-
if (line.startsWith("-"))
|
|
30352
|
-
outScope.push(line.slice(1).trim());
|
|
30353
|
-
else
|
|
30354
|
-
inScope.push(line);
|
|
30355
|
-
}
|
|
30356
|
-
let host = target;
|
|
30357
|
-
host = host.replace(/^https?:\/\//, "");
|
|
30358
|
-
host = host.split("/")[0].split(":")[0];
|
|
30359
|
-
let decided = false;
|
|
30360
|
-
result = { target, allowed: false, enforcement: "strict", reason: `target '${host}' not in scope` };
|
|
30361
|
-
for (const exc of outScope) {
|
|
30362
|
-
if (exc && (host.includes(exc) || host.endsWith("." + exc.replace(/^\./, "")))) {
|
|
30363
|
-
result = { target, allowed: false, enforcement: "strict", reason: `target '${host}' is explicitly out of scope (${exc})` };
|
|
30364
|
-
decided = true;
|
|
30365
|
-
break;
|
|
30366
|
-
}
|
|
30367
|
-
}
|
|
30368
|
-
if (!decided) {
|
|
30369
|
-
for (const allowed of inScope) {
|
|
30370
|
-
const a = allowed.replace(/\*/g, "");
|
|
30371
|
-
if (!a)
|
|
30372
|
-
continue;
|
|
30373
|
-
if (host.includes(a) || host.endsWith("." + a.replace(/^\./, ""))) {
|
|
30374
|
-
result = { target, allowed: true, enforcement: "strict", reason: `matched in-scope pattern '${allowed}'` };
|
|
30375
|
-
decided = true;
|
|
30376
|
-
break;
|
|
30377
|
-
}
|
|
30378
|
-
}
|
|
30379
|
-
}
|
|
30348
|
+
impact = 7.52 * (iscBase - 0.029) - 3.25 * Math.pow(iscBase - 0.02, 15);
|
|
30380
30349
|
}
|
|
30381
|
-
|
|
30382
|
-
|
|
30350
|
+
const exploitability = 8.22 * AV_WEIGHT[AV] * AC_WEIGHT[AC] * PR_WEIGHT[S][PR] * UI_WEIGHT[UI];
|
|
30351
|
+
if (impact <= 0)
|
|
30352
|
+
return 0;
|
|
30353
|
+
if (S === "U") {
|
|
30354
|
+
return roundup(Math.min(impact + exploitability, 10));
|
|
30355
|
+
}
|
|
30356
|
+
return roundup(Math.min(1.08 * (impact + exploitability), 10));
|
|
30383
30357
|
}
|
|
30384
|
-
function
|
|
30385
|
-
|
|
30386
|
-
|
|
30387
|
-
|
|
30388
|
-
|
|
30389
|
-
|
|
30390
|
-
|
|
30391
|
-
|
|
30392
|
-
|
|
30393
|
-
|
|
30394
|
-
|
|
30395
|
-
|
|
30396
|
-
return "sql_injection";
|
|
30397
|
-
if (/(wp_remote_get|wp_remote_post|file_get_contents)/.test(sink))
|
|
30398
|
-
return "ssrf_unvalidated_url";
|
|
30399
|
-
if (/(eval\(|assert\(|create_function)/.test(sink))
|
|
30400
|
-
return "unsafe_template_render";
|
|
30401
|
-
return "";
|
|
30358
|
+
function cvssVector(input) {
|
|
30359
|
+
const order = [
|
|
30360
|
+
["AV", input.AV ?? "N"],
|
|
30361
|
+
["AC", input.AC ?? "L"],
|
|
30362
|
+
["PR", input.PR ?? "N"],
|
|
30363
|
+
["UI", input.UI ?? "N"],
|
|
30364
|
+
["S", input.S ?? "U"],
|
|
30365
|
+
["C", input.C ?? "N"],
|
|
30366
|
+
["I", input.I ?? "N"],
|
|
30367
|
+
["A", input.A ?? "N"]
|
|
30368
|
+
];
|
|
30369
|
+
return "CVSS:3.1/" + order.map(([k, v]) => `${k}:${v}`).join("/");
|
|
30402
30370
|
}
|
|
30403
|
-
function
|
|
30404
|
-
|
|
30405
|
-
|
|
30406
|
-
|
|
30407
|
-
|
|
30408
|
-
|
|
30409
|
-
|
|
30410
|
-
|
|
30411
|
-
|
|
30412
|
-
|
|
30413
|
-
|
|
30414
|
-
|
|
30415
|
-
|
|
30416
|
-
|
|
30417
|
-
|
|
30371
|
+
function cvssSeverity(score) {
|
|
30372
|
+
if (score === 0)
|
|
30373
|
+
return "none";
|
|
30374
|
+
if (score < 4)
|
|
30375
|
+
return "low";
|
|
30376
|
+
if (score < 7)
|
|
30377
|
+
return "medium";
|
|
30378
|
+
if (score < 9)
|
|
30379
|
+
return "high";
|
|
30380
|
+
return "critical";
|
|
30381
|
+
}
|
|
30382
|
+
function cvssAssess(input) {
|
|
30383
|
+
const score = cvssBaseScore(input);
|
|
30384
|
+
return { score, vector: cvssVector(input), severity: cvssSeverity(score) };
|
|
30385
|
+
}
|
|
30386
|
+
|
|
30387
|
+
// src/dedup.ts
|
|
30388
|
+
function rootCauseSignature(finding) {
|
|
30389
|
+
const sinkType = (finding.sink?.type ?? "unknown").toLowerCase();
|
|
30390
|
+
const sourceType = (finding.source?.type ?? "unknown").toLowerCase();
|
|
30391
|
+
const cwe = (finding.classification?.cwe ?? "").toUpperCase();
|
|
30392
|
+
return [sinkType, sourceType, cwe].filter(Boolean).join("|");
|
|
30393
|
+
}
|
|
30394
|
+
function dedupFindings(findings) {
|
|
30395
|
+
const groups = new Map;
|
|
30396
|
+
for (const f of findings) {
|
|
30397
|
+
const sig = rootCauseSignature(f);
|
|
30398
|
+
const existing = groups.get(sig);
|
|
30399
|
+
if (existing) {
|
|
30400
|
+
existing.count += 1;
|
|
30401
|
+
existing.findings.push(f);
|
|
30402
|
+
} else {
|
|
30403
|
+
groups.set(sig, { signature: sig, count: 1, representative: f, findings: [f] });
|
|
30418
30404
|
}
|
|
30419
30405
|
}
|
|
30420
|
-
|
|
30421
|
-
|
|
30422
|
-
|
|
30423
|
-
|
|
30424
|
-
|
|
30425
|
-
|
|
30406
|
+
return [...groups.values()];
|
|
30407
|
+
}
|
|
30408
|
+
function uniqueFindings(findings) {
|
|
30409
|
+
return dedupFindings(findings).map((g) => g.representative);
|
|
30410
|
+
}
|
|
30411
|
+
|
|
30412
|
+
// src/report.ts
|
|
30413
|
+
function summarize(findings) {
|
|
30414
|
+
const byStatus = {};
|
|
30415
|
+
const bySeverity = {};
|
|
30416
|
+
let evidenceArtifacts = 0;
|
|
30417
|
+
for (const f of findings) {
|
|
30418
|
+
byStatus[f.status] = (byStatus[f.status] ?? 0) + 1;
|
|
30419
|
+
const sev = f.classification?.severity ?? "informational";
|
|
30420
|
+
bySeverity[sev] = (bySeverity[sev] ?? 0) + 1;
|
|
30421
|
+
evidenceArtifacts += f.evidence?.reduce((n, e) => n + (e.artifacts?.length ?? 0), 0) ?? 0;
|
|
30426
30422
|
}
|
|
30423
|
+
const groups = dedupFindings(findings);
|
|
30427
30424
|
return {
|
|
30428
|
-
|
|
30429
|
-
|
|
30430
|
-
|
|
30431
|
-
|
|
30432
|
-
|
|
30425
|
+
total_findings: findings.length,
|
|
30426
|
+
unique_root_causes: groups.length,
|
|
30427
|
+
by_status: byStatus,
|
|
30428
|
+
by_severity: bySeverity,
|
|
30429
|
+
confirmed: byStatus["confirmed"] ?? 0,
|
|
30430
|
+
false_positives: byStatus["false_positive"] ?? 0,
|
|
30431
|
+
evidence_artifacts: evidenceArtifacts
|
|
30433
30432
|
};
|
|
30434
30433
|
}
|
|
30435
|
-
function
|
|
30436
|
-
const
|
|
30437
|
-
|
|
30438
|
-
|
|
30439
|
-
|
|
30440
|
-
|
|
30441
|
-
|
|
30442
|
-
|
|
30443
|
-
|
|
30444
|
-
|
|
30445
|
-
|
|
30446
|
-
|
|
30447
|
-
|
|
30448
|
-
|
|
30449
|
-
|
|
30450
|
-
})
|
|
30451
|
-
|
|
30452
|
-
|
|
30453
|
-
|
|
30454
|
-
|
|
30455
|
-
|
|
30456
|
-
|
|
30457
|
-
|
|
30458
|
-
|
|
30459
|
-
|
|
30460
|
-
|
|
30461
|
-
}
|
|
30462
|
-
|
|
30463
|
-
|
|
30464
|
-
|
|
30465
|
-
|
|
30466
|
-
|
|
30467
|
-
|
|
30468
|
-
|
|
30469
|
-
|
|
30470
|
-
|
|
30471
|
-
const chains = loadChains();
|
|
30472
|
-
const chainById = new Map(chains.map((c) => [c.id, c]));
|
|
30473
|
-
const eagle = [];
|
|
30474
|
-
for (const mc of blitz.matched_chains.slice(0, 10)) {
|
|
30475
|
-
const chain = chainById.get(mc.chain_id);
|
|
30476
|
-
if (!chain)
|
|
30477
|
-
continue;
|
|
30478
|
-
eagle.push({
|
|
30479
|
-
chain_id: chain.id,
|
|
30480
|
-
name: chain.name,
|
|
30481
|
-
severity: chain.severity,
|
|
30482
|
-
prerequisites: chain.required_findings,
|
|
30483
|
-
recommended_tools: chain.tools ?? [],
|
|
30484
|
-
tool_manuals: (chain.tools ?? []).map((t) => {
|
|
30485
|
-
const m = findManual(t);
|
|
30486
|
-
return m.found ? { tool: t, manual_name: m.name, preview: m.content.slice(0, 600) } : { tool: t, manual_name: null, preview: null };
|
|
30487
|
-
}),
|
|
30488
|
-
steps: chain.steps.map((s) => ({
|
|
30489
|
-
order: s.order,
|
|
30490
|
-
action: s.action,
|
|
30491
|
-
tool_hint: s.tool_hint,
|
|
30492
|
-
success_criteria: s.success_criteria,
|
|
30493
|
-
invariant_check: s.invariant_check,
|
|
30494
|
-
negative_control: s.negative_control
|
|
30495
|
-
}))
|
|
30496
|
-
});
|
|
30434
|
+
function severityLabel(sev) {
|
|
30435
|
+
const s = sev.toUpperCase();
|
|
30436
|
+
if (s === "CRITICAL")
|
|
30437
|
+
return "\uD83D\uDD34 CRITICAL";
|
|
30438
|
+
if (s === "HIGH")
|
|
30439
|
+
return "\uD83D\uDFE0 HIGH";
|
|
30440
|
+
if (s === "MEDIUM")
|
|
30441
|
+
return "\uD83D\uDFE1 MEDIUM";
|
|
30442
|
+
if (s === "LOW")
|
|
30443
|
+
return "\uD83D\uDD35 LOW";
|
|
30444
|
+
return "⚪ INFORMATIONAL";
|
|
30445
|
+
}
|
|
30446
|
+
function reportMarkdown(findings, opts = {}) {
|
|
30447
|
+
const summary = summarize(findings);
|
|
30448
|
+
const lines = [];
|
|
30449
|
+
lines.push(`# ${opts.title ?? "Blitz Strike — Security Assessment Report"}`);
|
|
30450
|
+
lines.push("");
|
|
30451
|
+
lines.push(`- Scope: ${opts.scope ?? "(unset)"}`);
|
|
30452
|
+
lines.push(`- Blitz Strike version: ${opts.version ?? "unknown"}`);
|
|
30453
|
+
if (opts.include_timestamp)
|
|
30454
|
+
lines.push(`- Generated: ${new Date().toISOString()}`);
|
|
30455
|
+
lines.push("");
|
|
30456
|
+
lines.push("## Summary");
|
|
30457
|
+
lines.push("");
|
|
30458
|
+
lines.push(`| Metric | Value |`);
|
|
30459
|
+
lines.push(`|--------|-------|`);
|
|
30460
|
+
lines.push(`| Total findings | ${summary.total_findings} |`);
|
|
30461
|
+
lines.push(`| Unique root causes | ${summary.unique_root_causes} |`);
|
|
30462
|
+
lines.push(`| Confirmed | ${summary.confirmed} |`);
|
|
30463
|
+
lines.push(`| False positives | ${summary.false_positives} |`);
|
|
30464
|
+
lines.push(`| Evidence artifacts | ${summary.evidence_artifacts} |`);
|
|
30465
|
+
lines.push("");
|
|
30466
|
+
lines.push("### By status");
|
|
30467
|
+
lines.push("");
|
|
30468
|
+
for (const [status, n] of Object.entries(summary.by_status).sort()) {
|
|
30469
|
+
lines.push(`- ${status}: ${n}`);
|
|
30497
30470
|
}
|
|
30498
|
-
|
|
30499
|
-
|
|
30500
|
-
|
|
30501
|
-
const
|
|
30502
|
-
|
|
30503
|
-
|
|
30504
|
-
|
|
30505
|
-
|
|
30506
|
-
|
|
30507
|
-
|
|
30508
|
-
|
|
30509
|
-
|
|
30510
|
-
|
|
30511
|
-
|
|
30512
|
-
|
|
30513
|
-
|
|
30514
|
-
|
|
30515
|
-
|
|
30516
|
-
|
|
30517
|
-
|
|
30518
|
-
|
|
30519
|
-
|
|
30520
|
-
|
|
30521
|
-
|
|
30522
|
-
|
|
30523
|
-
|
|
30524
|
-
|
|
30525
|
-
|
|
30526
|
-
|
|
30527
|
-
|
|
30528
|
-
|
|
30529
|
-
confidence_level: f.confidence_level,
|
|
30530
|
-
chain_id: f.chain.id,
|
|
30531
|
-
evidence_count: f.evidence.length
|
|
30532
|
-
}));
|
|
30533
|
-
report.status = "COMPLETE";
|
|
30534
|
-
report.note = "All findings are HYPOTHESES until verified live with strike_verify. " + "Apply each chain's negative_control before reporting. Confidence reflects static evidence only.";
|
|
30535
|
-
const executedState = "select_chain";
|
|
30536
|
-
const scopeAllowed = report.scope?.allowed ?? false;
|
|
30537
|
-
const plan = buildPlan(scopeAllowed, mode, executedState);
|
|
30538
|
-
report.state = executedState;
|
|
30539
|
-
report.state_description = stateDefinition(executedState).description;
|
|
30540
|
-
report.plan = plan;
|
|
30541
|
-
report.guidance = computeGuidance(executedState, scopeAllowed);
|
|
30542
|
-
report.risk_policy = {
|
|
30543
|
-
note: "High-risk actions (strike_verify, confirm_finding) require scope + are never run server-side automatically.",
|
|
30544
|
-
gated: plan.filter((p) => !p.permitted).map((p) => p.tool)
|
|
30545
|
-
};
|
|
30546
|
-
if (remember) {
|
|
30547
|
-
let captured = 0;
|
|
30548
|
-
for (const mc of blitz.matched_chains) {
|
|
30549
|
-
const chain = chainById.get(mc.chain_id);
|
|
30550
|
-
const content = `Chain '${mc.chain_id}' (${mc.severity}) matched on target '${target}'. ` + `Detected sink signals: ${blitz.sink_signals.join(", ")}. ` + `Prerequisites: ${(chain?.required_findings ?? []).join(", ")}.`;
|
|
30551
|
-
if (rememberIfAbsent(mc.chain_id, content, "pattern", [mc.severity, "chain"], "engagement", false)) {
|
|
30552
|
-
captured++;
|
|
30553
|
-
}
|
|
30471
|
+
lines.push("");
|
|
30472
|
+
lines.push("### By severity");
|
|
30473
|
+
lines.push("");
|
|
30474
|
+
for (const [sev, n] of Object.entries(summary.by_severity).sort()) {
|
|
30475
|
+
lines.push(`- ${sev}: ${n}`);
|
|
30476
|
+
}
|
|
30477
|
+
lines.push("");
|
|
30478
|
+
lines.push("## Findings");
|
|
30479
|
+
lines.push("");
|
|
30480
|
+
if (findings.length === 0) {
|
|
30481
|
+
lines.push("_No findings._");
|
|
30482
|
+
return lines.join(`
|
|
30483
|
+
`) + `
|
|
30484
|
+
`;
|
|
30485
|
+
}
|
|
30486
|
+
for (const f of findings) {
|
|
30487
|
+
lines.push(`### ${f.id} — ${f.title} [${severityLabel(f.classification?.severity ?? "informational")}]`);
|
|
30488
|
+
lines.push("");
|
|
30489
|
+
lines.push(`- **Status**: \`${f.status}\``);
|
|
30490
|
+
lines.push(`- **Confidence**: ${f.confidence} (${f.confidence_level})`);
|
|
30491
|
+
if (f.classification?.cwe)
|
|
30492
|
+
lines.push(`- **CWE**: ${f.classification.cwe}`);
|
|
30493
|
+
if (f.classification?.cvss_score != null)
|
|
30494
|
+
lines.push(`- **CVSS**: ${f.classification.cvss_score} (${f.classification.cvss_vector ?? ""}) — ${cvssSeverity(f.classification.cvss_score)}`);
|
|
30495
|
+
lines.push(`- **Source**: ${f.source?.type} \`${f.source?.name ?? ""}\``);
|
|
30496
|
+
lines.push(`- **Sink**: ${f.sink?.type}${f.sink?.symbol ? ` \`${f.sink.symbol}\`` : ""}`);
|
|
30497
|
+
if (f.target?.host || f.target?.endpoint)
|
|
30498
|
+
lines.push(`- **Target**: ${[f.target.host, f.target.endpoint].filter(Boolean).join("")}`);
|
|
30499
|
+
lines.push(`- **Evidence**: ${f.evidence?.length ?? 0} record(s)`);
|
|
30500
|
+
if (f.remediation && Object.keys(f.remediation).length) {
|
|
30501
|
+
lines.push(`- **Note**: ${JSON.stringify(f.remediation)}`);
|
|
30554
30502
|
}
|
|
30555
|
-
|
|
30503
|
+
lines.push("");
|
|
30556
30504
|
}
|
|
30557
|
-
return
|
|
30505
|
+
return lines.join(`
|
|
30506
|
+
`) + `
|
|
30507
|
+
`;
|
|
30558
30508
|
}
|
|
30559
|
-
function
|
|
30560
|
-
const
|
|
30509
|
+
function reportJson(findings, opts = {}) {
|
|
30510
|
+
const summary = summarize(findings);
|
|
30511
|
+
const payload = {
|
|
30512
|
+
title: opts.title ?? "Blitz Strike — Security Assessment Report",
|
|
30513
|
+
scope: opts.scope ?? null,
|
|
30514
|
+
version: opts.version ?? null,
|
|
30515
|
+
summary,
|
|
30516
|
+
findings,
|
|
30517
|
+
integrity: sha256(JSON.stringify({ summary, findings }))
|
|
30518
|
+
};
|
|
30519
|
+
return JSON.stringify(payload, null, 2);
|
|
30520
|
+
}
|
|
30521
|
+
|
|
30522
|
+
// src/sarif.ts
|
|
30523
|
+
var SARIF_SCHEMA = "https://json.schemastore.org/sarif-2.1.0.json";
|
|
30524
|
+
function sarifLevel(severity) {
|
|
30525
|
+
const s = severity.toLowerCase();
|
|
30526
|
+
if (s === "critical" || s === "high")
|
|
30527
|
+
return "error";
|
|
30528
|
+
if (s === "medium")
|
|
30529
|
+
return "warning";
|
|
30530
|
+
return "note";
|
|
30531
|
+
}
|
|
30532
|
+
function toSarif(findings, opts = {}) {
|
|
30533
|
+
const tool = opts.tool ?? "Blitz Strike";
|
|
30534
|
+
const uri = opts.uri ?? "https://github.com/shinthink/blitzstrike";
|
|
30535
|
+
const ruleIds = [...new Set(findings.map((f) => f.ruleId))];
|
|
30536
|
+
const rules = ruleIds.map((id) => ({
|
|
30537
|
+
id,
|
|
30538
|
+
name: id,
|
|
30539
|
+
shortDescription: { text: id }
|
|
30540
|
+
}));
|
|
30541
|
+
const results = findings.map((f) => ({
|
|
30542
|
+
ruleId: f.ruleId,
|
|
30543
|
+
level: f.level,
|
|
30544
|
+
message: { text: f.message },
|
|
30545
|
+
locations: [
|
|
30546
|
+
{
|
|
30547
|
+
physicalLocation: {
|
|
30548
|
+
artifactLocation: { uri: f.file },
|
|
30549
|
+
region: { startLine: Math.max(1, f.line) }
|
|
30550
|
+
}
|
|
30551
|
+
}
|
|
30552
|
+
]
|
|
30553
|
+
}));
|
|
30561
30554
|
return {
|
|
30562
|
-
|
|
30563
|
-
|
|
30564
|
-
|
|
30565
|
-
|
|
30566
|
-
|
|
30567
|
-
|
|
30568
|
-
|
|
30569
|
-
|
|
30555
|
+
version: "2.1.0",
|
|
30556
|
+
$schema: SARIF_SCHEMA,
|
|
30557
|
+
runs: [
|
|
30558
|
+
{
|
|
30559
|
+
tool: {
|
|
30560
|
+
driver: {
|
|
30561
|
+
name: tool,
|
|
30562
|
+
version: VERSION,
|
|
30563
|
+
informationUri: uri,
|
|
30564
|
+
rules
|
|
30565
|
+
}
|
|
30566
|
+
},
|
|
30567
|
+
results
|
|
30568
|
+
}
|
|
30569
|
+
]
|
|
30570
30570
|
};
|
|
30571
30571
|
}
|
|
30572
30572
|
|
|
30573
|
-
// src/
|
|
30574
|
-
|
|
30575
|
-
|
|
30576
|
-
|
|
30577
|
-
|
|
30578
|
-
var ROOT2 = join7(fileURLToPath4(new URL(".", import.meta.url)), "..");
|
|
30579
|
-
var _toolsCache = null;
|
|
30580
|
-
var _skillsCache = null;
|
|
30581
|
-
function loadTools() {
|
|
30582
|
-
if (_toolsCache)
|
|
30583
|
-
return _toolsCache;
|
|
30573
|
+
// src/strike.ts
|
|
30574
|
+
var DEFAULT_TIMEOUT_MS = 15000;
|
|
30575
|
+
async function httpRequest(url, method, data, headers, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
30576
|
+
const controller = new AbortController;
|
|
30577
|
+
const t = setTimeout(() => controller.abort(), timeoutMs);
|
|
30584
30578
|
try {
|
|
30585
|
-
const
|
|
30586
|
-
|
|
30587
|
-
|
|
30588
|
-
|
|
30579
|
+
const res = await fetch(url, {
|
|
30580
|
+
method,
|
|
30581
|
+
body: method === "POST" ? data : undefined,
|
|
30582
|
+
headers: { "User-Agent": "blitzstrike/1.0", ...headers ?? {} },
|
|
30583
|
+
signal: controller.signal,
|
|
30584
|
+
redirect: "follow"
|
|
30585
|
+
});
|
|
30586
|
+
const body = await res.text();
|
|
30587
|
+
const hdrs = {};
|
|
30588
|
+
res.headers.forEach((v, k) => {
|
|
30589
|
+
hdrs[k] = v;
|
|
30590
|
+
});
|
|
30591
|
+
return { status: res.status, body: body.slice(0, 20000), headers: hdrs };
|
|
30592
|
+
} catch (e) {
|
|
30593
|
+
return { status: 0, body: String(e), headers: {} };
|
|
30594
|
+
} finally {
|
|
30595
|
+
clearTimeout(t);
|
|
30589
30596
|
}
|
|
30590
|
-
return _toolsCache;
|
|
30591
30597
|
}
|
|
30592
|
-
function
|
|
30593
|
-
|
|
30594
|
-
return _skillsCache;
|
|
30595
|
-
const dir = join7(ROOT2, "skills");
|
|
30596
|
-
const out = [];
|
|
30597
|
-
try {
|
|
30598
|
-
for (const f of readdirSync2(dir)) {
|
|
30599
|
-
if (!f.endsWith(".md"))
|
|
30600
|
-
continue;
|
|
30601
|
-
const p = join7(dir, f);
|
|
30602
|
-
const text = readFileSync7(p, "utf8");
|
|
30603
|
-
const descMatch = text.match(/^description:\s*(.+)$/m);
|
|
30604
|
-
out.push({
|
|
30605
|
-
name: f.replace(/\.md$/, ""),
|
|
30606
|
-
file: f,
|
|
30607
|
-
description: (descMatch?.[1] ?? "").trim().slice(0, 200),
|
|
30608
|
-
size: text.length
|
|
30609
|
-
});
|
|
30610
|
-
}
|
|
30611
|
-
} catch {}
|
|
30612
|
-
_skillsCache = out;
|
|
30613
|
-
return out;
|
|
30598
|
+
function defaultMarker() {
|
|
30599
|
+
return `BS${Date.now().toString(36).toUpperCase()}${Math.random().toString(36).slice(2, 8).toUpperCase()}`;
|
|
30614
30600
|
}
|
|
30615
|
-
function
|
|
30616
|
-
const
|
|
30617
|
-
|
|
30618
|
-
|
|
30619
|
-
|
|
30620
|
-
|
|
30621
|
-
|
|
30622
|
-
suggestions: tools.filter((x) => x.name.includes(name.toLowerCase()) || name.toLowerCase().includes(x.name)).slice(0, 5).map((x) => x.name)
|
|
30623
|
-
};
|
|
30624
|
-
}
|
|
30625
|
-
const manual = manualForTool(t.name);
|
|
30626
|
-
const result = { found: true, tool: t };
|
|
30627
|
-
if (manual && manual.content) {
|
|
30628
|
-
result.manual = {
|
|
30629
|
-
name: manual.name,
|
|
30630
|
-
category: manual.category,
|
|
30631
|
-
preview: manual.content.slice(0, 1500),
|
|
30632
|
-
full_length: manual.content.length
|
|
30633
|
-
};
|
|
30634
|
-
} else {
|
|
30635
|
-
result.manual = null;
|
|
30636
|
-
}
|
|
30637
|
-
return result;
|
|
30601
|
+
function inject(target, value, param) {
|
|
30602
|
+
const placeholder = "{{MARKER}}";
|
|
30603
|
+
if (target.includes(placeholder))
|
|
30604
|
+
return target.split(placeholder).join(value);
|
|
30605
|
+
if (target.includes("?"))
|
|
30606
|
+
return `${target}&${param}=${encodeURIComponent(value)}`;
|
|
30607
|
+
return `${target}?${param}=${encodeURIComponent(value)}`;
|
|
30638
30608
|
}
|
|
30639
|
-
function
|
|
30640
|
-
const
|
|
30641
|
-
const
|
|
30642
|
-
|
|
30643
|
-
|
|
30609
|
+
async function strikeVerify(input) {
|
|
30610
|
+
const method = input.method ?? "GET";
|
|
30611
|
+
const param = input.param ?? "q";
|
|
30612
|
+
const marker = input.marker ?? defaultMarker();
|
|
30613
|
+
const control = input.control ?? defaultMarker();
|
|
30614
|
+
const baseUrl = input.url;
|
|
30615
|
+
const timeoutMs = input.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
30616
|
+
const baselineTarget = inject(baseUrl, "", param).replace(/[?&]q=$/, "").replace(/[?&]$/, "");
|
|
30617
|
+
const markerTarget = inject(baseUrl, marker, param);
|
|
30618
|
+
const controlTarget = inject(baseUrl, control, param);
|
|
30619
|
+
const baseline = await httpRequest(baselineTarget, method, input.data, input.headers, timeoutMs);
|
|
30620
|
+
const markerResp = await httpRequest(markerTarget, method, input.data, input.headers, timeoutMs);
|
|
30621
|
+
const controlResp = await httpRequest(controlTarget, method, input.data, input.headers, timeoutMs);
|
|
30622
|
+
if (baseline.status === 0 || markerResp.status === 0) {
|
|
30623
|
+
return {
|
|
30624
|
+
status: "blocked",
|
|
30625
|
+
marker_reflected: false,
|
|
30626
|
+
control_reflected: false,
|
|
30627
|
+
baseline: { status: baseline.status, body_preview: redactSecrets(baseline.body.slice(0, 400)), body_hash: sha256(baseline.body) },
|
|
30628
|
+
marker_response: { status: markerResp.status, body_preview: redactSecrets(markerResp.body.slice(0, 400)), body_hash: sha256(markerResp.body) },
|
|
30629
|
+
control_response: { status: controlResp.status, body_preview: redactSecrets(controlResp.body.slice(0, 400)), body_hash: sha256(controlResp.body) },
|
|
30630
|
+
reason: "request failed or target unreachable — cannot validate",
|
|
30631
|
+
evidence: []
|
|
30632
|
+
};
|
|
30644
30633
|
}
|
|
30645
|
-
|
|
30646
|
-
|
|
30647
|
-
|
|
30648
|
-
|
|
30649
|
-
|
|
30650
|
-
|
|
30651
|
-
|
|
30652
|
-
|
|
30653
|
-
|
|
30654
|
-
|
|
30655
|
-
|
|
30656
|
-
|
|
30657
|
-
|
|
30658
|
-
|
|
30634
|
+
const markerReflected = markerResp.body.includes(marker);
|
|
30635
|
+
const controlReflected = controlResp.body.includes(control);
|
|
30636
|
+
let status;
|
|
30637
|
+
let reason;
|
|
30638
|
+
if (markerReflected && !controlReflected) {
|
|
30639
|
+
status = "confirmed";
|
|
30640
|
+
reason = "marker reflected; negative control did not — the sink reflects attacker-controlled input (real finding)";
|
|
30641
|
+
} else if (markerReflected && controlReflected) {
|
|
30642
|
+
status = "false_positive";
|
|
30643
|
+
reason = "marker AND negative control both reflected — behaviour is indistinguishable from benign reflection (likely false positive)";
|
|
30644
|
+
} else if (!markerReflected && !controlReflected) {
|
|
30645
|
+
status = "unconfirmed";
|
|
30646
|
+
reason = "marker not reflected — reachability/exploitability could not be demonstrated live";
|
|
30647
|
+
} else {
|
|
30648
|
+
status = "unconfirmed";
|
|
30649
|
+
reason = "negative control reflected but marker did not — inconsistent, requires deeper analysis";
|
|
30659
30650
|
}
|
|
30651
|
+
const evidence = [
|
|
30652
|
+
{
|
|
30653
|
+
type: "baseline_comparison",
|
|
30654
|
+
description: `Baseline response (status ${baseline.status}) for ${redactSecrets(baselineTarget)}`,
|
|
30655
|
+
artifacts: [{ name: "baseline", kind: "http_response", content: `${baseline.status}
|
|
30656
|
+
${baseline.body}` }]
|
|
30657
|
+
},
|
|
30658
|
+
{
|
|
30659
|
+
type: "validation_result",
|
|
30660
|
+
description: `Marker '${redactSecrets(marker)}' reflected=${markerReflected}; control reflected=${controlReflected}`,
|
|
30661
|
+
artifacts: [{ name: "marker_response", kind: "http_response", content: `${markerResp.status}
|
|
30662
|
+
${markerResp.body}` }]
|
|
30663
|
+
},
|
|
30664
|
+
{
|
|
30665
|
+
type: "negative_control",
|
|
30666
|
+
description: `Negative control '${redactSecrets(control)}' reflected=${controlReflected}`,
|
|
30667
|
+
artifacts: [{ name: "control_response", kind: "http_response", content: `${controlResp.status}
|
|
30668
|
+
${controlResp.body}` }]
|
|
30669
|
+
}
|
|
30670
|
+
];
|
|
30660
30671
|
return {
|
|
30661
|
-
|
|
30662
|
-
|
|
30672
|
+
status,
|
|
30673
|
+
marker_reflected: markerReflected,
|
|
30674
|
+
control_reflected: controlReflected,
|
|
30675
|
+
baseline: { status: baseline.status, body_preview: redactSecrets(baseline.body.slice(0, 400)), body_hash: sha256(baseline.body) },
|
|
30676
|
+
marker_response: { status: markerResp.status, body_preview: redactSecrets(markerResp.body.slice(0, 400)), body_hash: sha256(markerResp.body) },
|
|
30677
|
+
control_response: { status: controlResp.status, body_preview: redactSecrets(controlResp.body.slice(0, 400)), body_hash: sha256(controlResp.body) },
|
|
30678
|
+
reason,
|
|
30679
|
+
evidence
|
|
30663
30680
|
};
|
|
30664
30681
|
}
|
|
30665
|
-
function
|
|
30666
|
-
|
|
30667
|
-
|
|
30668
|
-
|
|
30669
|
-
function readSkill(name) {
|
|
30670
|
-
const skills = loadSkills();
|
|
30671
|
-
const s = skills.find((x) => x.name.toLowerCase() === name.toLowerCase());
|
|
30672
|
-
if (!s)
|
|
30673
|
-
return { found: false, query: name };
|
|
30674
|
-
try {
|
|
30675
|
-
const text = readFileSync7(join7(ROOT2, "skills", s.file), "utf8");
|
|
30676
|
-
return { found: true, name: s.name, content: text.slice(0, 12000) };
|
|
30677
|
-
} catch {
|
|
30678
|
-
return { found: false, query: name, error: "unreadable" };
|
|
30679
|
-
}
|
|
30680
|
-
}
|
|
30681
|
-
function ensureTool(name) {
|
|
30682
|
-
const tools = loadTools();
|
|
30683
|
-
const t = tools.find((x) => x.name.toLowerCase() === name.toLowerCase());
|
|
30684
|
-
if (!t) {
|
|
30685
|
-
return { found: false, query: name, suggestions: tools.slice(0, 5).map((x) => x.name) };
|
|
30686
|
-
}
|
|
30687
|
-
const check = spawnSync(t.check_installed.command, { shell: true, timeout: 15000 });
|
|
30688
|
-
if (check.status !== 127 && check.error === undefined) {
|
|
30689
|
-
return { name: t.name, installed: true, action: "none", note: "already installed" };
|
|
30682
|
+
function resolveFinding(finding, verdict) {
|
|
30683
|
+
let f = finding;
|
|
30684
|
+
if (f.status === "hypothesis") {
|
|
30685
|
+
f = transition(f, "validating");
|
|
30690
30686
|
}
|
|
30691
|
-
|
|
30692
|
-
|
|
30693
|
-
|
|
30694
|
-
|
|
30687
|
+
switch (verdict.status) {
|
|
30688
|
+
case "confirmed":
|
|
30689
|
+
return confirmFinding(f, {
|
|
30690
|
+
evidence: verdict.evidence,
|
|
30691
|
+
negative_control: true,
|
|
30692
|
+
baseline: true
|
|
30693
|
+
});
|
|
30694
|
+
case "false_positive":
|
|
30695
|
+
return rejectFinding(f, "false_positive", verdict.reason, verdict.evidence);
|
|
30696
|
+
case "blocked":
|
|
30697
|
+
return rejectFinding(f, "blocked", verdict.reason, verdict.evidence);
|
|
30698
|
+
case "unconfirmed":
|
|
30699
|
+
case "likely":
|
|
30700
|
+
default: {
|
|
30701
|
+
return {
|
|
30702
|
+
...f,
|
|
30703
|
+
validation: { performed: true, status: verdict.status === "likely" ? "likely" : "unconfirmed", negative_control: verdict.control_reflected === false, baseline: true },
|
|
30704
|
+
remediation: { ...f.remediation, note: verdict.reason },
|
|
30705
|
+
timestamps: { ...f.timestamps, updated: new Date().toISOString() }
|
|
30706
|
+
};
|
|
30707
|
+
}
|
|
30695
30708
|
}
|
|
30696
|
-
const r = spawnSync(cmd, { shell: true, timeout: 120000 });
|
|
30697
|
-
const ok = r.status === 0;
|
|
30698
|
-
return {
|
|
30699
|
-
name: t.name,
|
|
30700
|
-
installed: ok,
|
|
30701
|
-
action: "install",
|
|
30702
|
-
command: cmd,
|
|
30703
|
-
output: (r.stdout?.toString() ?? "").slice(-400) || (r.stderr?.toString() ?? "").slice(-400)
|
|
30704
|
-
};
|
|
30705
30709
|
}
|
|
30706
30710
|
|
|
30707
|
-
// src/
|
|
30708
|
-
|
|
30709
|
-
|
|
30710
|
-
|
|
30711
|
-
|
|
30712
|
-
import { homedir as homedir3 } from "node:os";
|
|
30713
|
-
var ROOT3 = join8(fileURLToPath5(new URL(".", import.meta.url)), "..");
|
|
30714
|
-
var INTEL = join8(ROOT3, "intelligence");
|
|
30715
|
-
function loadJson(name) {
|
|
30716
|
-
const p = join8(INTEL, `${name}.json`);
|
|
30717
|
-
if (!existsSync5(p))
|
|
30718
|
-
return null;
|
|
30711
|
+
// src/orchestrator.ts
|
|
30712
|
+
var _chainsCache = null;
|
|
30713
|
+
function loadChains() {
|
|
30714
|
+
if (_chainsCache)
|
|
30715
|
+
return _chainsCache;
|
|
30719
30716
|
try {
|
|
30720
|
-
|
|
30717
|
+
const dir = join6(fileURLToPath3(new URL(".", import.meta.url)), "..");
|
|
30718
|
+
const path = join6(dir, "chains.json");
|
|
30719
|
+
const raw = JSON.parse(readFileSync6(path, "utf8"));
|
|
30720
|
+
_chainsCache = raw.chains ?? [];
|
|
30721
|
+
return _chainsCache;
|
|
30721
30722
|
} catch {
|
|
30722
|
-
|
|
30723
|
+
_chainsCache = [];
|
|
30724
|
+
return [];
|
|
30723
30725
|
}
|
|
30724
30726
|
}
|
|
30725
|
-
function
|
|
30726
|
-
|
|
30727
|
-
if (
|
|
30728
|
-
|
|
30729
|
-
|
|
30730
|
-
|
|
30731
|
-
|
|
30732
|
-
|
|
30733
|
-
|
|
30734
|
-
const
|
|
30735
|
-
|
|
30736
|
-
|
|
30737
|
-
|
|
30738
|
-
|
|
30739
|
-
|
|
30740
|
-
|
|
30741
|
-
|
|
30742
|
-
|
|
30743
|
-
|
|
30744
|
-
|
|
30745
|
-
|
|
30727
|
+
function scopeCheck(target, scope = "", mode = "bug-bounty") {
|
|
30728
|
+
let result;
|
|
30729
|
+
if (mode === "ctf" || mode === "reverse-engineering" || mode === "offensive") {
|
|
30730
|
+
result = { target, allowed: true, enforcement: "off", reason: `mode=${mode} disables scope enforcement` };
|
|
30731
|
+
} else if (!scope.trim()) {
|
|
30732
|
+
result = { target, allowed: false, enforcement: "strict", reason: "no scope provided; refusing active testing" };
|
|
30733
|
+
} else {
|
|
30734
|
+
const inScope = [];
|
|
30735
|
+
const outScope = [];
|
|
30736
|
+
for (const raw of scope.split(/[,\n]/)) {
|
|
30737
|
+
const line = raw.trim();
|
|
30738
|
+
if (!line)
|
|
30739
|
+
continue;
|
|
30740
|
+
if (line.startsWith("-"))
|
|
30741
|
+
outScope.push(line.slice(1).trim());
|
|
30742
|
+
else
|
|
30743
|
+
inScope.push(line);
|
|
30744
|
+
}
|
|
30745
|
+
let host = target;
|
|
30746
|
+
host = host.replace(/^https?:\/\//, "");
|
|
30747
|
+
host = host.split("/")[0].split(":")[0];
|
|
30748
|
+
let decided = false;
|
|
30749
|
+
result = { target, allowed: false, enforcement: "strict", reason: `target '${host}' not in scope` };
|
|
30750
|
+
for (const exc of outScope) {
|
|
30751
|
+
if (exc && (host.includes(exc) || host.endsWith("." + exc.replace(/^\./, "")))) {
|
|
30752
|
+
result = { target, allowed: false, enforcement: "strict", reason: `target '${host}' is explicitly out of scope (${exc})` };
|
|
30753
|
+
decided = true;
|
|
30754
|
+
break;
|
|
30746
30755
|
}
|
|
30747
30756
|
}
|
|
30748
|
-
|
|
30749
|
-
|
|
30750
|
-
|
|
30751
|
-
|
|
30752
|
-
|
|
30757
|
+
if (!decided) {
|
|
30758
|
+
for (const allowed of inScope) {
|
|
30759
|
+
const a = allowed.replace(/\*/g, "");
|
|
30760
|
+
if (!a)
|
|
30761
|
+
continue;
|
|
30762
|
+
if (host.includes(a) || host.endsWith("." + a.replace(/^\./, ""))) {
|
|
30763
|
+
result = { target, allowed: true, enforcement: "strict", reason: `matched in-scope pattern '${allowed}'` };
|
|
30764
|
+
decided = true;
|
|
30765
|
+
break;
|
|
30766
|
+
}
|
|
30767
|
+
}
|
|
30753
30768
|
}
|
|
30754
30769
|
}
|
|
30755
|
-
|
|
30756
|
-
|
|
30757
|
-
|
|
30758
|
-
|
|
30759
|
-
|
|
30760
|
-
|
|
30770
|
+
recordAudit("scope_checked", { tool: "scope", target, result: result.allowed ? "allowed" : "denied" });
|
|
30771
|
+
return result;
|
|
30772
|
+
}
|
|
30773
|
+
function classifySink(sink) {
|
|
30774
|
+
if (/(move_uploaded_file|file_put_contents|fwrite)/.test(sink))
|
|
30775
|
+
return "upload_weak_validation";
|
|
30776
|
+
if (/unserialize/.test(sink))
|
|
30777
|
+
return "unserialize_user_input";
|
|
30778
|
+
if (/(exec\(|system\(|shell_exec|passthru|popen|proc_open)/.test(sink))
|
|
30779
|
+
return "command_injection";
|
|
30780
|
+
if (/(include|require)/.test(sink))
|
|
30781
|
+
return "lfi_unvalidated_include";
|
|
30782
|
+
if (/extract\(/.test(sink))
|
|
30783
|
+
return "extract_without_exstr_skip";
|
|
30784
|
+
if (/(\$wpdb|->query)/.test(sink))
|
|
30785
|
+
return "sql_injection";
|
|
30786
|
+
if (/(wp_remote_get|wp_remote_post|file_get_contents)/.test(sink))
|
|
30787
|
+
return "ssrf_unvalidated_url";
|
|
30788
|
+
if (/(eval\(|assert\(|create_function)/.test(sink))
|
|
30789
|
+
return "unsafe_template_render";
|
|
30790
|
+
return "";
|
|
30791
|
+
}
|
|
30792
|
+
function enrichScan(path, maxFiles = 2000) {
|
|
30793
|
+
const expanded = path.replace(/^~/, process.env.HOME ?? "~");
|
|
30794
|
+
const files = iterSourceFiles(expanded, maxFiles);
|
|
30795
|
+
const hits = [];
|
|
30796
|
+
const sinkKinds = new Set;
|
|
30797
|
+
for (const p of files) {
|
|
30798
|
+
const r = scanFile(p);
|
|
30799
|
+
for (const e of r.endpoints) {
|
|
30800
|
+
hits.push({ type: "endpoint", file: r.file, line: e.line, detail: e.hook });
|
|
30801
|
+
}
|
|
30802
|
+
for (const s of r.sinks) {
|
|
30803
|
+
hits.push({ type: "sink", file: r.file, line: s.line, detail: `${s.sink} -> ${s.class}` });
|
|
30804
|
+
const kind = classifySink(s.sink);
|
|
30805
|
+
if (kind)
|
|
30806
|
+
sinkKinds.add(kind);
|
|
30807
|
+
}
|
|
30808
|
+
}
|
|
30809
|
+
const chains = loadChains();
|
|
30810
|
+
const matched = [];
|
|
30811
|
+
for (const c of chains) {
|
|
30812
|
+
if ((c.required_findings ?? []).some((rf) => sinkKinds.has(rf))) {
|
|
30813
|
+
matched.push({ chain_id: c.id, name: c.name, severity: c.severity, steps: c.steps.length });
|
|
30814
|
+
}
|
|
30761
30815
|
}
|
|
30762
|
-
const detected = [...byWaf.values()].sort((a, b) => b.confidence - a.confidence);
|
|
30763
30816
|
return {
|
|
30764
|
-
|
|
30765
|
-
|
|
30766
|
-
|
|
30817
|
+
root: expanded,
|
|
30818
|
+
files_scanned: files.length,
|
|
30819
|
+
hits: hits.slice(0, 2000),
|
|
30820
|
+
sink_signals: [...sinkKinds].sort(),
|
|
30821
|
+
matched_chains: matched
|
|
30767
30822
|
};
|
|
30768
30823
|
}
|
|
30769
|
-
function
|
|
30770
|
-
const
|
|
30771
|
-
|
|
30772
|
-
|
|
30773
|
-
|
|
30774
|
-
|
|
30775
|
-
|
|
30776
|
-
|
|
30777
|
-
|
|
30824
|
+
function runEngagement(target, scope = "", mode = "bug-bounty", maxFiles = 2000, remember = true) {
|
|
30825
|
+
const report = {
|
|
30826
|
+
target,
|
|
30827
|
+
mode,
|
|
30828
|
+
tiers: {},
|
|
30829
|
+
findings: [],
|
|
30830
|
+
scope: null,
|
|
30831
|
+
memory: { captured: 0 }
|
|
30832
|
+
};
|
|
30833
|
+
const isPath = (() => {
|
|
30834
|
+
try {
|
|
30835
|
+
return existsSync4(target) && (statSync2(target).isDirectory() || statSync2(target).isFile());
|
|
30836
|
+
} catch {
|
|
30837
|
+
return false;
|
|
30838
|
+
}
|
|
30839
|
+
})();
|
|
30840
|
+
if (isPath) {
|
|
30841
|
+
report.scope = { target, allowed: true, enforcement: "source-audit", reason: "local source path — no network scope gate" };
|
|
30842
|
+
} else {
|
|
30843
|
+
const sc = scopeCheck(target, scope, mode);
|
|
30844
|
+
report.scope = sc;
|
|
30845
|
+
if (!sc.allowed) {
|
|
30846
|
+
report.status = "BLOCKED";
|
|
30847
|
+
report.reason = sc.reason;
|
|
30848
|
+
return report;
|
|
30849
|
+
}
|
|
30778
30850
|
}
|
|
30779
|
-
|
|
30780
|
-
|
|
30781
|
-
|
|
30782
|
-
|
|
30783
|
-
|
|
30784
|
-
|
|
30785
|
-
|
|
30786
|
-
|
|
30787
|
-
|
|
30788
|
-
|
|
30789
|
-
|
|
30790
|
-
|
|
30791
|
-
|
|
30792
|
-
|
|
30793
|
-
|
|
30794
|
-
|
|
30795
|
-
|
|
30796
|
-
|
|
30797
|
-
|
|
30798
|
-
|
|
30799
|
-
|
|
30800
|
-
|
|
30801
|
-
|
|
30802
|
-
|
|
30803
|
-
|
|
30804
|
-
|
|
30805
|
-
|
|
30806
|
-
|
|
30807
|
-
|
|
30851
|
+
const blitz = enrichScan(target, maxFiles);
|
|
30852
|
+
report.tiers = {
|
|
30853
|
+
blitz: {
|
|
30854
|
+
files_scanned: blitz.files_scanned,
|
|
30855
|
+
hits: blitz.hits.length,
|
|
30856
|
+
sink_signals: blitz.sink_signals,
|
|
30857
|
+
matched_chains: blitz.matched_chains
|
|
30858
|
+
}
|
|
30859
|
+
};
|
|
30860
|
+
const chains = loadChains();
|
|
30861
|
+
const chainById = new Map(chains.map((c) => [c.id, c]));
|
|
30862
|
+
const eagle = [];
|
|
30863
|
+
for (const mc of blitz.matched_chains.slice(0, 10)) {
|
|
30864
|
+
const chain = chainById.get(mc.chain_id);
|
|
30865
|
+
if (!chain)
|
|
30866
|
+
continue;
|
|
30867
|
+
eagle.push({
|
|
30868
|
+
chain_id: chain.id,
|
|
30869
|
+
name: chain.name,
|
|
30870
|
+
severity: chain.severity,
|
|
30871
|
+
prerequisites: chain.required_findings,
|
|
30872
|
+
recommended_tools: chain.tools ?? [],
|
|
30873
|
+
tool_manuals: (chain.tools ?? []).map((t) => {
|
|
30874
|
+
const m = findManual(t);
|
|
30875
|
+
return m.found ? { tool: t, manual_name: m.name, preview: m.content.slice(0, 600) } : { tool: t, manual_name: null, preview: null };
|
|
30876
|
+
}),
|
|
30877
|
+
steps: chain.steps.map((s) => ({
|
|
30878
|
+
order: s.order,
|
|
30879
|
+
action: s.action,
|
|
30880
|
+
tool_hint: s.tool_hint,
|
|
30881
|
+
success_criteria: s.success_criteria,
|
|
30882
|
+
invariant_check: s.invariant_check,
|
|
30883
|
+
negative_control: s.negative_control
|
|
30884
|
+
}))
|
|
30885
|
+
});
|
|
30808
30886
|
}
|
|
30809
|
-
|
|
30810
|
-
}
|
|
30811
|
-
|
|
30812
|
-
const
|
|
30813
|
-
|
|
30814
|
-
|
|
30815
|
-
|
|
30816
|
-
|
|
30817
|
-
|
|
30818
|
-
|
|
30819
|
-
|
|
30820
|
-
|
|
30821
|
-
|
|
30822
|
-
|
|
30823
|
-
|
|
30824
|
-
|
|
30825
|
-
|
|
30826
|
-
|
|
30827
|
-
|
|
30887
|
+
report.tiers = { ...report.tiers, eagle_eye: { traced_chains: eagle } };
|
|
30888
|
+
const rank = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
30889
|
+
const matchedSorted = [...blitz.matched_chains].sort((a, b) => (rank[a.severity] ?? 99) - (rank[b.severity] ?? 99));
|
|
30890
|
+
const findings = matchedSorted.map((f) => {
|
|
30891
|
+
const chain = chainById.get(f.chain_id);
|
|
30892
|
+
const severity = f.severity ?? "medium";
|
|
30893
|
+
const finding = makeFinding({
|
|
30894
|
+
title: f.name,
|
|
30895
|
+
target: { type: "source", path: target },
|
|
30896
|
+
severity,
|
|
30897
|
+
source: { type: "static_sink_signal", name: f.chain_id },
|
|
30898
|
+
sink: { type: f.chain_id, symbol: f.name },
|
|
30899
|
+
chainId: f.chain_id,
|
|
30900
|
+
chainName: f.name,
|
|
30901
|
+
status: "hypothesis"
|
|
30902
|
+
});
|
|
30903
|
+
const ev = makeEvidence({
|
|
30904
|
+
type: "sink_location",
|
|
30905
|
+
description: `Static sink signal matched escalation chain '${f.chain_id}' (${severity}). Hypothesis only — not yet validated.`,
|
|
30906
|
+
sink: { chain_id: f.chain_id, name: f.name, severity },
|
|
30907
|
+
artifacts: [{ name: "chain_match", kind: "json", content: JSON.stringify({ chain_id: f.chain_id, severity, steps: chain?.steps.length ?? 0 }) }]
|
|
30908
|
+
});
|
|
30909
|
+
finding.evidence.push(ev);
|
|
30910
|
+
return finding;
|
|
30911
|
+
});
|
|
30912
|
+
report.findings = findings.map((f) => ({
|
|
30913
|
+
id: f.id,
|
|
30914
|
+
status: f.status,
|
|
30915
|
+
title: f.title,
|
|
30916
|
+
severity: f.classification.severity,
|
|
30917
|
+
confidence: f.confidence,
|
|
30918
|
+
confidence_level: f.confidence_level,
|
|
30919
|
+
chain_id: f.chain.id,
|
|
30920
|
+
evidence_count: f.evidence.length
|
|
30921
|
+
}));
|
|
30922
|
+
const reportTitle = `Blitz Strike — Engagement Report (${target})`;
|
|
30923
|
+
const reportMarkdownText = reportMarkdown(findings, { title: reportTitle, scope: target, version: VERSION });
|
|
30924
|
+
const reportJsonText = reportJson(findings, { title: reportTitle, scope: target, version: VERSION });
|
|
30925
|
+
const sarifDoc = toSarif(findings.map((f) => ({
|
|
30926
|
+
ruleId: f.chain.id || f.classification.severity,
|
|
30927
|
+
level: sarifLevel(f.classification.severity),
|
|
30928
|
+
message: f.title,
|
|
30929
|
+
file: typeof f.target.path === "string" ? f.target.path : target,
|
|
30930
|
+
line: 1
|
|
30931
|
+
})));
|
|
30932
|
+
report.report = {
|
|
30933
|
+
summary: JSON.parse(reportJsonText).summary ?? null,
|
|
30934
|
+
markdown: reportMarkdownText,
|
|
30935
|
+
json: reportJsonText,
|
|
30936
|
+
sarif: sarifDoc
|
|
30937
|
+
};
|
|
30938
|
+
report.status = "COMPLETE";
|
|
30939
|
+
report.note = "All findings are HYPOTHESES until verified live with strike_verify. " + "Apply each chain's negative_control before reporting. Confidence reflects static evidence only.";
|
|
30940
|
+
const executedState = "select_chain";
|
|
30941
|
+
const scopeAllowed = report.scope?.allowed ?? false;
|
|
30942
|
+
const plan = buildPlan(scopeAllowed, mode, executedState);
|
|
30943
|
+
report.state = executedState;
|
|
30944
|
+
report.state_description = stateDefinition(executedState).description;
|
|
30945
|
+
report.plan = plan;
|
|
30946
|
+
report.guidance = computeGuidance(executedState, scopeAllowed);
|
|
30947
|
+
report.risk_policy = {
|
|
30948
|
+
note: "High-risk actions (strike_verify, confirm_finding) require scope + are never run server-side automatically.",
|
|
30949
|
+
gated: plan.filter((p) => !p.permitted).map((p) => p.tool)
|
|
30828
30950
|
};
|
|
30951
|
+
if (remember) {
|
|
30952
|
+
let captured = 0;
|
|
30953
|
+
for (const mc of blitz.matched_chains) {
|
|
30954
|
+
const chain = chainById.get(mc.chain_id);
|
|
30955
|
+
const content = `Chain '${mc.chain_id}' (${mc.severity}) matched on target '${target}'. ` + `Detected sink signals: ${blitz.sink_signals.join(", ")}. ` + `Prerequisites: ${(chain?.required_findings ?? []).join(", ")}.`;
|
|
30956
|
+
if (rememberIfAbsent(mc.chain_id, content, "pattern", [mc.severity, "chain"], "engagement", false)) {
|
|
30957
|
+
captured++;
|
|
30958
|
+
}
|
|
30959
|
+
}
|
|
30960
|
+
report.memory = { captured, deduped: blitz.matched_chains.length - captured };
|
|
30961
|
+
}
|
|
30962
|
+
return report;
|
|
30829
30963
|
}
|
|
30830
|
-
function
|
|
30831
|
-
const
|
|
30832
|
-
if (!data)
|
|
30833
|
-
return { error: "attack_vectors.json not loaded" };
|
|
30964
|
+
function listChains() {
|
|
30965
|
+
const chains = loadChains();
|
|
30834
30966
|
return {
|
|
30835
|
-
|
|
30836
|
-
|
|
30837
|
-
|
|
30967
|
+
total: chains.length,
|
|
30968
|
+
chains: chains.map((c) => ({
|
|
30969
|
+
id: c.id,
|
|
30970
|
+
name: c.name,
|
|
30971
|
+
severity: c.severity,
|
|
30972
|
+
steps: c.steps.length,
|
|
30973
|
+
triggers: c.triggers
|
|
30974
|
+
}))
|
|
30838
30975
|
};
|
|
30839
30976
|
}
|
|
30840
|
-
|
|
30841
|
-
|
|
30842
|
-
|
|
30843
|
-
|
|
30844
|
-
|
|
30845
|
-
|
|
30846
|
-
|
|
30847
|
-
|
|
30848
|
-
return
|
|
30977
|
+
var LIVE_SWEEP_PARAMS = ["q", "search", "query", "s", "id", "url", "redirect", "name", "page"];
|
|
30978
|
+
async function runLiveEngagement(url, scope = "", mode = "bug-bounty") {
|
|
30979
|
+
const report = { target: url, mode, findings: [], scope: null, status: "" };
|
|
30980
|
+
const sc = scopeCheck(url, scope, mode);
|
|
30981
|
+
report.scope = sc;
|
|
30982
|
+
if (!sc.allowed) {
|
|
30983
|
+
report.status = "BLOCKED";
|
|
30984
|
+
report.reason = sc.reason;
|
|
30985
|
+
return report;
|
|
30849
30986
|
}
|
|
30850
|
-
|
|
30851
|
-
|
|
30852
|
-
|
|
30853
|
-
|
|
30854
|
-
|
|
30855
|
-
|
|
30856
|
-
|
|
30857
|
-
|
|
30858
|
-
|
|
30859
|
-
}
|
|
30860
|
-
|
|
30861
|
-
|
|
30862
|
-
|
|
30863
|
-
|
|
30987
|
+
const findings = [];
|
|
30988
|
+
for (const param of LIVE_SWEEP_PARAMS) {
|
|
30989
|
+
const verdict = await strikeVerify({ url, param });
|
|
30990
|
+
const finding = makeFinding({
|
|
30991
|
+
title: `Reflected input in parameter '${param}'`,
|
|
30992
|
+
target: { type: "web", host: url, endpoint: `?${param}=` },
|
|
30993
|
+
severity: "medium",
|
|
30994
|
+
cwe: "CWE-79",
|
|
30995
|
+
cweName: "Cross-site Scripting",
|
|
30996
|
+
source: { type: "request_parameter", name: param },
|
|
30997
|
+
sink: { type: "reflected_input", symbol: param },
|
|
30998
|
+
chainId: "reflected_input",
|
|
30999
|
+
chainName: "Reflected input",
|
|
31000
|
+
status: "hypothesis"
|
|
31001
|
+
});
|
|
31002
|
+
findings.push(resolveFinding(finding, verdict));
|
|
31003
|
+
}
|
|
31004
|
+
report.findings = findings.map((f) => ({
|
|
31005
|
+
id: f.id,
|
|
31006
|
+
status: f.status,
|
|
31007
|
+
title: f.title,
|
|
31008
|
+
severity: f.classification.severity,
|
|
31009
|
+
confidence: f.confidence,
|
|
31010
|
+
confidence_level: f.confidence_level,
|
|
31011
|
+
chain_id: f.chain.id,
|
|
31012
|
+
validation: f.validation
|
|
31013
|
+
}));
|
|
31014
|
+
const title = `Blitz Strike — Live Engagement (${url})`;
|
|
31015
|
+
const markdown = reportMarkdown(findings, { title, scope: url, version: VERSION });
|
|
31016
|
+
const json = reportJson(findings, { title, scope: url, version: VERSION });
|
|
31017
|
+
report.report = {
|
|
31018
|
+
summary: JSON.parse(json).summary ?? null,
|
|
31019
|
+
markdown,
|
|
31020
|
+
json,
|
|
31021
|
+
sarif: toSarif(findings.map((f) => ({
|
|
31022
|
+
ruleId: f.chain.id || f.classification.cwe || f.classification.severity,
|
|
31023
|
+
level: sarifLevel(f.classification.severity),
|
|
31024
|
+
message: f.title,
|
|
31025
|
+
file: url,
|
|
31026
|
+
line: 1
|
|
31027
|
+
})))
|
|
31028
|
+
};
|
|
31029
|
+
report.status = "COMPLETE";
|
|
31030
|
+
report.note = "Autonomous live marker sweep. Each parameter was validated with a marker + " + "negative control; confirmed reflections are real findings, everything else " + "is a hypothesis or false positive. Re-run with explicit strike_verify for " + "deeper per-endpoint validation.";
|
|
31031
|
+
return report;
|
|
30864
31032
|
}
|
|
30865
|
-
|
|
30866
|
-
|
|
30867
|
-
|
|
31033
|
+
|
|
31034
|
+
// src/catalog.ts
|
|
31035
|
+
import { readFileSync as readFileSync7, readdirSync as readdirSync2 } from "node:fs";
|
|
31036
|
+
import { join as join7 } from "node:path";
|
|
31037
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
31038
|
+
import { spawnSync } from "node:child_process";
|
|
31039
|
+
var ROOT2 = join7(fileURLToPath4(new URL(".", import.meta.url)), "..");
|
|
31040
|
+
var _toolsCache = null;
|
|
31041
|
+
var _skillsCache = null;
|
|
31042
|
+
function loadTools() {
|
|
31043
|
+
if (_toolsCache)
|
|
31044
|
+
return _toolsCache;
|
|
30868
31045
|
try {
|
|
30869
|
-
|
|
31046
|
+
const raw = JSON.parse(readFileSync7(join7(ROOT2, "tools-catalog.json"), "utf8"));
|
|
31047
|
+
_toolsCache = raw.tools ?? [];
|
|
30870
31048
|
} catch {
|
|
30871
|
-
|
|
31049
|
+
_toolsCache = [];
|
|
30872
31050
|
}
|
|
31051
|
+
return _toolsCache;
|
|
30873
31052
|
}
|
|
30874
|
-
function
|
|
30875
|
-
|
|
30876
|
-
|
|
30877
|
-
const
|
|
30878
|
-
|
|
30879
|
-
|
|
30880
|
-
|
|
30881
|
-
|
|
30882
|
-
|
|
30883
|
-
|
|
30884
|
-
|
|
30885
|
-
|
|
30886
|
-
|
|
30887
|
-
|
|
30888
|
-
|
|
30889
|
-
|
|
30890
|
-
|
|
30891
|
-
|
|
30892
|
-
|
|
30893
|
-
|
|
30894
|
-
|
|
30895
|
-
|
|
30896
|
-
|
|
30897
|
-
|
|
30898
|
-
|
|
30899
|
-
|
|
31053
|
+
function loadSkills() {
|
|
31054
|
+
if (_skillsCache)
|
|
31055
|
+
return _skillsCache;
|
|
31056
|
+
const dir = join7(ROOT2, "skills");
|
|
31057
|
+
const out = [];
|
|
31058
|
+
try {
|
|
31059
|
+
for (const f of readdirSync2(dir)) {
|
|
31060
|
+
if (!f.endsWith(".md"))
|
|
31061
|
+
continue;
|
|
31062
|
+
const p = join7(dir, f);
|
|
31063
|
+
const text = readFileSync7(p, "utf8");
|
|
31064
|
+
const descMatch = text.match(/^description:\s*(.+)$/m);
|
|
31065
|
+
out.push({
|
|
31066
|
+
name: f.replace(/\.md$/, ""),
|
|
31067
|
+
file: f,
|
|
31068
|
+
description: (descMatch?.[1] ?? "").trim().slice(0, 200),
|
|
31069
|
+
size: text.length
|
|
31070
|
+
});
|
|
31071
|
+
}
|
|
31072
|
+
} catch {}
|
|
31073
|
+
_skillsCache = out;
|
|
31074
|
+
return out;
|
|
31075
|
+
}
|
|
31076
|
+
function toolLookup(name) {
|
|
31077
|
+
const tools = loadTools();
|
|
31078
|
+
const t = tools.find((x) => x.name.toLowerCase() === name.toLowerCase());
|
|
31079
|
+
if (!t) {
|
|
31080
|
+
return {
|
|
31081
|
+
found: false,
|
|
31082
|
+
query: name,
|
|
31083
|
+
suggestions: tools.filter((x) => x.name.includes(name.toLowerCase()) || name.toLowerCase().includes(x.name)).slice(0, 5).map((x) => x.name)
|
|
31084
|
+
};
|
|
30900
31085
|
}
|
|
30901
|
-
const
|
|
30902
|
-
const
|
|
30903
|
-
|
|
30904
|
-
|
|
30905
|
-
|
|
30906
|
-
|
|
30907
|
-
|
|
30908
|
-
|
|
30909
|
-
|
|
31086
|
+
const manual = manualForTool(t.name);
|
|
31087
|
+
const result = { found: true, tool: t };
|
|
31088
|
+
if (manual && manual.content) {
|
|
31089
|
+
result.manual = {
|
|
31090
|
+
name: manual.name,
|
|
31091
|
+
category: manual.category,
|
|
31092
|
+
preview: manual.content.slice(0, 1500),
|
|
31093
|
+
full_length: manual.content.length
|
|
31094
|
+
};
|
|
31095
|
+
} else {
|
|
31096
|
+
result.manual = null;
|
|
30910
31097
|
}
|
|
30911
|
-
return
|
|
31098
|
+
return result;
|
|
30912
31099
|
}
|
|
30913
|
-
function
|
|
30914
|
-
const
|
|
30915
|
-
const
|
|
30916
|
-
|
|
30917
|
-
|
|
30918
|
-
const dir = join8(PAYLOADS, exact);
|
|
30919
|
-
const files = listFiles(dir);
|
|
30920
|
-
if (!file) {
|
|
30921
|
-
const readme = files.find((f) => f.toLowerCase() === "readme.md");
|
|
30922
|
-
if (readme) {
|
|
30923
|
-
return { found: true, category: exact, file: readme, content: readFileSync8(join8(dir, readme), "utf8").slice(0, 20000) };
|
|
30924
|
-
}
|
|
30925
|
-
return { found: true, category: exact, files };
|
|
31100
|
+
function listTools() {
|
|
31101
|
+
const tools = loadTools();
|
|
31102
|
+
const byCat = {};
|
|
31103
|
+
for (const t of tools) {
|
|
31104
|
+
(byCat[t.category] ??= []).push(t.name);
|
|
30926
31105
|
}
|
|
30927
|
-
|
|
30928
|
-
if (!f)
|
|
30929
|
-
return { found: false, category: exact, file, suggestions: files };
|
|
30930
|
-
return { found: true, category: exact, file: f, content: readFileSync8(join8(dir, f), "utf8").slice(0, 20000) };
|
|
31106
|
+
return { total: tools.length, categories: byCat, tools: tools.map((t) => ({ name: t.name, category: t.category, description: t.description })) };
|
|
30931
31107
|
}
|
|
30932
|
-
function
|
|
31108
|
+
function skillLookup(topic) {
|
|
31109
|
+
const skills = loadSkills();
|
|
31110
|
+
const q = topic.toLowerCase();
|
|
31111
|
+
const scored = skills.map((s) => {
|
|
31112
|
+
const hay = `${s.name} ${s.description}`.toLowerCase();
|
|
31113
|
+
const nameHit = s.name.includes(q);
|
|
31114
|
+
const descHit = s.description.includes(q);
|
|
31115
|
+
const wordHit = q.split(/\s+/).filter((w) => w && hay.includes(w)).length;
|
|
31116
|
+
return { s, score: (nameHit ? 10 : 0) + (descHit ? 5 : 0) + wordHit * 2 };
|
|
31117
|
+
}).filter((x) => x.score > 0).sort((a, b) => b.score - a.score);
|
|
31118
|
+
if (scored.length === 0) {
|
|
31119
|
+
return { found: false, query: topic, suggestions: skills.map((s) => s.name).slice(0, 10) };
|
|
31120
|
+
}
|
|
31121
|
+
return {
|
|
31122
|
+
found: true,
|
|
31123
|
+
matches: scored.slice(0, 5).map((x) => ({ name: x.s.name, description: x.s.description, file: x.s.file }))
|
|
31124
|
+
};
|
|
31125
|
+
}
|
|
31126
|
+
function listSkills() {
|
|
31127
|
+
const skills = loadSkills();
|
|
31128
|
+
return { total: skills.length, skills: skills.map((s) => ({ name: s.name, description: s.description })) };
|
|
31129
|
+
}
|
|
31130
|
+
function readSkill(name) {
|
|
31131
|
+
const skills = loadSkills();
|
|
31132
|
+
const s = skills.find((x) => x.name.toLowerCase() === name.toLowerCase());
|
|
31133
|
+
if (!s)
|
|
31134
|
+
return { found: false, query: name };
|
|
30933
31135
|
try {
|
|
30934
|
-
|
|
31136
|
+
const text = readFileSync7(join7(ROOT2, "skills", s.file), "utf8");
|
|
31137
|
+
return { found: true, name: s.name, content: text.slice(0, 12000) };
|
|
30935
31138
|
} catch {
|
|
30936
|
-
return
|
|
31139
|
+
return { found: false, query: name, error: "unreadable" };
|
|
30937
31140
|
}
|
|
30938
31141
|
}
|
|
30939
|
-
function
|
|
31142
|
+
function ensureTool(name) {
|
|
31143
|
+
const tools = loadTools();
|
|
31144
|
+
const t = tools.find((x) => x.name.toLowerCase() === name.toLowerCase());
|
|
31145
|
+
if (!t) {
|
|
31146
|
+
return { found: false, query: name, suggestions: tools.slice(0, 5).map((x) => x.name) };
|
|
31147
|
+
}
|
|
31148
|
+
const check = spawnSync(t.check_installed.command, { shell: true, timeout: 15000 });
|
|
31149
|
+
if (check.status !== 127 && check.error === undefined) {
|
|
31150
|
+
return { name: t.name, installed: true, action: "none", note: "already installed" };
|
|
31151
|
+
}
|
|
31152
|
+
const platform = process.platform;
|
|
31153
|
+
const cmd = t.install[platform] ?? t.install.linux;
|
|
31154
|
+
if (!cmd) {
|
|
31155
|
+
return { name: t.name, installed: false, action: "none", note: `no install command for ${platform}` };
|
|
31156
|
+
}
|
|
31157
|
+
const r = spawnSync(cmd, { shell: true, timeout: 120000 });
|
|
31158
|
+
const ok = r.status === 0;
|
|
31159
|
+
return {
|
|
31160
|
+
name: t.name,
|
|
31161
|
+
installed: ok,
|
|
31162
|
+
action: "install",
|
|
31163
|
+
command: cmd,
|
|
31164
|
+
output: (r.stdout?.toString() ?? "").slice(-400) || (r.stderr?.toString() ?? "").slice(-400)
|
|
31165
|
+
};
|
|
31166
|
+
}
|
|
31167
|
+
|
|
31168
|
+
// src/intel.ts
|
|
31169
|
+
import { readFileSync as readFileSync8, existsSync as existsSync5 } from "node:fs";
|
|
31170
|
+
import { join as join8 } from "node:path";
|
|
31171
|
+
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
31172
|
+
import { readdirSync as readdirSync3 } from "node:fs";
|
|
31173
|
+
import { homedir as homedir3 } from "node:os";
|
|
31174
|
+
var ROOT3 = join8(fileURLToPath5(new URL(".", import.meta.url)), "..");
|
|
31175
|
+
var INTEL = join8(ROOT3, "intelligence");
|
|
31176
|
+
function loadJson(name) {
|
|
31177
|
+
const p = join8(INTEL, `${name}.json`);
|
|
31178
|
+
if (!existsSync5(p))
|
|
31179
|
+
return null;
|
|
30940
31180
|
try {
|
|
30941
|
-
|
|
30942
|
-
const walk = (dir) => {
|
|
30943
|
-
for (const e of readdirSync3(dir, { withFileTypes: true })) {
|
|
30944
|
-
if (e.isDirectory())
|
|
30945
|
-
walk(join8(dir, e.name));
|
|
30946
|
-
else if (e.name.endsWith(".yaml"))
|
|
30947
|
-
n++;
|
|
30948
|
-
}
|
|
30949
|
-
};
|
|
30950
|
-
walk(TEMPLATES);
|
|
30951
|
-
return n;
|
|
31181
|
+
return JSON.parse(readFileSync8(p, "utf8"));
|
|
30952
31182
|
} catch {
|
|
30953
|
-
return
|
|
31183
|
+
return null;
|
|
30954
31184
|
}
|
|
30955
31185
|
}
|
|
30956
|
-
function
|
|
30957
|
-
const
|
|
30958
|
-
|
|
30959
|
-
|
|
30960
|
-
|
|
30961
|
-
|
|
30962
|
-
|
|
30963
|
-
|
|
30964
|
-
|
|
30965
|
-
|
|
30966
|
-
|
|
30967
|
-
|
|
30968
|
-
|
|
30969
|
-
|
|
31186
|
+
function detectWaf(headers, body = "") {
|
|
31187
|
+
const sigs = loadJson("waf_signatures");
|
|
31188
|
+
if (!sigs)
|
|
31189
|
+
return { detected: false, wafs: [], error: "waf_signatures.json not loaded" };
|
|
31190
|
+
const headerSigs = sigs.header_signatures ?? [];
|
|
31191
|
+
const bodySigs = sigs.body_signatures ?? [];
|
|
31192
|
+
const matches = [];
|
|
31193
|
+
const headerKeys = Object.keys(headers).map((k) => k.toLowerCase());
|
|
31194
|
+
for (const s of headerSigs) {
|
|
31195
|
+
const h = (s.header ?? "").toLowerCase();
|
|
31196
|
+
const pattern = (s.pattern ?? "").toLowerCase();
|
|
31197
|
+
if (!h)
|
|
31198
|
+
continue;
|
|
31199
|
+
if (pattern) {
|
|
31200
|
+
const val = (headers[h] ?? headers[s.header ?? ""] ?? "").toLowerCase();
|
|
31201
|
+
if (val.includes(pattern)) {
|
|
31202
|
+
matches.push({ waf: s.waf, header: s.header, pattern: s.pattern, confidence: s.confidence, via: "header-value" });
|
|
31203
|
+
}
|
|
31204
|
+
} else {
|
|
31205
|
+
if (headerKeys.includes(h)) {
|
|
31206
|
+
matches.push({ waf: s.waf, header: s.header, confidence: s.confidence, via: "header-name" });
|
|
31207
|
+
}
|
|
31208
|
+
}
|
|
31209
|
+
}
|
|
31210
|
+
for (const s of bodySigs) {
|
|
31211
|
+
const pattern = (s.pattern ?? "").toLowerCase();
|
|
31212
|
+
if (pattern && body.toLowerCase().includes(pattern)) {
|
|
31213
|
+
matches.push({ waf: s.waf, pattern: s.pattern, confidence: s.confidence, via: "body" });
|
|
31214
|
+
}
|
|
31215
|
+
}
|
|
31216
|
+
const byWaf = new Map;
|
|
31217
|
+
for (const m of matches) {
|
|
31218
|
+
const key = m.waf;
|
|
31219
|
+
const prev = byWaf.get(key);
|
|
31220
|
+
if (!prev || m.confidence > prev.confidence)
|
|
31221
|
+
byWaf.set(key, m);
|
|
31222
|
+
}
|
|
31223
|
+
const detected = [...byWaf.values()].sort((a, b) => b.confidence - a.confidence);
|
|
31224
|
+
return {
|
|
31225
|
+
detected: detected.length > 0,
|
|
31226
|
+
wafs: detected,
|
|
31227
|
+
block_status_codes: sigs.block_status_codes ?? []
|
|
31228
|
+
};
|
|
31229
|
+
}
|
|
31230
|
+
function techCorrelation(tech) {
|
|
31231
|
+
const data = loadJson("tech_correlations");
|
|
31232
|
+
if (!data)
|
|
31233
|
+
return { found: false, error: "tech_correlations.json not loaded" };
|
|
31234
|
+
const key = tech.toLowerCase();
|
|
31235
|
+
const entry = data[key];
|
|
31236
|
+
if (!entry) {
|
|
31237
|
+
const matches = Object.keys(data).filter((k) => k.includes(key) || key.includes(k)).slice(0, 5);
|
|
31238
|
+
return { found: false, tech, suggestions: matches };
|
|
31239
|
+
}
|
|
31240
|
+
return { found: true, tech: key, vulns: entry.vulns ?? [], paths: entry.paths ?? [], detection: entry.detection ?? { vulns: entry.vulns, paths: entry.paths } };
|
|
31241
|
+
}
|
|
31242
|
+
function cveCorrelation(cveId) {
|
|
31243
|
+
const data = loadJson("cve_correlations");
|
|
31244
|
+
if (!data)
|
|
31245
|
+
return { error: "cve_correlations.json not loaded" };
|
|
31246
|
+
const key = cveId.toUpperCase();
|
|
31247
|
+
const entry = data[key];
|
|
31248
|
+
if (!entry)
|
|
31249
|
+
return { found: false, cve: key, total_known: Object.keys(data).length };
|
|
31250
|
+
return { found: true, cve: key, ...entry };
|
|
31251
|
+
}
|
|
31252
|
+
function portCorrelation(port) {
|
|
31253
|
+
const data = loadJson("port_correlations");
|
|
31254
|
+
if (!data)
|
|
31255
|
+
return { error: "port_correlations.json not loaded" };
|
|
31256
|
+
const key = String(port);
|
|
31257
|
+
const entry = data[key];
|
|
31258
|
+
if (!entry)
|
|
31259
|
+
return { found: false, port: key, total_known: Object.keys(data).length };
|
|
31260
|
+
return { found: true, port: key, ...entry };
|
|
31261
|
+
}
|
|
31262
|
+
function fuzzerPayloads(category) {
|
|
31263
|
+
const data = loadJson("fuzzer_data");
|
|
31264
|
+
if (!data)
|
|
31265
|
+
return { error: "fuzzer_data.json not loaded" };
|
|
31266
|
+
if (category) {
|
|
31267
|
+
const c = data[category] ?? data[category.toUpperCase()];
|
|
31268
|
+
return { category, found: Boolean(c), data: c ?? null };
|
|
31269
|
+
}
|
|
31270
|
+
return { categories: Object.keys(data), fuzz_points: data.FUZZ_POINTS ?? null, payloads: data.FUZZ_PAYLOADS ?? null };
|
|
31271
|
+
}
|
|
31272
|
+
function intelSummary() {
|
|
31273
|
+
const waf = loadJson("waf_signatures");
|
|
31274
|
+
const tech = loadJson("tech_correlations");
|
|
31275
|
+
const cve = loadJson("cve_correlations");
|
|
31276
|
+
const port = loadJson("port_correlations");
|
|
31277
|
+
const fuzz = loadJson("fuzzer_data");
|
|
31278
|
+
const av = loadJson("attack_vectors");
|
|
31279
|
+
return {
|
|
31280
|
+
waf_signatures: waf ? (waf.header_signatures?.length ?? 0) + (waf.body_signatures?.length ?? 0) : 0,
|
|
31281
|
+
tech_correlations: tech ? Object.keys(tech).length : 0,
|
|
31282
|
+
cve_correlations: cve ? Object.keys(cve).length : 0,
|
|
31283
|
+
port_correlations: port ? Object.keys(port).length : 0,
|
|
31284
|
+
fuzzer_categories: fuzz ? Object.keys(fuzz).length : 0,
|
|
31285
|
+
payload_categories: listPayloadCategories().length,
|
|
31286
|
+
nuclei_templates: countTemplates(),
|
|
31287
|
+
attack_vector_categories: av ? av.categories.length : 0,
|
|
31288
|
+
attack_vectors: av ? av.categories.reduce((n, c) => n + c.vectors.length, 0) : 0
|
|
31289
|
+
};
|
|
31290
|
+
}
|
|
31291
|
+
function listAttackVectors() {
|
|
31292
|
+
const data = loadJson("attack_vectors");
|
|
31293
|
+
if (!data)
|
|
31294
|
+
return { error: "attack_vectors.json not loaded" };
|
|
31295
|
+
return {
|
|
31296
|
+
total_categories: data.categories.length,
|
|
31297
|
+
total_vectors: data.categories.reduce((n, c) => n + c.vectors.length, 0),
|
|
31298
|
+
categories: data.categories.map((c) => ({ id: c.id, title: c.title, count: c.vectors.length }))
|
|
31299
|
+
};
|
|
31300
|
+
}
|
|
31301
|
+
function attackVectors(category) {
|
|
31302
|
+
const data = loadJson("attack_vectors");
|
|
31303
|
+
if (!data)
|
|
31304
|
+
return { error: "attack_vectors.json not loaded" };
|
|
31305
|
+
const q = category.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
31306
|
+
const found = data.categories.find((c) => c.id === q || c.id.includes(q) || c.title.toLowerCase().includes(category.toLowerCase()));
|
|
31307
|
+
if (!found) {
|
|
31308
|
+
const matches = data.categories.filter((c) => c.title.toLowerCase().includes(category.toLowerCase()) || c.id.includes(q)).slice(0, 5).map((c) => c.id);
|
|
31309
|
+
return { found: false, query: category, suggestions: matches };
|
|
31310
|
+
}
|
|
31311
|
+
return { found: true, id: found.id, title: found.title, count: found.vectors.length, vectors: found.vectors };
|
|
31312
|
+
}
|
|
31313
|
+
var PACKAGE_PAYLOADS = join8(ROOT3, "payloads");
|
|
31314
|
+
var PACKAGE_TEMPLATES = join8(ROOT3, "templates");
|
|
31315
|
+
var DATA_ROOT = process.env.BLITZSTRIKE_DATA ?? join8(homedir3(), ".blitzstrike", "data");
|
|
31316
|
+
function payloadsDir() {
|
|
31317
|
+
if (existsSync5(PACKAGE_PAYLOADS))
|
|
31318
|
+
return PACKAGE_PAYLOADS;
|
|
31319
|
+
return join8(DATA_ROOT, "payloads");
|
|
31320
|
+
}
|
|
31321
|
+
function templatesDir() {
|
|
31322
|
+
if (existsSync5(PACKAGE_TEMPLATES))
|
|
31323
|
+
return PACKAGE_TEMPLATES;
|
|
31324
|
+
return join8(DATA_ROOT, "templates");
|
|
31325
|
+
}
|
|
31326
|
+
var PAYLOADS = payloadsDir();
|
|
31327
|
+
var TEMPLATES = templatesDir();
|
|
31328
|
+
function listPayloadCategories() {
|
|
31329
|
+
try {
|
|
31330
|
+
return readdirSync3(PAYLOADS, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
|
|
31331
|
+
} catch {
|
|
31332
|
+
return [];
|
|
31333
|
+
}
|
|
31334
|
+
}
|
|
31335
|
+
function payloadLookup(topic) {
|
|
31336
|
+
const cats = listPayloadCategories();
|
|
31337
|
+
const key = topic.toLowerCase();
|
|
31338
|
+
const ALIASES = {
|
|
31339
|
+
sqli: "sql injection",
|
|
31340
|
+
xss: "xss injection",
|
|
31341
|
+
ssrf: "server side request forgery",
|
|
31342
|
+
ssti: "server side template injection",
|
|
31343
|
+
jwt: "json web token",
|
|
31344
|
+
lfi: "file inclusion",
|
|
31345
|
+
rfi: "file inclusion",
|
|
31346
|
+
xxe: "xxe injection",
|
|
31347
|
+
idor: "insecure direct object references",
|
|
31348
|
+
csrf: "cross-site request forgery",
|
|
31349
|
+
cmdi: "command injection",
|
|
31350
|
+
rce: "command injection",
|
|
31351
|
+
cors: "cors misconfiguration",
|
|
31352
|
+
crlf: "crlf injection",
|
|
31353
|
+
prototype: "prototype pollution",
|
|
31354
|
+
nosql: "nosql injection",
|
|
31355
|
+
upload: "upload insecure files"
|
|
31356
|
+
};
|
|
31357
|
+
const normalized = ALIASES[key] ?? key;
|
|
31358
|
+
const exact = cats.find((c) => c.toLowerCase() === normalized);
|
|
31359
|
+
if (exact) {
|
|
31360
|
+
return { found: true, category: exact, files: listFiles(join8(PAYLOADS, exact)) };
|
|
31361
|
+
}
|
|
31362
|
+
const keyTokens = normalized.split(/[^a-z0-9]+/).filter((t) => t.length >= 3);
|
|
31363
|
+
const matches = cats.filter((c) => {
|
|
31364
|
+
const cl = c.toLowerCase();
|
|
31365
|
+
if (cl.includes(normalized) || normalized.includes(cl))
|
|
31366
|
+
return true;
|
|
31367
|
+
return keyTokens.some((tok) => new RegExp(`(^|[^a-z0-9])${tok}`).test(cl));
|
|
31368
|
+
});
|
|
31369
|
+
if (matches.length > 0) {
|
|
31370
|
+
return { found: true, category: matches[0], files: listFiles(join8(PAYLOADS, matches[0])), alternatives: matches.slice(0, 10) };
|
|
31371
|
+
}
|
|
31372
|
+
return { found: false, topic, suggestions: cats.slice(0, 20) };
|
|
31373
|
+
}
|
|
31374
|
+
function readPayload(category, file) {
|
|
31375
|
+
const cats = listPayloadCategories();
|
|
31376
|
+
const exact = cats.find((c) => c.toLowerCase() === category.toLowerCase());
|
|
31377
|
+
if (!exact)
|
|
31378
|
+
return { found: false, category, suggestions: cats.slice(0, 20) };
|
|
31379
|
+
const dir = join8(PAYLOADS, exact);
|
|
31380
|
+
const files = listFiles(dir);
|
|
31381
|
+
if (!file) {
|
|
31382
|
+
const readme = files.find((f) => f.toLowerCase() === "readme.md");
|
|
31383
|
+
if (readme) {
|
|
31384
|
+
return { found: true, category: exact, file: readme, content: readFileSync8(join8(dir, readme), "utf8").slice(0, 20000) };
|
|
31385
|
+
}
|
|
31386
|
+
return { found: true, category: exact, files };
|
|
31387
|
+
}
|
|
31388
|
+
const f = files.find((x) => x.toLowerCase() === file.toLowerCase());
|
|
31389
|
+
if (!f)
|
|
31390
|
+
return { found: false, category: exact, file, suggestions: files };
|
|
31391
|
+
return { found: true, category: exact, file: f, content: readFileSync8(join8(dir, f), "utf8").slice(0, 20000) };
|
|
31392
|
+
}
|
|
31393
|
+
function listFiles(dir) {
|
|
31394
|
+
try {
|
|
31395
|
+
return readdirSync3(dir).filter((f) => f.endsWith(".md") || f.endsWith(".txt") || f.endsWith(".yaml")).sort();
|
|
31396
|
+
} catch {
|
|
31397
|
+
return [];
|
|
31398
|
+
}
|
|
31399
|
+
}
|
|
31400
|
+
function countTemplates() {
|
|
31401
|
+
try {
|
|
31402
|
+
let n = 0;
|
|
31403
|
+
const walk = (dir) => {
|
|
31404
|
+
for (const e of readdirSync3(dir, { withFileTypes: true })) {
|
|
31405
|
+
if (e.isDirectory())
|
|
31406
|
+
walk(join8(dir, e.name));
|
|
31407
|
+
else if (e.name.endsWith(".yaml"))
|
|
31408
|
+
n++;
|
|
31409
|
+
}
|
|
31410
|
+
};
|
|
31411
|
+
walk(TEMPLATES);
|
|
31412
|
+
return n;
|
|
31413
|
+
} catch {
|
|
31414
|
+
return 0;
|
|
31415
|
+
}
|
|
31416
|
+
}
|
|
31417
|
+
function templateLookup(topic, limit = 10) {
|
|
31418
|
+
const key = topic.toLowerCase();
|
|
31419
|
+
const hits = [];
|
|
31420
|
+
const walk = (dir, depth = 0) => {
|
|
31421
|
+
if (depth > 6 || hits.length >= limit * 3)
|
|
31422
|
+
return;
|
|
31423
|
+
let entries;
|
|
31424
|
+
try {
|
|
31425
|
+
entries = readdirSync3(dir, { withFileTypes: true });
|
|
31426
|
+
} catch {
|
|
31427
|
+
return;
|
|
31428
|
+
}
|
|
31429
|
+
for (const e of entries) {
|
|
31430
|
+
if (e.isDirectory())
|
|
30970
31431
|
walk(join8(dir, e.name), depth + 1);
|
|
30971
31432
|
else if (e.name.endsWith(".yaml")) {
|
|
30972
31433
|
const name = e.name.toLowerCase();
|
|
@@ -51632,506 +52093,168 @@ var API_PATHS = [
|
|
|
51632
52093
|
"/api-docs",
|
|
51633
52094
|
"/v2/api-docs",
|
|
51634
52095
|
"/v3/api-docs",
|
|
51635
|
-
"/graphql",
|
|
51636
|
-
"/graphiql",
|
|
51637
|
-
"/actuator",
|
|
51638
|
-
"/actuator/health",
|
|
51639
|
-
"/actuator/env",
|
|
51640
|
-
"/api/swagger.json",
|
|
51641
|
-
"/docs",
|
|
51642
|
-
"/redoc"
|
|
51643
|
-
];
|
|
51644
|
-
async function discoverApi(base) {
|
|
51645
|
-
const out = [];
|
|
51646
|
-
for (const p of API_PATHS) {
|
|
51647
|
-
const r = await httpGet(`${base}${p}`);
|
|
51648
|
-
if (r.status >= 200 && r.status < 400) {
|
|
51649
|
-
out.push({ path: p, status: r.status, body_size: r.body.length });
|
|
51650
|
-
}
|
|
51651
|
-
}
|
|
51652
|
-
return out;
|
|
51653
|
-
}
|
|
51654
|
-
function extractVersion(headers, body) {
|
|
51655
|
-
const out = [];
|
|
51656
|
-
const server = headers["server"] ?? headers["x-powered-by"] ?? "";
|
|
51657
|
-
const serverVer = /^(nginx|apache|microsoft-iis|openresty|liteSpeed|caddy)\/?([0-9][0-9.\-]*)?/i.exec(server);
|
|
51658
|
-
if (serverVer) {
|
|
51659
|
-
out.push({ product: serverVer[1].toLowerCase(), version: serverVer[2] ?? "unknown", evidence: server });
|
|
51660
|
-
}
|
|
51661
|
-
const phpVer = /php\/([0-9][0-9.\-]*)/i.exec(server);
|
|
51662
|
-
if (phpVer)
|
|
51663
|
-
out.push({ product: "php", version: phpVer[1], evidence: server });
|
|
51664
|
-
const wpVer = /content="WordPress\s+([0-9][0-9.]*)/i.exec(body);
|
|
51665
|
-
if (wpVer)
|
|
51666
|
-
out.push({ product: "wordpress", version: wpVer[1], evidence: `meta generator: WordPress ${wpVer[1]}` });
|
|
51667
|
-
const joomlaVer = /Joomla!?\s*([0-9][0-9.]*)/i.exec(body);
|
|
51668
|
-
if (joomlaVer)
|
|
51669
|
-
out.push({ product: "joomla", version: joomlaVer[1], evidence: `body: Joomla ${joomlaVer[1]}` });
|
|
51670
|
-
const drupalVer = /Drupal\s+([0-9][0-9.]*)/i.exec(body);
|
|
51671
|
-
if (drupalVer)
|
|
51672
|
-
out.push({ product: "drupal", version: drupalVer[1], evidence: `body: Drupal ${drupalVer[1]}` });
|
|
51673
|
-
const powered = headers["x-powered-by"] ?? "";
|
|
51674
|
-
if (powered && !out.some((o) => powered.includes(o.product))) {
|
|
51675
|
-
out.push({ product: powered.split("/")[0].toLowerCase(), version: powered.split("/")[1] ?? "unknown", evidence: powered });
|
|
51676
|
-
}
|
|
51677
|
-
return out;
|
|
51678
|
-
}
|
|
51679
|
-
function correlateIntel(techs, versionList, portList) {
|
|
51680
|
-
const tech = techs.map((t) => ({ tech: t, ...techCorrelation(t) }));
|
|
51681
|
-
const versions = versionList.map((v) => ({
|
|
51682
|
-
product: v.product,
|
|
51683
|
-
version: v.version,
|
|
51684
|
-
correlation: techCorrelation(v.product),
|
|
51685
|
-
payload_lookup: payloadLookup(v.product),
|
|
51686
|
-
template_lookup: templateLookup(v.product, 5)
|
|
51687
|
-
}));
|
|
51688
|
-
const ports = portList.map((p) => ({ port: p.port, service: p.service, ...portCorrelation(p.port) }));
|
|
51689
|
-
return { tech, versions, ports };
|
|
51690
|
-
}
|
|
51691
|
-
async function liveRecon(target, includeActive = false) {
|
|
51692
|
-
const base = normalizeTarget2(target);
|
|
51693
|
-
const host = (() => {
|
|
51694
|
-
try {
|
|
51695
|
-
return new URL(base).hostname;
|
|
51696
|
-
} catch {
|
|
51697
|
-
return target;
|
|
51698
|
-
}
|
|
51699
|
-
})();
|
|
51700
|
-
const root = await httpGet(base);
|
|
51701
|
-
const interestingHeaders = {};
|
|
51702
|
-
for (const k of ["server", "x-powered-by", "x-aspnet-version", "x-drupal-cache", "x-generator", "via", "x-cache", "cf-ray"]) {
|
|
51703
|
-
if (root.headers[k])
|
|
51704
|
-
interestingHeaders[k] = root.headers[k];
|
|
51705
|
-
}
|
|
51706
|
-
const fingerprint = {
|
|
51707
|
-
status: root.status,
|
|
51708
|
-
title: (root.body.match(/<title[^>]*>([^<]+)<\/title>/i) ?? [])[1]?.trim().slice(0, 200) ?? "",
|
|
51709
|
-
server: root.headers["server"] ?? root.headers["x-powered-by"] ?? "",
|
|
51710
|
-
headers: interestingHeaders,
|
|
51711
|
-
body_size: root.body.length
|
|
51712
|
-
};
|
|
51713
|
-
const waf = detectWaf(root.headers, root.body);
|
|
51714
|
-
const techSig = [
|
|
51715
|
-
[/wp-content|wp-includes|wp-json/i, "wordpress"],
|
|
51716
|
-
[/wp-login\.php|wp-admin/i, "wordpress"],
|
|
51717
|
-
[/powered by joomla|com_content/i, "joomla"],
|
|
51718
|
-
[/laravel|_token|csrf-token/i, "laravel"],
|
|
51719
|
-
[/react|__NEXT_DATA__|next\/static/i, "nextjs"],
|
|
51720
|
-
[/angular|ng-version/i, "angular"],
|
|
51721
|
-
[/vue|__vue__|v-data/i, "vue"],
|
|
51722
|
-
[/django|csrftoken|__debug__/i, "django"],
|
|
51723
|
-
[/ruby on rails|rails/i, "rails"],
|
|
51724
|
-
[/asp\.net|__VIEWSTATE|__EVENTVALIDATION/i, "aspnet"],
|
|
51725
|
-
[/phpBB|phpbb/i, "phpbb"],
|
|
51726
|
-
[/mybb|mybb/i, "mybb"],
|
|
51727
|
-
[/drupal|Drupal\.settings/i, "drupal"],
|
|
51728
|
-
[/magento|Mage\./i, "magento"],
|
|
51729
|
-
[/shopify|cdn\.shopify/i, "shopify"],
|
|
51730
|
-
[/grafana|kibana|elastic/i, "grafana-kibana"],
|
|
51731
|
-
[/node\.js|express/i, "nodejs"],
|
|
51732
|
-
[/flask|werkzeug/i, "flask"],
|
|
51733
|
-
[/spring|actuator/i, "spring"]
|
|
51734
|
-
];
|
|
51735
|
-
const techs = [...new Set(techSig.filter(([re]) => re.test(root.body)).map(([, t]) => t))];
|
|
51736
|
-
const versions = extractVersion(root.headers, root.body);
|
|
51737
|
-
const { links, scripts } = crawlLinks(base, root.body);
|
|
51738
|
-
const { robots, sitemap } = await fetchRobotsAndSitemap(base);
|
|
51739
|
-
const allUrls = [...links, ...scripts, ...robots, ...sitemap];
|
|
51740
|
-
const crawl = { links, scripts, endpoints: [...new Set(allUrls)], robots, sitemap, count: allUrls.length };
|
|
51741
|
-
const params = extractParams(allUrls);
|
|
51742
|
-
const { subdomains } = await subdomainEnum(host);
|
|
51743
|
-
const { found: dnsFindings } = await dnsBrute(host);
|
|
51744
|
-
const { urls: wayback } = await waybackUrls(host);
|
|
51745
|
-
const apiEndpoints = (await discoverApi(base)).map(({ path, status }) => ({ path, status }));
|
|
51746
|
-
let openPorts = [];
|
|
51747
|
-
if (includeActive && root.status > 0) {
|
|
51748
|
-
const scan = await scanPorts(host);
|
|
51749
|
-
openPorts = scan.map(({ port, service }) => ({ port, service }));
|
|
51750
|
-
}
|
|
51751
|
-
const intel = correlateIntel(techs, versions, openPorts);
|
|
51752
|
-
const nextSteps = [];
|
|
51753
|
-
if (waf.detected)
|
|
51754
|
-
nextSteps.push(`WAF detected (${waf.wafs?.map((w) => w.waf).join(", ")}) — consult detect_waf + WAF bypass playbook before exploitation.`);
|
|
51755
|
-
for (const t of techs) {
|
|
51756
|
-
const c = techCorrelation(t);
|
|
51757
|
-
if (c.found)
|
|
51758
|
-
nextSteps.push(`Tech '${t}' maps to known vuln classes — read tech_correlation('${t}') and fetch nuclei templates with template_lookup('${t}').`);
|
|
51759
|
-
}
|
|
51760
|
-
for (const v of versions) {
|
|
51761
|
-
nextSteps.push(`Version ${v.product}/${v.version} detected — check cve_correlation + nvd_lookup for known CVEs affecting ${v.product} ${v.version}.`);
|
|
51762
|
-
}
|
|
51763
|
-
if (crawl.endpoints.length > 0)
|
|
51764
|
-
nextSteps.push(`Found ${crawl.endpoints.length} endpoints — feed them to taint_file/blitz_scan for source analysis, or strike_verify for live validation.`);
|
|
51765
|
-
if (params.length > 0)
|
|
51766
|
-
nextSteps.push(`Discovered input params: ${params.join(", ")} — these are attack-surface entry points; fuzz them with payload_lookup after scope confirms.`);
|
|
51767
|
-
if (subdomains.length > 0)
|
|
51768
|
-
nextSteps.push(`Found ${subdomains.length} subdomains — each is a separate scope surface; enumerate further with fofa_search.`);
|
|
51769
|
-
if (dnsFindings.length > 0)
|
|
51770
|
-
nextSteps.push(`DNS-resolved ${dnsFindings.length} hosts (${dnsFindings.map((d) => d.host).slice(0, 8).join(", ")}${dnsFindings.length > 8 ? "…" : ""}) — each is a live subdomain to recon.`);
|
|
51771
|
-
if (wayback.length > 0)
|
|
51772
|
-
nextSteps.push(`Wayback Machine surfaced ${wayback.length} historical URLs — mine them for hidden endpoints, old params, and forgotten pages; feed to taint_file/blitz_scan.`);
|
|
51773
|
-
if (openPorts.length > 0)
|
|
51774
|
-
nextSteps.push(`Open ports: ${openPorts.map((p) => `${p.port}/${p.service}`).join(", ")} — map services via port_correlation and probe each with the relevant tool manual.`);
|
|
51775
|
-
if (apiEndpoints.length > 0)
|
|
51776
|
-
nextSteps.push(`API endpoints exposed (${apiEndpoints.map((e) => e.path).join(", ")}) — audit them with api-security playbook; check for BOLA/IDOR/auth gaps.`);
|
|
51777
|
-
if (nextSteps.length === 0)
|
|
51778
|
-
nextSteps.push("Nothing exposed on the surface — try subdomain enumeration, port scan (active), or request source access for EAGLE-EYE analysis.");
|
|
51779
|
-
nextSteps.push("Every confirmed finding: record with finding_create, attach confidence_score, redact secrets. A hit is a hypothesis until verified.");
|
|
51780
|
-
return {
|
|
51781
|
-
target: base,
|
|
51782
|
-
host,
|
|
51783
|
-
fingerprint,
|
|
51784
|
-
waf,
|
|
51785
|
-
tech: { detected: techs, server: fingerprint.server },
|
|
51786
|
-
versions,
|
|
51787
|
-
crawl,
|
|
51788
|
-
params,
|
|
51789
|
-
subdomains,
|
|
51790
|
-
dns_findings: dnsFindings,
|
|
51791
|
-
wayback_urls: wayback,
|
|
51792
|
-
api_endpoints: apiEndpoints,
|
|
51793
|
-
open_ports: openPorts,
|
|
51794
|
-
intel,
|
|
51795
|
-
next_steps: nextSteps
|
|
51796
|
-
};
|
|
51797
|
-
}
|
|
51798
|
-
|
|
51799
|
-
// src/strike.ts
|
|
51800
|
-
var DEFAULT_TIMEOUT_MS = 15000;
|
|
51801
|
-
async function httpRequest(url, method, data, headers, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
51802
|
-
const controller = new AbortController;
|
|
51803
|
-
const t = setTimeout(() => controller.abort(), timeoutMs);
|
|
51804
|
-
try {
|
|
51805
|
-
const res = await fetch(url, {
|
|
51806
|
-
method,
|
|
51807
|
-
body: method === "POST" ? data : undefined,
|
|
51808
|
-
headers: { "User-Agent": "blitzstrike/1.0", ...headers ?? {} },
|
|
51809
|
-
signal: controller.signal,
|
|
51810
|
-
redirect: "follow"
|
|
51811
|
-
});
|
|
51812
|
-
const body = await res.text();
|
|
51813
|
-
const hdrs = {};
|
|
51814
|
-
res.headers.forEach((v, k) => {
|
|
51815
|
-
hdrs[k] = v;
|
|
51816
|
-
});
|
|
51817
|
-
return { status: res.status, body: body.slice(0, 20000), headers: hdrs };
|
|
51818
|
-
} catch (e) {
|
|
51819
|
-
return { status: 0, body: String(e), headers: {} };
|
|
51820
|
-
} finally {
|
|
51821
|
-
clearTimeout(t);
|
|
51822
|
-
}
|
|
51823
|
-
}
|
|
51824
|
-
function defaultMarker() {
|
|
51825
|
-
return `BS${Date.now().toString(36).toUpperCase()}${Math.random().toString(36).slice(2, 8).toUpperCase()}`;
|
|
51826
|
-
}
|
|
51827
|
-
function inject(target, value, param) {
|
|
51828
|
-
const placeholder = "{{MARKER}}";
|
|
51829
|
-
if (target.includes(placeholder))
|
|
51830
|
-
return target.split(placeholder).join(value);
|
|
51831
|
-
if (target.includes("?"))
|
|
51832
|
-
return `${target}&${param}=${encodeURIComponent(value)}`;
|
|
51833
|
-
return `${target}?${param}=${encodeURIComponent(value)}`;
|
|
51834
|
-
}
|
|
51835
|
-
async function strikeVerify(input) {
|
|
51836
|
-
const method = input.method ?? "GET";
|
|
51837
|
-
const param = input.param ?? "q";
|
|
51838
|
-
const marker = input.marker ?? defaultMarker();
|
|
51839
|
-
const control = input.control ?? defaultMarker();
|
|
51840
|
-
const baseUrl = input.url;
|
|
51841
|
-
const timeoutMs = input.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
51842
|
-
const baselineTarget = inject(baseUrl, "", param).replace(/[?&]q=$/, "").replace(/[?&]$/, "");
|
|
51843
|
-
const markerTarget = inject(baseUrl, marker, param);
|
|
51844
|
-
const controlTarget = inject(baseUrl, control, param);
|
|
51845
|
-
const baseline = await httpRequest(baselineTarget, method, input.data, input.headers, timeoutMs);
|
|
51846
|
-
const markerResp = await httpRequest(markerTarget, method, input.data, input.headers, timeoutMs);
|
|
51847
|
-
const controlResp = await httpRequest(controlTarget, method, input.data, input.headers, timeoutMs);
|
|
51848
|
-
if (baseline.status === 0 || markerResp.status === 0) {
|
|
51849
|
-
return {
|
|
51850
|
-
status: "blocked",
|
|
51851
|
-
marker_reflected: false,
|
|
51852
|
-
control_reflected: false,
|
|
51853
|
-
baseline: { status: baseline.status, body_preview: redactSecrets(baseline.body.slice(0, 400)), body_hash: sha256(baseline.body) },
|
|
51854
|
-
marker_response: { status: markerResp.status, body_preview: redactSecrets(markerResp.body.slice(0, 400)), body_hash: sha256(markerResp.body) },
|
|
51855
|
-
control_response: { status: controlResp.status, body_preview: redactSecrets(controlResp.body.slice(0, 400)), body_hash: sha256(controlResp.body) },
|
|
51856
|
-
reason: "request failed or target unreachable — cannot validate",
|
|
51857
|
-
evidence: []
|
|
51858
|
-
};
|
|
51859
|
-
}
|
|
51860
|
-
const markerReflected = markerResp.body.includes(marker);
|
|
51861
|
-
const controlReflected = controlResp.body.includes(control);
|
|
51862
|
-
let status;
|
|
51863
|
-
let reason;
|
|
51864
|
-
if (markerReflected && !controlReflected) {
|
|
51865
|
-
status = "confirmed";
|
|
51866
|
-
reason = "marker reflected; negative control did not — the sink reflects attacker-controlled input (real finding)";
|
|
51867
|
-
} else if (markerReflected && controlReflected) {
|
|
51868
|
-
status = "false_positive";
|
|
51869
|
-
reason = "marker AND negative control both reflected — behaviour is indistinguishable from benign reflection (likely false positive)";
|
|
51870
|
-
} else if (!markerReflected && !controlReflected) {
|
|
51871
|
-
status = "unconfirmed";
|
|
51872
|
-
reason = "marker not reflected — reachability/exploitability could not be demonstrated live";
|
|
51873
|
-
} else {
|
|
51874
|
-
status = "unconfirmed";
|
|
51875
|
-
reason = "negative control reflected but marker did not — inconsistent, requires deeper analysis";
|
|
51876
|
-
}
|
|
51877
|
-
const evidence = [
|
|
51878
|
-
{
|
|
51879
|
-
type: "baseline_comparison",
|
|
51880
|
-
description: `Baseline response (status ${baseline.status}) for ${redactSecrets(baselineTarget)}`,
|
|
51881
|
-
artifacts: [{ name: "baseline", kind: "http_response", content: `${baseline.status}
|
|
51882
|
-
${baseline.body}` }]
|
|
51883
|
-
},
|
|
51884
|
-
{
|
|
51885
|
-
type: "validation_result",
|
|
51886
|
-
description: `Marker '${redactSecrets(marker)}' reflected=${markerReflected}; control reflected=${controlReflected}`,
|
|
51887
|
-
artifacts: [{ name: "marker_response", kind: "http_response", content: `${markerResp.status}
|
|
51888
|
-
${markerResp.body}` }]
|
|
51889
|
-
},
|
|
51890
|
-
{
|
|
51891
|
-
type: "negative_control",
|
|
51892
|
-
description: `Negative control '${redactSecrets(control)}' reflected=${controlReflected}`,
|
|
51893
|
-
artifacts: [{ name: "control_response", kind: "http_response", content: `${controlResp.status}
|
|
51894
|
-
${controlResp.body}` }]
|
|
51895
|
-
}
|
|
51896
|
-
];
|
|
51897
|
-
return {
|
|
51898
|
-
status,
|
|
51899
|
-
marker_reflected: markerReflected,
|
|
51900
|
-
control_reflected: controlReflected,
|
|
51901
|
-
baseline: { status: baseline.status, body_preview: redactSecrets(baseline.body.slice(0, 400)), body_hash: sha256(baseline.body) },
|
|
51902
|
-
marker_response: { status: markerResp.status, body_preview: redactSecrets(markerResp.body.slice(0, 400)), body_hash: sha256(markerResp.body) },
|
|
51903
|
-
control_response: { status: controlResp.status, body_preview: redactSecrets(controlResp.body.slice(0, 400)), body_hash: sha256(controlResp.body) },
|
|
51904
|
-
reason,
|
|
51905
|
-
evidence
|
|
51906
|
-
};
|
|
51907
|
-
}
|
|
51908
|
-
function resolveFinding(finding, verdict) {
|
|
51909
|
-
let f = finding;
|
|
51910
|
-
if (f.status === "hypothesis") {
|
|
51911
|
-
f = transition(f, "validating");
|
|
51912
|
-
}
|
|
51913
|
-
switch (verdict.status) {
|
|
51914
|
-
case "confirmed":
|
|
51915
|
-
return confirmFinding(f, {
|
|
51916
|
-
evidence: verdict.evidence,
|
|
51917
|
-
negative_control: true,
|
|
51918
|
-
baseline: true
|
|
51919
|
-
});
|
|
51920
|
-
case "false_positive":
|
|
51921
|
-
return rejectFinding(f, "false_positive", verdict.reason, verdict.evidence);
|
|
51922
|
-
case "blocked":
|
|
51923
|
-
return rejectFinding(f, "blocked", verdict.reason, verdict.evidence);
|
|
51924
|
-
case "unconfirmed":
|
|
51925
|
-
case "likely":
|
|
51926
|
-
default: {
|
|
51927
|
-
return {
|
|
51928
|
-
...f,
|
|
51929
|
-
validation: { performed: true, status: verdict.status === "likely" ? "likely" : "unconfirmed", negative_control: verdict.control_reflected === false, baseline: true },
|
|
51930
|
-
remediation: { ...f.remediation, note: verdict.reason },
|
|
51931
|
-
timestamps: { ...f.timestamps, updated: new Date().toISOString() }
|
|
51932
|
-
};
|
|
51933
|
-
}
|
|
51934
|
-
}
|
|
51935
|
-
}
|
|
51936
|
-
|
|
51937
|
-
// src/cvss.ts
|
|
51938
|
-
var AV_WEIGHT = { N: 0.85, A: 0.62, L: 0.55, P: 0.2 };
|
|
51939
|
-
var AC_WEIGHT = { L: 0.77, H: 0.44 };
|
|
51940
|
-
var PR_WEIGHT = {
|
|
51941
|
-
U: { N: 0.85, L: 0.62, H: 0.27 },
|
|
51942
|
-
C: { N: 0.85, L: 0.68, H: 0.5 }
|
|
51943
|
-
};
|
|
51944
|
-
var UI_WEIGHT = { N: 0.85, R: 0.62 };
|
|
51945
|
-
var CIA_WEIGHT = { H: 0.56, L: 0.22, N: 0 };
|
|
51946
|
-
function roundup(value) {
|
|
51947
|
-
return Math.ceil(value * 10) / 10;
|
|
51948
|
-
}
|
|
51949
|
-
function cvssBaseScore(input) {
|
|
51950
|
-
const AV = input.AV ?? "N";
|
|
51951
|
-
const AC = input.AC ?? "L";
|
|
51952
|
-
const PR = input.PR ?? "N";
|
|
51953
|
-
const UI = input.UI ?? "N";
|
|
51954
|
-
const S = input.S ?? "U";
|
|
51955
|
-
const C = input.C ?? "N";
|
|
51956
|
-
const I = input.I ?? "N";
|
|
51957
|
-
const A = input.A ?? "N";
|
|
51958
|
-
const iscBase = 1 - (1 - CIA_WEIGHT[C]) * (1 - CIA_WEIGHT[I]) * (1 - CIA_WEIGHT[A]);
|
|
51959
|
-
let impact;
|
|
51960
|
-
if (S === "U") {
|
|
51961
|
-
impact = 6.42 * iscBase;
|
|
51962
|
-
} else {
|
|
51963
|
-
impact = 7.52 * (iscBase - 0.029) - 3.25 * Math.pow(iscBase - 0.02, 15);
|
|
51964
|
-
}
|
|
51965
|
-
const exploitability = 8.22 * AV_WEIGHT[AV] * AC_WEIGHT[AC] * PR_WEIGHT[S][PR] * UI_WEIGHT[UI];
|
|
51966
|
-
if (impact <= 0)
|
|
51967
|
-
return 0;
|
|
51968
|
-
if (S === "U") {
|
|
51969
|
-
return roundup(Math.min(impact + exploitability, 10));
|
|
51970
|
-
}
|
|
51971
|
-
return roundup(Math.min(1.08 * (impact + exploitability), 10));
|
|
51972
|
-
}
|
|
51973
|
-
function cvssVector(input) {
|
|
51974
|
-
const order = [
|
|
51975
|
-
["AV", input.AV ?? "N"],
|
|
51976
|
-
["AC", input.AC ?? "L"],
|
|
51977
|
-
["PR", input.PR ?? "N"],
|
|
51978
|
-
["UI", input.UI ?? "N"],
|
|
51979
|
-
["S", input.S ?? "U"],
|
|
51980
|
-
["C", input.C ?? "N"],
|
|
51981
|
-
["I", input.I ?? "N"],
|
|
51982
|
-
["A", input.A ?? "N"]
|
|
51983
|
-
];
|
|
51984
|
-
return "CVSS:3.1/" + order.map(([k, v]) => `${k}:${v}`).join("/");
|
|
51985
|
-
}
|
|
51986
|
-
function cvssSeverity(score) {
|
|
51987
|
-
if (score === 0)
|
|
51988
|
-
return "none";
|
|
51989
|
-
if (score < 4)
|
|
51990
|
-
return "low";
|
|
51991
|
-
if (score < 7)
|
|
51992
|
-
return "medium";
|
|
51993
|
-
if (score < 9)
|
|
51994
|
-
return "high";
|
|
51995
|
-
return "critical";
|
|
51996
|
-
}
|
|
51997
|
-
function cvssAssess(input) {
|
|
51998
|
-
const score = cvssBaseScore(input);
|
|
51999
|
-
return { score, vector: cvssVector(input), severity: cvssSeverity(score) };
|
|
52000
|
-
}
|
|
52001
|
-
|
|
52002
|
-
// src/dedup.ts
|
|
52003
|
-
function rootCauseSignature(finding) {
|
|
52004
|
-
const sinkType = (finding.sink?.type ?? "unknown").toLowerCase();
|
|
52005
|
-
const sourceType = (finding.source?.type ?? "unknown").toLowerCase();
|
|
52006
|
-
const cwe = (finding.classification?.cwe ?? "").toUpperCase();
|
|
52007
|
-
return [sinkType, sourceType, cwe].filter(Boolean).join("|");
|
|
52008
|
-
}
|
|
52009
|
-
function dedupFindings(findings) {
|
|
52010
|
-
const groups = new Map;
|
|
52011
|
-
for (const f of findings) {
|
|
52012
|
-
const sig = rootCauseSignature(f);
|
|
52013
|
-
const existing = groups.get(sig);
|
|
52014
|
-
if (existing) {
|
|
52015
|
-
existing.count += 1;
|
|
52016
|
-
existing.findings.push(f);
|
|
52017
|
-
} else {
|
|
52018
|
-
groups.set(sig, { signature: sig, count: 1, representative: f, findings: [f] });
|
|
52096
|
+
"/graphql",
|
|
52097
|
+
"/graphiql",
|
|
52098
|
+
"/actuator",
|
|
52099
|
+
"/actuator/health",
|
|
52100
|
+
"/actuator/env",
|
|
52101
|
+
"/api/swagger.json",
|
|
52102
|
+
"/docs",
|
|
52103
|
+
"/redoc"
|
|
52104
|
+
];
|
|
52105
|
+
async function discoverApi(base) {
|
|
52106
|
+
const out = [];
|
|
52107
|
+
for (const p of API_PATHS) {
|
|
52108
|
+
const r = await httpGet(`${base}${p}`);
|
|
52109
|
+
if (r.status >= 200 && r.status < 400) {
|
|
52110
|
+
out.push({ path: p, status: r.status, body_size: r.body.length });
|
|
52019
52111
|
}
|
|
52020
52112
|
}
|
|
52021
|
-
return
|
|
52022
|
-
}
|
|
52023
|
-
function uniqueFindings(findings) {
|
|
52024
|
-
return dedupFindings(findings).map((g) => g.representative);
|
|
52113
|
+
return out;
|
|
52025
52114
|
}
|
|
52026
|
-
|
|
52027
|
-
|
|
52028
|
-
|
|
52029
|
-
const
|
|
52030
|
-
|
|
52031
|
-
|
|
52032
|
-
for (const f of findings) {
|
|
52033
|
-
byStatus[f.status] = (byStatus[f.status] ?? 0) + 1;
|
|
52034
|
-
const sev = f.classification?.severity ?? "informational";
|
|
52035
|
-
bySeverity[sev] = (bySeverity[sev] ?? 0) + 1;
|
|
52036
|
-
evidenceArtifacts += f.evidence?.reduce((n, e) => n + (e.artifacts?.length ?? 0), 0) ?? 0;
|
|
52115
|
+
function extractVersion(headers, body) {
|
|
52116
|
+
const out = [];
|
|
52117
|
+
const server = headers["server"] ?? headers["x-powered-by"] ?? "";
|
|
52118
|
+
const serverVer = /^(nginx|apache|microsoft-iis|openresty|liteSpeed|caddy)\/?([0-9][0-9.\-]*)?/i.exec(server);
|
|
52119
|
+
if (serverVer) {
|
|
52120
|
+
out.push({ product: serverVer[1].toLowerCase(), version: serverVer[2] ?? "unknown", evidence: server });
|
|
52037
52121
|
}
|
|
52038
|
-
const
|
|
52039
|
-
|
|
52040
|
-
|
|
52041
|
-
|
|
52042
|
-
|
|
52043
|
-
|
|
52044
|
-
|
|
52045
|
-
|
|
52046
|
-
|
|
52047
|
-
|
|
52122
|
+
const phpVer = /php\/([0-9][0-9.\-]*)/i.exec(server);
|
|
52123
|
+
if (phpVer)
|
|
52124
|
+
out.push({ product: "php", version: phpVer[1], evidence: server });
|
|
52125
|
+
const wpVer = /content="WordPress\s+([0-9][0-9.]*)/i.exec(body);
|
|
52126
|
+
if (wpVer)
|
|
52127
|
+
out.push({ product: "wordpress", version: wpVer[1], evidence: `meta generator: WordPress ${wpVer[1]}` });
|
|
52128
|
+
const joomlaVer = /Joomla!?\s*([0-9][0-9.]*)/i.exec(body);
|
|
52129
|
+
if (joomlaVer)
|
|
52130
|
+
out.push({ product: "joomla", version: joomlaVer[1], evidence: `body: Joomla ${joomlaVer[1]}` });
|
|
52131
|
+
const drupalVer = /Drupal\s+([0-9][0-9.]*)/i.exec(body);
|
|
52132
|
+
if (drupalVer)
|
|
52133
|
+
out.push({ product: "drupal", version: drupalVer[1], evidence: `body: Drupal ${drupalVer[1]}` });
|
|
52134
|
+
const powered = headers["x-powered-by"] ?? "";
|
|
52135
|
+
if (powered && !out.some((o) => powered.includes(o.product))) {
|
|
52136
|
+
out.push({ product: powered.split("/")[0].toLowerCase(), version: powered.split("/")[1] ?? "unknown", evidence: powered });
|
|
52137
|
+
}
|
|
52138
|
+
return out;
|
|
52048
52139
|
}
|
|
52049
|
-
function
|
|
52050
|
-
const
|
|
52051
|
-
|
|
52052
|
-
|
|
52053
|
-
|
|
52054
|
-
|
|
52055
|
-
|
|
52056
|
-
|
|
52057
|
-
|
|
52058
|
-
|
|
52059
|
-
return
|
|
52140
|
+
function correlateIntel(techs, versionList, portList) {
|
|
52141
|
+
const tech = techs.map((t) => ({ tech: t, ...techCorrelation(t) }));
|
|
52142
|
+
const versions = versionList.map((v) => ({
|
|
52143
|
+
product: v.product,
|
|
52144
|
+
version: v.version,
|
|
52145
|
+
correlation: techCorrelation(v.product),
|
|
52146
|
+
payload_lookup: payloadLookup(v.product),
|
|
52147
|
+
template_lookup: templateLookup(v.product, 5)
|
|
52148
|
+
}));
|
|
52149
|
+
const ports = portList.map((p) => ({ port: p.port, service: p.service, ...portCorrelation(p.port) }));
|
|
52150
|
+
return { tech, versions, ports };
|
|
52060
52151
|
}
|
|
52061
|
-
function
|
|
52062
|
-
const
|
|
52063
|
-
const
|
|
52064
|
-
|
|
52065
|
-
|
|
52066
|
-
|
|
52067
|
-
|
|
52068
|
-
|
|
52069
|
-
|
|
52070
|
-
|
|
52071
|
-
|
|
52072
|
-
|
|
52073
|
-
|
|
52074
|
-
|
|
52075
|
-
lines.push(`| Total findings | ${summary.total_findings} |`);
|
|
52076
|
-
lines.push(`| Unique root causes | ${summary.unique_root_causes} |`);
|
|
52077
|
-
lines.push(`| Confirmed | ${summary.confirmed} |`);
|
|
52078
|
-
lines.push(`| False positives | ${summary.false_positives} |`);
|
|
52079
|
-
lines.push(`| Evidence artifacts | ${summary.evidence_artifacts} |`);
|
|
52080
|
-
lines.push("");
|
|
52081
|
-
lines.push("### By status");
|
|
52082
|
-
lines.push("");
|
|
52083
|
-
for (const [status, n] of Object.entries(summary.by_status).sort()) {
|
|
52084
|
-
lines.push(`- ${status}: ${n}`);
|
|
52152
|
+
async function liveRecon(target, includeActive = false) {
|
|
52153
|
+
const base = normalizeTarget2(target);
|
|
52154
|
+
const host = (() => {
|
|
52155
|
+
try {
|
|
52156
|
+
return new URL(base).hostname;
|
|
52157
|
+
} catch {
|
|
52158
|
+
return target;
|
|
52159
|
+
}
|
|
52160
|
+
})();
|
|
52161
|
+
const root = await httpGet(base);
|
|
52162
|
+
const interestingHeaders = {};
|
|
52163
|
+
for (const k of ["server", "x-powered-by", "x-aspnet-version", "x-drupal-cache", "x-generator", "via", "x-cache", "cf-ray"]) {
|
|
52164
|
+
if (root.headers[k])
|
|
52165
|
+
interestingHeaders[k] = root.headers[k];
|
|
52085
52166
|
}
|
|
52086
|
-
|
|
52087
|
-
|
|
52088
|
-
|
|
52089
|
-
|
|
52090
|
-
|
|
52167
|
+
const fingerprint = {
|
|
52168
|
+
status: root.status,
|
|
52169
|
+
title: (root.body.match(/<title[^>]*>([^<]+)<\/title>/i) ?? [])[1]?.trim().slice(0, 200) ?? "",
|
|
52170
|
+
server: root.headers["server"] ?? root.headers["x-powered-by"] ?? "",
|
|
52171
|
+
headers: interestingHeaders,
|
|
52172
|
+
body_size: root.body.length
|
|
52173
|
+
};
|
|
52174
|
+
const waf = detectWaf(root.headers, root.body);
|
|
52175
|
+
const techSig = [
|
|
52176
|
+
[/wp-content|wp-includes|wp-json/i, "wordpress"],
|
|
52177
|
+
[/wp-login\.php|wp-admin/i, "wordpress"],
|
|
52178
|
+
[/powered by joomla|com_content/i, "joomla"],
|
|
52179
|
+
[/laravel|_token|csrf-token/i, "laravel"],
|
|
52180
|
+
[/react|__NEXT_DATA__|next\/static/i, "nextjs"],
|
|
52181
|
+
[/angular|ng-version/i, "angular"],
|
|
52182
|
+
[/vue|__vue__|v-data/i, "vue"],
|
|
52183
|
+
[/django|csrftoken|__debug__/i, "django"],
|
|
52184
|
+
[/ruby on rails|rails/i, "rails"],
|
|
52185
|
+
[/asp\.net|__VIEWSTATE|__EVENTVALIDATION/i, "aspnet"],
|
|
52186
|
+
[/phpBB|phpbb/i, "phpbb"],
|
|
52187
|
+
[/mybb|mybb/i, "mybb"],
|
|
52188
|
+
[/drupal|Drupal\.settings/i, "drupal"],
|
|
52189
|
+
[/magento|Mage\./i, "magento"],
|
|
52190
|
+
[/shopify|cdn\.shopify/i, "shopify"],
|
|
52191
|
+
[/grafana|kibana|elastic/i, "grafana-kibana"],
|
|
52192
|
+
[/node\.js|express/i, "nodejs"],
|
|
52193
|
+
[/flask|werkzeug/i, "flask"],
|
|
52194
|
+
[/spring|actuator/i, "spring"]
|
|
52195
|
+
];
|
|
52196
|
+
const techs = [...new Set(techSig.filter(([re]) => re.test(root.body)).map(([, t]) => t))];
|
|
52197
|
+
const versions = extractVersion(root.headers, root.body);
|
|
52198
|
+
const { links, scripts } = crawlLinks(base, root.body);
|
|
52199
|
+
const { robots, sitemap } = await fetchRobotsAndSitemap(base);
|
|
52200
|
+
const allUrls = [...links, ...scripts, ...robots, ...sitemap];
|
|
52201
|
+
const crawl = { links, scripts, endpoints: [...new Set(allUrls)], robots, sitemap, count: allUrls.length };
|
|
52202
|
+
const params = extractParams(allUrls);
|
|
52203
|
+
const { subdomains } = await subdomainEnum(host);
|
|
52204
|
+
const { found: dnsFindings } = await dnsBrute(host);
|
|
52205
|
+
const { urls: wayback } = await waybackUrls(host);
|
|
52206
|
+
const apiEndpoints = (await discoverApi(base)).map(({ path, status }) => ({ path, status }));
|
|
52207
|
+
let openPorts = [];
|
|
52208
|
+
if (includeActive && root.status > 0) {
|
|
52209
|
+
const scan = await scanPorts(host);
|
|
52210
|
+
openPorts = scan.map(({ port, service }) => ({ port, service }));
|
|
52091
52211
|
}
|
|
52092
|
-
|
|
52093
|
-
|
|
52094
|
-
|
|
52095
|
-
|
|
52096
|
-
|
|
52097
|
-
|
|
52098
|
-
|
|
52099
|
-
|
|
52212
|
+
const intel = correlateIntel(techs, versions, openPorts);
|
|
52213
|
+
const nextSteps = [];
|
|
52214
|
+
if (waf.detected)
|
|
52215
|
+
nextSteps.push(`WAF detected (${waf.wafs?.map((w) => w.waf).join(", ")}) — consult detect_waf + WAF bypass playbook before exploitation.`);
|
|
52216
|
+
for (const t of techs) {
|
|
52217
|
+
const c = techCorrelation(t);
|
|
52218
|
+
if (c.found)
|
|
52219
|
+
nextSteps.push(`Tech '${t}' maps to known vuln classes — read tech_correlation('${t}') and fetch nuclei templates with template_lookup('${t}').`);
|
|
52100
52220
|
}
|
|
52101
|
-
for (const
|
|
52102
|
-
|
|
52103
|
-
lines.push("");
|
|
52104
|
-
lines.push(`- **Status**: \`${f.status}\``);
|
|
52105
|
-
lines.push(`- **Confidence**: ${f.confidence} (${f.confidence_level})`);
|
|
52106
|
-
if (f.classification?.cwe)
|
|
52107
|
-
lines.push(`- **CWE**: ${f.classification.cwe}`);
|
|
52108
|
-
if (f.classification?.cvss_score != null)
|
|
52109
|
-
lines.push(`- **CVSS**: ${f.classification.cvss_score} (${f.classification.cvss_vector ?? ""}) — ${cvssSeverity(f.classification.cvss_score)}`);
|
|
52110
|
-
lines.push(`- **Source**: ${f.source?.type} \`${f.source?.name ?? ""}\``);
|
|
52111
|
-
lines.push(`- **Sink**: ${f.sink?.type}${f.sink?.symbol ? ` \`${f.sink.symbol}\`` : ""}`);
|
|
52112
|
-
if (f.target?.host || f.target?.endpoint)
|
|
52113
|
-
lines.push(`- **Target**: ${[f.target.host, f.target.endpoint].filter(Boolean).join("")}`);
|
|
52114
|
-
lines.push(`- **Evidence**: ${f.evidence?.length ?? 0} record(s)`);
|
|
52115
|
-
if (f.remediation && Object.keys(f.remediation).length) {
|
|
52116
|
-
lines.push(`- **Note**: ${JSON.stringify(f.remediation)}`);
|
|
52117
|
-
}
|
|
52118
|
-
lines.push("");
|
|
52221
|
+
for (const v of versions) {
|
|
52222
|
+
nextSteps.push(`Version ${v.product}/${v.version} detected — check cve_correlation + nvd_lookup for known CVEs affecting ${v.product} ${v.version}.`);
|
|
52119
52223
|
}
|
|
52120
|
-
|
|
52121
|
-
`
|
|
52122
|
-
|
|
52123
|
-
}
|
|
52124
|
-
|
|
52125
|
-
|
|
52126
|
-
|
|
52127
|
-
|
|
52128
|
-
|
|
52129
|
-
|
|
52130
|
-
|
|
52131
|
-
|
|
52132
|
-
|
|
52224
|
+
if (crawl.endpoints.length > 0)
|
|
52225
|
+
nextSteps.push(`Found ${crawl.endpoints.length} endpoints — feed them to taint_file/blitz_scan for source analysis, or strike_verify for live validation.`);
|
|
52226
|
+
if (params.length > 0)
|
|
52227
|
+
nextSteps.push(`Discovered input params: ${params.join(", ")} — these are attack-surface entry points; fuzz them with payload_lookup after scope confirms.`);
|
|
52228
|
+
if (subdomains.length > 0)
|
|
52229
|
+
nextSteps.push(`Found ${subdomains.length} subdomains — each is a separate scope surface; enumerate further with fofa_search.`);
|
|
52230
|
+
if (dnsFindings.length > 0)
|
|
52231
|
+
nextSteps.push(`DNS-resolved ${dnsFindings.length} hosts (${dnsFindings.map((d) => d.host).slice(0, 8).join(", ")}${dnsFindings.length > 8 ? "…" : ""}) — each is a live subdomain to recon.`);
|
|
52232
|
+
if (wayback.length > 0)
|
|
52233
|
+
nextSteps.push(`Wayback Machine surfaced ${wayback.length} historical URLs — mine them for hidden endpoints, old params, and forgotten pages; feed to taint_file/blitz_scan.`);
|
|
52234
|
+
if (openPorts.length > 0)
|
|
52235
|
+
nextSteps.push(`Open ports: ${openPorts.map((p) => `${p.port}/${p.service}`).join(", ")} — map services via port_correlation and probe each with the relevant tool manual.`);
|
|
52236
|
+
if (apiEndpoints.length > 0)
|
|
52237
|
+
nextSteps.push(`API endpoints exposed (${apiEndpoints.map((e) => e.path).join(", ")}) — audit them with api-security playbook; check for BOLA/IDOR/auth gaps.`);
|
|
52238
|
+
if (nextSteps.length === 0)
|
|
52239
|
+
nextSteps.push("Nothing exposed on the surface — try subdomain enumeration, port scan (active), or request source access for EAGLE-EYE analysis.");
|
|
52240
|
+
nextSteps.push("Every confirmed finding: record with finding_create, attach confidence_score, redact secrets. A hit is a hypothesis until verified.");
|
|
52241
|
+
return {
|
|
52242
|
+
target: base,
|
|
52243
|
+
host,
|
|
52244
|
+
fingerprint,
|
|
52245
|
+
waf,
|
|
52246
|
+
tech: { detected: techs, server: fingerprint.server },
|
|
52247
|
+
versions,
|
|
52248
|
+
crawl,
|
|
52249
|
+
params,
|
|
52250
|
+
subdomains,
|
|
52251
|
+
dns_findings: dnsFindings,
|
|
52252
|
+
wayback_urls: wayback,
|
|
52253
|
+
api_endpoints: apiEndpoints,
|
|
52254
|
+
open_ports: openPorts,
|
|
52255
|
+
intel,
|
|
52256
|
+
next_steps: nextSteps
|
|
52133
52257
|
};
|
|
52134
|
-
return JSON.stringify(payload, null, 2);
|
|
52135
52258
|
}
|
|
52136
52259
|
|
|
52137
52260
|
// src/benchmark.ts
|
|
@@ -52374,7 +52497,7 @@ function createServer() {
|
|
|
52374
52497
|
name: "blitzstrike",
|
|
52375
52498
|
version: "1.0.0"
|
|
52376
52499
|
}, {
|
|
52377
|
-
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" + "1. SCOPE FIRST. Call scope_check(target, scope, mode) before ANY active testing. " + " Passive analysis (reading source, fingerprinting public pages) needs no gate; " + " active probing (exploitation, fuzzing, path brute-force) is blocked until scope " + " authorizes it.\\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 gated alternative. Both re-check 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).\\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:
|
|
52500
|
+
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" + "1. SCOPE FIRST. Call scope_check(target, scope, mode) before ANY active testing. " + " Passive analysis (reading source, fingerprinting public pages) needs no gate; " + " active probing (exploitation, fuzzing, path brute-force) is blocked until scope " + " authorizes it.\\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 gated alternative. Both re-check 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).\\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 -> " + " autonomous live marker sweep + confirmed findings + report. Prefer this first.\\n" + "- list_languages / taint_file: multi-language taint (PHP, JS/TS, Python, Java).\\n" + "- list_chains: 57 escalation chains (source -> sink -> impact).\\n" + "- tool_lookup / ensure_tool / list_tools: 130-tool catalog, auto-install.\\n" + "- skill_lookup / read_skill / list_skills: 85 playbooks (WP/PHP 0-day, AD, " + " malware, mobile, cloud, etc.).\\n" + "- read_playbook / read_tool_manual / list_manuals: 270 tool manuals + engagement " + " playbooks.\\n" + "- detect_waf / tech_correlation / cve_correlation / port_correlation: signature " + " + correlation intelligence.\\n" + "- nvd_lookup / fofa_search: CVE + asset reconnaissance (FOFA needs env creds).\\n" + "- remember / memory_lookup / memory_list: persist + recall verified knowledge.\\n\\n" + "IRON RULES:\\n" + "- Scope first; authorized targets only. Refuse out-of-scope or destructive/DoS work.\\n" + "- A hit is a hypothesis; a verified exploit (or proven data-flow) is the finding.\\n" + "- Prefer the one-call run_engagement path; fall back to granular tools when you " + " need finer control."
|
|
52378
52501
|
});
|
|
52379
52502
|
server.registerTool("blitz_scan", {
|
|
52380
52503
|
title: "BLITZ scan",
|
|
@@ -52563,7 +52686,7 @@ function createServer() {
|
|
|
52563
52686
|
});
|
|
52564
52687
|
server.registerTool("run_engagement", {
|
|
52565
52688
|
title: "STRIKE full engagement",
|
|
52566
|
-
description: "STRIKE: run a full
|
|
52689
|
+
description: "STRIKE: run a full audit in ONE call (no babysitting). Source path -> scope gate -> triage -> chain enrichment -> findings -> inline report (markdown+JSON+SARIF). URL target -> autonomous live marker sweep (strike_verify per parameter) -> confirmed findings -> inline report. Scope-gated: URL targets require an authorized scope.",
|
|
52567
52690
|
inputSchema: {
|
|
52568
52691
|
target: string2().describe("Source path or URL"),
|
|
52569
52692
|
scope: string2().optional().describe("Scope (for URL targets)"),
|
|
@@ -52572,7 +52695,8 @@ function createServer() {
|
|
|
52572
52695
|
remember: boolean2().optional().describe("Auto-capture matched chains to memory (default true)")
|
|
52573
52696
|
}
|
|
52574
52697
|
}, async ({ target, scope, mode, max_files, remember }) => {
|
|
52575
|
-
const
|
|
52698
|
+
const isUrl = /^https?:\/\//i.test(target);
|
|
52699
|
+
const r = isUrl ? await runLiveEngagement(target, scope ?? "", mode ?? "bug-bounty") : runEngagement(target, scope ?? "", mode ?? "bug-bounty", max_files ?? 2000, remember ?? true);
|
|
52576
52700
|
return {
|
|
52577
52701
|
content: [{ type: "text", text: JSON.stringify(r) }]
|
|
52578
52702
|
};
|