blitzstrike 1.0.35 → 1.0.37
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 +43 -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 +369 -97
- 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" }]
|
|
@@ -31859,9 +31859,11 @@ function sinkClassOf2(call) {
|
|
|
31859
31859
|
return null;
|
|
31860
31860
|
let token = "";
|
|
31861
31861
|
if (what.kind === "propertylookup") {
|
|
31862
|
-
const
|
|
31862
|
+
const baseNode = what.what;
|
|
31863
|
+
const base = rootVarName2(baseNode);
|
|
31863
31864
|
const method = what.offset?.name ?? what.offset?.value;
|
|
31864
|
-
|
|
31865
|
+
const baseStr = isNode3(baseNode) && (baseNode.kind === "variable" || baseNode.kind === "offsetlookup") ? `$${base}` : base;
|
|
31866
|
+
token = `${baseStr}->${method}(`;
|
|
31865
31867
|
} else if (what.kind === "name") {
|
|
31866
31868
|
token = `${what.name}(`;
|
|
31867
31869
|
}
|
|
@@ -31918,26 +31920,27 @@ function summarizeFunction(fn, id, name, summaries) {
|
|
|
31918
31920
|
}
|
|
31919
31921
|
return summary;
|
|
31920
31922
|
}
|
|
31921
|
-
function analyzeBody(statements, env, summary, ctx, taintedParam, props, activeAuth, summaries) {
|
|
31923
|
+
function analyzeBody(statements, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating = false) {
|
|
31922
31924
|
for (const stmt of statements) {
|
|
31923
31925
|
if (!isNode3(stmt))
|
|
31924
31926
|
continue;
|
|
31925
31927
|
const kind = stmt.kind;
|
|
31926
31928
|
if (kind === "expressionstatement") {
|
|
31927
|
-
analyzeExpr(stmt.expression, env, summary, ctx, taintedParam, props, activeAuth, summaries);
|
|
31929
|
+
analyzeExpr(stmt.expression, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
31928
31930
|
} else if (kind === "if") {
|
|
31929
31931
|
const condGates = authGatesOf(stmt.test);
|
|
31930
31932
|
const branchAuth = [...activeAuth, ...condGates];
|
|
31933
|
+
const condValidating = validationGatesOf(stmt.test);
|
|
31931
31934
|
const thenStmts = stmt.body?.kind === "block" ? stmt.body.children ?? [] : [stmt.body];
|
|
31932
31935
|
const thenEnv = cloneEnv(env);
|
|
31933
31936
|
const thenCtx = { sinks: [], returnsTainted: false, calls: [] };
|
|
31934
|
-
analyzeBody(thenStmts, thenEnv, summary, thenCtx, taintedParam, cloneProps(props), branchAuth, summaries);
|
|
31937
|
+
analyzeBody(thenStmts, thenEnv, summary, thenCtx, taintedParam, cloneProps(props), branchAuth, summaries, validating || condValidating);
|
|
31935
31938
|
mergeCtx(ctx, thenCtx);
|
|
31936
31939
|
if (stmt.alternate) {
|
|
31937
31940
|
const altStmts = stmt.alternate.kind === "block" ? stmt.alternate.children ?? [] : [stmt.alternate];
|
|
31938
31941
|
const altEnv = cloneEnv(env);
|
|
31939
31942
|
const altCtx = { sinks: [], returnsTainted: false, calls: [] };
|
|
31940
|
-
analyzeBody(altStmts, altEnv, summary, altCtx, taintedParam, cloneProps(props), activeAuth, summaries);
|
|
31943
|
+
analyzeBody(altStmts, altEnv, summary, altCtx, taintedParam, cloneProps(props), activeAuth, summaries, validating);
|
|
31941
31944
|
mergeCtx(ctx, altCtx);
|
|
31942
31945
|
for (const [k, v] of thenEnv) {
|
|
31943
31946
|
const alt = altEnv.get(k);
|
|
@@ -31948,25 +31951,25 @@ function analyzeBody(statements, env, summary, ctx, taintedParam, props, activeA
|
|
|
31948
31951
|
env.set(k, v);
|
|
31949
31952
|
}
|
|
31950
31953
|
} else if (kind === "block") {
|
|
31951
|
-
analyzeBody(stmt.children ?? [], env, summary, ctx, taintedParam, props, activeAuth, summaries);
|
|
31954
|
+
analyzeBody(stmt.children ?? [], env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
31952
31955
|
} else if (kind === "return") {
|
|
31953
|
-
const t = analyzeExpr(stmt.expr, env, summary, ctx, taintedParam, props, activeAuth, summaries);
|
|
31956
|
+
const t = analyzeExpr(stmt.expr, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
31954
31957
|
if (t.tainted)
|
|
31955
31958
|
ctx.returnsTainted = true;
|
|
31956
31959
|
} else if (kind === "echo" || kind === "print") {
|
|
31957
|
-
analyzeExpr(stmt, env, summary, ctx, taintedParam, props, activeAuth, summaries);
|
|
31960
|
+
analyzeExpr(stmt, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
31958
31961
|
} else {
|
|
31959
31962
|
for (const c of children3(stmt)) {
|
|
31960
31963
|
if (c.kind === "block" || c.kind === "if") {
|
|
31961
|
-
analyzeBody([c], env, summary, ctx, taintedParam, props, activeAuth, summaries);
|
|
31964
|
+
analyzeBody([c], env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
31962
31965
|
} else {
|
|
31963
|
-
analyzeExpr(c, env, summary, ctx, taintedParam, props, activeAuth, summaries);
|
|
31966
|
+
analyzeExpr(c, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
31964
31967
|
}
|
|
31965
31968
|
}
|
|
31966
31969
|
}
|
|
31967
31970
|
}
|
|
31968
31971
|
}
|
|
31969
|
-
function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, summaries) {
|
|
31972
|
+
function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating = false) {
|
|
31970
31973
|
if (!isNode3(node))
|
|
31971
31974
|
return CLEAN;
|
|
31972
31975
|
const src = sourceOf2(node);
|
|
@@ -31974,7 +31977,7 @@ function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, s
|
|
|
31974
31977
|
return { tainted: true, sources: [src], sanitizers: [], sourceLine: node.loc?.start?.line };
|
|
31975
31978
|
if (node.kind === "assign") {
|
|
31976
31979
|
const byref = node.operator === "=&";
|
|
31977
|
-
const rhs = analyzeExpr(node.right, env, summary, ctx, taintedParam, props, activeAuth, summaries);
|
|
31980
|
+
const rhs = analyzeExpr(node.right, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
31978
31981
|
const lhs = node.left;
|
|
31979
31982
|
if (isNode3(lhs) && lhs.kind === "variable") {
|
|
31980
31983
|
const varName = lhs.name;
|
|
@@ -32006,7 +32009,7 @@ function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, s
|
|
|
32006
32009
|
}
|
|
32007
32010
|
if (node.kind === "call") {
|
|
32008
32011
|
const sink = sinkClassOf2(node);
|
|
32009
|
-
const argTaints = node.arguments.map((a) => analyzeExpr(a, env, summary, ctx, taintedParam, props, activeAuth, summaries));
|
|
32012
|
+
const argTaints = node.arguments.map((a) => analyzeExpr(a, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating));
|
|
32010
32013
|
const taintedArgs = argTaints.filter((t) => t.tainted);
|
|
32011
32014
|
if (sink) {
|
|
32012
32015
|
if (taintedArgs.length > 0) {
|
|
@@ -32016,7 +32019,7 @@ function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, s
|
|
|
32016
32019
|
ctx.sinks.push({ sink: sink.token, line: node.loc?.start?.line ?? 0, sanitized, authGated: gates.length > 0, authGates: gates });
|
|
32017
32020
|
}
|
|
32018
32021
|
for (const a of node.arguments)
|
|
32019
|
-
analyzeExpr(a, env, summary, ctx, taintedParam, props, activeAuth, summaries);
|
|
32022
|
+
analyzeExpr(a, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
32020
32023
|
return taintedArgs.length > 0 ? mergeTaint(...taintedArgs) : CLEAN;
|
|
32021
32024
|
}
|
|
32022
32025
|
const fnName = rootVarName2(node.what);
|
|
@@ -32059,6 +32062,9 @@ function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, s
|
|
|
32059
32062
|
}
|
|
32060
32063
|
}
|
|
32061
32064
|
}
|
|
32065
|
+
if (isNode3(node.what) && node.what.kind === "propertylookup") {
|
|
32066
|
+
analyzeExpr(node.what, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
32067
|
+
}
|
|
32062
32068
|
return CLEAN;
|
|
32063
32069
|
}
|
|
32064
32070
|
if (node.kind === "variable") {
|
|
@@ -32072,7 +32078,7 @@ function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, s
|
|
|
32072
32078
|
if (t)
|
|
32073
32079
|
return { ...t };
|
|
32074
32080
|
}
|
|
32075
|
-
return analyzeExpr(node.what, env, summary, ctx, taintedParam, props, activeAuth, summaries);
|
|
32081
|
+
return analyzeExpr(node.what, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
32076
32082
|
}
|
|
32077
32083
|
if (node.kind === "propertylookup") {
|
|
32078
32084
|
const objName = rootVarName2(node.what);
|
|
@@ -32082,35 +32088,55 @@ function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, s
|
|
|
32082
32088
|
if (pt)
|
|
32083
32089
|
return { ...pt };
|
|
32084
32090
|
}
|
|
32085
|
-
return analyzeExpr(node.what, env, summary, ctx, taintedParam, props, activeAuth, summaries);
|
|
32091
|
+
return analyzeExpr(node.what, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
32086
32092
|
}
|
|
32087
32093
|
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;
|
|
32094
|
+
const t = analyzeExpr(node.trueExpr ?? node.test, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
32095
|
+
const f = node.falseExpr ? analyzeExpr(node.falseExpr, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating) : CLEAN;
|
|
32090
32096
|
return mergeTaint(t, f);
|
|
32091
32097
|
}
|
|
32092
32098
|
if (node.kind === "bin" || node.kind === "concat" || node.kind === "encapsed") {
|
|
32093
32099
|
const parts = children3(node);
|
|
32094
|
-
const taints = parts.map((p) => analyzeExpr(p, env, summary, ctx, taintedParam, props, activeAuth, summaries));
|
|
32100
|
+
const taints = parts.map((p) => analyzeExpr(p, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating));
|
|
32095
32101
|
return mergeTaint(...taints);
|
|
32096
32102
|
}
|
|
32097
32103
|
if (node.kind === "encapsedpart") {
|
|
32098
|
-
return analyzeExpr(node.expression, env, summary, ctx, taintedParam, props, activeAuth, summaries);
|
|
32104
|
+
return analyzeExpr(node.expression, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
32099
32105
|
}
|
|
32100
32106
|
if (node.kind === "echo" || node.kind === "print") {
|
|
32101
32107
|
const exprs = node.expressions ?? [node.expression];
|
|
32102
32108
|
const cls = classifySink2(node.kind === "echo" ? "echo " : "print ");
|
|
32103
32109
|
let ret = CLEAN;
|
|
32104
32110
|
for (const e of exprs) {
|
|
32105
|
-
const t = analyzeExpr(e, env, summary, ctx, taintedParam, props, activeAuth, summaries);
|
|
32111
|
+
const t = analyzeExpr(e, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
32106
32112
|
if (t.tainted && cls) {
|
|
32107
32113
|
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 });
|
|
32114
|
+
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
32115
|
}
|
|
32110
32116
|
ret = mergeTaint(ret, t);
|
|
32111
32117
|
}
|
|
32112
32118
|
return ret;
|
|
32113
32119
|
}
|
|
32120
|
+
if (node.kind === "include") {
|
|
32121
|
+
const base = node.require ? "require" : "include";
|
|
32122
|
+
const suffix = node.once ? "_once" : "";
|
|
32123
|
+
const sinkToken = base + suffix + "(";
|
|
32124
|
+
const cls = classifySink2(sinkToken);
|
|
32125
|
+
const t = analyzeExpr(node.target, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
32126
|
+
if (t.tainted && cls) {
|
|
32127
|
+
const san = [...t.sanitizers, ...findSanitizers(JSON.stringify(node))];
|
|
32128
|
+
ctx.sinks.push({ sink: sinkToken, line: node.loc?.start?.line ?? 0, sanitized: isSanitized(san, cls.id) || validating, authGated: activeAuth.length > 0, authGates: activeAuth });
|
|
32129
|
+
}
|
|
32130
|
+
return t;
|
|
32131
|
+
}
|
|
32132
|
+
if (node.kind === "eval") {
|
|
32133
|
+
const cls = classifySink2("eval(");
|
|
32134
|
+
const t = analyzeExpr(node.source, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
32135
|
+
if (t.tainted && cls) {
|
|
32136
|
+
ctx.sinks.push({ sink: "eval(", line: node.loc?.start?.line ?? 0, sanitized: false, authGated: activeAuth.length > 0, authGates: activeAuth });
|
|
32137
|
+
}
|
|
32138
|
+
return t;
|
|
32139
|
+
}
|
|
32114
32140
|
return CLEAN;
|
|
32115
32141
|
}
|
|
32116
32142
|
function cloneEnv(env) {
|
|
@@ -32133,6 +32159,13 @@ function authGatesOf(node) {
|
|
|
32133
32159
|
return [];
|
|
32134
32160
|
return AUTH_GATES.filter((g) => JSON.stringify(node).includes(g));
|
|
32135
32161
|
}
|
|
32162
|
+
var VALIDATION_GATES = ["in_array", "preg_match", "ctype_digit", "ctype_alnum", "is_numeric"];
|
|
32163
|
+
function validationGatesOf(node) {
|
|
32164
|
+
if (!isNode3(node))
|
|
32165
|
+
return false;
|
|
32166
|
+
const text = JSON.stringify(node);
|
|
32167
|
+
return VALIDATION_GATES.some((g) => text.includes(g));
|
|
32168
|
+
}
|
|
32136
32169
|
function collectDecls(ast, out) {
|
|
32137
32170
|
walkDecls(ast, out);
|
|
32138
32171
|
}
|
|
@@ -32293,6 +32326,12 @@ function analyzeDataFlow2(path) {
|
|
|
32293
32326
|
return result;
|
|
32294
32327
|
}
|
|
32295
32328
|
|
|
32329
|
+
// src/differential.ts
|
|
32330
|
+
import { writeFileSync as writeFileSync2, rmSync, mkdtempSync, readFileSync as readFileSync13 } from "node:fs";
|
|
32331
|
+
import { join as join9 } from "node:path";
|
|
32332
|
+
import { tmpdir } from "node:os";
|
|
32333
|
+
import { execSync } from "node:child_process";
|
|
32334
|
+
|
|
32296
32335
|
// src/universal-taint.ts
|
|
32297
32336
|
function runTaintEngine(adapter, code, file) {
|
|
32298
32337
|
const result = { file, language: adapter.language, findings: [], suppressed: 0 };
|
|
@@ -50346,8 +50385,13 @@ var JS_SINKS = [
|
|
|
50346
50385
|
[/document\.write/i, "html_render", "CWE-79"],
|
|
50347
50386
|
[/fs\.writeFile/i, "file_operations", "CWE-434"],
|
|
50348
50387
|
[/fs\.appendFile/i, "file_operations", "CWE-434"],
|
|
50388
|
+
[/fs\.readFile/i, "path_traversal", "CWE-22"],
|
|
50389
|
+
[/fs\.readFileSync/i, "path_traversal", "CWE-22"],
|
|
50390
|
+
[/fs\.unlink/i, "file_operations", "CWE-434"],
|
|
50349
50391
|
[/require\s*\(/i, "file_inclusion", "CWE-98"],
|
|
50350
50392
|
[/fetch\s*\(/i, "http_request", "CWE-918"],
|
|
50393
|
+
[/http\.get|https\.get|axios/i, "http_request", "CWE-918"],
|
|
50394
|
+
[/http\.request|https\.request/i, "http_request", "CWE-918"],
|
|
50351
50395
|
[/redirect\s*\(/i, "redirect", "CWE-601"],
|
|
50352
50396
|
[/res\.redirect/i, "redirect", "CWE-601"]
|
|
50353
50397
|
];
|
|
@@ -50434,6 +50478,8 @@ var jsAdapter = {
|
|
|
50434
50478
|
argVars.push(c.name);
|
|
50435
50479
|
for (const [re, kind, cwe] of JS_SINKS) {
|
|
50436
50480
|
if (re.test(fullCallText) || re.test(calleeText + "(")) {
|
|
50481
|
+
if (kind === "sql_execution" && /\?/.test(fullCallText) && /,\s*\[/.test(fullCallText))
|
|
50482
|
+
continue;
|
|
50437
50483
|
for (const v of argVars)
|
|
50438
50484
|
sinks.push({ variable: v, kind, line: n.loc?.start?.line ?? 0, cwe });
|
|
50439
50485
|
}
|
|
@@ -50472,6 +50518,8 @@ var PY_SINKS = [
|
|
|
50472
50518
|
[/\.save\s*\(/i, "file_operations", "CWE-434"],
|
|
50473
50519
|
[/render_template/i, "html_render", "CWE-79"],
|
|
50474
50520
|
[/redirect\s*\(/i, "redirect", "CWE-601"],
|
|
50521
|
+
[/requests\.(get|post|put|head|request)\s*\(/i, "http_request", "CWE-918"],
|
|
50522
|
+
[/urllib\.request/i, "http_request", "CWE-918"],
|
|
50475
50523
|
[/pickle\.loads\s*\(/i, "deserialization", "CWE-502"],
|
|
50476
50524
|
[/yaml\.load\s*\(/i, "deserialization", "CWE-502"],
|
|
50477
50525
|
[/os\.popen\s*\(/i, "command_execution", "CWE-78"],
|
|
@@ -50556,9 +50604,14 @@ var pythonAdapter = {
|
|
|
50556
50604
|
if (m[1] !== callee.trim().split(".").pop())
|
|
50557
50605
|
argVars.push(m[1]);
|
|
50558
50606
|
for (const [re, kind, cwe] of PY_SINKS) {
|
|
50559
|
-
if (re.test(full))
|
|
50560
|
-
|
|
50561
|
-
|
|
50607
|
+
if (!re.test(full))
|
|
50608
|
+
continue;
|
|
50609
|
+
if (kind === "sql_execution" && /\?/.test(full) && /,\s*\(/.test(full))
|
|
50610
|
+
continue;
|
|
50611
|
+
if (kind === "command_execution" && /subprocess/.test(full) && /\[/.test(full))
|
|
50612
|
+
continue;
|
|
50613
|
+
for (const v of argVars)
|
|
50614
|
+
sinks.push({ variable: v, kind, line: lineOf(n), cwe });
|
|
50562
50615
|
}
|
|
50563
50616
|
for (const [re, kinds] of PY_SANITIZERS) {
|
|
50564
50617
|
if (re.test(full))
|
|
@@ -50594,6 +50647,10 @@ var JAVA_SINKS = [
|
|
|
50594
50647
|
[/readObject\s*\(/i, "deserialization", "CWE-502"],
|
|
50595
50648
|
[/new File\s*\(/i, "file_operations", "CWE-434"],
|
|
50596
50649
|
[/\.getInputStream\s*\(/i, "http_request", "CWE-918"],
|
|
50650
|
+
[/new URL\s*\(/i, "http_request", "CWE-918"],
|
|
50651
|
+
[/openConnection\s*\(/i, "http_request", "CWE-918"],
|
|
50652
|
+
[/DocumentBuilder|SAXParser|XMLReader/i, "xml_processing", "CWE-611"],
|
|
50653
|
+
[/\.parse\s*\(/i, "xml_processing", "CWE-611"],
|
|
50597
50654
|
[/sendRedirect\s*\(/i, "redirect", "CWE-601"],
|
|
50598
50655
|
[/\.forward\s*\(/i, "redirect", "CWE-601"]
|
|
50599
50656
|
];
|
|
@@ -50661,6 +50718,193 @@ registerLanguage(jsAdapter);
|
|
|
50661
50718
|
registerLanguage(pythonAdapter);
|
|
50662
50719
|
registerLanguage(javaAdapter);
|
|
50663
50720
|
|
|
50721
|
+
// src/differential.ts
|
|
50722
|
+
init_scanner();
|
|
50723
|
+
function runEngine(code, language, file) {
|
|
50724
|
+
if (language === "php") {
|
|
50725
|
+
const dir = mkdtempSync(join9(tmpdir(), "blitz-diff-"));
|
|
50726
|
+
const tmp = join9(dir, "old.php");
|
|
50727
|
+
writeFileSync2(tmp, code);
|
|
50728
|
+
try {
|
|
50729
|
+
const r = analyzeDataFlow2(tmp);
|
|
50730
|
+
return r.findings.map((f) => ({ sink: f.sink, sink_line: f.sink_line, category: f.category, source: f.source, cwe: f.cwe }));
|
|
50731
|
+
} finally {
|
|
50732
|
+
rmSync(dir, { recursive: true, force: true });
|
|
50733
|
+
}
|
|
50734
|
+
}
|
|
50735
|
+
const r = analyzeTaintUniversal(code, file);
|
|
50736
|
+
return r.findings.map((f) => ({ sink: f.sink, sink_line: f.sink_line, category: f.sink, source: f.source_kind, cwe: f.cwe }));
|
|
50737
|
+
}
|
|
50738
|
+
function diffLines(oldText, newText) {
|
|
50739
|
+
const a = oldText.split(`
|
|
50740
|
+
`);
|
|
50741
|
+
const b = newText.split(`
|
|
50742
|
+
`);
|
|
50743
|
+
const m = a.length;
|
|
50744
|
+
const n = b.length;
|
|
50745
|
+
const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
|
|
50746
|
+
for (let i = m - 1;i >= 0; i--) {
|
|
50747
|
+
for (let j = n - 1;j >= 0; j--) {
|
|
50748
|
+
dp[i][j] = a[i] === b[j] ? dp[i + 1][j + 1] + 1 : Math.max(dp[i + 1][j], dp[i][j + 1]);
|
|
50749
|
+
}
|
|
50750
|
+
}
|
|
50751
|
+
const added = [];
|
|
50752
|
+
const removed = [];
|
|
50753
|
+
let i = 0;
|
|
50754
|
+
let j = 0;
|
|
50755
|
+
while (i < m && j < n) {
|
|
50756
|
+
if (a[i] === b[j]) {
|
|
50757
|
+
i++;
|
|
50758
|
+
j++;
|
|
50759
|
+
} else if (dp[i + 1][j] >= dp[i][j + 1]) {
|
|
50760
|
+
removed.push({ line: i + 1, text: a[i] });
|
|
50761
|
+
i++;
|
|
50762
|
+
} else {
|
|
50763
|
+
added.push({ line: j + 1, text: b[j] });
|
|
50764
|
+
j++;
|
|
50765
|
+
}
|
|
50766
|
+
}
|
|
50767
|
+
while (i < m) {
|
|
50768
|
+
removed.push({ line: i + 1, text: a[i] });
|
|
50769
|
+
i++;
|
|
50770
|
+
}
|
|
50771
|
+
while (j < n) {
|
|
50772
|
+
added.push({ line: j + 1, text: b[j] });
|
|
50773
|
+
j++;
|
|
50774
|
+
}
|
|
50775
|
+
return { added, removed };
|
|
50776
|
+
}
|
|
50777
|
+
function analyzeDifferential(oldCode, newCode, language, file = "target") {
|
|
50778
|
+
const changes = [];
|
|
50779
|
+
const oldFindings = runEngine(oldCode, language, file);
|
|
50780
|
+
const newFindings = runEngine(newCode, language, file);
|
|
50781
|
+
const key = (f) => `${f.category}|${f.sink}|${f.sink_line}`;
|
|
50782
|
+
const oldKeys = new Set(oldFindings.map(key));
|
|
50783
|
+
const newKeys = new Set(newFindings.map(key));
|
|
50784
|
+
for (const f of newFindings) {
|
|
50785
|
+
if (!oldKeys.has(key(f))) {
|
|
50786
|
+
changes.push({
|
|
50787
|
+
kind: "new_vulnerability",
|
|
50788
|
+
severity: "high",
|
|
50789
|
+
sink: f.sink,
|
|
50790
|
+
category: f.category,
|
|
50791
|
+
source: f.source,
|
|
50792
|
+
cwe: f.cwe,
|
|
50793
|
+
line: f.sink_line,
|
|
50794
|
+
detail: `new ${f.category} at line ${f.sink_line} (${f.sink.trim()})`
|
|
50795
|
+
});
|
|
50796
|
+
}
|
|
50797
|
+
}
|
|
50798
|
+
for (const f of oldFindings) {
|
|
50799
|
+
if (!newKeys.has(key(f))) {
|
|
50800
|
+
changes.push({
|
|
50801
|
+
kind: "fixed_vulnerability",
|
|
50802
|
+
severity: "low",
|
|
50803
|
+
sink: f.sink,
|
|
50804
|
+
category: f.category,
|
|
50805
|
+
source: f.source,
|
|
50806
|
+
cwe: f.cwe,
|
|
50807
|
+
line: f.sink_line,
|
|
50808
|
+
detail: `removed ${f.category} at line ${f.sink_line} (${f.sink.trim()})`
|
|
50809
|
+
});
|
|
50810
|
+
}
|
|
50811
|
+
}
|
|
50812
|
+
const { added, removed } = diffLines(oldCode, newCode);
|
|
50813
|
+
for (const l of added) {
|
|
50814
|
+
const sink = classifySink2(l.text);
|
|
50815
|
+
if (sink) {
|
|
50816
|
+
changes.push({
|
|
50817
|
+
kind: "new_sink",
|
|
50818
|
+
severity: "medium",
|
|
50819
|
+
sink: l.text.trim(),
|
|
50820
|
+
category: sink.category,
|
|
50821
|
+
cwe: sink.cwe,
|
|
50822
|
+
line: l.line,
|
|
50823
|
+
detail: `added sink ${l.text.trim()} (${sink.category})`
|
|
50824
|
+
});
|
|
50825
|
+
}
|
|
50826
|
+
const source = classifySource(l.text);
|
|
50827
|
+
if (source) {
|
|
50828
|
+
changes.push({
|
|
50829
|
+
kind: "new_source",
|
|
50830
|
+
severity: source.attacker_controlled ? "medium" : "low",
|
|
50831
|
+
source: source.label,
|
|
50832
|
+
line: l.line,
|
|
50833
|
+
detail: `added source ${l.text.trim()} (${source.label})`
|
|
50834
|
+
});
|
|
50835
|
+
}
|
|
50836
|
+
}
|
|
50837
|
+
for (const l of removed) {
|
|
50838
|
+
const san = findSanitizers(l.text);
|
|
50839
|
+
if (san.length > 0) {
|
|
50840
|
+
changes.push({
|
|
50841
|
+
kind: "sanitizer_removed",
|
|
50842
|
+
severity: "high",
|
|
50843
|
+
line: l.line,
|
|
50844
|
+
detail: `removed sanitizer ${san.map((s) => s.label).join(", ")} (${l.text.trim()})`
|
|
50845
|
+
});
|
|
50846
|
+
}
|
|
50847
|
+
const gates = AUTH_GATES.filter((g) => l.text.includes(g));
|
|
50848
|
+
if (gates.length > 0) {
|
|
50849
|
+
changes.push({
|
|
50850
|
+
kind: "authorization_removed",
|
|
50851
|
+
severity: "high",
|
|
50852
|
+
line: l.line,
|
|
50853
|
+
detail: `removed authorization gate ${gates.join(", ")} (${l.text.trim()})`
|
|
50854
|
+
});
|
|
50855
|
+
}
|
|
50856
|
+
}
|
|
50857
|
+
const rank = { high: 0, medium: 1, low: 2 };
|
|
50858
|
+
changes.sort((x, y) => rank[x.severity] - rank[y.severity] || x.line - y.line);
|
|
50859
|
+
const count = (k) => changes.filter((c) => c.kind === k).length;
|
|
50860
|
+
return {
|
|
50861
|
+
language,
|
|
50862
|
+
changes,
|
|
50863
|
+
summary: {
|
|
50864
|
+
new_vulnerabilities: count("new_vulnerability"),
|
|
50865
|
+
fixed_vulnerabilities: count("fixed_vulnerability"),
|
|
50866
|
+
new_sinks: count("new_sink"),
|
|
50867
|
+
new_sources: count("new_source"),
|
|
50868
|
+
sanitizers_removed: count("sanitizer_removed"),
|
|
50869
|
+
authorizations_removed: count("authorization_removed")
|
|
50870
|
+
}
|
|
50871
|
+
};
|
|
50872
|
+
}
|
|
50873
|
+
function languageOf(file) {
|
|
50874
|
+
const adapter = detectLanguage(file);
|
|
50875
|
+
return adapter?.language ?? "php";
|
|
50876
|
+
}
|
|
50877
|
+
function analyzeDifferentialFiles(oldPath, newPath, language) {
|
|
50878
|
+
const oldCode = readFileSync13(oldPath, "utf8");
|
|
50879
|
+
const newCode = readFileSync13(newPath, "utf8");
|
|
50880
|
+
const lang = language ?? languageOf(newPath);
|
|
50881
|
+
return analyzeDifferential(oldCode, newCode, lang, newPath);
|
|
50882
|
+
}
|
|
50883
|
+
var SOURCE_EXTS = [".php", ".phtml", ".php5", ".js", ".mjs", ".ts", ".tsx", ".py", ".pyw", ".java"];
|
|
50884
|
+
function analyzeGitDiff(repo, base = "HEAD~1", head = "HEAD") {
|
|
50885
|
+
const git = (args) => {
|
|
50886
|
+
try {
|
|
50887
|
+
return execSync(`git ${args}`, { cwd: repo, encoding: "utf8", maxBuffer: 50 * 1024 * 1024 }).trim();
|
|
50888
|
+
} catch {
|
|
50889
|
+
return "";
|
|
50890
|
+
}
|
|
50891
|
+
};
|
|
50892
|
+
const changed = git(`diff --name-only ${base} ${head}`).split(`
|
|
50893
|
+
`).filter((f) => f && SOURCE_EXTS.some((e) => f.endsWith(e)));
|
|
50894
|
+
const files = [];
|
|
50895
|
+
for (const file of changed) {
|
|
50896
|
+
const oldCode = git(`show ${base}:${file}`);
|
|
50897
|
+
const newCode = git(`show ${head}:${file}`);
|
|
50898
|
+
if (!newCode)
|
|
50899
|
+
continue;
|
|
50900
|
+
const lang = languageOf(file);
|
|
50901
|
+
files.push({ file, result: analyzeDifferential(oldCode || "", newCode, lang, file) });
|
|
50902
|
+
}
|
|
50903
|
+
const total_changes = files.reduce((n, f) => n + f.result.changes.length, 0);
|
|
50904
|
+
const security_sensitive = files.reduce((n, f) => n + f.result.changes.filter((c) => c.severity === "high").length, 0);
|
|
50905
|
+
return { repo, base, head, files, total_changes, security_sensitive };
|
|
50906
|
+
}
|
|
50907
|
+
|
|
50664
50908
|
// src/guidance.ts
|
|
50665
50909
|
var SINK_DIRECTIVES = {
|
|
50666
50910
|
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 +51718,16 @@ function reportJson(findings, opts = {}) {
|
|
|
51474
51718
|
}
|
|
51475
51719
|
|
|
51476
51720
|
// src/benchmark.ts
|
|
51477
|
-
import { readFileSync as
|
|
51478
|
-
import { join as
|
|
51721
|
+
import { readFileSync as readFileSync14, readdirSync as readdirSync4, writeFileSync as writeFileSync3, rmSync as rmSync2 } from "node:fs";
|
|
51722
|
+
import { join as join10 } from "node:path";
|
|
51479
51723
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
51480
|
-
import { tmpdir } from "node:os";
|
|
51481
|
-
var ROOT4 =
|
|
51482
|
-
var CORPUS =
|
|
51724
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
51725
|
+
var ROOT4 = join10(fileURLToPath6(new URL(".", import.meta.url)), "..");
|
|
51726
|
+
var CORPUS = join10(ROOT4, "bench", "corpus");
|
|
51483
51727
|
function parseCorpusFile(path) {
|
|
51484
51728
|
let text;
|
|
51485
51729
|
try {
|
|
51486
|
-
text =
|
|
51730
|
+
text = readFileSync14(path, "utf8");
|
|
51487
51731
|
} catch {
|
|
51488
51732
|
return null;
|
|
51489
51733
|
}
|
|
@@ -51533,7 +51777,7 @@ function loadCorpus() {
|
|
|
51533
51777
|
for (const f of readdirSync4(CORPUS)) {
|
|
51534
51778
|
if (!f.endsWith(".md"))
|
|
51535
51779
|
continue;
|
|
51536
|
-
const e = parseCorpusFile(
|
|
51780
|
+
const e = parseCorpusFile(join10(CORPUS, f));
|
|
51537
51781
|
if (e)
|
|
51538
51782
|
entries.push(e);
|
|
51539
51783
|
}
|
|
@@ -51545,10 +51789,10 @@ function loadCorpus() {
|
|
|
51545
51789
|
function detect(entry) {
|
|
51546
51790
|
const lang = entry.language;
|
|
51547
51791
|
if (lang === "php") {
|
|
51548
|
-
const tmp =
|
|
51549
|
-
|
|
51550
|
-
const r =
|
|
51551
|
-
|
|
51792
|
+
const tmp = join10(tmpdir2(), `blitzstrike-bench-${entry.id}-${process.pid}.php`);
|
|
51793
|
+
writeFileSync3(tmp, entry.code);
|
|
51794
|
+
const r = analyzeDataFlow2(tmp);
|
|
51795
|
+
rmSync2(tmp, { force: true });
|
|
51552
51796
|
if (r.findings.length > 0)
|
|
51553
51797
|
return { detected: true, sinkKind: r.findings[0].sink };
|
|
51554
51798
|
return { detected: false };
|
|
@@ -52363,6 +52607,34 @@ function createServer() {
|
|
|
52363
52607
|
}]
|
|
52364
52608
|
};
|
|
52365
52609
|
});
|
|
52610
|
+
server.registerTool("diff_analyze", {
|
|
52611
|
+
title: "Differential security analysis",
|
|
52612
|
+
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.",
|
|
52613
|
+
inputSchema: {
|
|
52614
|
+
old_path: string2().optional().describe("Old file path"),
|
|
52615
|
+
new_path: string2().optional().describe("New file path"),
|
|
52616
|
+
old_code: string2().optional().describe("Old source code (when not using paths)"),
|
|
52617
|
+
new_code: string2().optional().describe("New source code (when not using paths)"),
|
|
52618
|
+
language: string2().optional().describe("Language (php/javascript/python/java); auto-detected from path if omitted"),
|
|
52619
|
+
repo: string2().optional().describe("Git repo path (for git-diff mode)"),
|
|
52620
|
+
base: string2().optional().describe("Git base ref (default HEAD~1)"),
|
|
52621
|
+
head: string2().optional().describe("Git head ref (default HEAD)")
|
|
52622
|
+
}
|
|
52623
|
+
}, async ({ old_path, new_path, old_code, new_code, language, repo, base, head }) => {
|
|
52624
|
+
if (repo) {
|
|
52625
|
+
const r = analyzeGitDiff(repo, base ?? "HEAD~1", head ?? "HEAD");
|
|
52626
|
+
return { content: [{ type: "text", text: JSON.stringify(r) }] };
|
|
52627
|
+
}
|
|
52628
|
+
let result;
|
|
52629
|
+
if (old_path && new_path) {
|
|
52630
|
+
result = analyzeDifferentialFiles(old_path, new_path, language);
|
|
52631
|
+
} else if (old_code !== undefined && new_code !== undefined) {
|
|
52632
|
+
result = analyzeDifferential(old_code, new_code, language ?? "php", new_path ?? "target");
|
|
52633
|
+
} else {
|
|
52634
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "provide (old_path+new_path), (old_code+new_code), or (repo)" }) }] };
|
|
52635
|
+
}
|
|
52636
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
52637
|
+
});
|
|
52366
52638
|
server.registerTool("taint_tree", {
|
|
52367
52639
|
title: "Whole-tree taint scan",
|
|
52368
52640
|
description: "EAGLE-EYE: run inter-procedural taint analysis across an entire source tree.",
|
|
@@ -52516,12 +52788,12 @@ async function serve() {
|
|
|
52516
52788
|
}
|
|
52517
52789
|
|
|
52518
52790
|
// src/cli.ts
|
|
52519
|
-
import { readFileSync as
|
|
52520
|
-
import { join as
|
|
52791
|
+
import { readFileSync as readFileSync16, existsSync as existsSync6, writeFileSync as writeFileSync5, mkdirSync as mkdirSync4 } from "node:fs";
|
|
52792
|
+
import { join as join12, dirname as dirname2 } from "node:path";
|
|
52521
52793
|
import { homedir as homedir5 } from "node:os";
|
|
52522
52794
|
import { fileURLToPath as fileURLToPath8 } from "node:url";
|
|
52523
52795
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
52524
|
-
var ROOT6 =
|
|
52796
|
+
var ROOT6 = join12(fileURLToPath8(new URL(".", import.meta.url)), "..");
|
|
52525
52797
|
function which(cmd) {
|
|
52526
52798
|
return spawnSync3(`command -v ${cmd}`, { shell: true }).status === 0;
|
|
52527
52799
|
}
|
|
@@ -52556,7 +52828,7 @@ function runDoctor() {
|
|
|
52556
52828
|
detail: fofa ? "set" : "FOFA_EMAIL/FOFA_KEY not set",
|
|
52557
52829
|
fix: fofa ? undefined : "export FOFA_EMAIL=... && export FOFA_KEY=... (enables fofa_search)"
|
|
52558
52830
|
});
|
|
52559
|
-
const dataOk = existsSync6(
|
|
52831
|
+
const dataOk = existsSync6(join12(ROOT6, "chains.json")) && existsSync6(join12(ROOT6, "tools-catalog.json"));
|
|
52560
52832
|
issues.push({
|
|
52561
52833
|
name: "Data layers (chains + tools-catalog)",
|
|
52562
52834
|
status: dataOk ? "ok" : "fail",
|
|
@@ -52584,21 +52856,21 @@ BlitzStrike — ${verdict}`);
|
|
|
52584
52856
|
function resolveCommand() {
|
|
52585
52857
|
if (which("blitzstrike"))
|
|
52586
52858
|
return { command: "blitzstrike", args: ["serve", "--mcp"] };
|
|
52587
|
-
const src =
|
|
52859
|
+
const src = join12(ROOT6, "src", "index.ts");
|
|
52588
52860
|
if (which("bun") && existsSync6(src))
|
|
52589
52861
|
return { command: "bun", args: ["run", src, "serve", "--mcp"] };
|
|
52590
52862
|
if (which("npx"))
|
|
52591
52863
|
return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
|
|
52592
52864
|
if (which("bunx"))
|
|
52593
52865
|
return { command: "bunx", args: ["blitzstrike", "serve", "--mcp"] };
|
|
52594
|
-
const dist =
|
|
52866
|
+
const dist = join12(ROOT6, "dist", "index.js");
|
|
52595
52867
|
if (existsSync6(dist))
|
|
52596
52868
|
return { command: "bun", args: [dist, "serve", "--mcp"] };
|
|
52597
52869
|
return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
|
|
52598
52870
|
}
|
|
52599
52871
|
function readJson(p) {
|
|
52600
52872
|
try {
|
|
52601
|
-
return JSON.parse(
|
|
52873
|
+
return JSON.parse(readFileSync16(p, "utf8"));
|
|
52602
52874
|
} catch {
|
|
52603
52875
|
return null;
|
|
52604
52876
|
}
|
|
@@ -52613,7 +52885,7 @@ function jsonMcpServersWrite(p) {
|
|
|
52613
52885
|
...existing.mcpServers ?? {},
|
|
52614
52886
|
blitzstrike: { command, args }
|
|
52615
52887
|
};
|
|
52616
|
-
|
|
52888
|
+
writeFileSync5(p, JSON.stringify(existing, null, 2));
|
|
52617
52889
|
};
|
|
52618
52890
|
}
|
|
52619
52891
|
function opencodeWrite(p) {
|
|
@@ -52626,22 +52898,22 @@ function opencodeWrite(p) {
|
|
|
52626
52898
|
...existing.mcp ?? {},
|
|
52627
52899
|
blitzstrike: { type: "local", command: ["npx", "-y", "blitzstrike", "serve", "--mcp"], enabled: true }
|
|
52628
52900
|
};
|
|
52629
|
-
|
|
52901
|
+
writeFileSync5(p, JSON.stringify(existing, null, 2));
|
|
52630
52902
|
copyOpenCodeAgents();
|
|
52631
52903
|
};
|
|
52632
52904
|
}
|
|
52633
52905
|
function copyOpenCodeAgents() {
|
|
52634
|
-
const srcDir =
|
|
52635
|
-
const dstDir =
|
|
52906
|
+
const srcDir = join12(ROOT6, "opencode-agents");
|
|
52907
|
+
const dstDir = join12(homedir5(), ".config", "opencode", "agents");
|
|
52636
52908
|
if (!existsSync6(srcDir))
|
|
52637
52909
|
return;
|
|
52638
52910
|
if (!existsSync6(dstDir))
|
|
52639
52911
|
mkdirSync4(dstDir, { recursive: true });
|
|
52640
52912
|
for (const f of ["Blitz Strike.md", "Blitz.md", "Eagle Eye.md", "Strike.md"]) {
|
|
52641
|
-
const src =
|
|
52642
|
-
const dst =
|
|
52913
|
+
const src = join12(srcDir, f);
|
|
52914
|
+
const dst = join12(dstDir, f);
|
|
52643
52915
|
if (existsSync6(src)) {
|
|
52644
|
-
|
|
52916
|
+
writeFileSync5(dst, readFileSync16(src, "utf8"));
|
|
52645
52917
|
}
|
|
52646
52918
|
}
|
|
52647
52919
|
}
|
|
@@ -52650,7 +52922,7 @@ function codexWrite(p) {
|
|
|
52650
52922
|
const dir = dirname2(p);
|
|
52651
52923
|
if (!existsSync6(dir))
|
|
52652
52924
|
mkdirSync4(dir, { recursive: true });
|
|
52653
|
-
let existing = existsSync6(p) ?
|
|
52925
|
+
let existing = existsSync6(p) ? readFileSync16(p, "utf8") : "";
|
|
52654
52926
|
if (!existing.trimEnd().endsWith(`
|
|
52655
52927
|
`))
|
|
52656
52928
|
existing += `
|
|
@@ -52661,7 +52933,7 @@ function codexWrite(p) {
|
|
|
52661
52933
|
command = "${command}"
|
|
52662
52934
|
args = ${JSON.stringify(args)}
|
|
52663
52935
|
`;
|
|
52664
|
-
|
|
52936
|
+
writeFileSync5(p, existing);
|
|
52665
52937
|
};
|
|
52666
52938
|
}
|
|
52667
52939
|
function hermesWrite(p) {
|
|
@@ -52669,7 +52941,7 @@ function hermesWrite(p) {
|
|
|
52669
52941
|
const dir = dirname2(p);
|
|
52670
52942
|
if (!existsSync6(dir))
|
|
52671
52943
|
mkdirSync4(dir, { recursive: true });
|
|
52672
|
-
let existing = existsSync6(p) ?
|
|
52944
|
+
let existing = existsSync6(p) ? readFileSync16(p, "utf8") : "";
|
|
52673
52945
|
existing = existing.replace(/^ blitzstrike:\n(?: .*\n?)*/m, "");
|
|
52674
52946
|
if (!existing.trimEnd().endsWith(`
|
|
52675
52947
|
`))
|
|
@@ -52690,80 +52962,80 @@ mcp_servers:
|
|
|
52690
52962
|
args: ${JSON.stringify(args)}
|
|
52691
52963
|
`;
|
|
52692
52964
|
}
|
|
52693
|
-
|
|
52965
|
+
writeFileSync5(p, existing);
|
|
52694
52966
|
};
|
|
52695
52967
|
}
|
|
52696
52968
|
function detectAgents() {
|
|
52697
52969
|
const home = homedir5();
|
|
52698
52970
|
const agents = [];
|
|
52699
|
-
const claudeJson =
|
|
52971
|
+
const claudeJson = join12(home, ".claude.json");
|
|
52700
52972
|
agents.push({
|
|
52701
52973
|
name: "Claude Code",
|
|
52702
52974
|
detectPath: claudeJson,
|
|
52703
52975
|
installed: () => existsSync6(claudeJson) || which("claude"),
|
|
52704
52976
|
write: jsonMcpServersWrite(claudeJson)
|
|
52705
52977
|
});
|
|
52706
|
-
const claudeDesktop =
|
|
52978
|
+
const claudeDesktop = join12(home, ".config", "Claude", "claude_desktop_config.json");
|
|
52707
52979
|
agents.push({
|
|
52708
52980
|
name: "Claude Desktop",
|
|
52709
52981
|
detectPath: claudeDesktop,
|
|
52710
52982
|
installed: () => existsSync6(claudeDesktop),
|
|
52711
52983
|
write: jsonMcpServersWrite(claudeDesktop)
|
|
52712
52984
|
});
|
|
52713
|
-
const cursor =
|
|
52985
|
+
const cursor = join12(home, ".cursor", "mcp.json");
|
|
52714
52986
|
agents.push({
|
|
52715
52987
|
name: "Cursor",
|
|
52716
52988
|
detectPath: cursor,
|
|
52717
|
-
installed: () => existsSync6(
|
|
52989
|
+
installed: () => existsSync6(join12(home, ".cursor")) || which("cursor"),
|
|
52718
52990
|
write: jsonMcpServersWrite(cursor)
|
|
52719
52991
|
});
|
|
52720
|
-
const opencode =
|
|
52992
|
+
const opencode = join12(home, ".config", "opencode", "opencode.jsonc");
|
|
52721
52993
|
agents.push({
|
|
52722
52994
|
name: "OpenCode",
|
|
52723
52995
|
detectPath: opencode,
|
|
52724
|
-
installed: () => existsSync6(
|
|
52996
|
+
installed: () => existsSync6(join12(home, ".config", "opencode")) || which("opencode"),
|
|
52725
52997
|
write: opencodeWrite(opencode)
|
|
52726
52998
|
});
|
|
52727
|
-
const codex =
|
|
52999
|
+
const codex = join12(home, ".codex", "config.toml");
|
|
52728
53000
|
agents.push({
|
|
52729
53001
|
name: "Codex",
|
|
52730
53002
|
detectPath: codex,
|
|
52731
|
-
installed: () => existsSync6(
|
|
53003
|
+
installed: () => existsSync6(join12(home, ".codex")) || which("codex"),
|
|
52732
53004
|
write: codexWrite(codex)
|
|
52733
53005
|
});
|
|
52734
|
-
const hermes =
|
|
53006
|
+
const hermes = join12(home, ".hermes", "config.yaml");
|
|
52735
53007
|
agents.push({
|
|
52736
53008
|
name: "Hermes",
|
|
52737
53009
|
detectPath: hermes,
|
|
52738
|
-
installed: () => existsSync6(
|
|
53010
|
+
installed: () => existsSync6(join12(home, ".hermes")) || which("hermes"),
|
|
52739
53011
|
write: hermesWrite(hermes)
|
|
52740
53012
|
});
|
|
52741
|
-
const gemini =
|
|
53013
|
+
const gemini = join12(home, ".gemini", "settings.json");
|
|
52742
53014
|
agents.push({
|
|
52743
53015
|
name: "Gemini CLI",
|
|
52744
53016
|
detectPath: gemini,
|
|
52745
|
-
installed: () => existsSync6(
|
|
53017
|
+
installed: () => existsSync6(join12(home, ".gemini")) || which("gemini"),
|
|
52746
53018
|
write: jsonMcpServersWrite(gemini)
|
|
52747
53019
|
});
|
|
52748
|
-
const windsurf =
|
|
53020
|
+
const windsurf = join12(home, ".codeium", "windsurf", "mcp_config.json");
|
|
52749
53021
|
agents.push({
|
|
52750
53022
|
name: "Windsurf",
|
|
52751
53023
|
detectPath: windsurf,
|
|
52752
|
-
installed: () => existsSync6(
|
|
53024
|
+
installed: () => existsSync6(join12(home, ".codeium")) || which("windsurf"),
|
|
52753
53025
|
write: jsonMcpServersWrite(windsurf)
|
|
52754
53026
|
});
|
|
52755
|
-
const copilot =
|
|
53027
|
+
const copilot = join12(home, ".copilot", "mcp.json");
|
|
52756
53028
|
agents.push({
|
|
52757
53029
|
name: "Copilot",
|
|
52758
53030
|
detectPath: copilot,
|
|
52759
|
-
installed: () => existsSync6(
|
|
53031
|
+
installed: () => existsSync6(join12(home, ".copilot")),
|
|
52760
53032
|
write: jsonMcpServersWrite(copilot)
|
|
52761
53033
|
});
|
|
52762
|
-
const cline =
|
|
53034
|
+
const cline = join12(home, ".cline", "mcp_settings.json");
|
|
52763
53035
|
agents.push({
|
|
52764
53036
|
name: "Cline",
|
|
52765
53037
|
detectPath: cline,
|
|
52766
|
-
installed: () => existsSync6(
|
|
53038
|
+
installed: () => existsSync6(join12(home, ".cline")),
|
|
52767
53039
|
write: jsonMcpServersWrite(cline)
|
|
52768
53040
|
});
|
|
52769
53041
|
return agents;
|
|
@@ -52807,15 +53079,15 @@ Registered with ${ok}/${installed.length} agent(s).`);
|
|
|
52807
53079
|
}
|
|
52808
53080
|
|
|
52809
53081
|
// src/index.ts
|
|
52810
|
-
import { readFileSync as
|
|
52811
|
-
import { join as
|
|
53082
|
+
import { readFileSync as readFileSync17, existsSync as existsSync7 } from "node:fs";
|
|
53083
|
+
import { join as join13 } from "node:path";
|
|
52812
53084
|
import { fileURLToPath as fileURLToPath9 } from "node:url";
|
|
52813
|
-
var ROOT7 =
|
|
53085
|
+
var ROOT7 = join13(fileURLToPath9(new URL(".", import.meta.url)), "..");
|
|
52814
53086
|
function readVersion() {
|
|
52815
53087
|
try {
|
|
52816
|
-
const p =
|
|
53088
|
+
const p = join13(ROOT7, "package.json");
|
|
52817
53089
|
if (existsSync7(p))
|
|
52818
|
-
return JSON.parse(
|
|
53090
|
+
return JSON.parse(readFileSync17(p, "utf8")).version ?? "1.0.0";
|
|
52819
53091
|
} catch {}
|
|
52820
53092
|
return "1.0.0";
|
|
52821
53093
|
}
|