Merge remote-tracking branch 'origin/master' into tests/extensions-modules

This commit is contained in:
zinduolis
2026-05-25 14:16:26 +10:00
11 changed files with 1090 additions and 624 deletions
+82
View File
@@ -0,0 +1,82 @@
name: JavaScript Linting
on:
pull_request:
branches: [ master ]
permissions:
contents: read
pull-requests: read
jobs:
lint-changed-files:
name: Lint Changed JavaScript Files (Blocking)
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '24.x'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Get changed JavaScript files
id: changed-js-files
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
with:
files: '**/*.js'
files_ignore: |
node_modules/**
core/main/client/lib/**
**/*.min.js
extensions/admin_ui/media/javascript-min/**
separator: ' '
- name: Lint changed JavaScript files
if: steps.changed-js-files.outputs.any_changed == 'true'
run: |
echo "Linting ${{ steps.changed-js-files.outputs.all_changed_files_count }} changed JavaScript file(s)"
npx eslint ${{ steps.changed-js-files.outputs.all_changed_files }}
- name: No JavaScript files changed
if: steps.changed-js-files.outputs.any_changed == 'false'
run: |
echo "No JavaScript files were changed in this PR. Skipping ESLint."
lint-all-files:
name: Lint All JavaScript Files (Non-blocking Report)
runs-on: ubuntu-latest
continue-on-error: true
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '24.x'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Lint all JavaScript files (report-only)
run: |
echo "Running ESLint on entire codebase"
echo "This is a report-only job and will not block the PR."
npx eslint . || true
- name: Summary
if: always()
run: |
echo "Full codebase linting completed. Check the logs above for details."
echo "This job does not affect PR merge status."
+1
View File
@@ -0,0 +1 @@
min-release-age=3
+6 -6
View File
@@ -18,13 +18,13 @@ gem 'mime-types', '~> 3.7'
gem 'execjs', '~> 2.10'
gem 'ansi', '~> 1.6'
gem 'term-ansicolor', :require => 'term/ansicolor'
gem 'rubyzip', '~> 3.2'
gem 'rubyzip', '~> 3.3'
gem 'espeak-ruby', '~> 1.1.0' # Text-to-Voice
gem 'rake', '~> 13.3'
gem 'rake', '~> 13.4'
gem 'activerecord', '~> 8.1'
gem 'otr-activerecord', '~> 2.6.0'
gem 'sqlite3', '~> 2.9'
gem 'rubocop', '~> 1.86.0', require: false
gem 'rubocop', '~> 1.86.2', require: false
# Geolocation support
group :geoip do
@@ -37,7 +37,7 @@ gem 'erubis', '~> 2.7'
# Metasploit Integration extension
group :ext_msf do
gem 'msfrpc-client', '~> 1.1', '>= 1.1.2'
gem 'xmlrpc', '~> 0.3.3'
gem 'xmlrpc', '~> 0.3.4'
end
# Notifications extension
@@ -67,7 +67,7 @@ group :test do
gem 'rdoc', '~> 7.2'
gem 'browserstack-local', '~> 1.4'
gem 'irb', '~> 1.17'
gem 'irb', '~> 1.18'
gem 'pry-byebug', '~> 3.12'
gem 'rest-client', '~> 2.1.0'
@@ -80,7 +80,7 @@ group :test do
# Note: selenium-webdriver 3.x is incompatible with Firefox version 48 and prior
# gem 'selenium' # Requires old version of selenium which is no longer available
gem 'geckodriver-helper', '~> 0.24.0'
gem 'selenium-webdriver', '~> 4.41'
gem 'selenium-webdriver', '~> 4.44'
# Note: nokogiri is needed by capybara which may require one of the below commands
# sudo apt-get install libxslt-dev libxml2-dev
+48 -47
View File
@@ -1,13 +1,13 @@
GEM
remote: https://rubygems.org/
specs:
activemodel (8.1.2.1)
activesupport (= 8.1.2.1)
activerecord (8.1.2.1)
activemodel (= 8.1.2.1)
activesupport (= 8.1.2.1)
activemodel (8.1.3)
activesupport (= 8.1.3)
activerecord (8.1.3)
activemodel (= 8.1.3)
activesupport (= 8.1.3)
timeout (>= 0.4.0)
activesupport (8.1.2.1)
activesupport (8.1.3)
base64
bigdecimal
concurrent-ruby (~> 1.0, >= 1.3.1)
@@ -20,7 +20,7 @@ GEM
securerandom (>= 0.3)
tzinfo (~> 2.0, >= 2.0.5)
uri (>= 0.13.1)
addressable (2.8.9)
addressable (2.9.0)
public_suffix (>= 2.0.2, < 8.0)
ansi (1.6.0)
archive-zip (0.13.1)
@@ -34,7 +34,7 @@ GEM
async
io-endpoint
base64 (0.3.0)
bigdecimal (4.0.1)
bigdecimal (4.1.2)
browserstack-local (1.4.3)
byebug (13.0.0)
reline (>= 0.6.0)
@@ -54,7 +54,7 @@ GEM
fiber-annotation
fiber-local (~> 1.1)
json
curb (1.3.1)
curb (1.3.5)
daemons (1.4.1)
date (3.5.1)
diff-lcs (1.6.2)
@@ -64,7 +64,7 @@ GEM
em-websocket (0.5.3)
eventmachine (>= 0.12.9)
http_parser.rb (~> 0)
erb (6.0.1)
erb (6.0.4)
erubis (2.7.0)
espeak-ruby (1.1.0)
event_emitter (0.2.6)
@@ -85,12 +85,12 @@ GEM
io-console (0.8.2)
io-endpoint (0.15.2)
io-like (0.4.0)
irb (1.17.0)
irb (1.18.0)
pp (>= 0.6.0)
prism (>= 1.3.0)
rdoc (>= 4.0.0)
reline (>= 0.4.2)
json (2.19.2)
json (2.19.5)
language_server-protocol (3.17.0.5)
lint_roller (1.1.0)
logger (1.7.0)
@@ -102,7 +102,7 @@ GEM
mime-types-data (~> 3.2025, >= 3.2025.0507)
mime-types-data (3.2025.0902)
mini_mime (1.1.5)
minitest (6.0.2)
minitest (6.0.6)
drb (~> 2.0)
prism (~> 1.5)
mojo_magick (0.6.8)
@@ -117,27 +117,27 @@ GEM
net-protocol
netrc (0.11.0)
nio4r (2.7.4)
nokogiri (1.19.1-aarch64-linux-gnu)
nokogiri (1.19.3-aarch64-linux-gnu)
racc (~> 1.4)
nokogiri (1.19.1-aarch64-linux-musl)
nokogiri (1.19.3-aarch64-linux-musl)
racc (~> 1.4)
nokogiri (1.19.1-arm-linux-gnu)
nokogiri (1.19.3-arm-linux-gnu)
racc (~> 1.4)
nokogiri (1.19.1-arm-linux-musl)
nokogiri (1.19.3-arm-linux-musl)
racc (~> 1.4)
nokogiri (1.19.1-arm64-darwin)
nokogiri (1.19.3-arm64-darwin)
racc (~> 1.4)
nokogiri (1.19.1-x86_64-darwin)
nokogiri (1.19.3-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.19.1-x86_64-linux-gnu)
nokogiri (1.19.3-x86_64-linux-gnu)
racc (~> 1.4)
nokogiri (1.19.1-x86_64-linux-musl)
nokogiri (1.19.3-x86_64-linux-musl)
racc (~> 1.4)
otr-activerecord (2.6.0)
activerecord (>= 6.0, < 9.0)
parallel (1.27.0)
parallel (2.1.0)
parseconfig (1.1.2)
parser (3.3.10.2)
parser (3.3.11.1)
ast (~> 2.4.1)
racc
power_assert (2.0.5)
@@ -165,18 +165,18 @@ GEM
base64 (>= 0.1.0)
logger (>= 1.6.0)
rack (>= 3.0.0, < 4)
rack-session (2.1.1)
rack-session (2.1.2)
base64 (>= 0.1.0)
rack (>= 3.0.0)
rack-test (2.2.0)
rack (>= 1.3)
rainbow (3.1.1)
rake (13.3.1)
rake (13.4.2)
rdoc (7.2.0)
erb
psych (>= 4.0.0)
tsort
regexp_parser (2.11.3)
regexp_parser (2.12.0)
reline (0.6.3)
io-console (~> 0.5)
rest-client (2.1.0)
@@ -200,11 +200,11 @@ GEM
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-support (3.13.6)
rubocop (1.86.0)
rubocop (1.86.2)
json (~> 2.3)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0)
parallel (~> 1.10)
parallel (>= 1.10)
parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 2.9.3, < 3.0)
@@ -216,12 +216,12 @@ GEM
prism (~> 1.7)
ruby-progressbar (1.13.0)
ruby2_keywords (0.0.5)
rubyzip (3.2.2)
rubyzip (3.3.0)
rushover (0.3.0)
json
rest-client
securerandom (0.4.1)
selenium-webdriver (4.41.0)
selenium-webdriver (4.44.0)
base64 (~> 0.2)
logger (~> 1.4)
rexml (~> 3.2, >= 3.2.5)
@@ -241,14 +241,14 @@ GEM
rack-session (>= 2.0.0, < 3)
tilt (~> 2.0)
slack-notifier (2.4.0)
sqlite3 (2.9.2-aarch64-linux-gnu)
sqlite3 (2.9.2-aarch64-linux-musl)
sqlite3 (2.9.2-arm-linux-gnu)
sqlite3 (2.9.2-arm-linux-musl)
sqlite3 (2.9.2-arm64-darwin)
sqlite3 (2.9.2-x86_64-darwin)
sqlite3 (2.9.2-x86_64-linux-gnu)
sqlite3 (2.9.2-x86_64-linux-musl)
sqlite3 (2.9.4-aarch64-linux-gnu)
sqlite3 (2.9.4-aarch64-linux-musl)
sqlite3 (2.9.4-arm-linux-gnu)
sqlite3 (2.9.4-arm-linux-musl)
sqlite3 (2.9.4-arm64-darwin)
sqlite3 (2.9.4-x86_64-darwin)
sqlite3 (2.9.4-x86_64-linux-gnu)
sqlite3 (2.9.4-x86_64-linux-musl)
stringio (3.2.0)
sync (0.5.0)
term-ansicolor (1.11.3)
@@ -276,7 +276,7 @@ GEM
logger
rack (>= 1, < 4)
tilt (2.6.1)
timeout (0.6.0)
timeout (0.6.1)
timers (4.4.0)
tins (1.43.0)
bigdecimal
@@ -290,12 +290,13 @@ GEM
unicode-emoji (~> 4.1)
unicode-emoji (4.2.0)
uri (1.1.1)
webrick (1.9.1)
webrick (1.9.2)
websocket (1.2.11)
websocket-client-simple (0.6.1)
event_emitter
websocket
xmlrpc (0.3.3)
xmlrpc (0.3.4)
rexml
webrick
xpath (3.2.0)
nokogiri (~> 1.8)
@@ -324,7 +325,7 @@ DEPENDENCIES
eventmachine (~> 1.2, >= 1.2.7)
execjs (~> 2.10)
geckodriver-helper (~> 0.24.0)
irb (~> 1.17)
irb (~> 1.18)
json
maxmind-db (~> 1.4)
mime-types (~> 3.7)
@@ -336,14 +337,14 @@ DEPENDENCIES
qr4r (~> 0.6.1)
rack (~> 3.2)
rack-protection (~> 4.2.1)
rake (~> 13.3)
rake (~> 13.4)
rdoc (~> 7.2)
rest-client (~> 2.1.0)
rspec (~> 3.13)
rubocop (~> 1.86.0)
rubyzip (~> 3.2)
rubocop (~> 1.86.2)
rubyzip (~> 3.3)
rushover (~> 0.3.0)
selenium-webdriver (~> 4.41)
selenium-webdriver (~> 4.44)
simplecov (~> 0.22)
sinatra (~> 4.1)
slack-notifier (~> 2.4)
@@ -353,7 +354,7 @@ DEPENDENCIES
thin (~> 2.0)
uglifier (~> 4.2)
websocket-client-simple (~> 0.6.1)
xmlrpc (~> 0.3.3)
xmlrpc (~> 0.3.4)
BUNDLED WITH
2.7.2
+61
View File
@@ -0,0 +1,61 @@
"use strict";
const js = require("@eslint/js");
const globals = require("globals");
module.exports = [
{
ignores: [
"core/main/client/lib/**",
"modules/**",
"node_modules/**",
"extensions/admin_ui/media/javascript-min/**",
"**/*.min.js",
],
},
js.configs.recommended,
{
files: ["**/*.js"],
ignores: ["eslint.config.js"],
languageOptions: {
ecmaVersion: 5,
sourceType: "script",
globals: {
...globals.browser,
// BeEF
beef: "readonly",
beef_init: "readonly",
// jQuery
$: "readonly",
jQuery: "readonly",
$j: "readonly",
// Libraries
MobileEsp: "readonly",
evercookie: "readonly",
swfobject: "readonly",
// Browser-specific (old IE / old Firefox)
XDomainRequest: "readonly",
MozWebSocket: "readonly",
clipboardData: "readonly",
// Debug
isDebug: "readonly",
},
},
},
{
files: ["eslint.config.js"],
languageOptions: {
ecmaVersion: "latest",
sourceType: "commonjs",
globals: {
module: "writable",
require: "readonly",
},
},
},
];
+883 -5
View File
File diff suppressed because it is too large Load Diff
+9 -4
View File
@@ -3,13 +3,18 @@
"version": "0.6.0.0",
"description": "The Browser Exploitation Framework Project",
"scripts": {
"docs": "./node_modules/.bin/jsdoc -c conf.json"
"docs": "./node_modules/.bin/jsdoc -c conf.json",
"lint": "eslint .",
"lint:fix": "eslint --fix ."
},
"author": "Wade Alcorn",
"license": "GNU General Public License v2.0",
"devDependencies": {
"jsdoc": "^4.0.5",
"jsdoc-to-markdown": "^9.1.3"
"@eslint/js": "10.0.1",
"eslint": "10.4.0",
"globals": "17.6.0",
"jsdoc": "4.0.5",
"jsdoc-to-markdown": "9.1.3"
},
"dependencies": {}
}
}
@@ -1,359 +0,0 @@
#
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
# Browser Exploitation Framework (BeEF) - https://beefproject.com
# See the file 'doc/COPYING' for copying permission
#
# Example: unit specs for AutorunEngine::Engine using mocks instead of a real server/DB.
#
require 'spec_helper'
RSpec.describe BeEF::Core::AutorunEngine::Engine do
let(:engine) { described_class.instance }
let(:config) { BeEF::Core::Configuration.instance }
before do
allow(engine).to receive(:print_debug)
allow(engine).to receive(:print_info)
allow(engine).to receive(:print_more)
allow(engine).to receive(:print_error)
end
# Fake rule object (could be a double or a persisted Rule with minimal attributes)
def rule_with(browser: 'ALL', browser_version: 'ALL', os: 'ALL', os_version: 'ALL')
double(
'Rule',
id: 1,
browser: browser,
browser_version: browser_version,
os: os,
os_version: os_version
)
end
describe '#zombie_matches_rule?' do
it 'returns false when rule is nil' do
expect(engine.zombie_matches_rule?('FF', '41', 'Windows', '7', nil)).to be false
end
it 'returns true when rule is ALL for browser and OS' do
rule = rule_with(browser: 'ALL', browser_version: 'ALL', os: 'ALL', os_version: 'ALL')
allow(engine).to receive(:zombie_browser_matches_rule?).with('FF', '41', rule).and_return(true)
allow(engine).to receive(:zombie_os_matches_rule?).with('Windows', '7', rule).and_return(true)
expect(engine.zombie_matches_rule?('FF', '41', 'Windows', '7', rule)).to be true
end
it 'returns false when browser does not match' do
rule = rule_with(browser: 'FF', browser_version: '>= 41', os: 'ALL', os_version: 'ALL')
allow(engine).to receive(:zombie_browser_matches_rule?).with('FF', '41', rule).and_return(false)
expect(engine.zombie_matches_rule?('FF', '41', 'Windows', '7', rule)).to be false
end
it 'returns false when OS does not match' do
rule = rule_with(browser: 'ALL', browser_version: 'ALL', os: 'Windows', os_version: '7')
allow(engine).to receive(:zombie_browser_matches_rule?).with('FF', '41', rule).and_return(true)
allow(engine).to receive(:zombie_os_matches_rule?).with('Windows', '7', rule).and_return(false)
expect(engine.zombie_matches_rule?('FF', '41', 'Windows', '7', rule)).to be false
end
end
describe '#zombie_os_matches_rule?' do
it 'returns false when rule is nil' do
expect(engine.zombie_os_matches_rule?('Windows', '7', nil)).to be false
end
it 'returns true when rule os is ALL' do
rule = double('Rule', os: 'ALL', os_version: 'ALL')
expect(engine.zombie_os_matches_rule?('Windows', '7', rule)).to be true
end
it 'returns false when hook os does not match rule os' do
rule = double('Rule', os: 'Linux', os_version: 'ALL')
expect(engine.zombie_os_matches_rule?('Windows', '7', rule)).to be false
end
it 'returns true when rule os matches and os_version is ALL' do
rule = double('Rule', os: 'Windows', os_version: 'ALL')
expect(engine.zombie_os_matches_rule?('Windows', '7', rule)).to be true
end
end
describe '#zombie_browser_matches_rule?' do
it 'returns false when rule is nil' do
expect(engine.zombie_browser_matches_rule?('FF', '41', nil)).to be false
end
it 'returns true when rule browser is ALL and version is ALL' do
rule = double('Rule', browser: 'ALL', browser_version: 'ALL')
expect(engine.zombie_browser_matches_rule?('FF', '41', rule)).to be true
end
it 'returns true when rule browser matches and version is ALL' do
rule = double('Rule', browser: 'FF', browser_version: 'ALL')
expect(engine.zombie_browser_matches_rule?('FF', '41', rule)).to be true
end
it 'returns false when rule browser does not match' do
rule = double('Rule', browser: 'IE', browser_version: 'ALL')
expect(engine.zombie_browser_matches_rule?('FF', '41', rule)).to be false
end
end
describe '#find_matching_rules_for_zombie' do
it 'returns nil when no rules exist' do
allow(BeEF::Core::Models::Rule).to receive(:all).and_return([])
expect(engine.find_matching_rules_for_zombie('FF', '41', 'Windows', '7')).to be_nil
end
it 'returns matching rule ids when rules match zombie' do
rule1 = double('Rule', id: 1, name: 'Rule1', browser: 'ALL', browser_version: 'ALL', os: 'ALL', os_version: 'ALL')
rule2 = double('Rule', id: 2, name: 'Rule2', browser: 'IE', browser_version: 'ALL', os: 'ALL', os_version: 'ALL')
allow(BeEF::Core::Models::Rule).to receive(:all).and_return([rule1, rule2])
allow(engine).to receive(:zombie_matches_rule?).with('FF', '41', 'Windows', '7', rule1).and_return(true)
allow(engine).to receive(:zombie_matches_rule?).with('FF', '41', 'Windows', '7', rule2).and_return(false)
expect(engine.find_matching_rules_for_zombie('FF', '41', 'Windows', '7')).to eq([1])
end
end
describe '#compare_versions' do
it 'returns true when cond is ALL' do
expect(engine.send(:compare_versions, '7', 'ALL', '8')).to be true
end
it 'returns true when cond is == and versions equal' do
expect(engine.send(:compare_versions, '41', '==', '41')).to be true
end
it 'returns false when cond is == and versions differ' do
expect(engine.send(:compare_versions, '41', '==', '42')).to be false
end
it 'returns true when cond is <= and ver_a <= ver_b' do
expect(engine.send(:compare_versions, '41', '<=', '42')).to be true
expect(engine.send(:compare_versions, '41', '<=', '41')).to be true
end
it 'returns false when cond is <= and ver_a > ver_b' do
expect(engine.send(:compare_versions, '42', '<=', '41')).to be false
end
it 'returns true when cond is < and ver_a < ver_b' do
expect(engine.send(:compare_versions, '41', '<', '42')).to be true
end
it 'returns false when cond is < and ver_a >= ver_b' do
expect(engine.send(:compare_versions, '42', '<', '41')).to be false
expect(engine.send(:compare_versions, '41', '<', '41')).to be false
end
it 'returns true when cond is >= and ver_a >= ver_b' do
expect(engine.send(:compare_versions, '42', '>=', '41')).to be true
expect(engine.send(:compare_versions, '41', '>=', '41')).to be true
end
it 'returns true when cond is > and ver_a > ver_b' do
expect(engine.send(:compare_versions, '42', '>', '41')).to be true
end
it 'returns false when cond is > and ver_a <= ver_b' do
expect(engine.send(:compare_versions, '41', '>', '42')).to be false
expect(engine.send(:compare_versions, '41', '>', '41')).to be false
end
it 'returns false for unknown cond' do
expect(engine.send(:compare_versions, '41', '!=', '42')).to be false
end
end
describe '#clean_command_body' do
it 'extracts body range and replaces single-quoted mod_input when replace_input is true' do
body = "beef.execute(function(){\n alert('<<mod_input>>');\n});\n"
result = engine.send(:clean_command_body, body, true)
expect(result).to include('alert(mod_input)')
expect(result).to include('beef.execute(function(){')
end
it 'returns cleaned body without mod_input replacement when replace_input is false' do
body = "beef.execute(function(){\n doSomething('<<mod_input>>');\n});\n"
result = engine.send(:clean_command_body, body, false)
expect(result).to include('<<mod_input>>')
end
it 'replaces double-quoted <<mod_input>> with mod_input when replace_input is true' do
body = "beef.execute(function(){\n x(\"<<mod_input>>\");\n});\n"
result = engine.send(:clean_command_body, body, true)
expect(result).to include('mod_input')
expect(result).not_to include('"<<mod_input>>"')
end
it 'replaces single-quoted <<mod_input>> with mod_input when replace_input is true' do
body = "beef.execute(function(){\n x('<<mod_input>>');\n});\n"
result = engine.send(:clean_command_body, body, true)
expect(result).to include('mod_input')
end
end
describe '#prepare_sequential_wrapper' do
it 'builds wrapper with mod bodies and setTimeout calls in order' do
mods = [
{ mod_name: 'mod_a', mod_body: 'var mod_a_mod_output = 1;' },
{ mod_name: 'mod_b', mod_body: 'var mod_b_mod_output = 2;' }
]
order = [0, 1]
delay = [0, 500]
token = 't1'
result = engine.send(:prepare_sequential_wrapper, mods, order, delay, token)
expect(result).to include('mod_a_t1')
expect(result).to include('mod_b_t1')
expect(result).to include('setTimeout(function(){mod_a_t1();}, 0)')
expect(result).to include('setTimeout(function(){mod_b_t1();}, 500)')
expect(result).to include('mod_a_t1_mod_output')
expect(result).to include('mod_b_t1_mod_output')
end
it 'handles single module' do
mods = [{ mod_name: 'single', mod_body: 'x();' }]
order = [0]
delay = [0]
result = engine.send(:prepare_sequential_wrapper, mods, order, delay, 'tk')
expect(result).to include('single_tk')
expect(result).to include('setTimeout(function(){single_tk();}, 0)')
end
end
describe '#prepare_nested_forward_wrapper' do
it 'builds wrapper for single module' do
mods = [{ mod_name: 'only', mod_body: 'only();' }]
code = ['null']
conditions = [true]
order = [0]
token = 'nf1'
result = engine.send(:prepare_nested_forward_wrapper, mods, code, conditions, order, token)
expect(result).to include('only_nf1')
expect(result).to include('only_nf1_f')
expect(result).to include('only_nf1_mod_output')
end
end
describe '#find_and_run_all_matching_rules_for_zombie' do
it 'returns without calling run_rules when hb_id is nil' do
expect(engine).not_to receive(:run_rules_on_zombie)
engine.find_and_run_all_matching_rules_for_zombie(nil)
end
it 'returns without calling run_rules when find_matching_rules returns nil' do
allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'browser.name').and_return('FF')
allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'browser.version').and_return('41')
allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'host.os.name').and_return('Windows')
allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'host.os.version').and_return('7')
allow(engine).to receive(:find_matching_rules_for_zombie).with('FF', '41', 'Windows', '7').and_return(nil)
expect(engine).not_to receive(:run_rules_on_zombie)
engine.find_and_run_all_matching_rules_for_zombie(1)
end
it 'returns without calling run_rules when find_matching_rules returns empty' do
allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'browser.name').and_return('FF')
allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'browser.version').and_return('41')
allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'host.os.name').and_return('Windows')
allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'host.os.version').and_return('7')
allow(engine).to receive(:find_matching_rules_for_zombie).with('FF', '41', 'Windows', '7').and_return([])
expect(engine).not_to receive(:run_rules_on_zombie)
engine.find_and_run_all_matching_rules_for_zombie(1)
end
it 'calls run_rules_on_zombie with matching rule ids when rules match' do
allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'browser.name').and_return('FF')
allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'browser.version').and_return('41')
allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'host.os.name').and_return('Windows')
allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'host.os.version').and_return('7')
allow(engine).to receive(:find_matching_rules_for_zombie).with('FF', '41', 'Windows', '7').and_return([1, 2])
expect(engine).to receive(:run_rules_on_zombie).with([1, 2], 1)
engine.find_and_run_all_matching_rules_for_zombie(1)
end
end
describe '#run_matching_rules_on_zombie' do
it 'returns when rule_ids is nil' do
expect(engine).not_to receive(:run_rules_on_zombie)
engine.run_matching_rules_on_zombie(nil, 1)
end
it 'returns when hb_id is nil' do
expect(engine).not_to receive(:run_rules_on_zombie)
engine.run_matching_rules_on_zombie([1], nil)
end
it 'returns without calling run_rules when find_matching_rules returns nil' do
allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'browser.name').and_return('FF')
allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'browser.version').and_return('41')
allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'host.os.name').and_return('Windows')
allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'host.os.version').and_return('7')
allow(engine).to receive(:find_matching_rules_for_zombie).with('FF', '41', 'Windows', '7').and_return(nil)
expect(engine).not_to receive(:run_rules_on_zombie)
engine.run_matching_rules_on_zombie([1], 1)
end
it 'calls run_rules_on_zombie with intersection of rule_ids and matching rules' do
allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'browser.name').and_return('FF')
allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'browser.version').and_return('41')
allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'host.os.name').and_return('Windows')
allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'host.os.version').and_return('7')
allow(engine).to receive(:find_matching_rules_for_zombie).with('FF', '41', 'Windows', '7').and_return([1, 2])
expect(engine).to receive(:run_rules_on_zombie).with([1], 1)
engine.run_matching_rules_on_zombie([1], 1)
end
it 'does not call run_rules_on_zombie when no rule_ids overlap matching rules' do
allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'browser.name').and_return('FF')
allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'browser.version').and_return('41')
allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'host.os.name').and_return('Windows')
allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'host.os.version').and_return('7')
allow(engine).to receive(:find_matching_rules_for_zombie).with('FF', '41', 'Windows', '7').and_return([1, 2])
expect(engine).not_to receive(:run_rules_on_zombie)
engine.run_matching_rules_on_zombie([99], 1)
end
end
describe '#run_rules_on_zombie' do
it 'returns when rule_ids is nil' do
expect(BeEF::HBManager).not_to receive(:get_by_id)
engine.run_rules_on_zombie(nil, 1)
end
it 'returns when hb_id is nil' do
expect(BeEF::HBManager).not_to receive(:get_by_id)
engine.run_rules_on_zombie([1], nil)
end
it 'normalizes single Integer rule_id to array and processes rule' do
hb = double('HookedBrowser', session: 'sess1')
allow(BeEF::HBManager).to receive(:get_by_id).with(1).and_return(hb)
rule = double(
'Rule',
modules: '[]',
execution_order: '[]',
execution_delay: '[]',
chain_mode: 'invalid'
)
allow(BeEF::Core::Models::Rule).to receive(:find).with(1).and_return(rule)
engine.run_rules_on_zombie(1, 1)
expect(BeEF::Core::Models::Rule).to have_received(:find).with(1)
expect(engine).to have_received(:print_error).with(/Invalid chain mode 'invalid'/)
end
it 'prints error and returns when rule has invalid chain_mode' do
hb = double('HookedBrowser', session: 'sess1')
allow(BeEF::HBManager).to receive(:get_by_id).with(1).and_return(hb)
rule = double(
'Rule',
modules: '[]',
execution_order: '[]',
execution_delay: '[]',
chain_mode: 'invalid'
)
allow(BeEF::Core::Models::Rule).to receive(:find).with(1).and_return(rule)
engine.run_rules_on_zombie([1], 1)
expect(engine).to have_received(:print_error).with(/Invalid chain mode 'invalid'/)
end
end
end
@@ -1,119 +0,0 @@
#
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
# Browser Exploitation Framework (BeEF) - https://beefproject.com
# See the file 'doc/COPYING' for copying permission
#
require 'spec_helper'
RSpec.describe BeEF::Core::AutorunEngine::Parser do
let(:parser) { described_class.instance }
def valid_minimal_args
{
name: 'Test Rule',
author: 'Test Author',
browser: 'ALL',
browser_version: 'ALL',
os: 'Windows',
os_version: 'ALL',
modules: [],
execution_order: [],
execution_delay: [],
chain_mode: 'sequential'
}
end
describe '#parse' do
it 'returns true for valid minimal args (empty modules)' do
result = parser.parse(
valid_minimal_args[:name],
valid_minimal_args[:author],
valid_minimal_args[:browser],
valid_minimal_args[:browser_version],
valid_minimal_args[:os],
valid_minimal_args[:os_version],
valid_minimal_args[:modules],
valid_minimal_args[:execution_order],
valid_minimal_args[:execution_delay],
valid_minimal_args[:chain_mode]
)
expect(result).to be true
end
it 'raises ArgumentError for empty name' do
expect do
parser.parse('', 'Author', 'ALL', 'ALL', 'Windows', 'ALL', [], [], [], 'sequential')
end.to raise_error(ArgumentError, /Invalid rule name/)
end
it 'raises ArgumentError for nil name' do
expect do
parser.parse(nil, 'Author', 'ALL', 'ALL', 'Windows', 'ALL', [], [], [], 'sequential')
end.to raise_error(ArgumentError, /Invalid rule name/)
end
it 'raises ArgumentError for empty author' do
expect do
parser.parse('Name', '', 'ALL', 'ALL', 'Windows', 'ALL', [], [], [], 'sequential')
end.to raise_error(ArgumentError, /Invalid author name/)
end
it 'raises ArgumentError for invalid chain_mode' do
expect do
parser.parse('Name', 'Author', 'ALL', 'ALL', 'Windows', 'ALL', [], [], [], 'invalid')
end.to raise_error(ArgumentError, /Invalid chain_mode definition/)
end
it 'raises ArgumentError for invalid os' do
expect do
parser.parse('Name', 'Author', 'ALL', 'ALL', 'InvalidOS', 'ALL', [], [], [], 'sequential')
end.to raise_error(ArgumentError, /Invalid os definition/)
end
it 'raises ArgumentError when execution_delay size does not match modules size' do
expect do
parser.parse('Name', 'Author', 'ALL', 'ALL', 'Windows', 'ALL', [{ 'name' => 'a' }], [1], [], 'sequential')
end.to raise_error(ArgumentError, /execution_delay.*consistent with number of modules/)
end
it 'raises ArgumentError when execution_order size does not match modules size' do
expect do
parser.parse('Name', 'Author', 'ALL', 'ALL', 'Windows', 'ALL', [{ 'name' => 'a' }], [], [0], 'sequential')
end.to raise_error(ArgumentError, /execution_order.*consistent with number of modules/)
end
it 'raises TypeError when execution_delay contains non-Integer' do
# Use one module so sizes match; then type check runs on execution_delay
expect do
parser.parse('Name', 'Author', 'ALL', 'ALL', 'Windows', 'ALL', [{}], [1], ['not_an_int'], 'sequential')
end.to raise_error(TypeError, /execution_delay.*Integers/)
end
it 'raises TypeError when execution_order contains non-Integer' do
# Use one module so sizes match; then type check runs on execution_order
expect do
parser.parse('Name', 'Author', 'ALL', 'ALL', 'Windows', 'ALL', [{}], ['x'], [0], 'sequential')
end.to raise_error(TypeError, /execution_order.*Integers/)
end
it 'raises ArgumentError for invalid browser' do
expect do
parser.parse('Name', 'Author', 'XX', 'ALL', 'Windows', 'ALL', [], [], [], 'sequential')
end.to raise_error(ArgumentError, /Invalid browser definition/)
end
it 'accepts nested-forward as chain_mode' do
result = parser.parse('Name', 'Author', 'ALL', 'ALL', 'Windows', 'ALL', [], [], [], 'nested-forward')
expect(result).to be true
end
it 'accepts valid os values' do
%w[Linux Windows OSX Android iOS BlackBerry ALL].each do |os|
result = parser.parse('Name', 'Author', 'ALL', 'ALL', os, 'ALL', [], [], [], 'sequential')
expect(result).to be true
end
end
end
end
@@ -1,83 +0,0 @@
#
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
# Browser Exploitation Framework (BeEF) - https://beefproject.com
# See the file 'doc/COPYING' for copying permission
#
require 'spec_helper'
RSpec.describe BeEF::Core::AutorunEngine::RuleLoader do
let(:loader) { described_class.instance }
def valid_rule_data
{
'name' => 'Test Rule',
'author' => 'Test Author',
'browser' => 'ALL',
'browser_version' => 'ALL',
'os' => 'Windows',
'os_version' => 'ALL',
'modules' => [],
'execution_order' => [],
'execution_delay' => [],
'chain_mode' => 'sequential'
}
end
before do
allow(loader).to receive(:print_error)
allow(loader).to receive(:print_info)
allow(loader).to receive(:print_more)
end
describe '#load_rule_json' do
it 'returns success and rule_id when parse succeeds and rule is new' do
# Parser will succeed with valid minimal data; no existing rule
result = loader.load_rule_json(valid_rule_data)
expect(result['success']).to be true
expect(result).to have_key('rule_id')
expect(result['rule_id']).to be_a(Integer)
end
it 'returns success false and error when parse raises' do
allow(BeEF::Core::AutorunEngine::Parser.instance).to receive(:parse).and_raise(ArgumentError.new('Invalid rule name'))
result = loader.load_rule_json(valid_rule_data.merge('name' => 'x'))
expect(result['success']).to be false
expect(result['error']).to include('Invalid rule name')
end
it 'returns success false and error when rule already exists' do
# Create the rule first so it already exists
BeEF::Core::Models::Rule.create!(
name: 'Duplicate Rule',
author: 'Test Author',
browser: 'ALL',
browser_version: 'ALL',
os: 'Windows',
os_version: 'ALL',
modules: [].to_json,
execution_order: [].to_s,
execution_delay: [].to_s,
chain_mode: 'sequential'
)
result = loader.load_rule_json(
valid_rule_data.merge('name' => 'Duplicate Rule')
)
expect(result['success']).to be false
expect(result['error']).to include('Duplicate rule already exists')
end
it 'uses default chain_mode sequential when missing' do
data = valid_rule_data.except('chain_mode')
result = loader.load_rule_json(data)
expect(result['success']).to be true
expect(result).to have_key('rule_id')
end
end
end
@@ -111,7 +111,6 @@ RSpec.describe BeEF::Core::Handlers::HookedBrowsers do
relation = double('Relation', first: hooked_browser)
allow(BeEF::Core::Models::HookedBrowser).to receive(:where).with(session: 'existing_session').and_return(relation)
allow(BeEF::Core::Models::Command).to receive(:where).with(hooked_browser_id: 1, instructions_sent: false).and_return([])
allow(BeEF::Core::Models::Execution).to receive(:where).with(is_sent: false, session_id: 'existing_session').and_return([])
allow(BeEF::API::Registrar.instance).to receive(:fire)
end