blitzstrike 2.4.101 → 2.4.102
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 +14 -0
- package/dist/index.js +166 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,20 @@ All notable changes to Blitz Strike are documented here.
|
|
|
5
5
|
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
7
7
|
|
|
8
|
+
## [2.4.102] — 2026-09-17
|
|
9
|
+
|
|
10
|
+
### Added — `broken_link_scan` (broken-link hijacking live detector)
|
|
11
|
+
|
|
12
|
+
- New `broken_link_scan(url)` tool: crawls the target's outbound references
|
|
13
|
+
(script/link/img/src + inline JS URLs), then checks each referenced hostname's
|
|
14
|
+
DNS + RDAP registration status. Unregistered (RDAP 404) or in-drop-cycle
|
|
15
|
+
(pendingDelete/redemptionPeriod) domains are flagged CLAIMABLE — register the
|
|
16
|
+
expired domain and inherit the trust of every page that still loads it.
|
|
17
|
+
Verdict is derived live from DNS + RDAP, never a static list. Distinct from
|
|
18
|
+
`subdomain_takeover` (dangling CNAME); this is a fully-expired registrable
|
|
19
|
+
domain. Verified live: example.com → registered (not claimable), a
|
|
20
|
+
nonexistent domain → RDAP 404 → claimable.
|
|
21
|
+
|
|
8
22
|
## [2.4.101] — 2026-09-17
|
|
9
23
|
|
|
10
24
|
### Added — three new hunting classes (techniques + attack vectors)
|
package/dist/index.js
CHANGED
|
@@ -59295,6 +59295,163 @@ function dataRefresh() {
|
|
|
59295
59295
|
init_verify_harness();
|
|
59296
59296
|
init_variants();
|
|
59297
59297
|
|
|
59298
|
+
// src/broken-link.ts
|
|
59299
|
+
import { resolve4 as resolve43 } from "node:dns/promises";
|
|
59300
|
+
function hostFromUrl(u) {
|
|
59301
|
+
try {
|
|
59302
|
+
const h = new URL(u.startsWith("//") ? `https:${u}` : u).hostname.toLowerCase();
|
|
59303
|
+
return h || null;
|
|
59304
|
+
} catch {
|
|
59305
|
+
return null;
|
|
59306
|
+
}
|
|
59307
|
+
}
|
|
59308
|
+
var ALWAYS_ALIVE = new Set([
|
|
59309
|
+
"googleapis.com",
|
|
59310
|
+
"gstatic.com",
|
|
59311
|
+
"google.com",
|
|
59312
|
+
"googletagmanager.com",
|
|
59313
|
+
"google-analytics.com",
|
|
59314
|
+
"cloudflare.com",
|
|
59315
|
+
"cloudflareinsights.com",
|
|
59316
|
+
"jsdelivr.net",
|
|
59317
|
+
"unpkg.com",
|
|
59318
|
+
"cdnjs.cloudflare.com",
|
|
59319
|
+
"bootstrapcdn.com",
|
|
59320
|
+
"jquery.com",
|
|
59321
|
+
"fontawesome.com",
|
|
59322
|
+
"stackpathcdn.com",
|
|
59323
|
+
"w3.org",
|
|
59324
|
+
"schema.org",
|
|
59325
|
+
"github.com",
|
|
59326
|
+
"github.io",
|
|
59327
|
+
"npmjs.com",
|
|
59328
|
+
"nodejs.org",
|
|
59329
|
+
"microsoft.com",
|
|
59330
|
+
"azureedge.net",
|
|
59331
|
+
"azure.com",
|
|
59332
|
+
"akamai.net",
|
|
59333
|
+
"akamaized.net",
|
|
59334
|
+
"amazonaws.com",
|
|
59335
|
+
"amazon.com",
|
|
59336
|
+
"cloudfront.net",
|
|
59337
|
+
"youtube.com",
|
|
59338
|
+
"youtu.be",
|
|
59339
|
+
"instagram.com",
|
|
59340
|
+
"facebook.com",
|
|
59341
|
+
"fbcdn.net",
|
|
59342
|
+
"twitter.com",
|
|
59343
|
+
"x.com",
|
|
59344
|
+
"tiktok.com",
|
|
59345
|
+
"pinterest.com",
|
|
59346
|
+
"linkedin.com",
|
|
59347
|
+
"apple.com",
|
|
59348
|
+
"mozilla.org",
|
|
59349
|
+
"gravatar.com",
|
|
59350
|
+
"twimg.com",
|
|
59351
|
+
"g.co",
|
|
59352
|
+
"goo.gl",
|
|
59353
|
+
"bit.ly",
|
|
59354
|
+
"tinyurl.com",
|
|
59355
|
+
"paypal.com",
|
|
59356
|
+
"stripe.com",
|
|
59357
|
+
"shopify.com",
|
|
59358
|
+
"wordpress.org",
|
|
59359
|
+
"wp.com"
|
|
59360
|
+
]);
|
|
59361
|
+
function extractOutboundHosts(html, baseHost) {
|
|
59362
|
+
const hosts = new Set;
|
|
59363
|
+
for (const m of html.matchAll(/(?:src|href)\s*=\s*["']([^"']+)["']/gi)) {
|
|
59364
|
+
const h = hostFromUrl(m[1]);
|
|
59365
|
+
if (h)
|
|
59366
|
+
hosts.add(h);
|
|
59367
|
+
}
|
|
59368
|
+
for (const m of html.matchAll(/https?:\/\/([a-z0-9.-]+)/gi)) {
|
|
59369
|
+
hosts.add(m[1].toLowerCase());
|
|
59370
|
+
}
|
|
59371
|
+
const base = baseHost.toLowerCase().replace(/^www\./, "");
|
|
59372
|
+
const out = [...hosts].filter((h) => {
|
|
59373
|
+
const lh = h.toLowerCase().replace(/^www\./, "");
|
|
59374
|
+
if (lh === base || lh.endsWith(`.${base}`))
|
|
59375
|
+
return false;
|
|
59376
|
+
if (ALWAYS_ALIVE.has(lh))
|
|
59377
|
+
return false;
|
|
59378
|
+
for (const s of ALWAYS_ALIVE)
|
|
59379
|
+
if (lh.endsWith(`.${s}`))
|
|
59380
|
+
return false;
|
|
59381
|
+
return true;
|
|
59382
|
+
});
|
|
59383
|
+
return [...new Set(out)].sort();
|
|
59384
|
+
}
|
|
59385
|
+
async function checkDomainStatus(domain) {
|
|
59386
|
+
let resolved = true;
|
|
59387
|
+
try {
|
|
59388
|
+
await resolve43(domain);
|
|
59389
|
+
} catch {
|
|
59390
|
+
resolved = false;
|
|
59391
|
+
}
|
|
59392
|
+
try {
|
|
59393
|
+
const res = await fetch(`https://rdap.org/domain/${domain}`, {
|
|
59394
|
+
signal: AbortSignal.timeout(7000),
|
|
59395
|
+
headers: { accept: "application/json" }
|
|
59396
|
+
});
|
|
59397
|
+
if (res.status === 404) {
|
|
59398
|
+
return { status: "unregistered", claimable: true, evidence: "RDAP 404 — domain not registered, registrable now" };
|
|
59399
|
+
}
|
|
59400
|
+
const data = await res.json();
|
|
59401
|
+
const statuses = data.status ?? [];
|
|
59402
|
+
if (statuses.includes("pendingDelete") || statuses.includes("redemptionPeriod")) {
|
|
59403
|
+
return { status: statuses.join(","), claimable: true, evidence: `RDAP ${statuses.join(",")} — in the drop cycle, registrable soon` };
|
|
59404
|
+
}
|
|
59405
|
+
if (statuses.length > 0) {
|
|
59406
|
+
return { status: statuses.join(","), claimable: false, evidence: `RDAP ${statuses.join(",")} — still registered${resolved ? "" : " (held, no DNS)"}` };
|
|
59407
|
+
}
|
|
59408
|
+
return { status: resolved ? "resolved" : "nxdomain", claimable: false, evidence: "RDAP returned no status (inconclusive)" };
|
|
59409
|
+
} catch {
|
|
59410
|
+
return { status: resolved ? "resolved" : "nxdomain", claimable: false, evidence: resolved ? "RDAP lookup failed — cannot confirm registration status" : "NXDOMAIN + RDAP lookup failed (inconclusive)" };
|
|
59411
|
+
}
|
|
59412
|
+
}
|
|
59413
|
+
async function brokenLinkScan(url, maxHosts = 30) {
|
|
59414
|
+
const base = url.startsWith("http") ? url : `https://${url}`;
|
|
59415
|
+
const baseHost = (() => {
|
|
59416
|
+
try {
|
|
59417
|
+
return new URL(base).hostname;
|
|
59418
|
+
} catch {
|
|
59419
|
+
return url;
|
|
59420
|
+
}
|
|
59421
|
+
})();
|
|
59422
|
+
let html = "";
|
|
59423
|
+
try {
|
|
59424
|
+
const res = await fetch(base, { signal: AbortSignal.timeout(15000), redirect: "follow", headers: researchHeaders() });
|
|
59425
|
+
html = (await res.text()).slice(0, 1e6);
|
|
59426
|
+
} catch {
|
|
59427
|
+
return { target: base, base_host: baseHost, outbound_hosts: 0, checked: 0, claimable: [], inconclusive: [] };
|
|
59428
|
+
}
|
|
59429
|
+
const hosts = extractOutboundHosts(html, baseHost).slice(0, maxHosts);
|
|
59430
|
+
const claimable = [];
|
|
59431
|
+
const inconclusive = [];
|
|
59432
|
+
for (let i = 0;i < hosts.length; i += 5) {
|
|
59433
|
+
const batch = hosts.slice(i, i + 5);
|
|
59434
|
+
const results = await Promise.all(batch.map(async (h) => {
|
|
59435
|
+
const r = await checkDomainStatus(h);
|
|
59436
|
+
return { domain: h, ...r };
|
|
59437
|
+
}));
|
|
59438
|
+
for (const r of results) {
|
|
59439
|
+
if (r.claimable)
|
|
59440
|
+
claimable.push({ domain: r.domain, status: r.status, claimable: true, evidence: r.evidence, source: "outbound-reference" });
|
|
59441
|
+
else if (r.status === "nxdomain")
|
|
59442
|
+
inconclusive.push(r.domain);
|
|
59443
|
+
}
|
|
59444
|
+
}
|
|
59445
|
+
return {
|
|
59446
|
+
target: base,
|
|
59447
|
+
base_host: baseHost,
|
|
59448
|
+
outbound_hosts: hosts.length,
|
|
59449
|
+
checked: hosts.length,
|
|
59450
|
+
claimable,
|
|
59451
|
+
inconclusive
|
|
59452
|
+
};
|
|
59453
|
+
}
|
|
59454
|
+
|
|
59298
59455
|
// src/benchmark.ts
|
|
59299
59456
|
import { readFileSync as readFileSync22, readdirSync as readdirSync5, writeFileSync as writeFileSync6, rmSync as rmSync3, mkdirSync as mkdirSync7 } from "node:fs";
|
|
59300
59457
|
import { join as join22 } from "node:path";
|
|
@@ -62890,6 +63047,15 @@ ${statusLine}` : statusLine },
|
|
|
62890
63047
|
}, async () => {
|
|
62891
63048
|
return { content: [{ type: "text", text: JSON.stringify(watchlist()) }] };
|
|
62892
63049
|
});
|
|
63050
|
+
server.registerTool("broken_link_scan", {
|
|
63051
|
+
title: "Broken-link hijacking / expired-domain takeover (live detector)",
|
|
63052
|
+
description: "Detect expired-domain takeover (broken-link hijacking): crawl the target's outbound references (script/link/img/src + inline JS URLs), then check each referenced hostname's DNS + RDAP registration status. A domain that is unregistered (RDAP 404) or in the drop cycle (pendingDelete/redemptionPeriod) is CLAIMABLE — register it and inherit the trust of every page that still loads it. Distinct from subdomain_takeover (a dangling CNAME); this is a fully-expired registrable domain. Verdict is derived live from DNS + RDAP, never a static list.",
|
|
63053
|
+
inputSchema: {
|
|
63054
|
+
url: string2().describe("Target URL (https://target.com) — crawls outbound references")
|
|
63055
|
+
}
|
|
63056
|
+
}, async ({ url }) => {
|
|
63057
|
+
return { content: [{ type: "text", text: JSON.stringify(await brokenLinkScan(url)) }] };
|
|
63058
|
+
});
|
|
62893
63059
|
server.registerTool("auto_poc", {
|
|
62894
63060
|
title: "Auto-PoC — generate a copy-paste-ready reproduction recipe",
|
|
62895
63061
|
description: "Turn a finding into a concrete reproduction recipe (the 'reproducible' gate question made executable). Given a vuln class + target + endpoint + param, emit the exact request (with a MARKER), a NEGATIVE CONTROL, and the expected differential that PROVES the bug — plus a Patchstack-style title, derived severity, and the fix. Covers sql_injection, missing_authz/idor, ssrf, xss, ssti, command_injection, file_upload, mass_assignment, jwt_alg_confusion, crlf, open_redirect, xxe, prototype_pollution, deserialization, hardcoded_secret, cache_deception, cors_misconfiguration, subdomain_takeover. Pass type='list' to enumerate supported classes.",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blitzstrike",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.102",
|
|
4
4
|
"description": "Blitz Strike — a universal MCP security-audit toolbelt. BLITZ sweeps the attack surface, EAGLE-EYE traces source-to-sink, STRIKE verifies live. 57 attack chains, 130-tool catalog, intelligence data layer. One server, every agent.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|