blitzstrike 1.0.35 → 1.0.38
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 +56 -0
- package/bench/corpus/java-cmd-vuln.md +8 -0
- package/bench/corpus/java-deser-vuln.md +8 -0
- package/bench/corpus/java-sqli-prep.md +9 -0
- package/bench/corpus/java-sqli-vuln.md +9 -0
- package/bench/corpus/java-ssrf-vuln.md +9 -0
- package/bench/corpus/java-xss-vuln.md +8 -0
- package/bench/corpus/java-xxe-vuln.md +10 -0
- package/bench/corpus/js-cmd-spawn.md +8 -0
- package/bench/corpus/js-cmd-vuln.md +8 -0
- package/bench/corpus/js-eval-vuln.md +8 -0
- package/bench/corpus/js-pathtraversal-vuln.md +8 -0
- package/bench/corpus/js-redirect-vuln.md +8 -0
- package/bench/corpus/js-sqli-param.md +8 -0
- package/bench/corpus/js-ssrf-vuln.md +8 -0
- package/bench/corpus/{js-xss-sanitized.md → js-xss-escape.md} +1 -1
- package/bench/corpus/js-xss-vuln.md +8 -0
- package/bench/corpus/php-cmd-authgated.md +8 -0
- package/bench/corpus/php-cmd-byref.md +11 -0
- package/bench/corpus/php-cmd-escaped.md +9 -0
- package/bench/corpus/php-cmd-escapeshellcmd.md +9 -0
- package/bench/corpus/php-cmd-intval.md +9 -0
- package/bench/corpus/php-cmd-passthru.md +9 -0
- package/bench/corpus/php-cmd-shell-exec.md +9 -0
- package/bench/corpus/php-cmd-transitive.md +10 -0
- package/bench/corpus/{php-command-injection-vuln.md → php-cmd-vuln.md} +1 -1
- package/bench/corpus/php-codeexec-assert.md +9 -0
- package/bench/corpus/php-codeexec-vuln.md +9 -0
- package/bench/corpus/php-fileupload-name.md +9 -0
- package/bench/corpus/php-fileupload-sanitized.md +9 -0
- package/bench/corpus/php-fileupload-vuln.md +9 -0
- package/bench/corpus/php-filewrite-vuln.md +9 -0
- package/bench/corpus/php-include-intval.md +9 -0
- package/bench/corpus/php-include-require.md +9 -0
- package/bench/corpus/php-include-vuln.md +9 -0
- package/bench/corpus/php-include-whitelist.md +10 -0
- package/bench/corpus/php-redirect-safe.md +9 -0
- package/bench/corpus/php-redirect-vuln.md +9 -0
- package/bench/corpus/php-sqli-authgated.md +9 -0
- package/bench/corpus/php-sqli-esc.md +9 -0
- package/bench/corpus/php-sqli-filter.md +9 -0
- package/bench/corpus/php-sqli-getvar.md +9 -0
- package/bench/corpus/php-sqli-interproc.md +10 -0
- package/bench/corpus/php-sqli-whereraw.md +9 -0
- package/bench/corpus/php-ssrf-curl.md +11 -0
- package/bench/corpus/php-ssrf-vuln.md +9 -0
- package/bench/corpus/php-ssrf-wpremote.md +9 -0
- package/bench/corpus/php-unserialize-vuln.md +9 -0
- package/bench/corpus/php-xss-attr.md +9 -0
- package/bench/corpus/php-xss-authgated.md +8 -0
- package/bench/corpus/php-xss-eschtml.md +9 -0
- package/bench/corpus/php-xss-htmlentities.md +9 -0
- package/bench/corpus/php-xss-return.md +9 -0
- package/bench/corpus/php-xss-ternary.md +9 -0
- package/bench/corpus/php-xss-vuln.md +9 -0
- package/bench/corpus/php-xxe-vuln.md +9 -0
- package/bench/corpus/php-zipslip-vuln.md +10 -0
- package/bench/corpus/py-cmd-subprocess.md +9 -0
- package/bench/corpus/{py-command-injection-vuln.md → py-cmd-vuln.md} +3 -3
- package/bench/corpus/py-deser-json.md +9 -0
- package/bench/corpus/py-deser-vuln.md +9 -0
- package/bench/corpus/py-eval-vuln.md +8 -0
- package/bench/corpus/py-pathtraversal-vuln.md +8 -0
- package/bench/corpus/py-sqli-param.md +9 -0
- package/bench/corpus/py-sqli-vuln.md +9 -0
- package/bench/corpus/py-ssrf-vuln.md +9 -0
- package/bench/corpus/py-xss-vuln.md +8 -0
- package/dist/index.js +404 -98
- package/package.json +1 -1
- package/bench/corpus/java-command-injection-vuln.md +0 -7
- package/bench/corpus/php-file-upload-vuln.md +0 -9
- package/bench/corpus/py-command-injection-sanitized.md +0 -9
package/dist/index.js
CHANGED
|
@@ -14820,8 +14820,8 @@ var require_src = __commonJS(function(exports, module) {
|
|
|
14820
14820
|
});
|
|
14821
14821
|
|
|
14822
14822
|
// src/sync.ts
|
|
14823
|
-
import { mkdirSync as mkdirSync3, writeFileSync as
|
|
14824
|
-
import { join as
|
|
14823
|
+
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4, readFileSync as readFileSync15, readdirSync as readdirSync5, statSync as statSync3, rmSync as rmSync3 } from "node:fs";
|
|
14824
|
+
import { join as join11 } from "node:path";
|
|
14825
14825
|
import { homedir as homedir4 } from "node:os";
|
|
14826
14826
|
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
14827
14827
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
@@ -14838,7 +14838,7 @@ function countFiles(dir) {
|
|
|
14838
14838
|
const walk = (d) => {
|
|
14839
14839
|
for (const e of readdirSync5(d, { withFileTypes: true })) {
|
|
14840
14840
|
if (e.isDirectory())
|
|
14841
|
-
walk(
|
|
14841
|
+
walk(join11(d, e.name));
|
|
14842
14842
|
else
|
|
14843
14843
|
n++;
|
|
14844
14844
|
}
|
|
@@ -14852,8 +14852,8 @@ function countFiles(dir) {
|
|
|
14852
14852
|
function syncData() {
|
|
14853
14853
|
if (!dirExists(DATA_ROOT2))
|
|
14854
14854
|
mkdirSync3(DATA_ROOT2, { recursive: true });
|
|
14855
|
-
const payloadsPath =
|
|
14856
|
-
const templatesPath =
|
|
14855
|
+
const payloadsPath = join11(DATA_ROOT2, "payloads");
|
|
14856
|
+
const templatesPath = join11(DATA_ROOT2, "templates");
|
|
14857
14857
|
const hasPayloads = dirExists(payloadsPath) && countFiles(payloadsPath) > 0;
|
|
14858
14858
|
const hasTemplates = dirExists(templatesPath) && countFiles(templatesPath) > 0;
|
|
14859
14859
|
if (hasPayloads && hasTemplates) {
|
|
@@ -14863,8 +14863,8 @@ function syncData() {
|
|
|
14863
14863
|
data_root: DATA_ROOT2
|
|
14864
14864
|
};
|
|
14865
14865
|
}
|
|
14866
|
-
const localPayloads =
|
|
14867
|
-
const localTemplates =
|
|
14866
|
+
const localPayloads = join11(ROOT5, "payloads");
|
|
14867
|
+
const localTemplates = join11(ROOT5, "templates");
|
|
14868
14868
|
let payloadsSynced = false;
|
|
14869
14869
|
let templatesSynced = false;
|
|
14870
14870
|
if (!hasPayloads && dirExists(localPayloads)) {
|
|
@@ -14877,23 +14877,23 @@ function syncData() {
|
|
|
14877
14877
|
}
|
|
14878
14878
|
const gitOk = spawnSync2("git", ["--version"]).status === 0;
|
|
14879
14879
|
if (gitOk && (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0 || !dirExists(templatesPath) || countFiles(templatesPath) === 0)) {
|
|
14880
|
-
const tmp =
|
|
14880
|
+
const tmp = join11(DATA_ROOT2, ".tmp-clone");
|
|
14881
14881
|
if (dirExists(tmp))
|
|
14882
|
-
|
|
14882
|
+
rmSync3(tmp, { recursive: true, force: true });
|
|
14883
14883
|
const r = spawnSync2("git", ["clone", "--depth", "1", "--filter=blob:none", "--sparse", GITHUB_REPO, tmp], { timeout: 120000 });
|
|
14884
14884
|
if (r.status === 0) {
|
|
14885
14885
|
const sparse = spawnSync2("git", ["-C", tmp, "sparse-checkout", "set", "payloads", "templates"], { timeout: 120000 });
|
|
14886
14886
|
if (sparse.status === 0) {
|
|
14887
14887
|
if (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0) {
|
|
14888
|
-
copyDir(
|
|
14888
|
+
copyDir(join11(tmp, "payloads"), payloadsPath);
|
|
14889
14889
|
payloadsSynced = true;
|
|
14890
14890
|
}
|
|
14891
14891
|
if (!dirExists(templatesPath) || countFiles(templatesPath) === 0) {
|
|
14892
|
-
copyDir(
|
|
14892
|
+
copyDir(join11(tmp, "templates"), templatesPath);
|
|
14893
14893
|
templatesSynced = true;
|
|
14894
14894
|
}
|
|
14895
14895
|
}
|
|
14896
|
-
|
|
14896
|
+
rmSync3(tmp, { recursive: true, force: true });
|
|
14897
14897
|
}
|
|
14898
14898
|
}
|
|
14899
14899
|
return {
|
|
@@ -14911,18 +14911,18 @@ function copyDir(src, dst) {
|
|
|
14911
14911
|
function manualCopy(src, dst) {
|
|
14912
14912
|
mkdirSync3(dst, { recursive: true });
|
|
14913
14913
|
for (const e of readdirSync5(src, { withFileTypes: true })) {
|
|
14914
|
-
const s =
|
|
14915
|
-
const d =
|
|
14914
|
+
const s = join11(src, e.name);
|
|
14915
|
+
const d = join11(dst, e.name);
|
|
14916
14916
|
if (e.isDirectory())
|
|
14917
14917
|
manualCopy(s, d);
|
|
14918
14918
|
else
|
|
14919
|
-
|
|
14919
|
+
writeFileSync4(d, readFileSync15(s));
|
|
14920
14920
|
}
|
|
14921
14921
|
}
|
|
14922
14922
|
var ROOT5, DATA_ROOT2, GITHUB_REPO = "https://github.com/shinthink/blitzstrike.git";
|
|
14923
14923
|
var init_sync = __esm(() => {
|
|
14924
|
-
ROOT5 =
|
|
14925
|
-
DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ??
|
|
14924
|
+
ROOT5 = join11(fileURLToPath7(new URL(".", import.meta.url)), "..");
|
|
14925
|
+
DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ?? join11(homedir4(), ".blitzstrike", "data");
|
|
14926
14926
|
});
|
|
14927
14927
|
|
|
14928
14928
|
// src/update.ts
|
|
@@ -31072,8 +31072,8 @@ var SINK_CLASS_MAP = [
|
|
|
31072
31072
|
[/\bmove_uploaded_file\(|\bfile_put_contents\(|\bfwrite\(|\bfopen\(|\bunlink\(/, { id: "file_operations", category: "File operations", cwe: "CWE-434" }],
|
|
31073
31073
|
[/\binclude\(|\brequire\(|\binclude_once\(|\brequire_once\(/, { id: "file_inclusion", category: "File inclusion", cwe: "CWE-98" }],
|
|
31074
31074
|
[/\bunserialize\(|\bmaybe_unserialize\(/, { id: "deserialization", category: "Deserialization", cwe: "CWE-502" }],
|
|
31075
|
-
[/\bwp_remote_get\(|\bwp_remote_post\(|\bfile_get_contents\(|\bcurl_exec\(/, { id: "http_request", category: "HTTP request (SSRF)", cwe: "CWE-918" }],
|
|
31076
|
-
[/\bheader\(|\bwp_redirect\(
|
|
31075
|
+
[/\bwp_remote_get\(|\bwp_remote_post\(|\bfile_get_contents\(|\bcurl_exec\(|\bcurl_setopt\(/, { id: "http_request", category: "HTTP request (SSRF)", cwe: "CWE-918" }],
|
|
31076
|
+
[/\bheader\(|\bwp_redirect\(/, { id: "redirect", category: "Redirect handling", cwe: "CWE-601" }],
|
|
31077
31077
|
[/echo\s|print\s|\bprintf\(/, { id: "html_render", category: "HTML rendering (XSS)", cwe: "CWE-79" }],
|
|
31078
31078
|
[/\bsimplexml_load_string\(|new SimpleXMLElement|DOMDocument/, { id: "xml_processing", category: "XML processing (XXE)", cwe: "CWE-611" }],
|
|
31079
31079
|
[/\bZipArchive|\bPharData|->extractTo\(/, { id: "archive_extraction", category: "Archive extraction (zip slip)", cwe: "CWE-22" }]
|
|
@@ -31777,6 +31777,30 @@ function resolveCall(g, call, callerId, ctx, file, line) {
|
|
|
31777
31777
|
return;
|
|
31778
31778
|
}
|
|
31779
31779
|
}
|
|
31780
|
+
function reachableFrom(g, entry) {
|
|
31781
|
+
const seen = new Set;
|
|
31782
|
+
const stack = [entry];
|
|
31783
|
+
while (stack.length) {
|
|
31784
|
+
const cur = stack.pop();
|
|
31785
|
+
if (seen.has(cur))
|
|
31786
|
+
continue;
|
|
31787
|
+
seen.add(cur);
|
|
31788
|
+
const n = g.nodes.get(cur);
|
|
31789
|
+
if (!n)
|
|
31790
|
+
continue;
|
|
31791
|
+
for (const c of n.callees)
|
|
31792
|
+
stack.push(c);
|
|
31793
|
+
}
|
|
31794
|
+
return seen;
|
|
31795
|
+
}
|
|
31796
|
+
function entryPoints(g) {
|
|
31797
|
+
const entries = [];
|
|
31798
|
+
for (const [id, n] of g.nodes) {
|
|
31799
|
+
if (n.callers.size === 0)
|
|
31800
|
+
entries.push(id);
|
|
31801
|
+
}
|
|
31802
|
+
return entries;
|
|
31803
|
+
}
|
|
31780
31804
|
|
|
31781
31805
|
// src/eagle2.ts
|
|
31782
31806
|
var parser3 = new import_php_parser3.Engine({
|
|
@@ -31859,9 +31883,11 @@ function sinkClassOf2(call) {
|
|
|
31859
31883
|
return null;
|
|
31860
31884
|
let token = "";
|
|
31861
31885
|
if (what.kind === "propertylookup") {
|
|
31862
|
-
const
|
|
31886
|
+
const baseNode = what.what;
|
|
31887
|
+
const base = rootVarName2(baseNode);
|
|
31863
31888
|
const method = what.offset?.name ?? what.offset?.value;
|
|
31864
|
-
|
|
31889
|
+
const baseStr = isNode3(baseNode) && (baseNode.kind === "variable" || baseNode.kind === "offsetlookup") ? `$${base}` : base;
|
|
31890
|
+
token = `${baseStr}->${method}(`;
|
|
31865
31891
|
} else if (what.kind === "name") {
|
|
31866
31892
|
token = `${what.name}(`;
|
|
31867
31893
|
}
|
|
@@ -31918,26 +31944,27 @@ function summarizeFunction(fn, id, name, summaries) {
|
|
|
31918
31944
|
}
|
|
31919
31945
|
return summary;
|
|
31920
31946
|
}
|
|
31921
|
-
function analyzeBody(statements, env, summary, ctx, taintedParam, props, activeAuth, summaries) {
|
|
31947
|
+
function analyzeBody(statements, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating = false) {
|
|
31922
31948
|
for (const stmt of statements) {
|
|
31923
31949
|
if (!isNode3(stmt))
|
|
31924
31950
|
continue;
|
|
31925
31951
|
const kind = stmt.kind;
|
|
31926
31952
|
if (kind === "expressionstatement") {
|
|
31927
|
-
analyzeExpr(stmt.expression, env, summary, ctx, taintedParam, props, activeAuth, summaries);
|
|
31953
|
+
analyzeExpr(stmt.expression, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
31928
31954
|
} else if (kind === "if") {
|
|
31929
31955
|
const condGates = authGatesOf(stmt.test);
|
|
31930
31956
|
const branchAuth = [...activeAuth, ...condGates];
|
|
31957
|
+
const condValidating = validationGatesOf(stmt.test);
|
|
31931
31958
|
const thenStmts = stmt.body?.kind === "block" ? stmt.body.children ?? [] : [stmt.body];
|
|
31932
31959
|
const thenEnv = cloneEnv(env);
|
|
31933
31960
|
const thenCtx = { sinks: [], returnsTainted: false, calls: [] };
|
|
31934
|
-
analyzeBody(thenStmts, thenEnv, summary, thenCtx, taintedParam, cloneProps(props), branchAuth, summaries);
|
|
31961
|
+
analyzeBody(thenStmts, thenEnv, summary, thenCtx, taintedParam, cloneProps(props), branchAuth, summaries, validating || condValidating);
|
|
31935
31962
|
mergeCtx(ctx, thenCtx);
|
|
31936
31963
|
if (stmt.alternate) {
|
|
31937
31964
|
const altStmts = stmt.alternate.kind === "block" ? stmt.alternate.children ?? [] : [stmt.alternate];
|
|
31938
31965
|
const altEnv = cloneEnv(env);
|
|
31939
31966
|
const altCtx = { sinks: [], returnsTainted: false, calls: [] };
|
|
31940
|
-
analyzeBody(altStmts, altEnv, summary, altCtx, taintedParam, cloneProps(props), activeAuth, summaries);
|
|
31967
|
+
analyzeBody(altStmts, altEnv, summary, altCtx, taintedParam, cloneProps(props), activeAuth, summaries, validating);
|
|
31941
31968
|
mergeCtx(ctx, altCtx);
|
|
31942
31969
|
for (const [k, v] of thenEnv) {
|
|
31943
31970
|
const alt = altEnv.get(k);
|
|
@@ -31948,25 +31975,25 @@ function analyzeBody(statements, env, summary, ctx, taintedParam, props, activeA
|
|
|
31948
31975
|
env.set(k, v);
|
|
31949
31976
|
}
|
|
31950
31977
|
} else if (kind === "block") {
|
|
31951
|
-
analyzeBody(stmt.children ?? [], env, summary, ctx, taintedParam, props, activeAuth, summaries);
|
|
31978
|
+
analyzeBody(stmt.children ?? [], env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
31952
31979
|
} else if (kind === "return") {
|
|
31953
|
-
const t = analyzeExpr(stmt.expr, env, summary, ctx, taintedParam, props, activeAuth, summaries);
|
|
31980
|
+
const t = analyzeExpr(stmt.expr, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
31954
31981
|
if (t.tainted)
|
|
31955
31982
|
ctx.returnsTainted = true;
|
|
31956
31983
|
} else if (kind === "echo" || kind === "print") {
|
|
31957
|
-
analyzeExpr(stmt, env, summary, ctx, taintedParam, props, activeAuth, summaries);
|
|
31984
|
+
analyzeExpr(stmt, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
31958
31985
|
} else {
|
|
31959
31986
|
for (const c of children3(stmt)) {
|
|
31960
31987
|
if (c.kind === "block" || c.kind === "if") {
|
|
31961
|
-
analyzeBody([c], env, summary, ctx, taintedParam, props, activeAuth, summaries);
|
|
31988
|
+
analyzeBody([c], env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
31962
31989
|
} else {
|
|
31963
|
-
analyzeExpr(c, env, summary, ctx, taintedParam, props, activeAuth, summaries);
|
|
31990
|
+
analyzeExpr(c, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
31964
31991
|
}
|
|
31965
31992
|
}
|
|
31966
31993
|
}
|
|
31967
31994
|
}
|
|
31968
31995
|
}
|
|
31969
|
-
function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, summaries) {
|
|
31996
|
+
function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating = false) {
|
|
31970
31997
|
if (!isNode3(node))
|
|
31971
31998
|
return CLEAN;
|
|
31972
31999
|
const src = sourceOf2(node);
|
|
@@ -31974,7 +32001,7 @@ function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, s
|
|
|
31974
32001
|
return { tainted: true, sources: [src], sanitizers: [], sourceLine: node.loc?.start?.line };
|
|
31975
32002
|
if (node.kind === "assign") {
|
|
31976
32003
|
const byref = node.operator === "=&";
|
|
31977
|
-
const rhs = analyzeExpr(node.right, env, summary, ctx, taintedParam, props, activeAuth, summaries);
|
|
32004
|
+
const rhs = analyzeExpr(node.right, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
31978
32005
|
const lhs = node.left;
|
|
31979
32006
|
if (isNode3(lhs) && lhs.kind === "variable") {
|
|
31980
32007
|
const varName = lhs.name;
|
|
@@ -32006,7 +32033,7 @@ function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, s
|
|
|
32006
32033
|
}
|
|
32007
32034
|
if (node.kind === "call") {
|
|
32008
32035
|
const sink = sinkClassOf2(node);
|
|
32009
|
-
const argTaints = node.arguments.map((a) => analyzeExpr(a, env, summary, ctx, taintedParam, props, activeAuth, summaries));
|
|
32036
|
+
const argTaints = node.arguments.map((a) => analyzeExpr(a, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating));
|
|
32010
32037
|
const taintedArgs = argTaints.filter((t) => t.tainted);
|
|
32011
32038
|
if (sink) {
|
|
32012
32039
|
if (taintedArgs.length > 0) {
|
|
@@ -32016,7 +32043,7 @@ function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, s
|
|
|
32016
32043
|
ctx.sinks.push({ sink: sink.token, line: node.loc?.start?.line ?? 0, sanitized, authGated: gates.length > 0, authGates: gates });
|
|
32017
32044
|
}
|
|
32018
32045
|
for (const a of node.arguments)
|
|
32019
|
-
analyzeExpr(a, env, summary, ctx, taintedParam, props, activeAuth, summaries);
|
|
32046
|
+
analyzeExpr(a, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
32020
32047
|
return taintedArgs.length > 0 ? mergeTaint(...taintedArgs) : CLEAN;
|
|
32021
32048
|
}
|
|
32022
32049
|
const fnName = rootVarName2(node.what);
|
|
@@ -32059,6 +32086,9 @@ function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, s
|
|
|
32059
32086
|
}
|
|
32060
32087
|
}
|
|
32061
32088
|
}
|
|
32089
|
+
if (isNode3(node.what) && node.what.kind === "propertylookup") {
|
|
32090
|
+
analyzeExpr(node.what, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
32091
|
+
}
|
|
32062
32092
|
return CLEAN;
|
|
32063
32093
|
}
|
|
32064
32094
|
if (node.kind === "variable") {
|
|
@@ -32072,7 +32102,7 @@ function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, s
|
|
|
32072
32102
|
if (t)
|
|
32073
32103
|
return { ...t };
|
|
32074
32104
|
}
|
|
32075
|
-
return analyzeExpr(node.what, env, summary, ctx, taintedParam, props, activeAuth, summaries);
|
|
32105
|
+
return analyzeExpr(node.what, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
32076
32106
|
}
|
|
32077
32107
|
if (node.kind === "propertylookup") {
|
|
32078
32108
|
const objName = rootVarName2(node.what);
|
|
@@ -32082,35 +32112,55 @@ function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, s
|
|
|
32082
32112
|
if (pt)
|
|
32083
32113
|
return { ...pt };
|
|
32084
32114
|
}
|
|
32085
|
-
return analyzeExpr(node.what, env, summary, ctx, taintedParam, props, activeAuth, summaries);
|
|
32115
|
+
return analyzeExpr(node.what, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
32086
32116
|
}
|
|
32087
32117
|
if (node.kind === "retif") {
|
|
32088
|
-
const t = analyzeExpr(node.trueExpr ?? node.test, env, summary, ctx, taintedParam, props, activeAuth, summaries);
|
|
32089
|
-
const f = node.falseExpr ? analyzeExpr(node.falseExpr, env, summary, ctx, taintedParam, props, activeAuth, summaries) : CLEAN;
|
|
32118
|
+
const t = analyzeExpr(node.trueExpr ?? node.test, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
32119
|
+
const f = node.falseExpr ? analyzeExpr(node.falseExpr, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating) : CLEAN;
|
|
32090
32120
|
return mergeTaint(t, f);
|
|
32091
32121
|
}
|
|
32092
32122
|
if (node.kind === "bin" || node.kind === "concat" || node.kind === "encapsed") {
|
|
32093
32123
|
const parts = children3(node);
|
|
32094
|
-
const taints = parts.map((p) => analyzeExpr(p, env, summary, ctx, taintedParam, props, activeAuth, summaries));
|
|
32124
|
+
const taints = parts.map((p) => analyzeExpr(p, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating));
|
|
32095
32125
|
return mergeTaint(...taints);
|
|
32096
32126
|
}
|
|
32097
32127
|
if (node.kind === "encapsedpart") {
|
|
32098
|
-
return analyzeExpr(node.expression, env, summary, ctx, taintedParam, props, activeAuth, summaries);
|
|
32128
|
+
return analyzeExpr(node.expression, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
32099
32129
|
}
|
|
32100
32130
|
if (node.kind === "echo" || node.kind === "print") {
|
|
32101
32131
|
const exprs = node.expressions ?? [node.expression];
|
|
32102
32132
|
const cls = classifySink2(node.kind === "echo" ? "echo " : "print ");
|
|
32103
32133
|
let ret = CLEAN;
|
|
32104
32134
|
for (const e of exprs) {
|
|
32105
|
-
const t = analyzeExpr(e, env, summary, ctx, taintedParam, props, activeAuth, summaries);
|
|
32135
|
+
const t = analyzeExpr(e, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
32106
32136
|
if (t.tainted && cls) {
|
|
32107
32137
|
const san = [...t.sanitizers, ...findSanitizers(JSON.stringify(e))];
|
|
32108
|
-
ctx.sinks.push({ sink: node.kind + " ", line: node.loc?.start?.line ?? 0, sanitized: isSanitized(san, cls.id), authGated: activeAuth.length > 0, authGates: activeAuth });
|
|
32138
|
+
ctx.sinks.push({ sink: node.kind + " ", line: node.loc?.start?.line ?? 0, sanitized: isSanitized(san, cls.id) || validating, authGated: activeAuth.length > 0, authGates: activeAuth });
|
|
32109
32139
|
}
|
|
32110
32140
|
ret = mergeTaint(ret, t);
|
|
32111
32141
|
}
|
|
32112
32142
|
return ret;
|
|
32113
32143
|
}
|
|
32144
|
+
if (node.kind === "include") {
|
|
32145
|
+
const base = node.require ? "require" : "include";
|
|
32146
|
+
const suffix = node.once ? "_once" : "";
|
|
32147
|
+
const sinkToken = base + suffix + "(";
|
|
32148
|
+
const cls = classifySink2(sinkToken);
|
|
32149
|
+
const t = analyzeExpr(node.target, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
32150
|
+
if (t.tainted && cls) {
|
|
32151
|
+
const san = [...t.sanitizers, ...findSanitizers(JSON.stringify(node))];
|
|
32152
|
+
ctx.sinks.push({ sink: sinkToken, line: node.loc?.start?.line ?? 0, sanitized: isSanitized(san, cls.id) || validating, authGated: activeAuth.length > 0, authGates: activeAuth });
|
|
32153
|
+
}
|
|
32154
|
+
return t;
|
|
32155
|
+
}
|
|
32156
|
+
if (node.kind === "eval") {
|
|
32157
|
+
const cls = classifySink2("eval(");
|
|
32158
|
+
const t = analyzeExpr(node.source, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
32159
|
+
if (t.tainted && cls) {
|
|
32160
|
+
ctx.sinks.push({ sink: "eval(", line: node.loc?.start?.line ?? 0, sanitized: false, authGated: activeAuth.length > 0, authGates: activeAuth });
|
|
32161
|
+
}
|
|
32162
|
+
return t;
|
|
32163
|
+
}
|
|
32114
32164
|
return CLEAN;
|
|
32115
32165
|
}
|
|
32116
32166
|
function cloneEnv(env) {
|
|
@@ -32133,6 +32183,13 @@ function authGatesOf(node) {
|
|
|
32133
32183
|
return [];
|
|
32134
32184
|
return AUTH_GATES.filter((g) => JSON.stringify(node).includes(g));
|
|
32135
32185
|
}
|
|
32186
|
+
var VALIDATION_GATES = ["in_array", "preg_match", "ctype_digit", "ctype_alnum", "is_numeric"];
|
|
32187
|
+
function validationGatesOf(node) {
|
|
32188
|
+
if (!isNode3(node))
|
|
32189
|
+
return false;
|
|
32190
|
+
const text = JSON.stringify(node);
|
|
32191
|
+
return VALIDATION_GATES.some((g) => text.includes(g));
|
|
32192
|
+
}
|
|
32136
32193
|
function collectDecls(ast, out) {
|
|
32137
32194
|
walkDecls(ast, out);
|
|
32138
32195
|
}
|
|
@@ -32173,11 +32230,17 @@ function resolveFuncId(name, summaries) {
|
|
|
32173
32230
|
}
|
|
32174
32231
|
function analyzeDataFlow2(path) {
|
|
32175
32232
|
const cg = buildCallGraph(path);
|
|
32233
|
+
const entries = entryPoints(cg);
|
|
32234
|
+
const reachable = new Set;
|
|
32235
|
+
for (const e of entries)
|
|
32236
|
+
for (const n of reachableFrom(cg, e))
|
|
32237
|
+
reachable.add(n);
|
|
32238
|
+
const unreachable = [...cg.nodes.keys()].filter((id) => !reachable.has(id));
|
|
32176
32239
|
const result = {
|
|
32177
32240
|
file: path,
|
|
32178
32241
|
findings: [],
|
|
32179
32242
|
suppressed: 0,
|
|
32180
|
-
summary: { functions: 0, methods: 0, edges: cg.edges.length,
|
|
32243
|
+
summary: { functions: 0, methods: 0, edges: cg.edges.length, entry_points: entries, unreachable }
|
|
32181
32244
|
};
|
|
32182
32245
|
let code;
|
|
32183
32246
|
try {
|
|
@@ -32293,6 +32356,12 @@ function analyzeDataFlow2(path) {
|
|
|
32293
32356
|
return result;
|
|
32294
32357
|
}
|
|
32295
32358
|
|
|
32359
|
+
// src/differential.ts
|
|
32360
|
+
import { writeFileSync as writeFileSync2, rmSync, mkdtempSync, readFileSync as readFileSync13 } from "node:fs";
|
|
32361
|
+
import { join as join9 } from "node:path";
|
|
32362
|
+
import { tmpdir } from "node:os";
|
|
32363
|
+
import { execSync } from "node:child_process";
|
|
32364
|
+
|
|
32296
32365
|
// src/universal-taint.ts
|
|
32297
32366
|
function runTaintEngine(adapter, code, file) {
|
|
32298
32367
|
const result = { file, language: adapter.language, findings: [], suppressed: 0 };
|
|
@@ -50346,8 +50415,13 @@ var JS_SINKS = [
|
|
|
50346
50415
|
[/document\.write/i, "html_render", "CWE-79"],
|
|
50347
50416
|
[/fs\.writeFile/i, "file_operations", "CWE-434"],
|
|
50348
50417
|
[/fs\.appendFile/i, "file_operations", "CWE-434"],
|
|
50418
|
+
[/fs\.readFile/i, "path_traversal", "CWE-22"],
|
|
50419
|
+
[/fs\.readFileSync/i, "path_traversal", "CWE-22"],
|
|
50420
|
+
[/fs\.unlink/i, "file_operations", "CWE-434"],
|
|
50349
50421
|
[/require\s*\(/i, "file_inclusion", "CWE-98"],
|
|
50350
50422
|
[/fetch\s*\(/i, "http_request", "CWE-918"],
|
|
50423
|
+
[/http\.get|https\.get|axios/i, "http_request", "CWE-918"],
|
|
50424
|
+
[/http\.request|https\.request/i, "http_request", "CWE-918"],
|
|
50351
50425
|
[/redirect\s*\(/i, "redirect", "CWE-601"],
|
|
50352
50426
|
[/res\.redirect/i, "redirect", "CWE-601"]
|
|
50353
50427
|
];
|
|
@@ -50434,6 +50508,8 @@ var jsAdapter = {
|
|
|
50434
50508
|
argVars.push(c.name);
|
|
50435
50509
|
for (const [re, kind, cwe] of JS_SINKS) {
|
|
50436
50510
|
if (re.test(fullCallText) || re.test(calleeText + "(")) {
|
|
50511
|
+
if (kind === "sql_execution" && /\?/.test(fullCallText) && /,\s*\[/.test(fullCallText))
|
|
50512
|
+
continue;
|
|
50437
50513
|
for (const v of argVars)
|
|
50438
50514
|
sinks.push({ variable: v, kind, line: n.loc?.start?.line ?? 0, cwe });
|
|
50439
50515
|
}
|
|
@@ -50472,6 +50548,8 @@ var PY_SINKS = [
|
|
|
50472
50548
|
[/\.save\s*\(/i, "file_operations", "CWE-434"],
|
|
50473
50549
|
[/render_template/i, "html_render", "CWE-79"],
|
|
50474
50550
|
[/redirect\s*\(/i, "redirect", "CWE-601"],
|
|
50551
|
+
[/requests\.(get|post|put|head|request)\s*\(/i, "http_request", "CWE-918"],
|
|
50552
|
+
[/urllib\.request/i, "http_request", "CWE-918"],
|
|
50475
50553
|
[/pickle\.loads\s*\(/i, "deserialization", "CWE-502"],
|
|
50476
50554
|
[/yaml\.load\s*\(/i, "deserialization", "CWE-502"],
|
|
50477
50555
|
[/os\.popen\s*\(/i, "command_execution", "CWE-78"],
|
|
@@ -50556,9 +50634,14 @@ var pythonAdapter = {
|
|
|
50556
50634
|
if (m[1] !== callee.trim().split(".").pop())
|
|
50557
50635
|
argVars.push(m[1]);
|
|
50558
50636
|
for (const [re, kind, cwe] of PY_SINKS) {
|
|
50559
|
-
if (re.test(full))
|
|
50560
|
-
|
|
50561
|
-
|
|
50637
|
+
if (!re.test(full))
|
|
50638
|
+
continue;
|
|
50639
|
+
if (kind === "sql_execution" && /\?/.test(full) && /,\s*\(/.test(full))
|
|
50640
|
+
continue;
|
|
50641
|
+
if (kind === "command_execution" && /subprocess/.test(full) && /\[/.test(full))
|
|
50642
|
+
continue;
|
|
50643
|
+
for (const v of argVars)
|
|
50644
|
+
sinks.push({ variable: v, kind, line: lineOf(n), cwe });
|
|
50562
50645
|
}
|
|
50563
50646
|
for (const [re, kinds] of PY_SANITIZERS) {
|
|
50564
50647
|
if (re.test(full))
|
|
@@ -50594,6 +50677,10 @@ var JAVA_SINKS = [
|
|
|
50594
50677
|
[/readObject\s*\(/i, "deserialization", "CWE-502"],
|
|
50595
50678
|
[/new File\s*\(/i, "file_operations", "CWE-434"],
|
|
50596
50679
|
[/\.getInputStream\s*\(/i, "http_request", "CWE-918"],
|
|
50680
|
+
[/new URL\s*\(/i, "http_request", "CWE-918"],
|
|
50681
|
+
[/openConnection\s*\(/i, "http_request", "CWE-918"],
|
|
50682
|
+
[/DocumentBuilder|SAXParser|XMLReader/i, "xml_processing", "CWE-611"],
|
|
50683
|
+
[/\.parse\s*\(/i, "xml_processing", "CWE-611"],
|
|
50597
50684
|
[/sendRedirect\s*\(/i, "redirect", "CWE-601"],
|
|
50598
50685
|
[/\.forward\s*\(/i, "redirect", "CWE-601"]
|
|
50599
50686
|
];
|
|
@@ -50661,6 +50748,195 @@ registerLanguage(jsAdapter);
|
|
|
50661
50748
|
registerLanguage(pythonAdapter);
|
|
50662
50749
|
registerLanguage(javaAdapter);
|
|
50663
50750
|
|
|
50751
|
+
// src/differential.ts
|
|
50752
|
+
init_scanner();
|
|
50753
|
+
function runEngine(code, language, file) {
|
|
50754
|
+
if (language === "php") {
|
|
50755
|
+
const dir = mkdtempSync(join9(tmpdir(), "blitz-diff-"));
|
|
50756
|
+
const tmp = join9(dir, "old.php");
|
|
50757
|
+
writeFileSync2(tmp, code);
|
|
50758
|
+
try {
|
|
50759
|
+
const r = analyzeDataFlow2(tmp);
|
|
50760
|
+
return r.findings.map((f) => ({ sink: f.sink, sink_line: f.sink_line, category: f.category, source: f.source, cwe: f.cwe }));
|
|
50761
|
+
} finally {
|
|
50762
|
+
rmSync(dir, { recursive: true, force: true });
|
|
50763
|
+
}
|
|
50764
|
+
}
|
|
50765
|
+
const extMap = { javascript: ".js", typescript: ".ts", python: ".py", java: ".java" };
|
|
50766
|
+
const probe = file.includes(".") ? file : `target${extMap[language] ?? ""}`;
|
|
50767
|
+
const r = analyzeTaintUniversal(code, probe);
|
|
50768
|
+
return r.findings.map((f) => ({ sink: f.sink, sink_line: f.sink_line, category: f.sink, source: f.source_kind, cwe: f.cwe }));
|
|
50769
|
+
}
|
|
50770
|
+
function diffLines(oldText, newText) {
|
|
50771
|
+
const a = oldText.split(`
|
|
50772
|
+
`);
|
|
50773
|
+
const b = newText.split(`
|
|
50774
|
+
`);
|
|
50775
|
+
const m = a.length;
|
|
50776
|
+
const n = b.length;
|
|
50777
|
+
const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
|
|
50778
|
+
for (let i = m - 1;i >= 0; i--) {
|
|
50779
|
+
for (let j = n - 1;j >= 0; j--) {
|
|
50780
|
+
dp[i][j] = a[i] === b[j] ? dp[i + 1][j + 1] + 1 : Math.max(dp[i + 1][j], dp[i][j + 1]);
|
|
50781
|
+
}
|
|
50782
|
+
}
|
|
50783
|
+
const added = [];
|
|
50784
|
+
const removed = [];
|
|
50785
|
+
let i = 0;
|
|
50786
|
+
let j = 0;
|
|
50787
|
+
while (i < m && j < n) {
|
|
50788
|
+
if (a[i] === b[j]) {
|
|
50789
|
+
i++;
|
|
50790
|
+
j++;
|
|
50791
|
+
} else if (dp[i + 1][j] >= dp[i][j + 1]) {
|
|
50792
|
+
removed.push({ line: i + 1, text: a[i] });
|
|
50793
|
+
i++;
|
|
50794
|
+
} else {
|
|
50795
|
+
added.push({ line: j + 1, text: b[j] });
|
|
50796
|
+
j++;
|
|
50797
|
+
}
|
|
50798
|
+
}
|
|
50799
|
+
while (i < m) {
|
|
50800
|
+
removed.push({ line: i + 1, text: a[i] });
|
|
50801
|
+
i++;
|
|
50802
|
+
}
|
|
50803
|
+
while (j < n) {
|
|
50804
|
+
added.push({ line: j + 1, text: b[j] });
|
|
50805
|
+
j++;
|
|
50806
|
+
}
|
|
50807
|
+
return { added, removed };
|
|
50808
|
+
}
|
|
50809
|
+
function analyzeDifferential(oldCode, newCode, language, file = "target") {
|
|
50810
|
+
const changes = [];
|
|
50811
|
+
const oldFindings = runEngine(oldCode, language, file);
|
|
50812
|
+
const newFindings = runEngine(newCode, language, file);
|
|
50813
|
+
const key = (f) => `${f.category}|${f.sink}|${f.sink_line}`;
|
|
50814
|
+
const oldKeys = new Set(oldFindings.map(key));
|
|
50815
|
+
const newKeys = new Set(newFindings.map(key));
|
|
50816
|
+
for (const f of newFindings) {
|
|
50817
|
+
if (!oldKeys.has(key(f))) {
|
|
50818
|
+
changes.push({
|
|
50819
|
+
kind: "new_vulnerability",
|
|
50820
|
+
severity: "high",
|
|
50821
|
+
sink: f.sink,
|
|
50822
|
+
category: f.category,
|
|
50823
|
+
source: f.source,
|
|
50824
|
+
cwe: f.cwe,
|
|
50825
|
+
line: f.sink_line,
|
|
50826
|
+
detail: `new ${f.category} at line ${f.sink_line} (${f.sink.trim()})`
|
|
50827
|
+
});
|
|
50828
|
+
}
|
|
50829
|
+
}
|
|
50830
|
+
for (const f of oldFindings) {
|
|
50831
|
+
if (!newKeys.has(key(f))) {
|
|
50832
|
+
changes.push({
|
|
50833
|
+
kind: "fixed_vulnerability",
|
|
50834
|
+
severity: "low",
|
|
50835
|
+
sink: f.sink,
|
|
50836
|
+
category: f.category,
|
|
50837
|
+
source: f.source,
|
|
50838
|
+
cwe: f.cwe,
|
|
50839
|
+
line: f.sink_line,
|
|
50840
|
+
detail: `removed ${f.category} at line ${f.sink_line} (${f.sink.trim()})`
|
|
50841
|
+
});
|
|
50842
|
+
}
|
|
50843
|
+
}
|
|
50844
|
+
const { added, removed } = diffLines(oldCode, newCode);
|
|
50845
|
+
for (const l of added) {
|
|
50846
|
+
const sink = classifySink2(l.text);
|
|
50847
|
+
if (sink) {
|
|
50848
|
+
changes.push({
|
|
50849
|
+
kind: "new_sink",
|
|
50850
|
+
severity: "medium",
|
|
50851
|
+
sink: l.text.trim(),
|
|
50852
|
+
category: sink.category,
|
|
50853
|
+
cwe: sink.cwe,
|
|
50854
|
+
line: l.line,
|
|
50855
|
+
detail: `added sink ${l.text.trim()} (${sink.category})`
|
|
50856
|
+
});
|
|
50857
|
+
}
|
|
50858
|
+
const source = classifySource(l.text);
|
|
50859
|
+
if (source) {
|
|
50860
|
+
changes.push({
|
|
50861
|
+
kind: "new_source",
|
|
50862
|
+
severity: source.attacker_controlled ? "medium" : "low",
|
|
50863
|
+
source: source.label,
|
|
50864
|
+
line: l.line,
|
|
50865
|
+
detail: `added source ${l.text.trim()} (${source.label})`
|
|
50866
|
+
});
|
|
50867
|
+
}
|
|
50868
|
+
}
|
|
50869
|
+
for (const l of removed) {
|
|
50870
|
+
const san = findSanitizers(l.text);
|
|
50871
|
+
if (san.length > 0) {
|
|
50872
|
+
changes.push({
|
|
50873
|
+
kind: "sanitizer_removed",
|
|
50874
|
+
severity: "high",
|
|
50875
|
+
line: l.line,
|
|
50876
|
+
detail: `removed sanitizer ${san.map((s) => s.label).join(", ")} (${l.text.trim()})`
|
|
50877
|
+
});
|
|
50878
|
+
}
|
|
50879
|
+
const gates = AUTH_GATES.filter((g) => l.text.includes(g));
|
|
50880
|
+
if (gates.length > 0) {
|
|
50881
|
+
changes.push({
|
|
50882
|
+
kind: "authorization_removed",
|
|
50883
|
+
severity: "high",
|
|
50884
|
+
line: l.line,
|
|
50885
|
+
detail: `removed authorization gate ${gates.join(", ")} (${l.text.trim()})`
|
|
50886
|
+
});
|
|
50887
|
+
}
|
|
50888
|
+
}
|
|
50889
|
+
const rank = { high: 0, medium: 1, low: 2 };
|
|
50890
|
+
changes.sort((x, y) => rank[x.severity] - rank[y.severity] || x.line - y.line);
|
|
50891
|
+
const count = (k) => changes.filter((c) => c.kind === k).length;
|
|
50892
|
+
return {
|
|
50893
|
+
language,
|
|
50894
|
+
changes,
|
|
50895
|
+
summary: {
|
|
50896
|
+
new_vulnerabilities: count("new_vulnerability"),
|
|
50897
|
+
fixed_vulnerabilities: count("fixed_vulnerability"),
|
|
50898
|
+
new_sinks: count("new_sink"),
|
|
50899
|
+
new_sources: count("new_source"),
|
|
50900
|
+
sanitizers_removed: count("sanitizer_removed"),
|
|
50901
|
+
authorizations_removed: count("authorization_removed")
|
|
50902
|
+
}
|
|
50903
|
+
};
|
|
50904
|
+
}
|
|
50905
|
+
function languageOf(file) {
|
|
50906
|
+
const adapter = detectLanguage(file);
|
|
50907
|
+
return adapter?.language ?? "php";
|
|
50908
|
+
}
|
|
50909
|
+
function analyzeDifferentialFiles(oldPath, newPath, language) {
|
|
50910
|
+
const oldCode = readFileSync13(oldPath, "utf8");
|
|
50911
|
+
const newCode = readFileSync13(newPath, "utf8");
|
|
50912
|
+
const lang = language ?? languageOf(newPath);
|
|
50913
|
+
return analyzeDifferential(oldCode, newCode, lang, newPath);
|
|
50914
|
+
}
|
|
50915
|
+
var SOURCE_EXTS = [".php", ".phtml", ".php5", ".js", ".mjs", ".ts", ".tsx", ".py", ".pyw", ".java"];
|
|
50916
|
+
function analyzeGitDiff(repo, base = "HEAD~1", head = "HEAD") {
|
|
50917
|
+
const git = (args) => {
|
|
50918
|
+
try {
|
|
50919
|
+
return execSync(`git ${args}`, { cwd: repo, encoding: "utf8", maxBuffer: 50 * 1024 * 1024 }).trim();
|
|
50920
|
+
} catch {
|
|
50921
|
+
return "";
|
|
50922
|
+
}
|
|
50923
|
+
};
|
|
50924
|
+
const changed = git(`diff --name-only ${base} ${head}`).split(`
|
|
50925
|
+
`).filter((f) => f && SOURCE_EXTS.some((e) => f.endsWith(e)));
|
|
50926
|
+
const files = [];
|
|
50927
|
+
for (const file of changed) {
|
|
50928
|
+
const oldCode = git(`show ${base}:${file}`);
|
|
50929
|
+
const newCode = git(`show ${head}:${file}`);
|
|
50930
|
+
if (!newCode)
|
|
50931
|
+
continue;
|
|
50932
|
+
const lang = languageOf(file);
|
|
50933
|
+
files.push({ file, result: analyzeDifferential(oldCode || "", newCode, lang, file) });
|
|
50934
|
+
}
|
|
50935
|
+
const total_changes = files.reduce((n, f) => n + f.result.changes.length, 0);
|
|
50936
|
+
const security_sensitive = files.reduce((n, f) => n + f.result.changes.filter((c) => c.severity === "high").length, 0);
|
|
50937
|
+
return { repo, base, head, files, total_changes, security_sensitive };
|
|
50938
|
+
}
|
|
50939
|
+
|
|
50664
50940
|
// src/guidance.ts
|
|
50665
50941
|
var SINK_DIRECTIVES = {
|
|
50666
50942
|
sql_execution: "Validate the SQL injection live with strike_verify (use a unique MARKER and a NEGATIVE CONTROL that stays inert), or read_tool_manual for sqlmap. Only a reflected marker + inert control = confirmed.",
|
|
@@ -51474,16 +51750,16 @@ function reportJson(findings, opts = {}) {
|
|
|
51474
51750
|
}
|
|
51475
51751
|
|
|
51476
51752
|
// src/benchmark.ts
|
|
51477
|
-
import { readFileSync as
|
|
51478
|
-
import { join as
|
|
51753
|
+
import { readFileSync as readFileSync14, readdirSync as readdirSync4, writeFileSync as writeFileSync3, rmSync as rmSync2 } from "node:fs";
|
|
51754
|
+
import { join as join10 } from "node:path";
|
|
51479
51755
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
51480
|
-
import { tmpdir } from "node:os";
|
|
51481
|
-
var ROOT4 =
|
|
51482
|
-
var CORPUS =
|
|
51756
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
51757
|
+
var ROOT4 = join10(fileURLToPath6(new URL(".", import.meta.url)), "..");
|
|
51758
|
+
var CORPUS = join10(ROOT4, "bench", "corpus");
|
|
51483
51759
|
function parseCorpusFile(path) {
|
|
51484
51760
|
let text;
|
|
51485
51761
|
try {
|
|
51486
|
-
text =
|
|
51762
|
+
text = readFileSync14(path, "utf8");
|
|
51487
51763
|
} catch {
|
|
51488
51764
|
return null;
|
|
51489
51765
|
}
|
|
@@ -51533,7 +51809,7 @@ function loadCorpus() {
|
|
|
51533
51809
|
for (const f of readdirSync4(CORPUS)) {
|
|
51534
51810
|
if (!f.endsWith(".md"))
|
|
51535
51811
|
continue;
|
|
51536
|
-
const e = parseCorpusFile(
|
|
51812
|
+
const e = parseCorpusFile(join10(CORPUS, f));
|
|
51537
51813
|
if (e)
|
|
51538
51814
|
entries.push(e);
|
|
51539
51815
|
}
|
|
@@ -51545,10 +51821,10 @@ function loadCorpus() {
|
|
|
51545
51821
|
function detect(entry) {
|
|
51546
51822
|
const lang = entry.language;
|
|
51547
51823
|
if (lang === "php") {
|
|
51548
|
-
const tmp =
|
|
51549
|
-
|
|
51550
|
-
const r =
|
|
51551
|
-
|
|
51824
|
+
const tmp = join10(tmpdir2(), `blitzstrike-bench-${entry.id}-${process.pid}.php`);
|
|
51825
|
+
writeFileSync3(tmp, entry.code);
|
|
51826
|
+
const r = analyzeDataFlow2(tmp);
|
|
51827
|
+
rmSync2(tmp, { force: true });
|
|
51552
51828
|
if (r.findings.length > 0)
|
|
51553
51829
|
return { detected: true, sinkKind: r.findings[0].sink };
|
|
51554
51830
|
return { detected: false };
|
|
@@ -52357,12 +52633,42 @@ function createServer() {
|
|
|
52357
52633
|
functions: r.summary.functions,
|
|
52358
52634
|
methods: r.summary.methods,
|
|
52359
52635
|
call_edges: r.summary.edges,
|
|
52636
|
+
entry_points: r.summary.entry_points,
|
|
52637
|
+
unreachable: r.summary.unreachable,
|
|
52360
52638
|
results: r.findings,
|
|
52361
52639
|
next_steps: guidance
|
|
52362
52640
|
})
|
|
52363
52641
|
}]
|
|
52364
52642
|
};
|
|
52365
52643
|
});
|
|
52644
|
+
server.registerTool("diff_analyze", {
|
|
52645
|
+
title: "Differential security analysis",
|
|
52646
|
+
description: "PHASE 4: compare two versions of code (files or a git commit range) and surface only security-sensitive changes — new vulnerabilities, fixed vulnerabilities, new sinks/sources, removed sanitizers, and removed authorization gates.",
|
|
52647
|
+
inputSchema: {
|
|
52648
|
+
old_path: string2().optional().describe("Old file path"),
|
|
52649
|
+
new_path: string2().optional().describe("New file path"),
|
|
52650
|
+
old_code: string2().optional().describe("Old source code (when not using paths)"),
|
|
52651
|
+
new_code: string2().optional().describe("New source code (when not using paths)"),
|
|
52652
|
+
language: string2().optional().describe("Language (php/javascript/python/java); auto-detected from path if omitted"),
|
|
52653
|
+
repo: string2().optional().describe("Git repo path (for git-diff mode)"),
|
|
52654
|
+
base: string2().optional().describe("Git base ref (default HEAD~1)"),
|
|
52655
|
+
head: string2().optional().describe("Git head ref (default HEAD)")
|
|
52656
|
+
}
|
|
52657
|
+
}, async ({ old_path, new_path, old_code, new_code, language, repo, base, head }) => {
|
|
52658
|
+
if (repo) {
|
|
52659
|
+
const r = analyzeGitDiff(repo, base ?? "HEAD~1", head ?? "HEAD");
|
|
52660
|
+
return { content: [{ type: "text", text: JSON.stringify(r) }] };
|
|
52661
|
+
}
|
|
52662
|
+
let result;
|
|
52663
|
+
if (old_path && new_path) {
|
|
52664
|
+
result = analyzeDifferentialFiles(old_path, new_path, language);
|
|
52665
|
+
} else if (old_code !== undefined && new_code !== undefined) {
|
|
52666
|
+
result = analyzeDifferential(old_code, new_code, language ?? "php", new_path ?? "target");
|
|
52667
|
+
} else {
|
|
52668
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "provide (old_path+new_path), (old_code+new_code), or (repo)" }) }] };
|
|
52669
|
+
}
|
|
52670
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
52671
|
+
});
|
|
52366
52672
|
server.registerTool("taint_tree", {
|
|
52367
52673
|
title: "Whole-tree taint scan",
|
|
52368
52674
|
description: "EAGLE-EYE: run inter-procedural taint analysis across an entire source tree.",
|
|
@@ -52516,12 +52822,12 @@ async function serve() {
|
|
|
52516
52822
|
}
|
|
52517
52823
|
|
|
52518
52824
|
// src/cli.ts
|
|
52519
|
-
import { readFileSync as
|
|
52520
|
-
import { join as
|
|
52825
|
+
import { readFileSync as readFileSync16, existsSync as existsSync6, writeFileSync as writeFileSync5, mkdirSync as mkdirSync4 } from "node:fs";
|
|
52826
|
+
import { join as join12, dirname as dirname2 } from "node:path";
|
|
52521
52827
|
import { homedir as homedir5 } from "node:os";
|
|
52522
52828
|
import { fileURLToPath as fileURLToPath8 } from "node:url";
|
|
52523
52829
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
52524
|
-
var ROOT6 =
|
|
52830
|
+
var ROOT6 = join12(fileURLToPath8(new URL(".", import.meta.url)), "..");
|
|
52525
52831
|
function which(cmd) {
|
|
52526
52832
|
return spawnSync3(`command -v ${cmd}`, { shell: true }).status === 0;
|
|
52527
52833
|
}
|
|
@@ -52556,7 +52862,7 @@ function runDoctor() {
|
|
|
52556
52862
|
detail: fofa ? "set" : "FOFA_EMAIL/FOFA_KEY not set",
|
|
52557
52863
|
fix: fofa ? undefined : "export FOFA_EMAIL=... && export FOFA_KEY=... (enables fofa_search)"
|
|
52558
52864
|
});
|
|
52559
|
-
const dataOk = existsSync6(
|
|
52865
|
+
const dataOk = existsSync6(join12(ROOT6, "chains.json")) && existsSync6(join12(ROOT6, "tools-catalog.json"));
|
|
52560
52866
|
issues.push({
|
|
52561
52867
|
name: "Data layers (chains + tools-catalog)",
|
|
52562
52868
|
status: dataOk ? "ok" : "fail",
|
|
@@ -52584,21 +52890,21 @@ BlitzStrike — ${verdict}`);
|
|
|
52584
52890
|
function resolveCommand() {
|
|
52585
52891
|
if (which("blitzstrike"))
|
|
52586
52892
|
return { command: "blitzstrike", args: ["serve", "--mcp"] };
|
|
52587
|
-
const src =
|
|
52893
|
+
const src = join12(ROOT6, "src", "index.ts");
|
|
52588
52894
|
if (which("bun") && existsSync6(src))
|
|
52589
52895
|
return { command: "bun", args: ["run", src, "serve", "--mcp"] };
|
|
52590
52896
|
if (which("npx"))
|
|
52591
52897
|
return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
|
|
52592
52898
|
if (which("bunx"))
|
|
52593
52899
|
return { command: "bunx", args: ["blitzstrike", "serve", "--mcp"] };
|
|
52594
|
-
const dist =
|
|
52900
|
+
const dist = join12(ROOT6, "dist", "index.js");
|
|
52595
52901
|
if (existsSync6(dist))
|
|
52596
52902
|
return { command: "bun", args: [dist, "serve", "--mcp"] };
|
|
52597
52903
|
return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
|
|
52598
52904
|
}
|
|
52599
52905
|
function readJson(p) {
|
|
52600
52906
|
try {
|
|
52601
|
-
return JSON.parse(
|
|
52907
|
+
return JSON.parse(readFileSync16(p, "utf8"));
|
|
52602
52908
|
} catch {
|
|
52603
52909
|
return null;
|
|
52604
52910
|
}
|
|
@@ -52613,7 +52919,7 @@ function jsonMcpServersWrite(p) {
|
|
|
52613
52919
|
...existing.mcpServers ?? {},
|
|
52614
52920
|
blitzstrike: { command, args }
|
|
52615
52921
|
};
|
|
52616
|
-
|
|
52922
|
+
writeFileSync5(p, JSON.stringify(existing, null, 2));
|
|
52617
52923
|
};
|
|
52618
52924
|
}
|
|
52619
52925
|
function opencodeWrite(p) {
|
|
@@ -52626,22 +52932,22 @@ function opencodeWrite(p) {
|
|
|
52626
52932
|
...existing.mcp ?? {},
|
|
52627
52933
|
blitzstrike: { type: "local", command: ["npx", "-y", "blitzstrike", "serve", "--mcp"], enabled: true }
|
|
52628
52934
|
};
|
|
52629
|
-
|
|
52935
|
+
writeFileSync5(p, JSON.stringify(existing, null, 2));
|
|
52630
52936
|
copyOpenCodeAgents();
|
|
52631
52937
|
};
|
|
52632
52938
|
}
|
|
52633
52939
|
function copyOpenCodeAgents() {
|
|
52634
|
-
const srcDir =
|
|
52635
|
-
const dstDir =
|
|
52940
|
+
const srcDir = join12(ROOT6, "opencode-agents");
|
|
52941
|
+
const dstDir = join12(homedir5(), ".config", "opencode", "agents");
|
|
52636
52942
|
if (!existsSync6(srcDir))
|
|
52637
52943
|
return;
|
|
52638
52944
|
if (!existsSync6(dstDir))
|
|
52639
52945
|
mkdirSync4(dstDir, { recursive: true });
|
|
52640
52946
|
for (const f of ["Blitz Strike.md", "Blitz.md", "Eagle Eye.md", "Strike.md"]) {
|
|
52641
|
-
const src =
|
|
52642
|
-
const dst =
|
|
52947
|
+
const src = join12(srcDir, f);
|
|
52948
|
+
const dst = join12(dstDir, f);
|
|
52643
52949
|
if (existsSync6(src)) {
|
|
52644
|
-
|
|
52950
|
+
writeFileSync5(dst, readFileSync16(src, "utf8"));
|
|
52645
52951
|
}
|
|
52646
52952
|
}
|
|
52647
52953
|
}
|
|
@@ -52650,7 +52956,7 @@ function codexWrite(p) {
|
|
|
52650
52956
|
const dir = dirname2(p);
|
|
52651
52957
|
if (!existsSync6(dir))
|
|
52652
52958
|
mkdirSync4(dir, { recursive: true });
|
|
52653
|
-
let existing = existsSync6(p) ?
|
|
52959
|
+
let existing = existsSync6(p) ? readFileSync16(p, "utf8") : "";
|
|
52654
52960
|
if (!existing.trimEnd().endsWith(`
|
|
52655
52961
|
`))
|
|
52656
52962
|
existing += `
|
|
@@ -52661,7 +52967,7 @@ function codexWrite(p) {
|
|
|
52661
52967
|
command = "${command}"
|
|
52662
52968
|
args = ${JSON.stringify(args)}
|
|
52663
52969
|
`;
|
|
52664
|
-
|
|
52970
|
+
writeFileSync5(p, existing);
|
|
52665
52971
|
};
|
|
52666
52972
|
}
|
|
52667
52973
|
function hermesWrite(p) {
|
|
@@ -52669,7 +52975,7 @@ function hermesWrite(p) {
|
|
|
52669
52975
|
const dir = dirname2(p);
|
|
52670
52976
|
if (!existsSync6(dir))
|
|
52671
52977
|
mkdirSync4(dir, { recursive: true });
|
|
52672
|
-
let existing = existsSync6(p) ?
|
|
52978
|
+
let existing = existsSync6(p) ? readFileSync16(p, "utf8") : "";
|
|
52673
52979
|
existing = existing.replace(/^ blitzstrike:\n(?: .*\n?)*/m, "");
|
|
52674
52980
|
if (!existing.trimEnd().endsWith(`
|
|
52675
52981
|
`))
|
|
@@ -52690,80 +52996,80 @@ mcp_servers:
|
|
|
52690
52996
|
args: ${JSON.stringify(args)}
|
|
52691
52997
|
`;
|
|
52692
52998
|
}
|
|
52693
|
-
|
|
52999
|
+
writeFileSync5(p, existing);
|
|
52694
53000
|
};
|
|
52695
53001
|
}
|
|
52696
53002
|
function detectAgents() {
|
|
52697
53003
|
const home = homedir5();
|
|
52698
53004
|
const agents = [];
|
|
52699
|
-
const claudeJson =
|
|
53005
|
+
const claudeJson = join12(home, ".claude.json");
|
|
52700
53006
|
agents.push({
|
|
52701
53007
|
name: "Claude Code",
|
|
52702
53008
|
detectPath: claudeJson,
|
|
52703
53009
|
installed: () => existsSync6(claudeJson) || which("claude"),
|
|
52704
53010
|
write: jsonMcpServersWrite(claudeJson)
|
|
52705
53011
|
});
|
|
52706
|
-
const claudeDesktop =
|
|
53012
|
+
const claudeDesktop = join12(home, ".config", "Claude", "claude_desktop_config.json");
|
|
52707
53013
|
agents.push({
|
|
52708
53014
|
name: "Claude Desktop",
|
|
52709
53015
|
detectPath: claudeDesktop,
|
|
52710
53016
|
installed: () => existsSync6(claudeDesktop),
|
|
52711
53017
|
write: jsonMcpServersWrite(claudeDesktop)
|
|
52712
53018
|
});
|
|
52713
|
-
const cursor =
|
|
53019
|
+
const cursor = join12(home, ".cursor", "mcp.json");
|
|
52714
53020
|
agents.push({
|
|
52715
53021
|
name: "Cursor",
|
|
52716
53022
|
detectPath: cursor,
|
|
52717
|
-
installed: () => existsSync6(
|
|
53023
|
+
installed: () => existsSync6(join12(home, ".cursor")) || which("cursor"),
|
|
52718
53024
|
write: jsonMcpServersWrite(cursor)
|
|
52719
53025
|
});
|
|
52720
|
-
const opencode =
|
|
53026
|
+
const opencode = join12(home, ".config", "opencode", "opencode.jsonc");
|
|
52721
53027
|
agents.push({
|
|
52722
53028
|
name: "OpenCode",
|
|
52723
53029
|
detectPath: opencode,
|
|
52724
|
-
installed: () => existsSync6(
|
|
53030
|
+
installed: () => existsSync6(join12(home, ".config", "opencode")) || which("opencode"),
|
|
52725
53031
|
write: opencodeWrite(opencode)
|
|
52726
53032
|
});
|
|
52727
|
-
const codex =
|
|
53033
|
+
const codex = join12(home, ".codex", "config.toml");
|
|
52728
53034
|
agents.push({
|
|
52729
53035
|
name: "Codex",
|
|
52730
53036
|
detectPath: codex,
|
|
52731
|
-
installed: () => existsSync6(
|
|
53037
|
+
installed: () => existsSync6(join12(home, ".codex")) || which("codex"),
|
|
52732
53038
|
write: codexWrite(codex)
|
|
52733
53039
|
});
|
|
52734
|
-
const hermes =
|
|
53040
|
+
const hermes = join12(home, ".hermes", "config.yaml");
|
|
52735
53041
|
agents.push({
|
|
52736
53042
|
name: "Hermes",
|
|
52737
53043
|
detectPath: hermes,
|
|
52738
|
-
installed: () => existsSync6(
|
|
53044
|
+
installed: () => existsSync6(join12(home, ".hermes")) || which("hermes"),
|
|
52739
53045
|
write: hermesWrite(hermes)
|
|
52740
53046
|
});
|
|
52741
|
-
const gemini =
|
|
53047
|
+
const gemini = join12(home, ".gemini", "settings.json");
|
|
52742
53048
|
agents.push({
|
|
52743
53049
|
name: "Gemini CLI",
|
|
52744
53050
|
detectPath: gemini,
|
|
52745
|
-
installed: () => existsSync6(
|
|
53051
|
+
installed: () => existsSync6(join12(home, ".gemini")) || which("gemini"),
|
|
52746
53052
|
write: jsonMcpServersWrite(gemini)
|
|
52747
53053
|
});
|
|
52748
|
-
const windsurf =
|
|
53054
|
+
const windsurf = join12(home, ".codeium", "windsurf", "mcp_config.json");
|
|
52749
53055
|
agents.push({
|
|
52750
53056
|
name: "Windsurf",
|
|
52751
53057
|
detectPath: windsurf,
|
|
52752
|
-
installed: () => existsSync6(
|
|
53058
|
+
installed: () => existsSync6(join12(home, ".codeium")) || which("windsurf"),
|
|
52753
53059
|
write: jsonMcpServersWrite(windsurf)
|
|
52754
53060
|
});
|
|
52755
|
-
const copilot =
|
|
53061
|
+
const copilot = join12(home, ".copilot", "mcp.json");
|
|
52756
53062
|
agents.push({
|
|
52757
53063
|
name: "Copilot",
|
|
52758
53064
|
detectPath: copilot,
|
|
52759
|
-
installed: () => existsSync6(
|
|
53065
|
+
installed: () => existsSync6(join12(home, ".copilot")),
|
|
52760
53066
|
write: jsonMcpServersWrite(copilot)
|
|
52761
53067
|
});
|
|
52762
|
-
const cline =
|
|
53068
|
+
const cline = join12(home, ".cline", "mcp_settings.json");
|
|
52763
53069
|
agents.push({
|
|
52764
53070
|
name: "Cline",
|
|
52765
53071
|
detectPath: cline,
|
|
52766
|
-
installed: () => existsSync6(
|
|
53072
|
+
installed: () => existsSync6(join12(home, ".cline")),
|
|
52767
53073
|
write: jsonMcpServersWrite(cline)
|
|
52768
53074
|
});
|
|
52769
53075
|
return agents;
|
|
@@ -52807,15 +53113,15 @@ Registered with ${ok}/${installed.length} agent(s).`);
|
|
|
52807
53113
|
}
|
|
52808
53114
|
|
|
52809
53115
|
// src/index.ts
|
|
52810
|
-
import { readFileSync as
|
|
52811
|
-
import { join as
|
|
53116
|
+
import { readFileSync as readFileSync17, existsSync as existsSync7 } from "node:fs";
|
|
53117
|
+
import { join as join13 } from "node:path";
|
|
52812
53118
|
import { fileURLToPath as fileURLToPath9 } from "node:url";
|
|
52813
|
-
var ROOT7 =
|
|
53119
|
+
var ROOT7 = join13(fileURLToPath9(new URL(".", import.meta.url)), "..");
|
|
52814
53120
|
function readVersion() {
|
|
52815
53121
|
try {
|
|
52816
|
-
const p =
|
|
53122
|
+
const p = join13(ROOT7, "package.json");
|
|
52817
53123
|
if (existsSync7(p))
|
|
52818
|
-
return JSON.parse(
|
|
53124
|
+
return JSON.parse(readFileSync17(p, "utf8")).version ?? "1.0.0";
|
|
52819
53125
|
} catch {}
|
|
52820
53126
|
return "1.0.0";
|
|
52821
53127
|
}
|