blitzstrike 1.0.12 → 1.0.14

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.
Files changed (2) hide show
  1. package/dist/index.js +463 -262
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -6930,8 +6930,269 @@ var require_dist = __commonJS(function(exports, module) {
6930
6930
  exports.default = formatsPlugin;
6931
6931
  });
6932
6932
 
6933
+ // src/scanner.ts
6934
+ import { readFileSync, readdirSync, statSync } from "node:fs";
6935
+ import { join } from "node:path";
6936
+ function escapeRegExp(s) {
6937
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6938
+ }
6939
+ function lineno(text, pos) {
6940
+ let n = 1;
6941
+ for (let i = 0;i < pos; i++) {
6942
+ if (text.charCodeAt(i) === 10)
6943
+ n++;
6944
+ }
6945
+ return n;
6946
+ }
6947
+ function iterSourceFiles(root, maxFiles = 5000) {
6948
+ const out = [];
6949
+ const walk = (dir) => {
6950
+ if (out.length >= maxFiles)
6951
+ return;
6952
+ let entries;
6953
+ try {
6954
+ entries = readdirSync(dir);
6955
+ } catch {
6956
+ return;
6957
+ }
6958
+ for (const name of entries) {
6959
+ if (out.length >= maxFiles)
6960
+ return;
6961
+ const full = join(dir, name);
6962
+ let st;
6963
+ try {
6964
+ st = statSync(full);
6965
+ } catch {
6966
+ continue;
6967
+ }
6968
+ if (st.isDirectory()) {
6969
+ if (SKIP_PARTS.has(name) || LIB_PARTS.has(name))
6970
+ continue;
6971
+ walk(full);
6972
+ } else if (st.isFile()) {
6973
+ const dot = name.lastIndexOf(".");
6974
+ const ext = dot >= 0 ? name.slice(dot).toLowerCase() : "";
6975
+ if (!TARGET_EXTS.has(ext))
6976
+ continue;
6977
+ out.push(full);
6978
+ }
6979
+ }
6980
+ };
6981
+ walk(root);
6982
+ return out;
6983
+ }
6984
+ function findFunctionBounds(text, funcStart) {
6985
+ const brace = text.indexOf("{", funcStart);
6986
+ if (brace === -1)
6987
+ return null;
6988
+ let depth = 0;
6989
+ for (let i = brace;i < text.length; i++) {
6990
+ const c = text[i];
6991
+ if (c === "{")
6992
+ depth++;
6993
+ else if (c === "}") {
6994
+ depth--;
6995
+ if (depth === 0)
6996
+ return [funcStart, i + 1];
6997
+ }
6998
+ }
6999
+ return null;
7000
+ }
7001
+ function scanFile(path) {
7002
+ let text;
7003
+ try {
7004
+ text = readFileSync(path, "utf8");
7005
+ } catch {
7006
+ return { file: path, endpoints: [], sinks: [], auth_gates_present: [], error: "unreadable" };
7007
+ }
7008
+ const endpoints = [];
7009
+ for (const hook of NOPRIV_HOOKS) {
7010
+ const re = new RegExp(escapeRegExp(hook), "g");
7011
+ let m;
7012
+ while ((m = re.exec(text)) !== null) {
7013
+ endpoints.push({ hook, line: lineno(text, m.index) });
7014
+ }
7015
+ }
7016
+ const sinks = [];
7017
+ for (const [sink, bugclass] of Object.entries(SINKS)) {
7018
+ const re = new RegExp(escapeRegExp(sink), "g");
7019
+ let m;
7020
+ while ((m = re.exec(text)) !== null) {
7021
+ sinks.push({ sink, class: bugclass, line: lineno(text, m.index) });
7022
+ }
7023
+ }
7024
+ const gates = AUTH_GATES.filter((g) => text.includes(g));
7025
+ const lines = text.split(`
7026
+ `).length;
7027
+ return { file: path, lines, endpoints, sinks, auth_gates_present: gates };
7028
+ }
7029
+ function traceFunction(path, symbol) {
7030
+ let text;
7031
+ try {
7032
+ text = readFileSync(path, "utf8");
7033
+ } catch {
7034
+ return { file: path, symbol, definitions: [], definition_count: 0, error: "unreadable" };
7035
+ }
7036
+ const pattern = new RegExp(`function\\s+&?${escapeRegExp(symbol)}\\s*\\(`, "g");
7037
+ const results = [];
7038
+ let m;
7039
+ while ((m = pattern.exec(text)) !== null) {
7040
+ const bounds = findFunctionBounds(text, m.index);
7041
+ if (!bounds)
7042
+ continue;
7043
+ const [start, end] = bounds;
7044
+ const body = text.slice(start, end);
7045
+ const bodyStartLine = lineno(text, start);
7046
+ const bodyEndLine = lineno(text, end);
7047
+ const innerSinks = [];
7048
+ for (const [sink, bugclass] of Object.entries(SINKS)) {
7049
+ const re = new RegExp(escapeRegExp(sink), "g");
7050
+ let sm;
7051
+ while ((sm = re.exec(body)) !== null) {
7052
+ innerSinks.push({
7053
+ sink,
7054
+ class: bugclass,
7055
+ line: bodyStartLine + body.slice(0, sm.index).split(`
7056
+ `).length - 1
7057
+ });
7058
+ }
7059
+ }
7060
+ const innerGates = AUTH_GATES.filter((g) => body.includes(g));
7061
+ results.push({
7062
+ line: bodyStartLine,
7063
+ line_end: bodyEndLine,
7064
+ length: end - start,
7065
+ sinks_in_scope: innerSinks,
7066
+ auth_gates_in_scope: innerGates,
7067
+ body_preview: body.slice(0, 4000)
7068
+ });
7069
+ }
7070
+ return {
7071
+ file: path,
7072
+ symbol,
7073
+ definitions: results,
7074
+ definition_count: results.length
7075
+ };
7076
+ }
7077
+ function grepInFunctions(root, sink, maxHits = 50) {
7078
+ const hits = [];
7079
+ for (const p of iterSourceFiles(root)) {
7080
+ if (hits.length >= maxHits)
7081
+ break;
7082
+ let text;
7083
+ try {
7084
+ text = readFileSync(p, "utf8");
7085
+ } catch {
7086
+ continue;
7087
+ }
7088
+ const re = new RegExp(escapeRegExp(sink), "g");
7089
+ let m;
7090
+ while ((m = re.exec(text)) !== null) {
7091
+ const line = lineno(text, m.index);
7092
+ const head = text.slice(0, m.index);
7093
+ const funcRe = /function\s+&?\w+\s*\(/g;
7094
+ const funcs = [];
7095
+ let fm;
7096
+ while ((fm = funcRe.exec(head)) !== null)
7097
+ funcs.push(fm);
7098
+ if (funcs.length === 0)
7099
+ continue;
7100
+ const fstart = funcs[funcs.length - 1].index;
7101
+ const bounds = findFunctionBounds(text, fstart);
7102
+ if (!bounds || !(bounds[0] <= m.index && m.index < bounds[1]))
7103
+ continue;
7104
+ const body = text.slice(bounds[0], bounds[1]);
7105
+ const gates = AUTH_GATES.filter((g) => body.includes(g));
7106
+ hits.push({
7107
+ file: p,
7108
+ line,
7109
+ sink,
7110
+ auth_gates_in_scope: gates,
7111
+ guarded: gates.length > 0
7112
+ });
7113
+ if (hits.length >= maxHits)
7114
+ break;
7115
+ }
7116
+ }
7117
+ return hits;
7118
+ }
7119
+ var NOPRIV_HOOKS, SINKS, AUTH_GATES, TARGET_EXTS, SKIP_PARTS, LIB_PARTS;
7120
+ var init_scanner = __esm(() => {
7121
+ NOPRIV_HOOKS = [
7122
+ "wp_ajax_nopriv_",
7123
+ "admin_post_nopriv_",
7124
+ "register_rest_route",
7125
+ "wp_ajax_"
7126
+ ];
7127
+ SINKS = {
7128
+ "eval(": "RCE (code execution)",
7129
+ "assert(": "RCE (code execution, PHP <8)",
7130
+ "system(": "RCE (command execution)",
7131
+ "exec(": "RCE (command execution)",
7132
+ "shell_exec(": "RCE (command execution)",
7133
+ "passthru(": "RCE (command execution)",
7134
+ "proc_open(": "RCE (command execution)",
7135
+ "popen(": "RCE (command execution)",
7136
+ "move_uploaded_file(": "File upload (-> RCE if .php lands in webroot)",
7137
+ "file_put_contents(": "Arbitrary file write (-> RCE via CF-003)",
7138
+ "fwrite(": "Arbitrary file write",
7139
+ "unserialize(": "PHP object injection (POP gadget chain)",
7140
+ "maybe_unserialize(": "PHP object injection (weak)",
7141
+ "include(": "Local file inclusion",
7142
+ "require(": "Local file inclusion",
7143
+ "include_once(": "Local file inclusion",
7144
+ "require_once(": "Local file inclusion",
7145
+ "$wpdb->query(": "SQL injection (unprepared query)",
7146
+ "$wpdb->get_var(": "SQL injection (unprepared query)",
7147
+ "$wpdb->get_results(": "SQL injection (unprepared query)",
7148
+ "->query(": "SQL injection (query builder)",
7149
+ "->whereRaw(": "SQL injection (raw where)",
7150
+ "wp_remote_get(": "SSRF (unvalidated URL fetch)",
7151
+ "wp_remote_post(": "SSRF (unvalidated URL fetch)",
7152
+ "file_get_contents(": "SSRF / file read",
7153
+ "extract(": "Variable injection (-> LFI/RCE without EXTR_SKIP)",
7154
+ "call_user_func(": "Dynamic dispatch (attacker-controlled callback)",
7155
+ "call_user_func_array(": "Dynamic dispatch",
7156
+ "create_function(": "RCE (deprecated eval wrapper)",
7157
+ "preg_replace(": "RCE (if /e modifier or code in pattern)",
7158
+ "echo ": "XSS (reflected output)",
7159
+ "print ": "XSS (reflected output)",
7160
+ "printf(": "XSS (reflected output)",
7161
+ "header(": "Open redirect / header injection",
7162
+ "wp_redirect(": "Open redirect"
7163
+ };
7164
+ AUTH_GATES = [
7165
+ "check_ajax_referer",
7166
+ "check_admin_referer",
7167
+ "wp_verify_nonce",
7168
+ "current_user_can",
7169
+ "is_user_logged_in",
7170
+ "JSession::checkToken",
7171
+ "->authorise(",
7172
+ "->authorize(",
7173
+ "permission_callback"
7174
+ ];
7175
+ TARGET_EXTS = new Set([
7176
+ ".php",
7177
+ ".phtml",
7178
+ ".php5",
7179
+ ".php7",
7180
+ ".inc",
7181
+ ".module",
7182
+ ".install"
7183
+ ]);
7184
+ SKIP_PARTS = new Set(["vendor", "node_modules", ".git", "tests", "test"]);
7185
+ LIB_PARTS = new Set([
7186
+ "lib",
7187
+ "libraries",
7188
+ "third-party",
7189
+ "third_party",
7190
+ "libs"
7191
+ ]);
7192
+ });
7193
+
6933
7194
  // src/sync.ts
6934
- import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync2, readFileSync as readFileSync8, readdirSync as readdirSync4, statSync as statSync3, rmSync } from "node:fs";
7195
+ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync2, readFileSync as readFileSync9, readdirSync as readdirSync4, statSync as statSync3, rmSync } from "node:fs";
6935
7196
  import { join as join8 } from "node:path";
6936
7197
  import { homedir as homedir4 } from "node:os";
6937
7198
  import { fileURLToPath as fileURLToPath6 } from "node:url";
@@ -7027,7 +7288,7 @@ function manualCopy(src, dst) {
7027
7288
  if (e.isDirectory())
7028
7289
  manualCopy(s, d);
7029
7290
  else
7030
- writeFileSync2(d, readFileSync8(s));
7291
+ writeFileSync2(d, readFileSync9(s));
7031
7292
  }
7032
7293
  }
7033
7294
  var ROOT5, DATA_ROOT2, GITHUB_REPO = "https://github.com/shinthink/blitzstrike.git";
@@ -21669,258 +21930,8 @@ class StdioServerTransport {
21669
21930
  }
21670
21931
  }
21671
21932
 
21672
- // src/scanner.ts
21673
- import { readFileSync, readdirSync, statSync } from "node:fs";
21674
- import { join } from "node:path";
21675
- var NOPRIV_HOOKS = [
21676
- "wp_ajax_nopriv_",
21677
- "admin_post_nopriv_",
21678
- "register_rest_route",
21679
- "wp_ajax_"
21680
- ];
21681
- var SINKS = {
21682
- "eval(": "RCE (code execution)",
21683
- "assert(": "RCE (code execution, PHP <8)",
21684
- "system(": "RCE (command execution)",
21685
- "exec(": "RCE (command execution)",
21686
- "shell_exec(": "RCE (command execution)",
21687
- "passthru(": "RCE (command execution)",
21688
- "proc_open(": "RCE (command execution)",
21689
- "popen(": "RCE (command execution)",
21690
- "move_uploaded_file(": "File upload (-> RCE if .php lands in webroot)",
21691
- "file_put_contents(": "Arbitrary file write (-> RCE via CF-003)",
21692
- "fwrite(": "Arbitrary file write",
21693
- "unserialize(": "PHP object injection (POP gadget chain)",
21694
- "maybe_unserialize(": "PHP object injection (weak)",
21695
- "include(": "Local file inclusion",
21696
- "require(": "Local file inclusion",
21697
- "include_once(": "Local file inclusion",
21698
- "require_once(": "Local file inclusion",
21699
- "$wpdb->query(": "SQL injection (unprepared query)",
21700
- "$wpdb->get_var(": "SQL injection (unprepared query)",
21701
- "$wpdb->get_results(": "SQL injection (unprepared query)",
21702
- "->query(": "SQL injection (query builder)",
21703
- "->whereRaw(": "SQL injection (raw where)",
21704
- "wp_remote_get(": "SSRF (unvalidated URL fetch)",
21705
- "wp_remote_post(": "SSRF (unvalidated URL fetch)",
21706
- "file_get_contents(": "SSRF / file read",
21707
- "extract(": "Variable injection (-> LFI/RCE without EXTR_SKIP)",
21708
- "call_user_func(": "Dynamic dispatch (attacker-controlled callback)",
21709
- "call_user_func_array(": "Dynamic dispatch",
21710
- "create_function(": "RCE (deprecated eval wrapper)",
21711
- "preg_replace(": "RCE (if /e modifier or code in pattern)"
21712
- };
21713
- var AUTH_GATES = [
21714
- "check_ajax_referer",
21715
- "check_admin_referer",
21716
- "wp_verify_nonce",
21717
- "current_user_can",
21718
- "is_user_logged_in",
21719
- "JSession::checkToken",
21720
- "->authorise(",
21721
- "->authorize(",
21722
- "permission_callback"
21723
- ];
21724
- var TARGET_EXTS = new Set([
21725
- ".php",
21726
- ".phtml",
21727
- ".php5",
21728
- ".php7",
21729
- ".inc",
21730
- ".module",
21731
- ".install"
21732
- ]);
21733
- var SKIP_PARTS = new Set(["vendor", "node_modules", ".git", "tests", "test"]);
21734
- var LIB_PARTS = new Set([
21735
- "lib",
21736
- "libraries",
21737
- "third-party",
21738
- "third_party",
21739
- "libs"
21740
- ]);
21741
- function escapeRegExp(s) {
21742
- return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
21743
- }
21744
- function lineno(text, pos) {
21745
- let n = 1;
21746
- for (let i = 0;i < pos; i++) {
21747
- if (text.charCodeAt(i) === 10)
21748
- n++;
21749
- }
21750
- return n;
21751
- }
21752
- function iterSourceFiles(root, maxFiles = 5000) {
21753
- const out = [];
21754
- const walk = (dir) => {
21755
- if (out.length >= maxFiles)
21756
- return;
21757
- let entries;
21758
- try {
21759
- entries = readdirSync(dir);
21760
- } catch {
21761
- return;
21762
- }
21763
- for (const name of entries) {
21764
- if (out.length >= maxFiles)
21765
- return;
21766
- const full = join(dir, name);
21767
- let st;
21768
- try {
21769
- st = statSync(full);
21770
- } catch {
21771
- continue;
21772
- }
21773
- if (st.isDirectory()) {
21774
- if (SKIP_PARTS.has(name) || LIB_PARTS.has(name))
21775
- continue;
21776
- walk(full);
21777
- } else if (st.isFile()) {
21778
- const dot = name.lastIndexOf(".");
21779
- const ext = dot >= 0 ? name.slice(dot).toLowerCase() : "";
21780
- if (!TARGET_EXTS.has(ext))
21781
- continue;
21782
- out.push(full);
21783
- }
21784
- }
21785
- };
21786
- walk(root);
21787
- return out;
21788
- }
21789
- function findFunctionBounds(text, funcStart) {
21790
- const brace = text.indexOf("{", funcStart);
21791
- if (brace === -1)
21792
- return null;
21793
- let depth = 0;
21794
- for (let i = brace;i < text.length; i++) {
21795
- const c = text[i];
21796
- if (c === "{")
21797
- depth++;
21798
- else if (c === "}") {
21799
- depth--;
21800
- if (depth === 0)
21801
- return [funcStart, i + 1];
21802
- }
21803
- }
21804
- return null;
21805
- }
21806
- function scanFile(path) {
21807
- let text;
21808
- try {
21809
- text = readFileSync(path, "utf8");
21810
- } catch {
21811
- return { file: path, endpoints: [], sinks: [], auth_gates_present: [], error: "unreadable" };
21812
- }
21813
- const endpoints = [];
21814
- for (const hook of NOPRIV_HOOKS) {
21815
- const re = new RegExp(escapeRegExp(hook), "g");
21816
- let m;
21817
- while ((m = re.exec(text)) !== null) {
21818
- endpoints.push({ hook, line: lineno(text, m.index) });
21819
- }
21820
- }
21821
- const sinks = [];
21822
- for (const [sink, bugclass] of Object.entries(SINKS)) {
21823
- const re = new RegExp(escapeRegExp(sink), "g");
21824
- let m;
21825
- while ((m = re.exec(text)) !== null) {
21826
- sinks.push({ sink, class: bugclass, line: lineno(text, m.index) });
21827
- }
21828
- }
21829
- const gates = AUTH_GATES.filter((g) => text.includes(g));
21830
- const lines = text.split(`
21831
- `).length;
21832
- return { file: path, lines, endpoints, sinks, auth_gates_present: gates };
21833
- }
21834
- function traceFunction(path, symbol) {
21835
- let text;
21836
- try {
21837
- text = readFileSync(path, "utf8");
21838
- } catch {
21839
- return { file: path, symbol, definitions: [], definition_count: 0, error: "unreadable" };
21840
- }
21841
- const pattern = new RegExp(`function\\s+&?${escapeRegExp(symbol)}\\s*\\(`, "g");
21842
- const results = [];
21843
- let m;
21844
- while ((m = pattern.exec(text)) !== null) {
21845
- const bounds = findFunctionBounds(text, m.index);
21846
- if (!bounds)
21847
- continue;
21848
- const [start, end] = bounds;
21849
- const body = text.slice(start, end);
21850
- const bodyStartLine = lineno(text, start);
21851
- const bodyEndLine = lineno(text, end);
21852
- const innerSinks = [];
21853
- for (const [sink, bugclass] of Object.entries(SINKS)) {
21854
- const re = new RegExp(escapeRegExp(sink), "g");
21855
- let sm;
21856
- while ((sm = re.exec(body)) !== null) {
21857
- innerSinks.push({
21858
- sink,
21859
- class: bugclass,
21860
- line: bodyStartLine + body.slice(0, sm.index).split(`
21861
- `).length - 1
21862
- });
21863
- }
21864
- }
21865
- const innerGates = AUTH_GATES.filter((g) => body.includes(g));
21866
- results.push({
21867
- line: bodyStartLine,
21868
- line_end: bodyEndLine,
21869
- length: end - start,
21870
- sinks_in_scope: innerSinks,
21871
- auth_gates_in_scope: innerGates,
21872
- body_preview: body.slice(0, 4000)
21873
- });
21874
- }
21875
- return {
21876
- file: path,
21877
- symbol,
21878
- definitions: results,
21879
- definition_count: results.length
21880
- };
21881
- }
21882
- function grepInFunctions(root, sink, maxHits = 50) {
21883
- const hits = [];
21884
- for (const p of iterSourceFiles(root)) {
21885
- if (hits.length >= maxHits)
21886
- break;
21887
- let text;
21888
- try {
21889
- text = readFileSync(p, "utf8");
21890
- } catch {
21891
- continue;
21892
- }
21893
- const re = new RegExp(escapeRegExp(sink), "g");
21894
- let m;
21895
- while ((m = re.exec(text)) !== null) {
21896
- const line = lineno(text, m.index);
21897
- const head = text.slice(0, m.index);
21898
- const funcRe = /function\s+&?\w+\s*\(/g;
21899
- const funcs = [];
21900
- let fm;
21901
- while ((fm = funcRe.exec(head)) !== null)
21902
- funcs.push(fm);
21903
- if (funcs.length === 0)
21904
- continue;
21905
- const fstart = funcs[funcs.length - 1].index;
21906
- const bounds = findFunctionBounds(text, fstart);
21907
- if (!bounds || !(bounds[0] <= m.index && m.index < bounds[1]))
21908
- continue;
21909
- const body = text.slice(bounds[0], bounds[1]);
21910
- const gates = AUTH_GATES.filter((g) => body.includes(g));
21911
- hits.push({
21912
- file: p,
21913
- line,
21914
- sink,
21915
- auth_gates_in_scope: gates,
21916
- guarded: gates.length > 0
21917
- });
21918
- if (hits.length >= maxHits)
21919
- break;
21920
- }
21921
- }
21922
- return hits;
21923
- }
21933
+ // src/server.ts
21934
+ init_scanner();
21924
21935
 
21925
21936
  // src/recon.ts
21926
21937
  var TIMEOUT_MS = 25000;
@@ -22002,6 +22013,7 @@ async function nvdLookup(cveId) {
22002
22013
  }
22003
22014
 
22004
22015
  // src/orchestrator.ts
22016
+ init_scanner();
22005
22017
  import { readFileSync as readFileSync4, existsSync as existsSync3, statSync as statSync2 } from "node:fs";
22006
22018
  import { join as join4 } from "node:path";
22007
22019
  import { fileURLToPath as fileURLToPath2 } from "node:url";
@@ -22294,7 +22306,7 @@ var LIFECYCLE_TRANSITIONS = {
22294
22306
  detected: ["triaged", "rejected"],
22295
22307
  triaged: ["hypothesis", "rejected", "out_of_scope"],
22296
22308
  hypothesis: ["validating", "false_positive", "rejected", "blocked"],
22297
- validating: ["confirmed", "likely", "unconfirmed", "false_positive", "blocked", "out_of_scope"],
22309
+ validating: ["confirmed", "false_positive", "blocked", "out_of_scope"],
22298
22310
  confirmed: [],
22299
22311
  false_positive: [],
22300
22312
  rejected: [],
@@ -23146,6 +23158,166 @@ async function activeScan(target, scope = "", mode = "bug-bounty", allowed = und
23146
23158
  return result;
23147
23159
  }
23148
23160
 
23161
+ // src/dataflow.ts
23162
+ init_scanner();
23163
+ import { readFileSync as readFileSync7 } from "node:fs";
23164
+ var SOURCES = {
23165
+ $_GET: { id: "http_get", label: "HTTP GET parameter", attacker_controlled: true },
23166
+ $_POST: { id: "http_post", label: "HTTP POST body", attacker_controlled: true },
23167
+ $_REQUEST: { id: "http_request", label: "HTTP request (merged)", attacker_controlled: true },
23168
+ $_COOKIE: { id: "http_cookie", label: "HTTP cookie", attacker_controlled: true },
23169
+ $_FILES: { id: "uploaded_file", label: "Uploaded file", attacker_controlled: true },
23170
+ $_SERVER: { id: "http_header", label: "HTTP header / server env", attacker_controlled: true },
23171
+ "file_get_contents('php://input')": { id: "raw_body", label: "Raw request body", attacker_controlled: true },
23172
+ "php://input": { id: "raw_body", label: "Raw request body", attacker_controlled: true },
23173
+ json_decode: { id: "json_input", label: "JSON input", attacker_controlled: true },
23174
+ getallheaders: { id: "http_header", label: "HTTP header", attacker_controlled: true },
23175
+ $argv: { id: "cli_argument", label: "CLI argument", attacker_controlled: false },
23176
+ getenv: { id: "environment_variable", label: "Environment variable", attacker_controlled: false },
23177
+ "$wpdb->get_results": { id: "database_value", label: "Database value", attacker_controlled: false },
23178
+ wp_remote_get: { id: "external_api", label: "External API data", attacker_controlled: false }
23179
+ };
23180
+ function classifySource(line) {
23181
+ for (const [token, cls] of Object.entries(SOURCES)) {
23182
+ if (line.includes(token))
23183
+ return cls;
23184
+ }
23185
+ return null;
23186
+ }
23187
+ var SINK_CLASS_MAP = [
23188
+ [/->query\(|\$wpdb->query|\$wpdb->get_var|\$wpdb->get_results|->whereRaw|->selectRaw/, { id: "sql_execution", category: "SQL execution", cwe: "CWE-89" }],
23189
+ [/eval\(|assert\(|create_function\(|call_user_func\(|preg_replace\(/, { id: "code_execution", category: "Dynamic evaluation / code execution", cwe: "CWE-94" }],
23190
+ [/system\(|exec\(|shell_exec\(|passthru\(|proc_open\(|popen\(/, { id: "command_execution", category: "Command execution", cwe: "CWE-78" }],
23191
+ [/move_uploaded_file\(|file_put_contents\(|fwrite\(|fopen\(|unlink\(/, { id: "file_operations", category: "File operations", cwe: "CWE-434" }],
23192
+ [/include\(|require\(|include_once\(|require_once\(/, { id: "file_inclusion", category: "File inclusion", cwe: "CWE-98" }],
23193
+ [/unserialize\(|maybe_unserialize\(/, { id: "deserialization", category: "Deserialization", cwe: "CWE-502" }],
23194
+ [/wp_remote_get\(|wp_remote_post\(|file_get_contents\(|curl_exec\(/, { id: "http_request", category: "HTTP request (SSRF)", cwe: "CWE-918" }],
23195
+ [/header\(|wp_redirect\(|wp_safe_redirect\(/, { id: "redirect", category: "Redirect handling", cwe: "CWE-601" }],
23196
+ [/echo\s|print\s|printf\(/, { id: "html_render", category: "HTML rendering (XSS)", cwe: "CWE-79" }],
23197
+ [/simplexml_load_string\(|new SimpleXMLElement|DOMDocument/, { id: "xml_processing", category: "XML processing (XXE)", cwe: "CWE-611" }],
23198
+ [/ZipArchive|PharData|->extractTo\(/, { id: "archive_extraction", category: "Archive extraction (zip slip)", cwe: "CWE-22" }]
23199
+ ];
23200
+ function classifySink2(sink) {
23201
+ for (const [re, cls] of SINK_CLASS_MAP) {
23202
+ if (re.test(sink))
23203
+ return cls;
23204
+ }
23205
+ return null;
23206
+ }
23207
+ var SANITIZERS = {
23208
+ htmlspecialchars: { id: "html_escape", label: "HTML entity encoding", neutralizes: ["html_render"] },
23209
+ esc_html: { id: "html_escape", label: "HTML escape", neutralizes: ["html_render"] },
23210
+ esc_attr: { id: "html_escape", label: "HTML attribute escape", neutralizes: ["html_render"] },
23211
+ htmlentities: { id: "html_escape", label: "HTML entity encoding", neutralizes: ["html_render"] },
23212
+ esc_sql: { id: "sql_escape", label: "SQL escape", neutralizes: ["sql_execution"] },
23213
+ "$wpdb->prepare": { id: "sql_prepare", label: "Prepared SQL statement", neutralizes: ["sql_execution"] },
23214
+ "->prepare(": { id: "sql_prepare", label: "Prepared statement", neutralizes: ["sql_execution"] },
23215
+ filter_var: { id: "filter_var", label: "Filter input", neutralizes: ["html_render", "sql_execution", "command_execution"] },
23216
+ filter_input: { id: "filter_var", label: "Filter input", neutralizes: ["html_render", "sql_execution", "command_execution"] },
23217
+ sanitize_text_field: { id: "wp_sanitize", label: "WordPress sanitize", neutralizes: ["html_render"] },
23218
+ sanitize_file_name: { id: "wp_sanitize_file", label: "File-name sanitize", neutralizes: ["file_operations", "file_inclusion"] },
23219
+ wp_verify_nonce: { id: "nonce_check", label: "Nonce verification", neutralizes: ["authorization"] },
23220
+ check_ajax_referer: { id: "nonce_check", label: "Nonce verification", neutralizes: ["authorization"] },
23221
+ escapeshellarg: { id: "shell_escape", label: "Shell argument escape", neutralizes: ["command_execution"] },
23222
+ escapeshellcmd: { id: "shell_escape", label: "Shell command escape", neutralizes: ["command_execution"] },
23223
+ intval: { id: "int_cast", label: "Integer cast", neutralizes: ["sql_execution", "command_execution", "file_inclusion"] },
23224
+ absint: { id: "int_cast", label: "Absolute integer cast", neutralizes: ["sql_execution", "command_execution", "file_inclusion"] },
23225
+ "preg_replace.*FILTER": { id: "regex_filter", label: "Regex filter", neutralizes: ["html_render"] }
23226
+ };
23227
+ function findSanitizers(line) {
23228
+ const out = [];
23229
+ for (const [token, san] of Object.entries(SANITIZERS)) {
23230
+ if (line.includes(token))
23231
+ out.push(san);
23232
+ }
23233
+ return out;
23234
+ }
23235
+ function isSanitized(sanitizers, sinkCategory) {
23236
+ return sanitizers.some((s) => s.neutralizes.includes(sinkCategory) || s.neutralizes.includes("authorization"));
23237
+ }
23238
+ function traceDataFlow(path) {
23239
+ const scan = scanFile(path);
23240
+ const result = { file: path, candidates: [], sanitized: [], authorized: [] };
23241
+ if (scan.error)
23242
+ return result;
23243
+ let text;
23244
+ try {
23245
+ text = readFileSync7(path, "utf8");
23246
+ } catch {
23247
+ return result;
23248
+ }
23249
+ const lines = text.split(`
23250
+ `);
23251
+ const seen = new Set;
23252
+ const sinks = scan.sinks.filter((s) => {
23253
+ const cls = classifySink2(s.sink);
23254
+ if (!cls)
23255
+ return false;
23256
+ const key = `${cls.id}|${s.line}`;
23257
+ if (seen.has(key))
23258
+ return false;
23259
+ seen.add(key);
23260
+ return true;
23261
+ });
23262
+ for (const s of sinks) {
23263
+ const sinkClass = classifySink2(s.sink);
23264
+ const wideStart = Math.max(0, s.line - 15);
23265
+ const wideWindow = lines.slice(wideStart, s.line).join(`
23266
+ `);
23267
+ const sanitizerWindow = lines.slice(Math.max(0, s.line - 3), s.line + 1).join(`
23268
+ `);
23269
+ const sinkLine = lines[s.line - 1] ?? "";
23270
+ const prevLine = lines[s.line - 2] ?? "";
23271
+ const authContext = sinkLine + `
23272
+ ` + prevLine;
23273
+ const source = classifySource(wideWindow);
23274
+ const sanitizers = findSanitizers(sanitizerWindow);
23275
+ const authGates = AUTH_GATES.filter((g) => authContext.includes(g));
23276
+ const edge = {
23277
+ source,
23278
+ sanitizers,
23279
+ auth_gates: authGates,
23280
+ sink: sinkClass,
23281
+ sink_line: s.line,
23282
+ sink_token: s.sink
23283
+ };
23284
+ if (authGates.length > 0) {
23285
+ result.authorized.push(edge);
23286
+ continue;
23287
+ }
23288
+ if (isSanitized(sanitizers, sinkClass.id)) {
23289
+ result.sanitized.push(edge);
23290
+ continue;
23291
+ }
23292
+ result.candidates.push(edge);
23293
+ }
23294
+ return result;
23295
+ }
23296
+ function groupVariants(results) {
23297
+ const groups = new Map;
23298
+ for (const r of results) {
23299
+ for (const c of r.candidates) {
23300
+ const sig = `${c.sink?.category ?? "?"}|${c.source?.id ?? "unknown"}`;
23301
+ const g = groups.get(sig);
23302
+ if (g) {
23303
+ g.occurrences += 1;
23304
+ if (!g.files.includes(r.file))
23305
+ g.files.push(r.file);
23306
+ } else {
23307
+ groups.set(sig, {
23308
+ signature: sig,
23309
+ root_cause: `unsanitized ${c.source?.label ?? "unknown"} reaching ${c.sink?.category ?? "sink"}`,
23310
+ sink_category: c.sink?.category ?? "?",
23311
+ cwe: c.sink?.cwe,
23312
+ occurrences: 1,
23313
+ files: [r.file]
23314
+ });
23315
+ }
23316
+ }
23317
+ }
23318
+ return [...groups.values()].sort((a, b) => b.occurrences - a.occurrences);
23319
+ }
23320
+
23149
23321
  // src/server.ts
23150
23322
  function createServer() {
23151
23323
  const server = new McpServer({
@@ -23665,6 +23837,35 @@ function createServer() {
23665
23837
  }, async ({ text }) => {
23666
23838
  return { content: [{ type: "text", text: JSON.stringify({ redacted: redactSecrets(text) }) }] };
23667
23839
  });
23840
+ server.registerTool("trace_data_flow", {
23841
+ title: "Trace source-to-sink data flow",
23842
+ description: "EAGLE-EYE: trace data flow in a file — classify sources, sinks, sanitizers, and authorization gates. Returns candidates (reachable+unsanitized), sanitized (not findings), and authorized (lower priority).",
23843
+ inputSchema: { path: string2().describe("Source file path") }
23844
+ }, async ({ path }) => {
23845
+ const r = traceDataFlow(path);
23846
+ return { content: [{ type: "text", text: JSON.stringify(r) }] };
23847
+ });
23848
+ server.registerTool("variant_analysis", {
23849
+ title: "Group findings by root cause",
23850
+ description: "EAGLE-EYE: variant analysis — group data-flow candidates by root cause (sink category + source), deduplicating identical patterns across files.",
23851
+ inputSchema: { path: string2().describe("Source directory path") }
23852
+ }, async ({ path }) => {
23853
+ await Promise.resolve().then(() => init_scanner());
23854
+ const files = iterSourceFiles(path, 5000);
23855
+ const results = files.map((f) => traceDataFlow(f));
23856
+ const groups = groupVariants(results);
23857
+ return {
23858
+ content: [{
23859
+ type: "text",
23860
+ text: JSON.stringify({
23861
+ files_analyzed: files.length,
23862
+ total_candidates: results.reduce((n, r) => n + r.candidates.length, 0),
23863
+ variant_groups: groups.length,
23864
+ groups
23865
+ })
23866
+ }]
23867
+ };
23868
+ });
23668
23869
  return server;
23669
23870
  }
23670
23871
  async function serve() {
@@ -23674,7 +23875,7 @@ async function serve() {
23674
23875
  }
23675
23876
 
23676
23877
  // src/cli.ts
23677
- import { readFileSync as readFileSync7, existsSync as existsSync6, writeFileSync, mkdirSync as mkdirSync2 } from "node:fs";
23878
+ import { readFileSync as readFileSync8, existsSync as existsSync6, writeFileSync, mkdirSync as mkdirSync2 } from "node:fs";
23678
23879
  import { join as join7, dirname } from "node:path";
23679
23880
  import { homedir as homedir3 } from "node:os";
23680
23881
  import { fileURLToPath as fileURLToPath5 } from "node:url";
@@ -23756,7 +23957,7 @@ function resolveCommand() {
23756
23957
  }
23757
23958
  function readJson(p) {
23758
23959
  try {
23759
- return JSON.parse(readFileSync7(p, "utf8"));
23960
+ return JSON.parse(readFileSync8(p, "utf8"));
23760
23961
  } catch {
23761
23962
  return null;
23762
23963
  }
@@ -23799,7 +24000,7 @@ function copyOpenCodeAgents() {
23799
24000
  const src = join7(srcDir, f);
23800
24001
  const dst = join7(dstDir, f);
23801
24002
  if (existsSync6(src)) {
23802
- writeFileSync(dst, readFileSync7(src, "utf8"));
24003
+ writeFileSync(dst, readFileSync8(src, "utf8"));
23803
24004
  }
23804
24005
  }
23805
24006
  }
@@ -23808,7 +24009,7 @@ function codexWrite(p) {
23808
24009
  const dir = dirname(p);
23809
24010
  if (!existsSync6(dir))
23810
24011
  mkdirSync2(dir, { recursive: true });
23811
- let existing = existsSync6(p) ? readFileSync7(p, "utf8") : "";
24012
+ let existing = existsSync6(p) ? readFileSync8(p, "utf8") : "";
23812
24013
  if (!existing.trimEnd().endsWith(`
23813
24014
  `))
23814
24015
  existing += `
@@ -23827,7 +24028,7 @@ function hermesWrite(p) {
23827
24028
  const dir = dirname(p);
23828
24029
  if (!existsSync6(dir))
23829
24030
  mkdirSync2(dir, { recursive: true });
23830
- let existing = existsSync6(p) ? readFileSync7(p, "utf8") : "";
24031
+ let existing = existsSync6(p) ? readFileSync8(p, "utf8") : "";
23831
24032
  existing = existing.replace(/^ blitzstrike:\n(?: .*\n?)*/m, "");
23832
24033
  if (!existing.trimEnd().endsWith(`
23833
24034
  `))
@@ -23965,7 +24166,7 @@ Registered with ${ok}/${installed.length} agent(s).`);
23965
24166
  }
23966
24167
 
23967
24168
  // src/index.ts
23968
- import { readFileSync as readFileSync9, existsSync as existsSync8 } from "node:fs";
24169
+ import { readFileSync as readFileSync10, existsSync as existsSync8 } from "node:fs";
23969
24170
  import { join as join9 } from "node:path";
23970
24171
  import { fileURLToPath as fileURLToPath7 } from "node:url";
23971
24172
  var ROOT6 = join9(fileURLToPath7(new URL(".", import.meta.url)), "..");
@@ -23973,7 +24174,7 @@ function readVersion() {
23973
24174
  try {
23974
24175
  const p = join9(ROOT6, "package.json");
23975
24176
  if (existsSync8(p))
23976
- return JSON.parse(readFileSync9(p, "utf8")).version ?? "1.0.0";
24177
+ return JSON.parse(readFileSync10(p, "utf8")).version ?? "1.0.0";
23977
24178
  } catch {}
23978
24179
  return "1.0.0";
23979
24180
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blitzstrike",
3
- "version": "1.0.12",
3
+ "version": "1.0.14",
4
4
  "description": "Blitz Strike — a universal MCP security-audit toolbelt. BLITZ sweeps the attack surface, EAGLE-EYE traces source-to-sink, STRIKE verifies live. 57 attack chains, 130-tool catalog, intelligence data layer. One server, every agent.",
5
5
  "type": "module",
6
6
  "bin": {