blitzstrike 2.4.96 → 2.4.98
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 +26 -0
- package/README.md +35 -8
- package/dist/index.js +227 -202
- package/intelligence/watchlist.json +352 -28
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -17285,12 +17285,12 @@ document.getElementById('out').innerHTML = u;
|
|
|
17285
17285
|
});
|
|
17286
17286
|
|
|
17287
17287
|
// src/variants.ts
|
|
17288
|
-
import { readFileSync as
|
|
17289
|
-
import { join as
|
|
17288
|
+
import { readFileSync as readFileSync21 } from "node:fs";
|
|
17289
|
+
import { join as join21, dirname as dirname5 } from "node:path";
|
|
17290
17290
|
import { fileURLToPath as fileURLToPath11 } from "node:url";
|
|
17291
17291
|
function loadVariants() {
|
|
17292
17292
|
try {
|
|
17293
|
-
const doc = JSON.parse(
|
|
17293
|
+
const doc = JSON.parse(readFileSync21(join21(__dirname5, "..", "intelligence", "variants.json"), "utf8"));
|
|
17294
17294
|
return doc.variants ?? [];
|
|
17295
17295
|
} catch {
|
|
17296
17296
|
return [];
|
|
@@ -32693,8 +32693,8 @@ async function nvdLookup(cveId) {
|
|
|
32693
32693
|
|
|
32694
32694
|
// src/orchestrator.ts
|
|
32695
32695
|
init_scanner();
|
|
32696
|
-
import { readFileSync as
|
|
32697
|
-
import { join as
|
|
32696
|
+
import { readFileSync as readFileSync11, existsSync as existsSync8, statSync as statSync2 } from "node:fs";
|
|
32697
|
+
import { join as join13 } from "node:path";
|
|
32698
32698
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
32699
32699
|
|
|
32700
32700
|
// src/memory.ts
|
|
@@ -36067,8 +36067,135 @@ function toSarif(findings, opts = {}) {
|
|
|
36067
36067
|
// src/orchestrator.ts
|
|
36068
36068
|
init_version();
|
|
36069
36069
|
|
|
36070
|
+
// src/hunting-intel.ts
|
|
36071
|
+
import { execFileSync } from "node:child_process";
|
|
36072
|
+
import { resolve4, Resolver } from "node:dns/promises";
|
|
36073
|
+
import { readFileSync as readFileSync10 } from "node:fs";
|
|
36074
|
+
import { join as join12 } from "node:path";
|
|
36075
|
+
var BLOCKING_KEYWORDS = /internet positif|kominfo|\.go\.id\b|pemblokiran|ditutup|dilarang|akses dibatasi|blocked|restricted|illegal|advertencia|coljuegos|regulat|forbidden|this (site|website) has been (blocked|banned)/i;
|
|
36076
|
+
var HOSTNAME_RE = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$/i;
|
|
36077
|
+
function isValidHostname(host) {
|
|
36078
|
+
return host.length <= 253 && HOSTNAME_RE.test(host);
|
|
36079
|
+
}
|
|
36080
|
+
async function localResolve(host) {
|
|
36081
|
+
try {
|
|
36082
|
+
return await resolve4(host);
|
|
36083
|
+
} catch {
|
|
36084
|
+
return [];
|
|
36085
|
+
}
|
|
36086
|
+
}
|
|
36087
|
+
async function publicResolve(host) {
|
|
36088
|
+
const r = new Resolver;
|
|
36089
|
+
r.setServers(["8.8.8.8", "1.1.1.1"]);
|
|
36090
|
+
try {
|
|
36091
|
+
return await r.resolve4(host);
|
|
36092
|
+
} catch {
|
|
36093
|
+
return [];
|
|
36094
|
+
}
|
|
36095
|
+
}
|
|
36096
|
+
function probeBlockingPage(host, ip, timeoutMs = 8000) {
|
|
36097
|
+
for (const port of ["443", "80"]) {
|
|
36098
|
+
const scheme = port === "443" ? "https" : "http";
|
|
36099
|
+
try {
|
|
36100
|
+
const out = execFileSync("curl", ["-sk", "-m", "6", "--resolve", `${host}:${port}:${ip}`, `${scheme}://${host}/`], { encoding: "utf8", timeout: timeoutMs, stdio: ["ignore", "pipe", "ignore"] });
|
|
36101
|
+
const preview = out.replace(/\s+/g, " ").trim().slice(0, 300);
|
|
36102
|
+
if (!preview)
|
|
36103
|
+
continue;
|
|
36104
|
+
return { probed: true, sinkhole: BLOCKING_KEYWORDS.test(preview), preview };
|
|
36105
|
+
} catch {}
|
|
36106
|
+
}
|
|
36107
|
+
return { probed: false, sinkhole: false, preview: "" };
|
|
36108
|
+
}
|
|
36109
|
+
async function resolveCheck(host) {
|
|
36110
|
+
const clean = host.replace(/^https?:\/\//, "").split("/")[0];
|
|
36111
|
+
if (!isValidHostname(clean)) {
|
|
36112
|
+
return { host: clean, local_ips: [], public_ips: [], sinkhole: false, verdict: "invalid", evidence: "not a valid hostname — rejected before DNS/subprocess" };
|
|
36113
|
+
}
|
|
36114
|
+
const local = await localResolve(clean);
|
|
36115
|
+
const pub = await publicResolve(clean);
|
|
36116
|
+
if (local.length === 0 && pub.length === 0) {
|
|
36117
|
+
return { host: clean, local_ips: [], public_ips: [], sinkhole: false, verdict: "unresolvable", evidence: "no A record from local or public DNS" };
|
|
36118
|
+
}
|
|
36119
|
+
if (local.length === 0) {
|
|
36120
|
+
return { host: clean, local_ips: [], public_ips: pub, sinkhole: false, verdict: "local-unresolvable", evidence: "local resolver returned no A record; public DNS resolves. Use the public IP to reach the real target." };
|
|
36121
|
+
}
|
|
36122
|
+
const firstLocal = local[0];
|
|
36123
|
+
const probe = probeBlockingPage(clean, firstLocal);
|
|
36124
|
+
if (probe.sinkhole) {
|
|
36125
|
+
return {
|
|
36126
|
+
host: clean,
|
|
36127
|
+
local_ips: local,
|
|
36128
|
+
public_ips: pub,
|
|
36129
|
+
sinkhole: true,
|
|
36130
|
+
verdict: "sinkhole",
|
|
36131
|
+
evidence: `local IP ${firstLocal} serves a blocking/sinkhole page ("${probe.preview.slice(0, 120)}"). Audit the PUBLIC IP (${pub[0] ?? "n/a"}) instead — e.g. curl --resolve ${clean}:443:${pub[0]} https://${clean}/`
|
|
36132
|
+
};
|
|
36133
|
+
}
|
|
36134
|
+
if (!probe.probed) {
|
|
36135
|
+
return { host: clean, local_ips: local, public_ips: pub, sinkhole: false, verdict: "inconclusive", evidence: "local IP did not respond on 443/80 — inconclusive; for sensitive niches prefer the public IP anyway." };
|
|
36136
|
+
}
|
|
36137
|
+
const ipDiff = new Set(local).size !== new Set(pub).size || !pub.every((ip) => local.includes(ip));
|
|
36138
|
+
return {
|
|
36139
|
+
host: clean,
|
|
36140
|
+
local_ips: local,
|
|
36141
|
+
public_ips: pub,
|
|
36142
|
+
sinkhole: false,
|
|
36143
|
+
verdict: ipDiff ? "clean-cdn" : "clean",
|
|
36144
|
+
evidence: ipDiff ? "local and public IPs differ but the local page shows no blocking content (normal for CDN/anycast/round-robin)." : "local resolution matches public DNS and shows no blocking content."
|
|
36145
|
+
};
|
|
36146
|
+
}
|
|
36147
|
+
function loadWatchlist() {
|
|
36148
|
+
const raw = readFileSync10(join12(process.cwd(), "intelligence", "watchlist.json"), "utf8");
|
|
36149
|
+
const data = JSON.parse(raw);
|
|
36150
|
+
return { cwe_frequency: data.cwe_frequency ?? [], cve_watchlist: data.cve_watchlist ?? [] };
|
|
36151
|
+
}
|
|
36152
|
+
function fmtPayout(min, max) {
|
|
36153
|
+
if (min == null || max == null)
|
|
36154
|
+
return "n/a";
|
|
36155
|
+
const f = (n) => n >= 1000 ? `$${(n / 1000).toFixed(n % 1000 === 0 ? 0 : 1)}K` : `$${n}`;
|
|
36156
|
+
return `${f(min)}–${f(max)}`;
|
|
36157
|
+
}
|
|
36158
|
+
function watchlist() {
|
|
36159
|
+
const { cwe_frequency, cve_watchlist } = loadWatchlist();
|
|
36160
|
+
const entries = cwe_frequency.map((e) => {
|
|
36161
|
+
const pat = e.pattern ? patternLookup(e.pattern) : null;
|
|
36162
|
+
const payoutMin = e.payout_min ?? pat?.payout_min ?? null;
|
|
36163
|
+
const payoutMax = e.payout_max ?? pat?.payout_max ?? null;
|
|
36164
|
+
return {
|
|
36165
|
+
cwe: e.cwe,
|
|
36166
|
+
name: e.name,
|
|
36167
|
+
rank: e.rank,
|
|
36168
|
+
saturated: e.saturated,
|
|
36169
|
+
pattern: e.pattern ?? null,
|
|
36170
|
+
payout_min: payoutMin,
|
|
36171
|
+
payout_max: payoutMax,
|
|
36172
|
+
typical_payout: fmtPayout(payoutMin, payoutMax),
|
|
36173
|
+
notes: e.notes,
|
|
36174
|
+
grounded: pat != null
|
|
36175
|
+
};
|
|
36176
|
+
}).sort((a, b) => a.rank - b.rank);
|
|
36177
|
+
const unsaturated = entries.filter((e) => !e.saturated);
|
|
36178
|
+
const topCve = cve_watchlist.filter((c) => (c.epss ?? 0) >= 0.8 || (c.reports ?? 0) >= 100).slice(0, 4).map((c) => c.cve).join(", ");
|
|
36179
|
+
const recommendation = `Prioritize the ${unsaturated.length} non-saturated classes (top: ${unsaturated.slice(0, 3).map((e) => e.cwe).join(", ")}) — high frequency + high payout + not yet saturated. Skip saturated/low-value classes (XSS/CSRF/open-redirect standalone) unless they chain into account takeover. FINGERPRINT FIRST: if the target stack matches a watchlist product (Next.js, Kibana, Langflow, Confluence/Jira, PAN-OS, Grafana), test that specific CVE — top EPSS/report CVEs: ${topCve}.`;
|
|
36180
|
+
return { cwe_frequency: entries, cve_watchlist, recommendation };
|
|
36181
|
+
}
|
|
36182
|
+
function watchlistCveMatches(techs, headers = {}, body = "") {
|
|
36183
|
+
const { cve_watchlist } = loadWatchlist();
|
|
36184
|
+
const haystack = `${techs.join(" ")} ${Object.keys(headers).join(" ")} ${Object.values(headers).join(" ")} ${body}`.toLowerCase();
|
|
36185
|
+
return cve_watchlist.filter((c) => {
|
|
36186
|
+
const m = c.match;
|
|
36187
|
+
if (!m)
|
|
36188
|
+
return false;
|
|
36189
|
+
try {
|
|
36190
|
+
return new RegExp(m, "i").test(haystack);
|
|
36191
|
+
} catch {
|
|
36192
|
+
return false;
|
|
36193
|
+
}
|
|
36194
|
+
});
|
|
36195
|
+
}
|
|
36196
|
+
|
|
36070
36197
|
// src/live-recon.ts
|
|
36071
|
-
import { resolve4 } from "node:dns/promises";
|
|
36198
|
+
import { resolve4 as resolve42 } from "node:dns/promises";
|
|
36072
36199
|
import { connect } from "node:net";
|
|
36073
36200
|
var HTTP_TIMEOUT_MS = 20000;
|
|
36074
36201
|
function isLikelyCdn(ip) {
|
|
@@ -36284,7 +36411,7 @@ async function dnsBrute(domain) {
|
|
|
36284
36411
|
const hosts = COMMON_SUBDOMAINS.map((p) => `${p}.${base}`).filter((h) => !seen.has(h));
|
|
36285
36412
|
const settled = await Promise.allSettled(hosts.map(async (host) => {
|
|
36286
36413
|
try {
|
|
36287
|
-
const ips = await
|
|
36414
|
+
const ips = await resolve42(host);
|
|
36288
36415
|
return ips.length > 0 ? { host, ips } : null;
|
|
36289
36416
|
} catch {
|
|
36290
36417
|
return null;
|
|
@@ -36501,6 +36628,7 @@ async function liveRecon(target, includeActive = false) {
|
|
|
36501
36628
|
const waf = detectWaf(root.headers, root.body);
|
|
36502
36629
|
const techs = detectTech(root.headers, root.body);
|
|
36503
36630
|
const versions = extractVersion(root.headers, root.body);
|
|
36631
|
+
const suggestedCves = watchlistCveMatches(techs, root.headers, root.body);
|
|
36504
36632
|
const { links, scripts } = crawlLinks(base, root.body);
|
|
36505
36633
|
const { robots, sitemap } = await fetchRobotsAndSitemap(base);
|
|
36506
36634
|
const allUrls = [...links, ...scripts, ...robots, ...sitemap];
|
|
@@ -36534,6 +36662,9 @@ async function liveRecon(target, includeActive = false) {
|
|
|
36534
36662
|
for (const v of versions) {
|
|
36535
36663
|
nextSteps.push(`Version ${v.product}/${v.version} detected — check cve_correlation + nvd_lookup for known CVEs affecting ${v.product} ${v.version}.`);
|
|
36536
36664
|
}
|
|
36665
|
+
if (suggestedCves.length > 0) {
|
|
36666
|
+
nextSteps.push(`FINGERPRINT MATCH: the stack matches a high-value watchlist product — test these specific CVEs first instead of generic scanning: ${suggestedCves.map((c) => `${c.cve} (${c.name}, EPSS ${((c.epss ?? 0) * 100).toFixed(0)}%)`).join("; ")}.`);
|
|
36667
|
+
}
|
|
36537
36668
|
if (crawl.endpoints.length > 0)
|
|
36538
36669
|
nextSteps.push(`Found ${crawl.endpoints.length} endpoints — feed them to taint_file/blitz_scan for source analysis, or strike_verify for live validation.`);
|
|
36539
36670
|
if (params.length > 0)
|
|
@@ -36560,6 +36691,7 @@ async function liveRecon(target, includeActive = false) {
|
|
|
36560
36691
|
waf,
|
|
36561
36692
|
tech: { detected: techs, server: fingerprint.server },
|
|
36562
36693
|
versions,
|
|
36694
|
+
suggested_cves: suggestedCves.map((c) => ({ cve: c.cve, name: c.name, product: c.product, epss: c.epss, reports: c.reports })),
|
|
36563
36695
|
crawl,
|
|
36564
36696
|
params,
|
|
36565
36697
|
subdomains,
|
|
@@ -36636,9 +36768,9 @@ function loadChains() {
|
|
|
36636
36768
|
if (_chainsCache)
|
|
36637
36769
|
return _chainsCache;
|
|
36638
36770
|
try {
|
|
36639
|
-
const dir =
|
|
36640
|
-
const path =
|
|
36641
|
-
const raw = JSON.parse(
|
|
36771
|
+
const dir = join13(fileURLToPath6(new URL(".", import.meta.url)), "..");
|
|
36772
|
+
const path = join13(dir, "chains.json");
|
|
36773
|
+
const raw = JSON.parse(readFileSync11(path, "utf8"));
|
|
36642
36774
|
_chainsCache = raw.chains ?? [];
|
|
36643
36775
|
return _chainsCache;
|
|
36644
36776
|
} catch {
|
|
@@ -37419,8 +37551,8 @@ var CHAIN_ID_TO_ESCALATION = {
|
|
|
37419
37551
|
};
|
|
37420
37552
|
function loadChainLinks() {
|
|
37421
37553
|
try {
|
|
37422
|
-
const dir =
|
|
37423
|
-
const data = JSON.parse(
|
|
37554
|
+
const dir = join13(fileURLToPath6(new URL(".", import.meta.url)), "..");
|
|
37555
|
+
const data = JSON.parse(readFileSync11(join13(dir, "intelligence", "chain_links.json"), "utf8"));
|
|
37424
37556
|
return data.links ?? [];
|
|
37425
37557
|
} catch {
|
|
37426
37558
|
return [];
|
|
@@ -37491,7 +37623,7 @@ async function runAutonomous(target, scope = "", mode = "bug-bounty", maxPasses
|
|
|
37491
37623
|
}
|
|
37492
37624
|
|
|
37493
37625
|
// src/ops.ts
|
|
37494
|
-
import { execSync, execFileSync } from "node:child_process";
|
|
37626
|
+
import { execSync, execFileSync as execFileSync2 } from "node:child_process";
|
|
37495
37627
|
function decomposeBatches(tasks) {
|
|
37496
37628
|
const remaining = [...tasks];
|
|
37497
37629
|
const done = new Set;
|
|
@@ -37536,13 +37668,13 @@ function worktree(action, path, branch) {
|
|
|
37536
37668
|
if (!path)
|
|
37537
37669
|
return { action, ok: false, error: "path is required for worktree add" };
|
|
37538
37670
|
const b = branch ?? `blitz-${Date.now().toString(36)}`;
|
|
37539
|
-
const out =
|
|
37671
|
+
const out = execFileSync2("git", ["worktree", "add", "-b", b, path], { encoding: "utf8" }).trim();
|
|
37540
37672
|
return { action, ok: true, branch: b, path, out };
|
|
37541
37673
|
}
|
|
37542
37674
|
if (action === "remove") {
|
|
37543
37675
|
if (!path)
|
|
37544
37676
|
return { action, ok: false, error: "path is required for worktree remove" };
|
|
37545
|
-
|
|
37677
|
+
execFileSync2("git", ["worktree", "remove", path, "--force"], { encoding: "utf8" });
|
|
37546
37678
|
return { action, ok: true, path };
|
|
37547
37679
|
}
|
|
37548
37680
|
return { action, ok: false, error: "unknown action (use add | list | remove)" };
|
|
@@ -37576,18 +37708,18 @@ function compactState(phase, findings) {
|
|
|
37576
37708
|
}
|
|
37577
37709
|
|
|
37578
37710
|
// src/tasks.ts
|
|
37579
|
-
import { mkdirSync as mkdirSync6, readFileSync as
|
|
37580
|
-
import { join as
|
|
37711
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync12, writeFileSync as writeFileSync3 } from "node:fs";
|
|
37712
|
+
import { join as join14 } from "node:path";
|
|
37581
37713
|
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
37582
37714
|
function tasksDir() {
|
|
37583
|
-
return
|
|
37715
|
+
return join14(fileURLToPath7(new URL(".", import.meta.url)), "..", ".blitzstrike", "tasks");
|
|
37584
37716
|
}
|
|
37585
37717
|
function taskPath(id) {
|
|
37586
|
-
return
|
|
37718
|
+
return join14(tasksDir(), `${id}.json`);
|
|
37587
37719
|
}
|
|
37588
37720
|
function load2(id) {
|
|
37589
37721
|
try {
|
|
37590
|
-
return JSON.parse(
|
|
37722
|
+
return JSON.parse(readFileSync12(taskPath(id), "utf8"));
|
|
37591
37723
|
} catch {
|
|
37592
37724
|
return null;
|
|
37593
37725
|
}
|
|
@@ -37925,18 +38057,18 @@ function scaffoldEngagement(target, scope = [], out_of_scope = []) {
|
|
|
37925
38057
|
}
|
|
37926
38058
|
|
|
37927
38059
|
// src/incremental.ts
|
|
37928
|
-
import { execFileSync as
|
|
38060
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
37929
38061
|
import { writeFileSync as writeFileSync4, mkdtempSync, rmSync } from "node:fs";
|
|
37930
|
-
import { join as
|
|
38062
|
+
import { join as join15 } from "node:path";
|
|
37931
38063
|
import { tmpdir } from "node:os";
|
|
37932
38064
|
|
|
37933
38065
|
// src/eagle2.ts
|
|
37934
38066
|
var import_php_parser2 = __toESM(require_src(), 1);
|
|
37935
|
-
import { readFileSync as
|
|
38067
|
+
import { readFileSync as readFileSync15 } from "node:fs";
|
|
37936
38068
|
|
|
37937
38069
|
// src/dataflow.ts
|
|
37938
38070
|
init_scanner();
|
|
37939
|
-
import { readFileSync as
|
|
38071
|
+
import { readFileSync as readFileSync13 } from "node:fs";
|
|
37940
38072
|
var SOURCES = {
|
|
37941
38073
|
$_GET: { id: "http_get", label: "HTTP GET parameter", attacker_controlled: true },
|
|
37942
38074
|
$_POST: { id: "http_post", label: "HTTP POST body", attacker_controlled: true },
|
|
@@ -38021,7 +38153,7 @@ function traceDataFlow(path) {
|
|
|
38021
38153
|
return result;
|
|
38022
38154
|
let text;
|
|
38023
38155
|
try {
|
|
38024
|
-
text =
|
|
38156
|
+
text = readFileSync13(path, "utf8");
|
|
38025
38157
|
} catch {
|
|
38026
38158
|
return result;
|
|
38027
38159
|
}
|
|
@@ -38102,7 +38234,7 @@ init_scanner();
|
|
|
38102
38234
|
|
|
38103
38235
|
// src/callgraph.ts
|
|
38104
38236
|
var import_php_parser = __toESM(require_src(), 1);
|
|
38105
|
-
import { readFileSync as
|
|
38237
|
+
import { readFileSync as readFileSync14 } from "node:fs";
|
|
38106
38238
|
var parser = new import_php_parser.Engine({
|
|
38107
38239
|
parser: { extractDoc: false, php7: true },
|
|
38108
38240
|
ast: { withPositions: true, withSource: false }
|
|
@@ -38163,7 +38295,7 @@ function buildCallGraph(path) {
|
|
|
38163
38295
|
const g = { nodes: new Map, edges: [] };
|
|
38164
38296
|
let code;
|
|
38165
38297
|
try {
|
|
38166
|
-
code =
|
|
38298
|
+
code = readFileSync14(path, "utf8");
|
|
38167
38299
|
} catch {
|
|
38168
38300
|
return g;
|
|
38169
38301
|
}
|
|
@@ -38825,7 +38957,7 @@ function resolveFuncId(name, summaries) {
|
|
|
38825
38957
|
function analyzeDataFlow2(path) {
|
|
38826
38958
|
let cacheCode = "";
|
|
38827
38959
|
try {
|
|
38828
|
-
cacheCode =
|
|
38960
|
+
cacheCode = readFileSync15(path, "utf8");
|
|
38829
38961
|
} catch {}
|
|
38830
38962
|
if (cacheCode) {
|
|
38831
38963
|
const cached = getCache("eagle2", cacheCode);
|
|
@@ -38847,7 +38979,7 @@ function analyzeDataFlow2(path) {
|
|
|
38847
38979
|
};
|
|
38848
38980
|
let code;
|
|
38849
38981
|
try {
|
|
38850
|
-
code = cacheCode ||
|
|
38982
|
+
code = cacheCode || readFileSync15(path, "utf8");
|
|
38851
38983
|
} catch {
|
|
38852
38984
|
return result;
|
|
38853
38985
|
}
|
|
@@ -57518,7 +57650,7 @@ registerLanguage(rustAdapter);
|
|
|
57518
57650
|
var SOURCE_EXTS = [".php", ".phtml", ".js", ".mjs", ".ts", ".tsx", ".py", ".pyw", ".java"];
|
|
57519
57651
|
function git(repo, args) {
|
|
57520
57652
|
try {
|
|
57521
|
-
return
|
|
57653
|
+
return execFileSync3("git", args.split(/\s+/).filter(Boolean), { cwd: repo, encoding: "utf8", maxBuffer: 50 * 1024 * 1024 }).trim();
|
|
57522
57654
|
} catch {
|
|
57523
57655
|
return "";
|
|
57524
57656
|
}
|
|
@@ -57533,8 +57665,8 @@ function analyzeChangedFiles(repo, base = "HEAD~1", head = "HEAD") {
|
|
|
57533
57665
|
continue;
|
|
57534
57666
|
const lang = detectLanguage(file)?.language;
|
|
57535
57667
|
if (lang === "php") {
|
|
57536
|
-
const dir = mkdtempSync(
|
|
57537
|
-
const tmp =
|
|
57668
|
+
const dir = mkdtempSync(join15(tmpdir(), "blitz-incr-"));
|
|
57669
|
+
const tmp = join15(dir, "changed.php");
|
|
57538
57670
|
writeFileSync4(tmp, code);
|
|
57539
57671
|
try {
|
|
57540
57672
|
const r = analyzeDataFlow2(tmp);
|
|
@@ -57555,7 +57687,7 @@ function analyzeChangedFiles(repo, base = "HEAD~1", head = "HEAD") {
|
|
|
57555
57687
|
// src/taint.ts
|
|
57556
57688
|
init_scanner();
|
|
57557
57689
|
var import_php_parser4 = __toESM(require_src(), 1);
|
|
57558
|
-
import { readFileSync as
|
|
57690
|
+
import { readFileSync as readFileSync16 } from "node:fs";
|
|
57559
57691
|
var parser4 = new import_php_parser4.Engine({
|
|
57560
57692
|
parser: { extractDoc: false, php7: true },
|
|
57561
57693
|
ast: { withPositions: true, withSource: false }
|
|
@@ -57853,7 +57985,7 @@ function analyzeTaint(path) {
|
|
|
57853
57985
|
const result = { file: path, findings: [], suppressed: 0 };
|
|
57854
57986
|
let code;
|
|
57855
57987
|
try {
|
|
57856
|
-
code =
|
|
57988
|
+
code = readFileSync16(path, "utf8");
|
|
57857
57989
|
} catch {
|
|
57858
57990
|
return result;
|
|
57859
57991
|
}
|
|
@@ -57947,15 +58079,15 @@ function taintTree(root, maxFiles = 2000) {
|
|
|
57947
58079
|
}
|
|
57948
58080
|
|
|
57949
58081
|
// src/differential.ts
|
|
57950
|
-
import { writeFileSync as writeFileSync5, rmSync as rmSync2, mkdtempSync as mkdtempSync2, readFileSync as
|
|
57951
|
-
import { join as
|
|
58082
|
+
import { writeFileSync as writeFileSync5, rmSync as rmSync2, mkdtempSync as mkdtempSync2, readFileSync as readFileSync17 } from "node:fs";
|
|
58083
|
+
import { join as join16 } from "node:path";
|
|
57952
58084
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
57953
|
-
import { execFileSync as
|
|
58085
|
+
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
57954
58086
|
init_scanner();
|
|
57955
58087
|
function runEngine(code, language, file) {
|
|
57956
58088
|
if (language === "php") {
|
|
57957
|
-
const dir = mkdtempSync2(
|
|
57958
|
-
const tmp =
|
|
58089
|
+
const dir = mkdtempSync2(join16(tmpdir2(), "blitz-diff-"));
|
|
58090
|
+
const tmp = join16(dir, "old.php");
|
|
57959
58091
|
writeFileSync5(tmp, code);
|
|
57960
58092
|
try {
|
|
57961
58093
|
const r = analyzeDataFlow2(tmp);
|
|
@@ -58109,8 +58241,8 @@ function languageOf(file) {
|
|
|
58109
58241
|
return adapter?.language ?? "php";
|
|
58110
58242
|
}
|
|
58111
58243
|
function analyzeDifferentialFiles(oldPath, newPath, language) {
|
|
58112
|
-
const oldCode =
|
|
58113
|
-
const newCode =
|
|
58244
|
+
const oldCode = readFileSync17(oldPath, "utf8");
|
|
58245
|
+
const newCode = readFileSync17(newPath, "utf8");
|
|
58114
58246
|
const lang = language ?? languageOf(newPath);
|
|
58115
58247
|
return analyzeDifferential(oldCode, newCode, lang, newPath);
|
|
58116
58248
|
}
|
|
@@ -58118,7 +58250,7 @@ var SOURCE_EXTS2 = [".php", ".phtml", ".php5", ".js", ".mjs", ".ts", ".tsx", ".p
|
|
|
58118
58250
|
function analyzeGitDiff(repo, base = "HEAD~1", head = "HEAD") {
|
|
58119
58251
|
const git = (args) => {
|
|
58120
58252
|
try {
|
|
58121
|
-
return
|
|
58253
|
+
return execFileSync4("git", args.split(/\s+/).filter(Boolean), { cwd: repo, encoding: "utf8", maxBuffer: 50 * 1024 * 1024 }).trim();
|
|
58122
58254
|
} catch {
|
|
58123
58255
|
return "";
|
|
58124
58256
|
}
|
|
@@ -58237,27 +58369,27 @@ function analyzePatch(oldCode, newCode, language = "php") {
|
|
|
58237
58369
|
};
|
|
58238
58370
|
}
|
|
58239
58371
|
function analyzePatchFiles(oldPath, newPath, language) {
|
|
58240
|
-
const oldCode =
|
|
58241
|
-
const newCode =
|
|
58372
|
+
const oldCode = readFileSync17(oldPath, "utf8");
|
|
58373
|
+
const newCode = readFileSync17(newPath, "utf8");
|
|
58242
58374
|
const lang = language ?? languageOf(newPath);
|
|
58243
58375
|
return analyzePatch(oldCode, newCode, lang);
|
|
58244
58376
|
}
|
|
58245
58377
|
|
|
58246
58378
|
// src/frameworks.ts
|
|
58247
|
-
import { readFileSync as
|
|
58248
|
-
import { join as
|
|
58379
|
+
import { readFileSync as readFileSync18, existsSync as existsSync9 } from "node:fs";
|
|
58380
|
+
import { join as join17 } from "node:path";
|
|
58249
58381
|
import { fileURLToPath as fileURLToPath8 } from "node:url";
|
|
58250
|
-
var ROOT4 =
|
|
58251
|
-
var INTEL2 =
|
|
58382
|
+
var ROOT4 = join17(fileURLToPath8(new URL(".", import.meta.url)), "..");
|
|
58383
|
+
var INTEL2 = join17(ROOT4, "intelligence");
|
|
58252
58384
|
var cache = null;
|
|
58253
58385
|
function load3() {
|
|
58254
58386
|
if (cache)
|
|
58255
58387
|
return cache;
|
|
58256
|
-
const p =
|
|
58388
|
+
const p = join17(INTEL2, "frameworks.json");
|
|
58257
58389
|
if (!existsSync9(p))
|
|
58258
58390
|
return null;
|
|
58259
58391
|
try {
|
|
58260
|
-
cache = JSON.parse(
|
|
58392
|
+
cache = JSON.parse(readFileSync18(p, "utf8"));
|
|
58261
58393
|
return cache;
|
|
58262
58394
|
} catch {
|
|
58263
58395
|
return null;
|
|
@@ -58767,9 +58899,9 @@ function nextSteps(ctx) {
|
|
|
58767
58899
|
|
|
58768
58900
|
// src/git-history.ts
|
|
58769
58901
|
init_complex_bugs();
|
|
58770
|
-
import { execFileSync as
|
|
58902
|
+
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
58771
58903
|
import { existsSync as existsSync11 } from "node:fs";
|
|
58772
|
-
import { join as
|
|
58904
|
+
import { join as join18 } from "node:path";
|
|
58773
58905
|
function parseDeletions(output) {
|
|
58774
58906
|
const out = [];
|
|
58775
58907
|
let commit = "";
|
|
@@ -58790,7 +58922,7 @@ function parseDeletions(output) {
|
|
|
58790
58922
|
return out;
|
|
58791
58923
|
}
|
|
58792
58924
|
function blobBeforeDeletion(root, commit, path) {
|
|
58793
|
-
return
|
|
58925
|
+
return execFileSync5("git", ["-C", root, "show", `${commit}^:${path}`], {
|
|
58794
58926
|
encoding: "utf8",
|
|
58795
58927
|
maxBuffer: 10 * 1024 * 1024
|
|
58796
58928
|
});
|
|
@@ -58798,12 +58930,12 @@ function blobBeforeDeletion(root, commit, path) {
|
|
|
58798
58930
|
function scanGitHistory(root, opts) {
|
|
58799
58931
|
const limit = opts?.limit ?? 30;
|
|
58800
58932
|
const findings = [];
|
|
58801
|
-
if (!existsSync11(
|
|
58933
|
+
if (!existsSync11(join18(root, ".git"))) {
|
|
58802
58934
|
return { root, is_git_repo: false, total: 0, findings: [], error: "not a git repository (no .git dir)" };
|
|
58803
58935
|
}
|
|
58804
58936
|
if (opts?.secretPattern) {
|
|
58805
58937
|
try {
|
|
58806
|
-
const pick =
|
|
58938
|
+
const pick = execFileSync5("git", ["-C", root, "log", "-S", opts.secretPattern, "--all", "--format=__COMMIT__ %H %s"], { encoding: "utf8", maxBuffer: 5 * 1024 * 1024 });
|
|
58807
58939
|
const commits = pick.split(`
|
|
58808
58940
|
`).filter(Boolean).map((l) => {
|
|
58809
58941
|
const rest = l.startsWith("__COMMIT__ ") ? l.slice("__COMMIT__ ".length) : l;
|
|
@@ -58824,7 +58956,7 @@ function scanGitHistory(root, opts) {
|
|
|
58824
58956
|
}
|
|
58825
58957
|
}
|
|
58826
58958
|
try {
|
|
58827
|
-
const log =
|
|
58959
|
+
const log = execFileSync5("git", ["-C", root, "log", "--all", "--diff-filter=D", "--name-status", "--format=__COMMIT__ %H %s", "--no-renames"], { encoding: "utf8", maxBuffer: 10 * 1024 * 1024 });
|
|
58828
58960
|
const deletions = parseDeletions(log);
|
|
58829
58961
|
const seen = new Set;
|
|
58830
58962
|
let checked = 0;
|
|
@@ -58945,13 +59077,13 @@ async function validateLeakedKey(input) {
|
|
|
58945
59077
|
}
|
|
58946
59078
|
|
|
58947
59079
|
// src/hackbot-arena.ts
|
|
58948
|
-
import { readFileSync as
|
|
58949
|
-
import { join as
|
|
59080
|
+
import { readFileSync as readFileSync19 } from "node:fs";
|
|
59081
|
+
import { join as join19, dirname as dirname3 } from "node:path";
|
|
58950
59082
|
import { fileURLToPath as fileURLToPath9 } from "node:url";
|
|
58951
59083
|
var __dirname3 = dirname3(fileURLToPath9(import.meta.url));
|
|
58952
59084
|
function load4() {
|
|
58953
59085
|
try {
|
|
58954
|
-
return JSON.parse(
|
|
59086
|
+
return JSON.parse(readFileSync19(join19(__dirname3, "..", "intelligence", "hackbot-arena.json"), "utf8"));
|
|
58955
59087
|
} catch {
|
|
58956
59088
|
return { total: 0, flag_format: "FLAG{nusasec-<32 hex>}", labs: [] };
|
|
58957
59089
|
}
|
|
@@ -59113,13 +59245,13 @@ function listSeverityMap() {
|
|
|
59113
59245
|
}
|
|
59114
59246
|
|
|
59115
59247
|
// src/datarefresh.ts
|
|
59116
|
-
import { readFileSync as
|
|
59117
|
-
import { join as
|
|
59248
|
+
import { readFileSync as readFileSync20 } from "node:fs";
|
|
59249
|
+
import { join as join20, dirname as dirname4 } from "node:path";
|
|
59118
59250
|
import { fileURLToPath as fileURLToPath10 } from "node:url";
|
|
59119
59251
|
var __dirname4 = dirname4(fileURLToPath10(import.meta.url));
|
|
59120
59252
|
function loadJson2(rel) {
|
|
59121
59253
|
try {
|
|
59122
|
-
return JSON.parse(
|
|
59254
|
+
return JSON.parse(readFileSync20(join20(__dirname4, "..", rel), "utf8"));
|
|
59123
59255
|
} catch {
|
|
59124
59256
|
return null;
|
|
59125
59257
|
}
|
|
@@ -59154,18 +59286,18 @@ init_verify_harness();
|
|
|
59154
59286
|
init_variants();
|
|
59155
59287
|
|
|
59156
59288
|
// src/benchmark.ts
|
|
59157
|
-
import { readFileSync as
|
|
59158
|
-
import { join as
|
|
59289
|
+
import { readFileSync as readFileSync22, readdirSync as readdirSync5, writeFileSync as writeFileSync6, rmSync as rmSync3, mkdirSync as mkdirSync7 } from "node:fs";
|
|
59290
|
+
import { join as join22 } from "node:path";
|
|
59159
59291
|
import { fileURLToPath as fileURLToPath12 } from "node:url";
|
|
59160
59292
|
import { tmpdir as tmpdir3, homedir as homedir8 } from "node:os";
|
|
59161
59293
|
init_complex_bugs();
|
|
59162
|
-
var ROOT5 =
|
|
59163
|
-
var CORPUS =
|
|
59164
|
-
var CAPTURED_CORPUS_DIR = process.env.BLITZSTRIKE_CORPUS_DIR ??
|
|
59294
|
+
var ROOT5 = join22(fileURLToPath12(new URL(".", import.meta.url)), "..");
|
|
59295
|
+
var CORPUS = join22(ROOT5, "bench", "corpus");
|
|
59296
|
+
var CAPTURED_CORPUS_DIR = process.env.BLITZSTRIKE_CORPUS_DIR ?? join22(homedir8(), ".blitzstrike", "corpus");
|
|
59165
59297
|
function parseCorpusFile(path) {
|
|
59166
59298
|
let text;
|
|
59167
59299
|
try {
|
|
59168
|
-
text =
|
|
59300
|
+
text = readFileSync22(path, "utf8");
|
|
59169
59301
|
} catch {
|
|
59170
59302
|
return null;
|
|
59171
59303
|
}
|
|
@@ -59221,7 +59353,7 @@ function loadDir(dir) {
|
|
|
59221
59353
|
for (const f of readdirSync5(dir)) {
|
|
59222
59354
|
if (!f.endsWith(".md"))
|
|
59223
59355
|
continue;
|
|
59224
|
-
const e = parseCorpusFile(
|
|
59356
|
+
const e = parseCorpusFile(join22(dir, f));
|
|
59225
59357
|
if (e)
|
|
59226
59358
|
entries.push(e);
|
|
59227
59359
|
}
|
|
@@ -59248,7 +59380,7 @@ function captureFalsePositive(input) {
|
|
|
59248
59380
|
input.code,
|
|
59249
59381
|
"```"
|
|
59250
59382
|
].filter((l) => l !== null);
|
|
59251
|
-
const path =
|
|
59383
|
+
const path = join22(CAPTURED_CORPUS_DIR, `${id}.md`);
|
|
59252
59384
|
writeFileSync6(path, lines.join(`
|
|
59253
59385
|
`) + `
|
|
59254
59386
|
`, "utf8");
|
|
@@ -59270,7 +59402,7 @@ function detect(entry) {
|
|
|
59270
59402
|
}
|
|
59271
59403
|
const lang = entry.language;
|
|
59272
59404
|
if (lang === "php") {
|
|
59273
|
-
const tmp =
|
|
59405
|
+
const tmp = join22(tmpdir3(), `blitzstrike-bench-${entry.id}-${process.pid}.php`);
|
|
59274
59406
|
writeFileSync6(tmp, entry.code);
|
|
59275
59407
|
const r = analyzeDataFlow2(tmp);
|
|
59276
59408
|
rmSync3(tmp, { force: true });
|
|
@@ -59339,12 +59471,12 @@ function runBenchmark() {
|
|
|
59339
59471
|
}
|
|
59340
59472
|
|
|
59341
59473
|
// src/obligations.ts
|
|
59342
|
-
import { readFileSync as
|
|
59343
|
-
import { join as
|
|
59474
|
+
import { readFileSync as readFileSync23, appendFileSync as appendFileSync4, mkdirSync as mkdirSync8, existsSync as existsSync12, writeFileSync as writeFileSync7 } from "node:fs";
|
|
59475
|
+
import { join as join23 } from "node:path";
|
|
59344
59476
|
import { createHash as createHash6 } from "node:crypto";
|
|
59345
59477
|
import { homedir as homedir9 } from "node:os";
|
|
59346
|
-
var HOME_DIR4 = process.env.BLITZSTRIKE_HOME ??
|
|
59347
|
-
var OBLIGATION_PATH =
|
|
59478
|
+
var HOME_DIR4 = process.env.BLITZSTRIKE_HOME ?? join23(homedir9(), ".blitzstrike");
|
|
59479
|
+
var OBLIGATION_PATH = join23(HOME_DIR4, "obligations.jsonl");
|
|
59348
59480
|
function ensureDir3() {
|
|
59349
59481
|
if (!existsSync12(HOME_DIR4))
|
|
59350
59482
|
mkdirSync8(HOME_DIR4, { recursive: true });
|
|
@@ -59357,7 +59489,7 @@ function loadObligations() {
|
|
|
59357
59489
|
return [];
|
|
59358
59490
|
try {
|
|
59359
59491
|
const out = [];
|
|
59360
|
-
for (const line of
|
|
59492
|
+
for (const line of readFileSync23(OBLIGATION_PATH, "utf8").split(`
|
|
59361
59493
|
`)) {
|
|
59362
59494
|
const t = line.trim();
|
|
59363
59495
|
if (!t)
|
|
@@ -59461,11 +59593,11 @@ init_framework_security();
|
|
|
59461
59593
|
// src/variant-scan.ts
|
|
59462
59594
|
init_scanner();
|
|
59463
59595
|
init_complex_bugs();
|
|
59464
|
-
import { readFileSync as
|
|
59596
|
+
import { readFileSync as readFileSync25 } from "node:fs";
|
|
59465
59597
|
|
|
59466
59598
|
// src/cross-file.ts
|
|
59467
59599
|
init_scanner();
|
|
59468
|
-
import { readFileSync as
|
|
59600
|
+
import { readFileSync as readFileSync24 } from "node:fs";
|
|
59469
59601
|
var PUBLIC_HOOK_RE = /(?:wp_ajax_nopriv_\w+|admin_post_nopriv_\w+|init|template_redirect|wp_loaded|parse_request|user_register|um_user_register|register_new_user|authenticate)/;
|
|
59470
59602
|
var PRIV_SINK_RE = /\b(set_role|add_role|remove_role|set_user_role|wp_set_current_user|wp_set_auth_cookie|wp_insert_user|wp_create_user|wp_update_user)\s*\(/;
|
|
59471
59603
|
var ROLE_META_RE = /update_user_meta\s*\(\s*[^,]+,\s*['"](wp_capabilities|role|roles|capabilities|wp_user_level)['"]/;
|
|
@@ -59517,7 +59649,7 @@ function detectCrossFilePrivesc(root) {
|
|
|
59517
59649
|
for (const file of files) {
|
|
59518
59650
|
let code;
|
|
59519
59651
|
try {
|
|
59520
|
-
code = stripComments(
|
|
59652
|
+
code = stripComments(readFileSync24(file, "utf8"));
|
|
59521
59653
|
} catch {
|
|
59522
59654
|
continue;
|
|
59523
59655
|
}
|
|
@@ -59558,7 +59690,7 @@ function detectCrossFilePrivesc(root) {
|
|
|
59558
59690
|
for (const file of files) {
|
|
59559
59691
|
let code;
|
|
59560
59692
|
try {
|
|
59561
|
-
code = stripComments(
|
|
59693
|
+
code = stripComments(readFileSync24(file, "utf8"));
|
|
59562
59694
|
} catch {
|
|
59563
59695
|
continue;
|
|
59564
59696
|
}
|
|
@@ -59581,7 +59713,7 @@ function detectCrossFilePrivesc(root) {
|
|
|
59581
59713
|
for (const file of files) {
|
|
59582
59714
|
let code;
|
|
59583
59715
|
try {
|
|
59584
|
-
code = stripComments(
|
|
59716
|
+
code = stripComments(readFileSync24(file, "utf8"));
|
|
59585
59717
|
} catch {
|
|
59586
59718
|
continue;
|
|
59587
59719
|
}
|
|
@@ -59696,7 +59828,7 @@ function variantScan(root, opts = {}) {
|
|
|
59696
59828
|
for (const file of files) {
|
|
59697
59829
|
let code;
|
|
59698
59830
|
try {
|
|
59699
|
-
code =
|
|
59831
|
+
code = readFileSync25(file, "utf8");
|
|
59700
59832
|
} catch {
|
|
59701
59833
|
continue;
|
|
59702
59834
|
}
|
|
@@ -60943,6 +61075,11 @@ var HUNTING_DOCTRINE = [
|
|
|
60943
61075
|
id: "rotation",
|
|
60944
61076
|
title: "20-Minute Rotation",
|
|
60945
61077
|
rule: "Every 20 minutes ask 'am I making progress?'. No → rotate to the next endpoint / subdomain / class. Fresh context finds more bugs than brute force."
|
|
61078
|
+
},
|
|
61079
|
+
{
|
|
61080
|
+
id: "dedup_before_report",
|
|
61081
|
+
title: "Search for Duplicates Before Reporting",
|
|
61082
|
+
rule: "The #1 cause of 'Duplicate'/'N/A' rejections is not searching first. Before writing the report, search Hacktivity (and the program's prior public reports) for the same endpoint/tech/vuln class; an already-reported open S3 bucket, CORS misconfig, or XSS param is not worth a second report. Only submit what is genuinely new."
|
|
60946
61083
|
}
|
|
60947
61084
|
];
|
|
60948
61085
|
function huntingDoctrine() {
|
|
@@ -61371,7 +61508,7 @@ function listPocTypes() {
|
|
|
61371
61508
|
import { execSync as execSync3 } from "node:child_process";
|
|
61372
61509
|
import { mkdtempSync as mkdtempSync3, mkdirSync as mkdirSync9, writeFileSync as writeFileSync8, rmSync as rmSync4, existsSync as existsSync14 } from "node:fs";
|
|
61373
61510
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
61374
|
-
import { join as
|
|
61511
|
+
import { join as join24 } from "node:path";
|
|
61375
61512
|
function findForge() {
|
|
61376
61513
|
try {
|
|
61377
61514
|
const p = execSync3("which forge 2>/dev/null", { encoding: "utf8", timeout: 3000 }).trim();
|
|
@@ -61420,14 +61557,14 @@ async function foundryRun(input) {
|
|
|
61420
61557
|
};
|
|
61421
61558
|
}
|
|
61422
61559
|
const poc = input.poc_code ?? foundryPoc(input.class_ ?? "missing_access_control", name);
|
|
61423
|
-
const dir = mkdtempSync3(
|
|
61560
|
+
const dir = mkdtempSync3(join24(tmpdir4(), "blitzstrike-foundry-"));
|
|
61424
61561
|
try {
|
|
61425
61562
|
try {
|
|
61426
61563
|
execSync3(`${forge} init --force --no-git ${dir}`, { stdio: "ignore", timeout: 60000 });
|
|
61427
61564
|
} catch {
|
|
61428
|
-
mkdirSync9(
|
|
61429
|
-
mkdirSync9(
|
|
61430
|
-
writeFileSync8(
|
|
61565
|
+
mkdirSync9(join24(dir, "src"), { recursive: true });
|
|
61566
|
+
mkdirSync9(join24(dir, "test"), { recursive: true });
|
|
61567
|
+
writeFileSync8(join24(dir, "foundry.toml"), `[profile.default]
|
|
61431
61568
|
src = "src"
|
|
61432
61569
|
out = "out"
|
|
61433
61570
|
libs = ["lib"]
|
|
@@ -61437,12 +61574,12 @@ solc = "0.8.20"
|
|
|
61437
61574
|
for (const f of ["Counter.sol", "Counter.t.sol", "Counter.s.sol"]) {
|
|
61438
61575
|
for (const sub of ["src", "test", "script"]) {
|
|
61439
61576
|
try {
|
|
61440
|
-
rmSync4(
|
|
61577
|
+
rmSync4(join24(dir, sub, f), { force: true });
|
|
61441
61578
|
} catch {}
|
|
61442
61579
|
}
|
|
61443
61580
|
}
|
|
61444
|
-
writeFileSync8(
|
|
61445
|
-
writeFileSync8(
|
|
61581
|
+
writeFileSync8(join24(dir, "src", `${name}.sol`), input.contract_code);
|
|
61582
|
+
writeFileSync8(join24(dir, "test", `${name}.t.sol`), poc);
|
|
61446
61583
|
const { out } = runForge(forge, dir, input.timeout_ms ?? 60000);
|
|
61447
61584
|
const tests = parseTestResults(out);
|
|
61448
61585
|
const anyFail = tests.some((t) => t.status === "fail");
|
|
@@ -61459,118 +61596,6 @@ solc = "0.8.20"
|
|
|
61459
61596
|
}
|
|
61460
61597
|
}
|
|
61461
61598
|
|
|
61462
|
-
// src/hunting-intel.ts
|
|
61463
|
-
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
61464
|
-
import { resolve4 as resolve42, Resolver } from "node:dns/promises";
|
|
61465
|
-
import { readFileSync as readFileSync25 } from "node:fs";
|
|
61466
|
-
import { join as join24 } from "node:path";
|
|
61467
|
-
var BLOCKING_KEYWORDS = /internet positif|kominfo|\.go\.id\b|pemblokiran|ditutup|dilarang|akses dibatasi|blocked|restricted|illegal|advertencia|coljuegos|regulat|forbidden|this (site|website) has been (blocked|banned)/i;
|
|
61468
|
-
var HOSTNAME_RE = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$/i;
|
|
61469
|
-
function isValidHostname(host) {
|
|
61470
|
-
return host.length <= 253 && HOSTNAME_RE.test(host);
|
|
61471
|
-
}
|
|
61472
|
-
async function localResolve(host) {
|
|
61473
|
-
try {
|
|
61474
|
-
return await resolve42(host);
|
|
61475
|
-
} catch {
|
|
61476
|
-
return [];
|
|
61477
|
-
}
|
|
61478
|
-
}
|
|
61479
|
-
async function publicResolve(host) {
|
|
61480
|
-
const r = new Resolver;
|
|
61481
|
-
r.setServers(["8.8.8.8", "1.1.1.1"]);
|
|
61482
|
-
try {
|
|
61483
|
-
return await r.resolve4(host);
|
|
61484
|
-
} catch {
|
|
61485
|
-
return [];
|
|
61486
|
-
}
|
|
61487
|
-
}
|
|
61488
|
-
function probeBlockingPage(host, ip, timeoutMs = 8000) {
|
|
61489
|
-
for (const port of ["443", "80"]) {
|
|
61490
|
-
const scheme = port === "443" ? "https" : "http";
|
|
61491
|
-
try {
|
|
61492
|
-
const out = execFileSync5("curl", ["-sk", "-m", "6", "--resolve", `${host}:${port}:${ip}`, `${scheme}://${host}/`], { encoding: "utf8", timeout: timeoutMs, stdio: ["ignore", "pipe", "ignore"] });
|
|
61493
|
-
const preview = out.replace(/\s+/g, " ").trim().slice(0, 300);
|
|
61494
|
-
if (!preview)
|
|
61495
|
-
continue;
|
|
61496
|
-
return { probed: true, sinkhole: BLOCKING_KEYWORDS.test(preview), preview };
|
|
61497
|
-
} catch {}
|
|
61498
|
-
}
|
|
61499
|
-
return { probed: false, sinkhole: false, preview: "" };
|
|
61500
|
-
}
|
|
61501
|
-
async function resolveCheck(host) {
|
|
61502
|
-
const clean = host.replace(/^https?:\/\//, "").split("/")[0];
|
|
61503
|
-
if (!isValidHostname(clean)) {
|
|
61504
|
-
return { host: clean, local_ips: [], public_ips: [], sinkhole: false, verdict: "invalid", evidence: "not a valid hostname — rejected before DNS/subprocess" };
|
|
61505
|
-
}
|
|
61506
|
-
const local = await localResolve(clean);
|
|
61507
|
-
const pub = await publicResolve(clean);
|
|
61508
|
-
if (local.length === 0 && pub.length === 0) {
|
|
61509
|
-
return { host: clean, local_ips: [], public_ips: [], sinkhole: false, verdict: "unresolvable", evidence: "no A record from local or public DNS" };
|
|
61510
|
-
}
|
|
61511
|
-
if (local.length === 0) {
|
|
61512
|
-
return { host: clean, local_ips: [], public_ips: pub, sinkhole: false, verdict: "local-unresolvable", evidence: "local resolver returned no A record; public DNS resolves. Use the public IP to reach the real target." };
|
|
61513
|
-
}
|
|
61514
|
-
const firstLocal = local[0];
|
|
61515
|
-
const probe = probeBlockingPage(clean, firstLocal);
|
|
61516
|
-
if (probe.sinkhole) {
|
|
61517
|
-
return {
|
|
61518
|
-
host: clean,
|
|
61519
|
-
local_ips: local,
|
|
61520
|
-
public_ips: pub,
|
|
61521
|
-
sinkhole: true,
|
|
61522
|
-
verdict: "sinkhole",
|
|
61523
|
-
evidence: `local IP ${firstLocal} serves a blocking/sinkhole page ("${probe.preview.slice(0, 120)}"). Audit the PUBLIC IP (${pub[0] ?? "n/a"}) instead — e.g. curl --resolve ${clean}:443:${pub[0]} https://${clean}/`
|
|
61524
|
-
};
|
|
61525
|
-
}
|
|
61526
|
-
if (!probe.probed) {
|
|
61527
|
-
return { host: clean, local_ips: local, public_ips: pub, sinkhole: false, verdict: "inconclusive", evidence: "local IP did not respond on 443/80 — inconclusive; for sensitive niches prefer the public IP anyway." };
|
|
61528
|
-
}
|
|
61529
|
-
const ipDiff = new Set(local).size !== new Set(pub).size || !pub.every((ip) => local.includes(ip));
|
|
61530
|
-
return {
|
|
61531
|
-
host: clean,
|
|
61532
|
-
local_ips: local,
|
|
61533
|
-
public_ips: pub,
|
|
61534
|
-
sinkhole: false,
|
|
61535
|
-
verdict: ipDiff ? "clean-cdn" : "clean",
|
|
61536
|
-
evidence: ipDiff ? "local and public IPs differ but the local page shows no blocking content (normal for CDN/anycast/round-robin)." : "local resolution matches public DNS and shows no blocking content."
|
|
61537
|
-
};
|
|
61538
|
-
}
|
|
61539
|
-
function loadWatchlist() {
|
|
61540
|
-
const raw = readFileSync25(join24(process.cwd(), "intelligence", "watchlist.json"), "utf8");
|
|
61541
|
-
const data = JSON.parse(raw);
|
|
61542
|
-
return { cwe_frequency: data.cwe_frequency ?? [], cve_watchlist: data.cve_watchlist ?? [] };
|
|
61543
|
-
}
|
|
61544
|
-
function fmtPayout(min, max) {
|
|
61545
|
-
if (min == null || max == null)
|
|
61546
|
-
return "n/a";
|
|
61547
|
-
const f = (n) => n >= 1000 ? `$${(n / 1000).toFixed(n % 1000 === 0 ? 0 : 1)}K` : `$${n}`;
|
|
61548
|
-
return `${f(min)}–${f(max)}`;
|
|
61549
|
-
}
|
|
61550
|
-
function watchlist() {
|
|
61551
|
-
const { cwe_frequency, cve_watchlist } = loadWatchlist();
|
|
61552
|
-
const entries = cwe_frequency.map((e) => {
|
|
61553
|
-
const pat = e.pattern ? patternLookup(e.pattern) : null;
|
|
61554
|
-
const payoutMin = e.payout_min ?? pat?.payout_min ?? null;
|
|
61555
|
-
const payoutMax = e.payout_max ?? pat?.payout_max ?? null;
|
|
61556
|
-
return {
|
|
61557
|
-
cwe: e.cwe,
|
|
61558
|
-
name: e.name,
|
|
61559
|
-
rank: e.rank,
|
|
61560
|
-
saturated: e.saturated,
|
|
61561
|
-
pattern: e.pattern ?? null,
|
|
61562
|
-
payout_min: payoutMin,
|
|
61563
|
-
payout_max: payoutMax,
|
|
61564
|
-
typical_payout: fmtPayout(payoutMin, payoutMax),
|
|
61565
|
-
notes: e.notes,
|
|
61566
|
-
grounded: pat != null
|
|
61567
|
-
};
|
|
61568
|
-
}).sort((a, b) => a.rank - b.rank);
|
|
61569
|
-
const unsaturated = entries.filter((e) => !e.saturated);
|
|
61570
|
-
const recommendation = `Prioritize the ${unsaturated.length} non-saturated classes (top: ${unsaturated.slice(0, 3).map((e) => e.cwe).join(", ")}) — high frequency + high payout + not yet saturated. Skip saturated/low-value classes (XSS/CSRF/open-redirect standalone) unless they chain into account takeover.`;
|
|
61571
|
-
return { cwe_frequency: entries, cve_watchlist, recommendation };
|
|
61572
|
-
}
|
|
61573
|
-
|
|
61574
61599
|
// src/enterprise-benchmark.ts
|
|
61575
61600
|
import { existsSync as existsSync15, readFileSync as readFileSync26, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
|
|
61576
61601
|
import { join as join25, resolve } from "node:path";
|