From f9c630b5d67360ed586c81b86783d6332c4eda57 Mon Sep 17 00:00:00 2001 From: zinduolis Date: Mon, 25 Aug 2025 22:24:15 +1000 Subject: [PATCH 01/22] Update .gitignore to include secrets for local GitHub Actions testing; refactor spec_helper.rb for improved fork handling --- .gitignore | 3 + spec/spec_helper.rb | 255 +++++++++++++++++++++++++++----------------- 2 files changed, 159 insertions(+), 99 deletions(-) diff --git a/.gitignore b/.gitignore index 4322aed64..64282a1c5 100644 --- a/.gitignore +++ b/.gitignore @@ -131,3 +131,6 @@ node_modules/ # Generated files out/ doc/rdoc/ + +# Secrets for testing github actions locally +.secrets \ No newline at end of file diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 4eb5c992d..9571463f9 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,17 +1,21 @@ +# frozen_string_literal: true # # Copyright (c) 2006-2025 Wade Alcorn - wade@bindshell.net # Browser Exploitation Framework (BeEF) - https://beefproject.com # See the file 'doc/COPYING' for copying permission # + # Set external and internal character encodings to UTF-8 Encoding.default_external = Encoding::UTF_8 Encoding.default_internal = Encoding::UTF_8 +require 'logger' +require 'net/http' +require 'uri' + require 'core/loader.rb' -# @note We need to load variables that 'beef' usually does for us - -# @todo review this config (this isn't used or is shadowed by the monkey patching, needs a further look to fix properly) +# We need to load variables that 'beef' usually does for us config = BeEF::Core::Configuration.new('config.yaml') $home_dir = Dir.pwd $root_dir = Dir.pwd @@ -26,13 +30,13 @@ require 'browserstack/local' require 'byebug' # Require supports -Dir['spec/support/*.rb'].each do |f| - require f -end +Dir['spec/support/*.rb'].each { |f| require f } ENV['RACK_ENV'] ||= 'test' # Set the environment to test ARGV.clear +SERVER_START_TIMEOUT = Integer(ENV['SERVER_START_TIMEOUT'] || 20) + ## BrowserStack config # Monkey patch to avoid reset sessions @@ -42,29 +46,83 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base end end -TASK_ID = (ENV['TASK_ID'] || 0).to_i +TASK_ID = (ENV['TASK_ID'] || 0).to_i CONFIG_FILE = ENV['CONFIG_FILE'] || 'windows/win10/win10_chrome_81.config.yml' -CONFIG = YAML.safe_load(File.read("./spec/support/browserstack/#{CONFIG_FILE}")) +CONFIG = YAML.safe_load(File.read("./spec/support/browserstack/#{CONFIG_FILE}")) CONFIG['user'] = ENV['BROWSERSTACK_USERNAME'] || '' -CONFIG['key'] = ENV['BROWSERSTACK_ACCESS_KEY'] || '' +CONFIG['key'] = ENV['BROWSERSTACK_ACCESS_KEY'] || '' -## DB config +## DB config for unit tests (in-memory). We will disconnect these before forking the server. ActiveRecord::Base.logger = nil OTR::ActiveRecord.configure_from_hash!(adapter: 'sqlite3', database: ':memory:') - -# otr-activerecord requires manually establishing the connection with the following line -# Also a check to confirm that the correct Gem version is installed to require it, likely easier for old systems. if Gem.loaded_specs['otr-activerecord'].version > Gem::Version.create('1.4.2') OTR::ActiveRecord.establish_connection! end ActiveRecord::Schema.verbose = false -# Migrate (if required) -ActiveRecord::Migration.verbose = false # silence activerecord migration stdout messages +# Migrate (if required) for the in-memory schema used by non-server specs +ActiveRecord::Migration.verbose = false ActiveRecord::Migrator.migrations_paths = [File.join('core', 'main', 'ar-migrations')] -context = ActiveRecord::MigrationContext.new(ActiveRecord::Migrator.migrations_paths) -if context.needs_migration? - ActiveRecord::Migrator.new(:up, context.migrations, context.schema_migration, context.internal_metadata).migrate +mem_context = ActiveRecord::MigrationContext.new(ActiveRecord::Migrator.migrations_paths) +if mem_context.needs_migration? + ActiveRecord::Migrator + .new(:up, mem_context.migrations, mem_context.schema_migration, mem_context.internal_metadata) + .migrate +end + +# ------------------------------------------------------------------- +# Console logger shims +# Some extensions may call Console.level= or BeEF::Core::Console.level= +# Ensure both are safe. +# ------------------------------------------------------------------- +module BeEF + module Core + module Console + class << self + attr_accessor :logger + def level=(val) + (self.logger ||= Logger.new($stdout)).level = val + end + def level + (self.logger ||= Logger.new($stdout)).level + end + # Proxy common logger methods if called directly (info, warn, error, etc.) + def method_missing(m, *args, &blk) + lg = (self.logger ||= Logger.new($stdout)) + return lg.public_send(m, *args, &blk) if lg.respond_to?(m) + super + end + def respond_to_missing?(m, include_priv = false) + (self.logger ||= Logger.new($stdout)).respond_to?(m, include_priv) || super + end + end + end + end +end +BeEF::Core::Console.logger ||= Logger.new($stdout) + +# Some code may reference a top-level ::Console constant (not namespaced) +unless defined?(::Console) && ::Console.respond_to?(:level=) + module ::Console + class << self + attr_accessor :logger + def level=(val) + (self.logger ||= Logger.new($stdout)).level = val + end + def level + (self.logger ||= Logger.new($stdout)).level + end + # Proxy to logger for typical logging calls + def method_missing(m, *args, &blk) + lg = (self.logger ||= Logger.new($stdout)) + return lg.public_send(m, *args, &blk) if lg.respond_to?(m) + super + end + def respond_to_missing?(m, include_priv = false) + (self.logger ||= Logger.new($stdout)).respond_to?(m, include_priv) || super + end + end + end end RSpec.configure do |config| @@ -76,9 +134,9 @@ RSpec.configure do |config| config.expect_with :rspec do |c| c.syntax = :expect end + config.around do |example| ActiveRecord::Base.transaction do - # byebug example.run raise ActiveRecord::Rollback end @@ -94,146 +152,145 @@ RSpec.configure do |config| exit 0 ensure print_info 'Shutting down server' - Process.kill('KILL', server_pid) - Process.kill('KILL', server_pids) + Process.kill('KILL', server_pid) if server_pid + Array(server_pids).compact.each { |pid| Process.kill('KILL', pid) } end -######################################## + ######################################## -def reset_beef_db - begin - db_file = BeEF::Core::Configuration.instance.get('beef.database.file') - File.delete(db_file) if File.exist?(db_file) + def reset_beef_db + db_file = BeEF::Core::Configuration.instance.get('beef.database.file') + File.delete(db_file) if File.exist?(db_file) rescue => e - print_error("Could not remove '#{db_file}' database file: #{e.message}") + print_error("Could not remove '#{db_file}' database file: #{e.message}") end -end -require 'socket' + require 'socket' def port_available? socket = TCPSocket.new(@host, @port) socket.close - false # If a connection is made, the port is in use, so it's not available. - rescue Errno::ECONNREFUSED - true # If the connection is refused, the port is not in use, so it's available. - rescue Errno::EADDRNOTAVAIL - true # If the connection is refused, the port is not in use, so it's available. + false # If a connection is made, the port is in use. + rescue Errno::ECONNREFUSED, Errno::EADDRNOTAVAIL + true # Connection refused/unavailable => port is free. end def configure_beef # Reset or re-initialise the configuration to a default state @config = BeEF::Core::Configuration.instance - - @config.set('beef.credentials.user', "beef") - @config.set('beef.credentials.passwd', "beef") + @config.set('beef.credentials.user', 'beef') + @config.set('beef.credentials.passwd','beef') @config.set('beef.http.https.enable', false) end # Load the server def load_beef_extensions_and_modules - # Load BeEF extensions - BeEF::Extensions.load + # Load BeEF extensions + BeEF::Extensions.load + # Load BeEF modules only if they are not already loaded + BeEF::Modules.load if @config.get('beef.module').nil? + end - # Load BeEF modules only if they are not already loaded - BeEF::Modules.load if @config.get('beef.module').nil? + # --- HARD fork-safety: disconnect every pool/adapter we can find --- + def disconnect_all_active_record! + if defined?(ActiveRecord::Base) + # Disconnect every connection pool explicitly + handler = ActiveRecord::Base.connection_handler + handler.connection_pool_list.each { |pool| pool.disconnect! } if handler.respond_to?(:connection_pool_list) + ActiveRecord::Base.clear_active_connections! + ActiveRecord::Base.clear_all_connections! + end + OTR::ActiveRecord.disconnect! if defined?(OTR::ActiveRecord) end def start_beef_server configure_beef @port = @config.get('beef.http.port') - @host = @config.get('beef.http.host') @host = '127.0.0.1' unless port_available? print_error "Port #{@port} is already in use. Exiting." - exit + exit 1 end load_beef_extensions_and_modules - - # Grab DB file and regenerate if requested + + # DB file for BeEF runtime (not the in-memory test DB) db_file = @config.get('beef.database.file') if BeEF::Core::Console::CommandLine.parse[:resetdb] File.delete(db_file) if File.exist?(db_file) end - # Load up DB and migrate if necessary - ActiveRecord::Base.logger = nil - OTR::ActiveRecord.configure_from_hash!(adapter:'sqlite3', database: db_file) - # otr-activerecord require you to manually establish the connection with the following line - #Also a check to confirm that the correct Gem version is installed to require it, likely easier for old systems. - if Gem.loaded_specs['otr-activerecord'].version > Gem::Version.create('1.4.2') - OTR::ActiveRecord.establish_connection! - end + # ***** IMPORTANT: close any and all AR/OTR connections before forking ***** + disconnect_all_active_record! - # Migrate (if required) - ActiveRecord::Migration.verbose = false # silence activerecord migration stdout messages - ActiveRecord::Migrator.migrations_paths = [File.join('core', 'main', 'ar-migrations')] - context = ActiveRecord::MigrationContext.new(ActiveRecord::Migrator.migrations_paths) - if context.needs_migration? - ActiveRecord::Migrator.new(:up, context.migrations, context.schema_migration, context.internal_metadata).migrate - end - - BeEF::Core::Migration.instance.update_db! - - # Spawn HTTP Server - # print_info "Starting HTTP Hook Server" - http_hook_server = BeEF::Core::Server.instance - http_hook_server.prepare - - # Generate a token for the server to respond with - BeEF::Core::Crypto::api_token - - # Initiate server start-up - BeEF::API::Registrar.instance.fire(BeEF::API::Server, 'pre_http_start', http_hook_server) pid = fork do + # Child: establish a fresh connection to the file DB + OTR::ActiveRecord.configure_from_hash!(adapter: 'sqlite3', database: db_file) + if Gem.loaded_specs['otr-activerecord'].version > Gem::Version.create('1.4.2') + OTR::ActiveRecord.establish_connection! + end + + # Apply migrations for runtime DB + ActiveRecord::Migration.verbose = false + ActiveRecord::Migrator.migrations_paths = [File.join('core', 'main', 'ar-migrations')] + context = ActiveRecord::MigrationContext.new(ActiveRecord::Migrator.migrations_paths) + if context.needs_migration? + ActiveRecord::Migrator + .new(:up, context.migrations, context.schema_migration, context.internal_metadata) + .migrate + end + + BeEF::Core::Migration.instance.update_db! + + # Spawn HTTP Server + http_hook_server = BeEF::Core::Server.instance + http_hook_server.prepare + + # Generate a token for the server to respond with + BeEF::Core::Crypto::api_token + + # Fire pre_http_start hooks (Dns extension, etc.) + BeEF::API::Registrar.instance.fire(BeEF::API::Server, 'pre_http_start', http_hook_server) + + # Start server (blocking call) http_hook_server.start end - return pid + pid end def beef_server_running?(uri_str) - begin - uri = URI.parse(uri_str) - response = Net::HTTP.get_response(uri) - response.is_a?(Net::HTTPSuccess) - rescue Errno::ECONNREFUSED - return false # Connection refused means the server is not running - rescue StandardError => e - return false # Any other error means the server is not running - end + uri = URI.parse(uri_str) + response = Net::HTTP.get_response(uri) + response.is_a?(Net::HTTPSuccess) + rescue Errno::ECONNREFUSED, StandardError + false end def wait_for_beef_server_to_start(uri_str, timeout: 5) - start_time = Time.now # Record the time we started - until beef_server_running?(uri_str) || (Time.now - start_time) > timeout do - sleep 0.1 # Wait a bit before checking again + start_time = Time.now + until beef_server_running?(uri_str) || (Time.now - start_time) > timeout + sleep 0.1 end - beef_server_running?(uri_str) # Return the result of the check + beef_server_running?(uri_str) end def start_beef_server_and_wait - puts "Starting BeEF server" + puts 'Starting BeEF server' pid = start_beef_server puts "BeEF server started with PID: #{pid}" - if wait_for_beef_server_to_start('http://localhost:3000', timeout: SERVER_START_TIMEOUT) - # print_info "Server started successfully." - else - print_error "Server failed to start within timeout." + unless wait_for_beef_server_to_start('http://localhost:3000', timeout: SERVER_START_TIMEOUT) + print_error 'Server failed to start within timeout.' end pid end def stop_beef_server(pid) - exit if pid.nil? - # Shutting down server - Process.kill("KILL", pid) unless pid.nil? - Process.wait(pid) unless pid.nil? # Ensure the process has exited and the port is released - pid = nil + return if pid.nil? + Process.kill('KILL', pid) + Process.wait(pid) end - -end +end \ No newline at end of file From e93dc2817491f69e77535fb5565d70797fc789fd Mon Sep 17 00:00:00 2001 From: zinduolis Date: Mon, 25 Aug 2025 23:30:54 +1000 Subject: [PATCH 02/22] Add disconnect_all_active_record! calls before forking in multiple specs for improved SQL connection handling safety --- .../main/autorun_engine/autorun_engine_spec.rb | 3 +++ .../handlers/browser_details_handler_spec.rb | 3 +++ .../handlers/dynamic_reconstruction_spec.rb | 4 ++++ .../network_stack/handlers/redirector_spec.rb | 5 +++++ .../extensions/websocket_hooked_browser_spec.rb | 5 +++++ spec/beef/modules/debug/test_beef_debugs_spec.rb | 3 +++ spec/spec_helper.rb | 16 +++++++++++----- 7 files changed, 34 insertions(+), 5 deletions(-) diff --git a/spec/beef/core/main/autorun_engine/autorun_engine_spec.rb b/spec/beef/core/main/autorun_engine/autorun_engine_spec.rb index aef99a958..6b42e8eb6 100644 --- a/spec/beef/core/main/autorun_engine/autorun_engine_spec.rb +++ b/spec/beef/core/main/autorun_engine/autorun_engine_spec.rb @@ -68,6 +68,9 @@ RSpec.describe 'AutoRunEngine Test', run_on_browserstack: true do # Generate a token for the server to respond with @token = BeEF::Core::Crypto.api_token + # ***** IMPORTANT: close any and all AR/OTR connections before forking ***** + disconnect_all_active_record! + # Initiate server start-up @pids = fork do BeEF::API::Registrar.instance.fire(BeEF::API::Server, 'pre_http_start', http_hook_server) diff --git a/spec/beef/core/main/handlers/browser_details_handler_spec.rb b/spec/beef/core/main/handlers/browser_details_handler_spec.rb index 4c54f2d37..eca9e33bc 100644 --- a/spec/beef/core/main/handlers/browser_details_handler_spec.rb +++ b/spec/beef/core/main/handlers/browser_details_handler_spec.rb @@ -63,6 +63,9 @@ RSpec.describe 'Browser Details Handler', run_on_browserstack: true do # Generate a token for the server to respond with @token = BeEF::Core::Crypto.api_token + # ***** IMPORTANT: close any and all AR/OTR connections before forking ***** + disconnect_all_active_record! + # Initiate server start-up @pids = fork do BeEF::API::Registrar.instance.fire(BeEF::API::Server, 'pre_http_start', http_hook_server) diff --git a/spec/beef/core/main/network_stack/handlers/dynamic_reconstruction_spec.rb b/spec/beef/core/main/network_stack/handlers/dynamic_reconstruction_spec.rb index 458bc3d70..3cfd652fe 100644 --- a/spec/beef/core/main/network_stack/handlers/dynamic_reconstruction_spec.rb +++ b/spec/beef/core/main/network_stack/handlers/dynamic_reconstruction_spec.rb @@ -12,6 +12,10 @@ RSpec.describe 'BeEF Dynamic Reconsturction' do @server = Thin::Server.new('127.0.0.1', @port.to_s, @rackApp) trap("INT") { @server.stop } trap("TERM") { @server.stop } + + # ***** IMPORTANT: close any and all AR/OTR connections before forking ***** + disconnect_all_active_record! + @pid = fork do @server.start! end diff --git a/spec/beef/core/main/network_stack/handlers/redirector_spec.rb b/spec/beef/core/main/network_stack/handlers/redirector_spec.rb index 8333ab459..e17293127 100644 --- a/spec/beef/core/main/network_stack/handlers/redirector_spec.rb +++ b/spec/beef/core/main/network_stack/handlers/redirector_spec.rb @@ -12,6 +12,11 @@ RSpec.describe 'BeEF Redirector' do @server = Thin::Server.new('127.0.0.1', @port.to_s, @rackApp) trap("INT") { @server.stop } trap("TERM") { @server.stop } + + + # ***** IMPORTANT: close any and all AR/OTR connections before forking ***** + disconnect_all_active_record! + @pid = fork do @server.start! end diff --git a/spec/beef/extensions/websocket_hooked_browser_spec.rb b/spec/beef/extensions/websocket_hooked_browser_spec.rb index 6acf55641..45b02cc89 100644 --- a/spec/beef/extensions/websocket_hooked_browser_spec.rb +++ b/spec/beef/extensions/websocket_hooked_browser_spec.rb @@ -59,6 +59,11 @@ RSpec.describe 'Browser hooking with Websockets', run_on_browserstack: true do http_hook_server.prepare # Generate a token for the server to respond with @token = BeEF::Core::Crypto.api_token + + + # ***** IMPORTANT: close any and all AR/OTR connections before forking ***** + disconnect_all_active_record! + # Initiate server start-up @pids = fork do BeEF::API::Registrar.instance.fire(BeEF::API::Server, 'pre_http_start', http_hook_server) diff --git a/spec/beef/modules/debug/test_beef_debugs_spec.rb b/spec/beef/modules/debug/test_beef_debugs_spec.rb index 1bc89d813..054cf287c 100644 --- a/spec/beef/modules/debug/test_beef_debugs_spec.rb +++ b/spec/beef/modules/debug/test_beef_debugs_spec.rb @@ -60,6 +60,9 @@ RSpec.describe 'BeEF Debug Command Modules:', run_on_browserstack: true do # Generate a token for the server to respond with @token = BeEF::Core::Crypto.api_token + # ***** IMPORTANT: close any and all AR/OTR connections before forking ***** + disconnect_all_active_record! + # Initiate server start-up @pids = fork do BeEF::API::Registrar.instance.fire(BeEF::API::Server, 'pre_http_start', http_hook_server) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 9571463f9..323a72938 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -193,14 +193,20 @@ RSpec.configure do |config| # --- HARD fork-safety: disconnect every pool/adapter we can find --- def disconnect_all_active_record! + print_info "Entering disconnect_all_active_record!" if defined?(ActiveRecord::Base) - # Disconnect every connection pool explicitly + print_info "Disconnecting ActiveRecord connections" handler = ActiveRecord::Base.connection_handler - handler.connection_pool_list.each { |pool| pool.disconnect! } if handler.respond_to?(:connection_pool_list) - ActiveRecord::Base.clear_active_connections! - ActiveRecord::Base.clear_all_connections! + if handler.respond_to?(:connection_pool_list) + print_info "Using connection_pool_list" + handler.connection_pool_list.each { |pool| pool.disconnect! } + elsif handler.respond_to?(:connection_pools) + print_info "Using connection_pools" + handler.connection_pools.each_value { |pool| pool.disconnect! } + end + else + print_info "ActiveRecord::Base not defined" end - OTR::ActiveRecord.disconnect! if defined?(OTR::ActiveRecord) end def start_beef_server From 56b34649b7865598f93bc7caaba6e3a898931961 Mon Sep 17 00:00:00 2001 From: zinduolis Date: Wed, 27 Aug 2025 21:20:24 +1000 Subject: [PATCH 03/22] Comment out SERVER_START_TIMEOUT definition in spec_helper.rb --- spec/spec_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 323a72938..d4f6d8c73 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -35,7 +35,7 @@ Dir['spec/support/*.rb'].each { |f| require f } ENV['RACK_ENV'] ||= 'test' # Set the environment to test ARGV.clear -SERVER_START_TIMEOUT = Integer(ENV['SERVER_START_TIMEOUT'] || 20) +# SERVER_START_TIMEOUT = Integer(ENV['SERVER_START_TIMEOUT'] || 20) ## BrowserStack config From 1a4127c1980755e714e23bd590c5bd66e7139166 Mon Sep 17 00:00:00 2001 From: zinduolis Date: Wed, 27 Aug 2025 21:39:44 +1000 Subject: [PATCH 04/22] Update Gemfile.lock to bump dependencies and add platform support --- Gemfile.lock | 70 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 21 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 76b158d2e..2bb7d7de3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -22,8 +22,8 @@ GEM addressable (2.8.7) public_suffix (>= 2.0.2, < 7.0) ansi (1.5.0) - archive-zip (0.12.0) - io-like (~> 0.3.0) + archive-zip (0.13.1) + io-like (~> 0.4.0) ast (2.4.3) async (1.32.1) console (~> 1.10) @@ -49,7 +49,7 @@ GEM coderay (1.1.3) concurrent-ruby (1.3.5) connection_pool (2.5.3) - console (1.29.2) + console (1.33.0) fiber-annotation fiber-local (~> 1.1) json @@ -71,7 +71,7 @@ GEM fiber-annotation (0.2.0) fiber-local (1.1.0) fiber-storage - fiber-storage (1.0.0) + fiber-storage (1.0.1) geckodriver-helper (0.24.0) archive-zip (~> 0.7) http-accept (1.7.0) @@ -80,9 +80,9 @@ GEM http_parser.rb (0.8.0) i18n (1.14.7) concurrent-ruby (~> 1.0) - io-console (0.8.0) - io-endpoint (0.15.1) - io-like (0.3.1) + io-console (0.8.1) + io-endpoint (0.15.2) + io-like (0.4.0) irb (1.15.2) pp (>= 0.6.0) rdoc (>= 4.0.0) @@ -91,20 +91,20 @@ GEM language_server-protocol (3.17.0.5) lint_roller (1.1.0) logger (1.7.0) - matrix (0.4.2) + matrix (0.4.3) maxmind-db (1.3.2) method_source (1.1.0) mime-types (3.7.0) logger mime-types-data (~> 3.2025, >= 3.2025.0507) - mime-types-data (3.2025.0520) + mime-types-data (3.2025.0819) mini_mime (1.1.5) minitest (5.25.5) mojo_magick (0.6.8) msfrpc-client (1.1.2) msgpack (~> 1) - msgpack (1.7.5) - mustermann (3.0.3) + msgpack (1.8.0) + mustermann (3.0.4) ruby2_keywords (~> 0.0.1) net-protocol (0.2.2) timeout @@ -112,8 +112,22 @@ GEM net-protocol netrc (0.11.0) nio4r (2.7.4) + nokogiri (1.18.9-aarch64-linux-gnu) + racc (~> 1.4) + nokogiri (1.18.9-aarch64-linux-musl) + racc (~> 1.4) + nokogiri (1.18.9-arm-linux-gnu) + racc (~> 1.4) + nokogiri (1.18.9-arm-linux-musl) + racc (~> 1.4) + nokogiri (1.18.9-arm64-darwin) + racc (~> 1.4) + nokogiri (1.18.9-x86_64-darwin) + racc (~> 1.4) nokogiri (1.18.9-x86_64-linux-gnu) racc (~> 1.4) + nokogiri (1.18.9-x86_64-linux-musl) + racc (~> 1.4) otr-activerecord (2.5.0) activerecord (>= 6.0, < 8.1) parallel (1.27.0) @@ -135,7 +149,7 @@ GEM psych (5.2.6) date stringio - public_suffix (6.0.1) + public_suffix (6.0.2) qr4r (0.6.2) mojo_magick (~> 0.6.5) rqrcode_core (~> 1.0) @@ -152,7 +166,7 @@ GEM erb psych (>= 4.0.0) regexp_parser (2.11.2) - reline (0.6.0) + reline (0.6.2) io-console (~> 0.5) rest-client (2.1.0) http-accept (>= 1.7.0, < 2.0) @@ -161,12 +175,12 @@ GEM netrc (~> 0.8) rexml (3.4.1) rqrcode_core (1.2.0) - rr (3.1.1) + rr (3.1.2) rspec (3.13.1) rspec-core (~> 3.13.0) rspec-expectations (~> 3.13.0) rspec-mocks (~> 3.13.0) - rspec-core (3.13.4) + rspec-core (3.13.5) rspec-support (~> 3.13.0) rspec-expectations (3.13.5) diff-lcs (>= 1.2.0, < 2.0) @@ -174,7 +188,7 @@ GEM rspec-mocks (3.13.5) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-support (3.13.4) + rspec-support (3.13.5) rubocop (1.79.2) json (~> 2.3) language_server-protocol (~> 3.17.0.2) @@ -208,12 +222,19 @@ GEM rack-protection (= 3.2.0) tilt (~> 2.0) slack-notifier (2.4.0) + sqlite3 (2.7.3-aarch64-linux-gnu) + sqlite3 (2.7.3-aarch64-linux-musl) + sqlite3 (2.7.3-arm-linux-gnu) + sqlite3 (2.7.3-arm-linux-musl) + sqlite3 (2.7.3-arm64-darwin) + sqlite3 (2.7.3-x86_64-darwin) sqlite3 (2.7.3-x86_64-linux-gnu) + sqlite3 (2.7.3-x86_64-linux-musl) stringio (3.1.7) sync (0.5.0) term-ansicolor (1.11.2) tins (~> 1.0) - test-unit (3.6.7) + test-unit (3.7.0) power_assert test-unit-context (0.5.1) test-unit (>= 2.4.0) @@ -234,10 +255,10 @@ GEM daemons (~> 1.0, >= 1.0.9) eventmachine (~> 1.0, >= 1.0.4) rack (>= 1, < 3) - tilt (2.5.0) + tilt (2.6.1) timeout (0.4.3) timers (4.4.0) - tins (1.37.1) + tins (1.42.0) bigdecimal sync tzinfo (2.0.6) @@ -258,7 +279,14 @@ GEM nokogiri (~> 1.8) PLATFORMS - x86_64-linux + aarch64-linux-gnu + aarch64-linux-musl + arm-linux-gnu + arm-linux-musl + arm64-darwin + x86_64-darwin + x86_64-linux-gnu + x86_64-linux-musl DEPENDENCIES activerecord (~> 7.2) @@ -305,4 +333,4 @@ DEPENDENCIES xmlrpc (~> 0.3.3) BUNDLED WITH - 2.4.10 + 2.5.18 From 180c2f2c4e9d4a903364e33459b20d9194d985d2 Mon Sep 17 00:00:00 2001 From: zinduolis Date: Thu, 28 Aug 2025 18:44:13 +1000 Subject: [PATCH 05/22] Troubleshooting discrepancy between local tests passing and remote not --- .github/workflows/github_actions.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/github_actions.yml b/.github/workflows/github_actions.yml index 5182fe894..adf29abbf 100644 --- a/.github/workflows/github_actions.yml +++ b/.github/workflows/github_actions.yml @@ -1,7 +1,7 @@ name: 'BrowserStack Test' on: - pull_request_target: + pull_request: branches: [ master ] jobs: From 418dc226fd3a2a63177521dae86c27bf5cbbd7c4 Mon Sep 17 00:00:00 2001 From: zinduolis <101160195+zinduolis@users.noreply.github.com> Date: Thu, 28 Aug 2025 18:47:03 +1000 Subject: [PATCH 06/22] Update github_actions.yml Troubleshooting different behaviour of tests when running locally and on remote branch --- .github/workflows/github_actions.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/github_actions.yml b/.github/workflows/github_actions.yml index 5182fe894..adf29abbf 100644 --- a/.github/workflows/github_actions.yml +++ b/.github/workflows/github_actions.yml @@ -1,7 +1,7 @@ name: 'BrowserStack Test' on: - pull_request_target: + pull_request: branches: [ master ] jobs: From e394f2e15fa2cffe719cb16d2aa4e63ed85974f1 Mon Sep 17 00:00:00 2001 From: zinduolis Date: Mon, 1 Sep 2025 21:30:58 +1000 Subject: [PATCH 07/22] Update checkout action --- .github/workflows/github_actions.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/github_actions.yml b/.github/workflows/github_actions.yml index adf29abbf..bee7787b5 100644 --- a/.github/workflows/github_actions.yml +++ b/.github/workflows/github_actions.yml @@ -1,13 +1,13 @@ name: 'BrowserStack Test' on: - pull_request: + pull_request_target: branches: [ master ] jobs: ubuntu-job: name: 'BrowserStack Test on Ubuntu' - runs-on: ubuntu-latest # Can be self-hosted runner also + runs-on: ubuntu-latest environment: name: Integrate Pull Request env: @@ -28,9 +28,6 @@ jobs: - name: 'Checkout the repository' uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha }} - fetch-depth: 2 - name: 'Setting up Ruby' uses: ruby/setup-ruby@v1 From 24944438299ec3eb89dc892473a0eadc7cfaa3de Mon Sep 17 00:00:00 2001 From: zinduolis Date: Mon, 1 Sep 2025 21:57:56 +1000 Subject: [PATCH 08/22] Testing code running in github actions --- spec/beef/core/main/handlers/browser_details_handler_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/beef/core/main/handlers/browser_details_handler_spec.rb b/spec/beef/core/main/handlers/browser_details_handler_spec.rb index eca9e33bc..d021faedd 100644 --- a/spec/beef/core/main/handlers/browser_details_handler_spec.rb +++ b/spec/beef/core/main/handlers/browser_details_handler_spec.rb @@ -37,6 +37,7 @@ RSpec.describe 'Browser Details Handler', run_on_browserstack: true do end # Grab DB file and regenerate if requested + print_info 'TESTING MESSAGE' print_info 'Loading database' # Load up DB and migrate if necessary From dbc9fed9f28a5c6870f00a53990976e0d0e414ed Mon Sep 17 00:00:00 2001 From: zinduolis Date: Wed, 3 Sep 2025 09:40:41 +1000 Subject: [PATCH 09/22] Revert back to the original before troubleshooting sql forking issue --- .github/workflows/github_actions.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/github_actions.yml b/.github/workflows/github_actions.yml index bee7787b5..880302da3 100644 --- a/.github/workflows/github_actions.yml +++ b/.github/workflows/github_actions.yml @@ -7,7 +7,7 @@ on: jobs: ubuntu-job: name: 'BrowserStack Test on Ubuntu' - runs-on: ubuntu-latest + runs-on: ubuntu-latest # Can be self-hosted runner also environment: name: Integrate Pull Request env: @@ -28,6 +28,9 @@ jobs: - name: 'Checkout the repository' uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 2 - name: 'Setting up Ruby' uses: ruby/setup-ruby@v1 @@ -49,4 +52,4 @@ jobs: - name: 'BrowserStackLocal Stop' # Terminating the BrowserStackLocal tunnel connection uses: browserstack/github-actions/setup-local@master with: - local-testing: stop + local-testing: stop \ No newline at end of file From eecc73b941b7222ba94bc49b9dbc2473c38a1c9d Mon Sep 17 00:00:00 2001 From: zinduolis Date: Wed, 3 Sep 2025 10:04:31 +1000 Subject: [PATCH 10/22] Remove testing message and improve code formatting in spec files --- .../handlers/browser_details_handler_spec.rb | 1 - spec/spec_helper.rb | 187 ++++++++++-------- 2 files changed, 100 insertions(+), 88 deletions(-) diff --git a/spec/beef/core/main/handlers/browser_details_handler_spec.rb b/spec/beef/core/main/handlers/browser_details_handler_spec.rb index d021faedd..eca9e33bc 100644 --- a/spec/beef/core/main/handlers/browser_details_handler_spec.rb +++ b/spec/beef/core/main/handlers/browser_details_handler_spec.rb @@ -37,7 +37,6 @@ RSpec.describe 'Browser Details Handler', run_on_browserstack: true do end # Grab DB file and regenerate if requested - print_info 'TESTING MESSAGE' print_info 'Loading database' # Load up DB and migrate if necessary diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index d4f6d8c73..ecaa803b1 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,21 +1,17 @@ -# frozen_string_literal: true # # Copyright (c) 2006-2025 Wade Alcorn - wade@bindshell.net # Browser Exploitation Framework (BeEF) - https://beefproject.com # See the file 'doc/COPYING' for copying permission # - # Set external and internal character encodings to UTF-8 Encoding.default_external = Encoding::UTF_8 Encoding.default_internal = Encoding::UTF_8 -require 'logger' -require 'net/http' -require 'uri' - require 'core/loader.rb' -# We need to load variables that 'beef' usually does for us +# @note We need to load variables that 'beef' usually does for us + +# @todo review this config (this isn't used or is shadowed by the monkey patching, needs a further look to fix properly) config = BeEF::Core::Configuration.new('config.yaml') $home_dir = Dir.pwd $root_dir = Dir.pwd @@ -30,13 +26,13 @@ require 'browserstack/local' require 'byebug' # Require supports -Dir['spec/support/*.rb'].each { |f| require f } +Dir['spec/support/*.rb'].each do |f| + require f +end ENV['RACK_ENV'] ||= 'test' # Set the environment to test ARGV.clear -# SERVER_START_TIMEOUT = Integer(ENV['SERVER_START_TIMEOUT'] || 20) - ## BrowserStack config # Monkey patch to avoid reset sessions @@ -46,28 +42,29 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base end end -TASK_ID = (ENV['TASK_ID'] || 0).to_i +TASK_ID = (ENV['TASK_ID'] || 0).to_i CONFIG_FILE = ENV['CONFIG_FILE'] || 'windows/win10/win10_chrome_81.config.yml' -CONFIG = YAML.safe_load(File.read("./spec/support/browserstack/#{CONFIG_FILE}")) +CONFIG = YAML.safe_load(File.read("./spec/support/browserstack/#{CONFIG_FILE}")) CONFIG['user'] = ENV['BROWSERSTACK_USERNAME'] || '' -CONFIG['key'] = ENV['BROWSERSTACK_ACCESS_KEY'] || '' +CONFIG['key'] = ENV['BROWSERSTACK_ACCESS_KEY'] || '' -## DB config for unit tests (in-memory). We will disconnect these before forking the server. +## DB config ActiveRecord::Base.logger = nil OTR::ActiveRecord.configure_from_hash!(adapter: 'sqlite3', database: ':memory:') + +# otr-activerecord requires manually establishing the connection with the following line +# Also a check to confirm that the correct Gem version is installed to require it, likely easier for old systems. if Gem.loaded_specs['otr-activerecord'].version > Gem::Version.create('1.4.2') OTR::ActiveRecord.establish_connection! end ActiveRecord::Schema.verbose = false -# Migrate (if required) for the in-memory schema used by non-server specs -ActiveRecord::Migration.verbose = false +# Migrate (if required) +ActiveRecord::Migration.verbose = false # silence activerecord migration stdout messages ActiveRecord::Migrator.migrations_paths = [File.join('core', 'main', 'ar-migrations')] -mem_context = ActiveRecord::MigrationContext.new(ActiveRecord::Migrator.migrations_paths) -if mem_context.needs_migration? - ActiveRecord::Migrator - .new(:up, mem_context.migrations, mem_context.schema_migration, mem_context.internal_metadata) - .migrate +context = ActiveRecord::MigrationContext.new(ActiveRecord::Migrator.migrations_paths) +if context.needs_migration? + ActiveRecord::Migrator.new(:up, context.migrations, context.schema_migration, context.internal_metadata).migrate end # ------------------------------------------------------------------- @@ -134,9 +131,9 @@ RSpec.configure do |config| config.expect_with :rspec do |c| c.syntax = :expect end - config.around do |example| ActiveRecord::Base.transaction do + # byebug example.run raise ActiveRecord::Rollback end @@ -152,43 +149,49 @@ RSpec.configure do |config| exit 0 ensure print_info 'Shutting down server' - Process.kill('KILL', server_pid) if server_pid - Array(server_pids).compact.each { |pid| Process.kill('KILL', pid) } + Process.kill('KILL', server_pid) + Process.kill('KILL', server_pids) end - ######################################## +######################################## - def reset_beef_db - db_file = BeEF::Core::Configuration.instance.get('beef.database.file') - File.delete(db_file) if File.exist?(db_file) +def reset_beef_db + begin + db_file = BeEF::Core::Configuration.instance.get('beef.database.file') + File.delete(db_file) if File.exist?(db_file) rescue => e - print_error("Could not remove '#{db_file}' database file: #{e.message}") + print_error("Could not remove '#{db_file}' database file: #{e.message}") end +end - require 'socket' +require 'socket' def port_available? socket = TCPSocket.new(@host, @port) socket.close - false # If a connection is made, the port is in use. - rescue Errno::ECONNREFUSED, Errno::EADDRNOTAVAIL - true # Connection refused/unavailable => port is free. + false # If a connection is made, the port is in use, so it's not available. + rescue Errno::ECONNREFUSED + true # If the connection is refused, the port is not in use, so it's available. + rescue Errno::EADDRNOTAVAIL + true # If the connection is refused, the port is not in use, so it's available. end def configure_beef # Reset or re-initialise the configuration to a default state @config = BeEF::Core::Configuration.instance - @config.set('beef.credentials.user', 'beef') - @config.set('beef.credentials.passwd','beef') + + @config.set('beef.credentials.user', "beef") + @config.set('beef.credentials.passwd', "beef") @config.set('beef.http.https.enable', false) end # Load the server def load_beef_extensions_and_modules - # Load BeEF extensions - BeEF::Extensions.load - # Load BeEF modules only if they are not already loaded - BeEF::Modules.load if @config.get('beef.module').nil? + # Load BeEF extensions + BeEF::Extensions.load + + # Load BeEF modules only if they are not already loaded + BeEF::Modules.load if @config.get('beef.module').nil? end # --- HARD fork-safety: disconnect every pool/adapter we can find --- @@ -212,15 +215,16 @@ RSpec.configure do |config| def start_beef_server configure_beef @port = @config.get('beef.http.port') + @host = @config.get('beef.http.host') @host = '127.0.0.1' unless port_available? print_error "Port #{@port} is already in use. Exiting." - exit 1 + exit end load_beef_extensions_and_modules - - # DB file for BeEF runtime (not the in-memory test DB) + + # Grab DB file and regenerate if requested db_file = @config.get('beef.database.file') if BeEF::Core::Console::CommandLine.parse[:resetdb] @@ -230,73 +234,82 @@ RSpec.configure do |config| # ***** IMPORTANT: close any and all AR/OTR connections before forking ***** disconnect_all_active_record! + # Load up DB and migrate if necessary + ActiveRecord::Base.logger = nil + OTR::ActiveRecord.configure_from_hash!(adapter:'sqlite3', database: db_file) + # otr-activerecord require you to manually establish the connection with the following line + #Also a check to confirm that the correct Gem version is installed to require it, likely easier for old systems. + if Gem.loaded_specs['otr-activerecord'].version > Gem::Version.create('1.4.2') + OTR::ActiveRecord.establish_connection! + end + + # Migrate (if required) + ActiveRecord::Migration.verbose = false # silence activerecord migration stdout messages + ActiveRecord::Migrator.migrations_paths = [File.join('core', 'main', 'ar-migrations')] + context = ActiveRecord::MigrationContext.new(ActiveRecord::Migrator.migrations_paths) + if context.needs_migration? + ActiveRecord::Migrator.new(:up, context.migrations, context.schema_migration, context.internal_metadata).migrate + end + + BeEF::Core::Migration.instance.update_db! + + # Spawn HTTP Server + # print_info "Starting HTTP Hook Server" + http_hook_server = BeEF::Core::Server.instance + http_hook_server.prepare + + # Generate a token for the server to respond with + BeEF::Core::Crypto::api_token + + # Initiate server start-up + BeEF::API::Registrar.instance.fire(BeEF::API::Server, 'pre_http_start', http_hook_server) pid = fork do - # Child: establish a fresh connection to the file DB - OTR::ActiveRecord.configure_from_hash!(adapter: 'sqlite3', database: db_file) - if Gem.loaded_specs['otr-activerecord'].version > Gem::Version.create('1.4.2') - OTR::ActiveRecord.establish_connection! - end - - # Apply migrations for runtime DB - ActiveRecord::Migration.verbose = false - ActiveRecord::Migrator.migrations_paths = [File.join('core', 'main', 'ar-migrations')] - context = ActiveRecord::MigrationContext.new(ActiveRecord::Migrator.migrations_paths) - if context.needs_migration? - ActiveRecord::Migrator - .new(:up, context.migrations, context.schema_migration, context.internal_metadata) - .migrate - end - - BeEF::Core::Migration.instance.update_db! - - # Spawn HTTP Server - http_hook_server = BeEF::Core::Server.instance - http_hook_server.prepare - - # Generate a token for the server to respond with - BeEF::Core::Crypto::api_token - - # Fire pre_http_start hooks (Dns extension, etc.) - BeEF::API::Registrar.instance.fire(BeEF::API::Server, 'pre_http_start', http_hook_server) - - # Start server (blocking call) http_hook_server.start end - pid + return pid end def beef_server_running?(uri_str) - uri = URI.parse(uri_str) - response = Net::HTTP.get_response(uri) - response.is_a?(Net::HTTPSuccess) - rescue Errno::ECONNREFUSED, StandardError - false + begin + uri = URI.parse(uri_str) + response = Net::HTTP.get_response(uri) + response.is_a?(Net::HTTPSuccess) + rescue Errno::ECONNREFUSED + return false # Connection refused means the server is not running + rescue StandardError => e + return false # Any other error means the server is not running + end end def wait_for_beef_server_to_start(uri_str, timeout: 5) - start_time = Time.now - until beef_server_running?(uri_str) || (Time.now - start_time) > timeout - sleep 0.1 + start_time = Time.now # Record the time we started + until beef_server_running?(uri_str) || (Time.now - start_time) > timeout do + sleep 0.1 # Wait a bit before checking again end - beef_server_running?(uri_str) + beef_server_running?(uri_str) # Return the result of the check end def start_beef_server_and_wait - puts 'Starting BeEF server' + puts "Starting BeEF server" pid = start_beef_server puts "BeEF server started with PID: #{pid}" - unless wait_for_beef_server_to_start('http://localhost:3000', timeout: SERVER_START_TIMEOUT) - print_error 'Server failed to start within timeout.' + if wait_for_beef_server_to_start('http://localhost:3000', timeout: SERVER_START_TIMEOUT) + # print_info "Server started successfully." + else + print_error "Server failed to start within timeout." end pid end def stop_beef_server(pid) - return if pid.nil? - Process.kill('KILL', pid) - Process.wait(pid) + exit if pid.nil? + # Shutting down server + Process.kill("KILL", pid) unless pid.nil? + Process.wait(pid) unless pid.nil? # Ensure the process has exited and the port is released + pid = nil end + end \ No newline at end of file From f42fa9f599fa11ed08cb378212fb3260ea871eef Mon Sep 17 00:00:00 2001 From: zinduolis Date: Wed, 3 Sep 2025 10:18:20 +1000 Subject: [PATCH 11/22] Add connection pool cleanup after each example in RSpec --- spec/spec_helper.rb | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index ecaa803b1..be93ad5aa 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -139,6 +139,11 @@ RSpec.configure do |config| end end + # Ensure every example starts with a fresh connection pool + config.after(:each) do + disconnect_all_active_record! + end + def server_teardown(webdriver, server_pid, server_pids) webdriver.quit rescue StandardError => e @@ -196,15 +201,15 @@ require 'socket' # --- HARD fork-safety: disconnect every pool/adapter we can find --- def disconnect_all_active_record! - print_info "Entering disconnect_all_active_record!" + # print_info "Entering disconnect_all_active_record!" if defined?(ActiveRecord::Base) - print_info "Disconnecting ActiveRecord connections" + # print_info "Disconnecting ActiveRecord connections" handler = ActiveRecord::Base.connection_handler if handler.respond_to?(:connection_pool_list) - print_info "Using connection_pool_list" + # print_info "Using connection_pool_list" handler.connection_pool_list.each { |pool| pool.disconnect! } elsif handler.respond_to?(:connection_pools) - print_info "Using connection_pools" + # print_info "Using connection_pools" handler.connection_pools.each_value { |pool| pool.disconnect! } end else From 6d0531f6b36cb955c2b9d780fe116308f3a32589 Mon Sep 17 00:00:00 2001 From: zinduolis Date: Wed, 3 Sep 2025 10:31:34 +1000 Subject: [PATCH 12/22] Change connection pool cleanup to run before each example in RSpec --- spec/spec_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index be93ad5aa..33bac6d73 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -140,7 +140,7 @@ RSpec.configure do |config| end # Ensure every example starts with a fresh connection pool - config.after(:each) do + config.before(:each) do disconnect_all_active_record! end From e5c8a0c86debceff3a002080090fb0f006703f95 Mon Sep 17 00:00:00 2001 From: zinduolis Date: Wed, 3 Sep 2025 10:43:40 +1000 Subject: [PATCH 13/22] Refactor server teardown to improve error handling and cleanup process --- spec/spec_helper.rb | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 33bac6d73..f011e4fa1 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -139,23 +139,26 @@ RSpec.configure do |config| end end - # Ensure every example starts with a fresh connection pool - config.before(:each) do - disconnect_all_active_record! - end - def server_teardown(webdriver, server_pid, server_pids) - webdriver.quit - rescue StandardError => e - print_info "Exception: #{e}" - print_info "Exception Class: #{e.class}" - print_info "Exception Message: #{e.message}" - print_info "Exception Stack Trace: #{e.backtrace}" - exit 0 - ensure - print_info 'Shutting down server' - Process.kill('KILL', server_pid) - Process.kill('KILL', server_pids) + begin + webdriver&.quit + rescue => e + warn "[server_teardown] webdriver quit failed: #{e.class}: #{e.message}" + end + + begin + Process.kill('KILL', server_pid) if server_pid + rescue => e + warn "[server_teardown] kill failed: #{e.class}: #{e.message}" + end + + Array(server_pids).each do |pid| + begin + Process.kill('KILL', pid) if pid + rescue + # ignore + end + end end ######################################## From 15fd00a69a80d8085dd515fa898734faef501ee7 Mon Sep 17 00:00:00 2001 From: zinduolis Date: Sat, 6 Sep 2025 09:58:00 +1000 Subject: [PATCH 14/22] Refactor server start-up process in AutoRunEngine and Websocket tests for improved initialization --- spec/beef/core/main/autorun_engine/autorun_engine_spec.rb | 5 ++--- spec/beef/extensions/websocket_hooked_browser_spec.rb | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/spec/beef/core/main/autorun_engine/autorun_engine_spec.rb b/spec/beef/core/main/autorun_engine/autorun_engine_spec.rb index 6b42e8eb6..d94f14727 100644 --- a/spec/beef/core/main/autorun_engine/autorun_engine_spec.rb +++ b/spec/beef/core/main/autorun_engine/autorun_engine_spec.rb @@ -72,10 +72,9 @@ RSpec.describe 'AutoRunEngine Test', run_on_browserstack: true do disconnect_all_active_record! # Initiate server start-up - @pids = fork do - BeEF::API::Registrar.instance.fire(BeEF::API::Server, 'pre_http_start', http_hook_server) - end @pid = fork do + http_hook_server.prepare + BeEF::API::Registrar.instance.fire(BeEF::API::Server, 'pre_http_start', http_hook_server) http_hook_server.start end diff --git a/spec/beef/extensions/websocket_hooked_browser_spec.rb b/spec/beef/extensions/websocket_hooked_browser_spec.rb index 45b02cc89..3c8ea5e7b 100644 --- a/spec/beef/extensions/websocket_hooked_browser_spec.rb +++ b/spec/beef/extensions/websocket_hooked_browser_spec.rb @@ -65,10 +65,9 @@ RSpec.describe 'Browser hooking with Websockets', run_on_browserstack: true do disconnect_all_active_record! # Initiate server start-up - @pids = fork do - BeEF::API::Registrar.instance.fire(BeEF::API::Server, 'pre_http_start', http_hook_server) - end @pid = fork do + http_hook_server.prepare + BeEF::API::Registrar.instance.fire(BeEF::API::Server, 'pre_http_start', http_hook_server) http_hook_server.start end From 81a4caa836e15a6a4636f9774184644b708d0e90 Mon Sep 17 00:00:00 2001 From: zinduolis Date: Sat, 6 Sep 2025 10:19:23 +1000 Subject: [PATCH 15/22] Refactor server start-up process in BeEF debug modules for improved consistency and teardown handling --- .../core/main/handlers/browser_details_handler_spec.rb | 5 ++--- spec/beef/modules/debug/test_beef_debugs_spec.rb | 8 ++++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/spec/beef/core/main/handlers/browser_details_handler_spec.rb b/spec/beef/core/main/handlers/browser_details_handler_spec.rb index eca9e33bc..001f80637 100644 --- a/spec/beef/core/main/handlers/browser_details_handler_spec.rb +++ b/spec/beef/core/main/handlers/browser_details_handler_spec.rb @@ -67,10 +67,9 @@ RSpec.describe 'Browser Details Handler', run_on_browserstack: true do disconnect_all_active_record! # Initiate server start-up - @pids = fork do - BeEF::API::Registrar.instance.fire(BeEF::API::Server, 'pre_http_start', http_hook_server) - end @pid = fork do + http_hook_server.prepare + BeEF::API::Registrar.instance.fire(BeEF::API::Server, 'pre_http_start', http_hook_server) http_hook_server.start end diff --git a/spec/beef/modules/debug/test_beef_debugs_spec.rb b/spec/beef/modules/debug/test_beef_debugs_spec.rb index 054cf287c..91cb44793 100644 --- a/spec/beef/modules/debug/test_beef_debugs_spec.rb +++ b/spec/beef/modules/debug/test_beef_debugs_spec.rb @@ -14,6 +14,7 @@ RSpec.describe 'BeEF Debug Command Modules:', run_on_browserstack: true do before(:all) do # Grab config and set creds in variables for ease of access @config = BeEF::Core::Configuration.instance + @pids = [] # ensure defined for teardown consistency # Grab DB file and regenerate if requested print_info 'Loading database' db_file = @config.get('beef.database.file') @@ -55,7 +56,6 @@ RSpec.describe 'BeEF Debug Command Modules:', run_on_browserstack: true do # Spawn HTTP Server print_info 'Starting HTTP Hook Server' http_hook_server = BeEF::Core::Server.instance - http_hook_server.prepare # Generate a token for the server to respond with @token = BeEF::Core::Crypto.api_token @@ -64,10 +64,9 @@ RSpec.describe 'BeEF Debug Command Modules:', run_on_browserstack: true do disconnect_all_active_record! # Initiate server start-up - @pids = fork do - BeEF::API::Registrar.instance.fire(BeEF::API::Server, 'pre_http_start', http_hook_server) - end @pid = fork do + http_hook_server.prepare + BeEF::API::Registrar.instance.fire(BeEF::API::Server, 'pre_http_start', http_hook_server) http_hook_server.start end @@ -120,6 +119,7 @@ RSpec.describe 'BeEF Debug Command Modules:', run_on_browserstack: true do after(:all) do server_teardown(@driver, @pid, @pids) + disconnect_all_active_record! end it 'The Test_beef.debug() command module successfully executes' do From ac6dfe50a5745eef76b3109eff24507bd4a9ff36 Mon Sep 17 00:00:00 2001 From: zinduolis Date: Sat, 6 Sep 2025 10:36:13 +1000 Subject: [PATCH 16/22] Refactor database reset and migration handling in BeEF debug modules for improved synchronization and conditional execution --- spec/beef/modules/debug/test_beef_debugs_spec.rb | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/spec/beef/modules/debug/test_beef_debugs_spec.rb b/spec/beef/modules/debug/test_beef_debugs_spec.rb index 91cb44793..0821f8478 100644 --- a/spec/beef/modules/debug/test_beef_debugs_spec.rb +++ b/spec/beef/modules/debug/test_beef_debugs_spec.rb @@ -10,6 +10,8 @@ require_relative '../../../spec_helper' require_relative '../../../support/constants' require_relative '../../../support/beef_test' +MUTEX = Mutex.new + RSpec.describe 'BeEF Debug Command Modules:', run_on_browserstack: true do before(:all) do # Grab config and set creds in variables for ease of access @@ -19,7 +21,11 @@ RSpec.describe 'BeEF Debug Command Modules:', run_on_browserstack: true do print_info 'Loading database' db_file = @config.get('beef.database.file') print_info 'Resetting the database for BeEF.' - File.delete(db_file) if File.exist?(db_file) + + if ENV['RESET_DB'] + File.delete(db_file) if File.exist?(db_file) + end + @username = @config.get('beef.credentials.user') @password = @config.get('beef.credentials.passwd') @@ -48,8 +54,12 @@ RSpec.describe 'BeEF Debug Command Modules:', run_on_browserstack: true do end ActiveRecord::Migrator.migrations_paths = [File.join('core', 'main', 'ar-migrations')] - context = ActiveRecord::MigrationContext.new(ActiveRecord::Migrator.migrations_paths) - ActiveRecord::Migrator.new(:up, context.migrations, context.schema_migration, context.internal_metadata).migrate if context.needs_migration? + MUTEX.synchronize do + context = ActiveRecord::MigrationContext.new(ActiveRecord::Migrator.migrations_paths) + if context.needs_migration? + ActiveRecord::Migrator.new(:up, context.migrations, context.schema_migration, context.internal_metadata).migrate + end + end BeEF::Core::Migration.instance.update_db! From 5a333d45a966e64ca64b9dd90b5c343059a496ab Mon Sep 17 00:00:00 2001 From: zinduolis Date: Sat, 6 Sep 2025 10:43:05 +1000 Subject: [PATCH 17/22] Remove unnecessary server preparation step and ensure ActiveRecord disconnection in teardown --- spec/beef/core/main/handlers/browser_details_handler_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/beef/core/main/handlers/browser_details_handler_spec.rb b/spec/beef/core/main/handlers/browser_details_handler_spec.rb index 001f80637..14750f293 100644 --- a/spec/beef/core/main/handlers/browser_details_handler_spec.rb +++ b/spec/beef/core/main/handlers/browser_details_handler_spec.rb @@ -58,7 +58,6 @@ RSpec.describe 'Browser Details Handler', run_on_browserstack: true do # Spawn HTTP Server print_info 'Starting HTTP Hook Server' http_hook_server = BeEF::Core::Server.instance - http_hook_server.prepare # Generate a token for the server to respond with @token = BeEF::Core::Crypto.api_token @@ -99,6 +98,7 @@ RSpec.describe 'Browser Details Handler', run_on_browserstack: true do after(:all) do server_teardown(@driver, @pid, @pids) + disconnect_all_active_record! end it 'can successfully hook a browser' do From f3834f730bfec68adcdcee1c740c04c924b7acb1 Mon Sep 17 00:00:00 2001 From: zinduolis Date: Sat, 6 Sep 2025 10:52:04 +1000 Subject: [PATCH 18/22] Remove unnecessary server preparation step and ensure ActiveRecord disconnection in teardown --- spec/beef/extensions/websocket_hooked_browser_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/beef/extensions/websocket_hooked_browser_spec.rb b/spec/beef/extensions/websocket_hooked_browser_spec.rb index 3c8ea5e7b..d25ff37d9 100644 --- a/spec/beef/extensions/websocket_hooked_browser_spec.rb +++ b/spec/beef/extensions/websocket_hooked_browser_spec.rb @@ -56,7 +56,6 @@ RSpec.describe 'Browser hooking with Websockets', run_on_browserstack: true do # Spawn HTTP Server print_info 'Starting HTTP Hook Server' http_hook_server = BeEF::Core::Server.instance - http_hook_server.prepare # Generate a token for the server to respond with @token = BeEF::Core::Crypto.api_token @@ -90,12 +89,13 @@ RSpec.describe 'Browser hooking with Websockets', run_on_browserstack: true do sleep 1 until wait.until { @driver.execute_script('return window.beef.session.get_hook_session_id().length') > 0 } - @session = @driver.execute_script('return window.beef.session.get_hook_session_id().length') + @session = @driver.execute_script('return window.beef.session.get_hook_session_id()') end end after(:all) do server_teardown(@driver, @pid, @pids) + disconnect_all_active_record! end it 'confirms a websocket server has been started' do From 41b595ae004f9f1abd335cc2fcd0746426669572 Mon Sep 17 00:00:00 2001 From: zinduolis Date: Sat, 6 Sep 2025 10:56:17 +1000 Subject: [PATCH 19/22] Remove unnecessary server preparation step and ensure ActiveRecord disconnection in teardown --- spec/beef/core/main/autorun_engine/autorun_engine_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/beef/core/main/autorun_engine/autorun_engine_spec.rb b/spec/beef/core/main/autorun_engine/autorun_engine_spec.rb index d94f14727..370a9588c 100644 --- a/spec/beef/core/main/autorun_engine/autorun_engine_spec.rb +++ b/spec/beef/core/main/autorun_engine/autorun_engine_spec.rb @@ -63,7 +63,6 @@ RSpec.describe 'AutoRunEngine Test', run_on_browserstack: true do # Spawn HTTP Server print_info 'Starting HTTP Hook Server' http_hook_server = BeEF::Core::Server.instance - http_hook_server.prepare # Generate a token for the server to respond with @token = BeEF::Core::Crypto.api_token @@ -103,6 +102,7 @@ RSpec.describe 'AutoRunEngine Test', run_on_browserstack: true do after(:all) do server_teardown(@driver, @pid, @pids) + disconnect_all_active_record! end it 'AutoRunEngine is working' do From 3df5bcb904a734f87fe0f56d64e3b3d5c6a2dd13 Mon Sep 17 00:00:00 2001 From: zinduolis Date: Sat, 6 Sep 2025 11:04:23 +1000 Subject: [PATCH 20/22] Refactor database migration handling in browser details and websocket hooked browser specs for improved synchronization --- .../handlers/browser_details_handler_spec.rb | 17 +++++++++++++---- .../extensions/websocket_hooked_browser_spec.rb | 10 ++++++++-- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/spec/beef/core/main/handlers/browser_details_handler_spec.rb b/spec/beef/core/main/handlers/browser_details_handler_spec.rb index 14750f293..33497330b 100644 --- a/spec/beef/core/main/handlers/browser_details_handler_spec.rb +++ b/spec/beef/core/main/handlers/browser_details_handler_spec.rb @@ -10,13 +10,19 @@ require_relative '../../../../spec_helper' require_relative '../../../../support/constants' require_relative '../../../../support/beef_test' +MUTEX = Mutex.new + RSpec.describe 'Browser Details Handler', run_on_browserstack: true do before(:all) do @config = BeEF::Core::Configuration.instance db_file = @config.get('beef.database.file') print_info 'Resetting the database for BeEF.' - File.delete(db_file) if File.exist?(db_file) + + if ENV['RESET_DB'] + File.delete(db_file) if File.exist?(db_file) + end + @config.set('beef.credentials.user', 'beef') @config.set('beef.credentials.passwd', 'beef') @username = @config.get('beef.credentials.user') @@ -50,9 +56,12 @@ RSpec.describe 'Browser Details Handler', run_on_browserstack: true do # Migrate (if required) ActiveRecord::Migrator.migrations_paths = [File.join('core', 'main', 'ar-migrations')] - context = ActiveRecord::MigrationContext.new(ActiveRecord::Migrator.migrations_paths) - ActiveRecord::Migrator.new(:up, context.migrations, context.schema_migration, context.internal_metadata).migrate if context.needs_migration? - + MUTEX.synchronize do + context = ActiveRecord::MigrationContext.new(ActiveRecord::Migrator.migrations_paths) + if context.needs_migration? + ActiveRecord::Migrator.new(:up, context.migrations, context.schema_migration, context.internal_metadata).migrate + end + end BeEF::Core::Migration.instance.update_db! # Spawn HTTP Server diff --git a/spec/beef/extensions/websocket_hooked_browser_spec.rb b/spec/beef/extensions/websocket_hooked_browser_spec.rb index d25ff37d9..fa5f668ba 100644 --- a/spec/beef/extensions/websocket_hooked_browser_spec.rb +++ b/spec/beef/extensions/websocket_hooked_browser_spec.rb @@ -11,6 +11,8 @@ require_relative '../../support/beef_test' require 'core/main/network_stack/websocket/websocket' require 'websocket-client-simple' +MUTEX = Mutex.new + RSpec.describe 'Browser hooking with Websockets', run_on_browserstack: true do before(:all) do @config = BeEF::Core::Configuration.instance @@ -49,8 +51,12 @@ RSpec.describe 'Browser hooking with Websockets', run_on_browserstack: true do end ActiveRecord::Migrator.migrations_paths = [File.join('core', 'main', 'ar-migrations')] - context = ActiveRecord::MigrationContext.new(ActiveRecord::Migrator.migrations_paths) - ActiveRecord::Migrator.new(:up, context.migrations, context.schema_migration, context.internal_metadata).migrate if context.needs_migration? + MUTEX.synchronize do + context = ActiveRecord::MigrationContext.new(ActiveRecord::Migrator.migrations_paths) + if context.needs_migration? + ActiveRecord::Migrator.new(:up, context.migrations, context.schema_migration, context.internal_metadata).migrate + end + end BeEF::Core::Migration.instance.update_db! # Spawn HTTP Server From c91b74e0ba9e7e30ff5b4fd48758ace4d80af97a Mon Sep 17 00:00:00 2001 From: zinduolis Date: Sat, 6 Sep 2025 11:10:56 +1000 Subject: [PATCH 21/22] Refactor database reset handling in AutoRunEngine and Websocket hooked browser specs for improved synchronization --- .../main/autorun_engine/autorun_engine_spec.rb | 17 +++++++++++++---- .../extensions/websocket_hooked_browser_spec.rb | 6 +++++- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/spec/beef/core/main/autorun_engine/autorun_engine_spec.rb b/spec/beef/core/main/autorun_engine/autorun_engine_spec.rb index 370a9588c..4c7ce1f94 100644 --- a/spec/beef/core/main/autorun_engine/autorun_engine_spec.rb +++ b/spec/beef/core/main/autorun_engine/autorun_engine_spec.rb @@ -10,6 +10,8 @@ require_relative '../../../../spec_helper' require_relative '../../../../support/constants' require_relative '../../../../support/beef_test' +MUTEX = Mutex.new + RSpec.describe 'AutoRunEngine Test', run_on_browserstack: true do before(:all) do @config = BeEF::Core::Configuration.instance @@ -18,7 +20,10 @@ RSpec.describe 'AutoRunEngine Test', run_on_browserstack: true do print_info 'Loading database' db_file = @config.get('beef.database.file') print_info 'Resetting the database for BeEF.' - File.delete(db_file) if File.exist?(db_file) + + if ENV['RESET_DB'] + File.delete(db_file) if File.exist?(db_file) + end @config.set('beef.credentials.user', 'beef') @config.set('beef.credentials.passwd', 'beef') @@ -49,9 +54,13 @@ RSpec.describe 'AutoRunEngine Test', run_on_browserstack: true do OTR::ActiveRecord.establish_connection! end ActiveRecord::Migrator.migrations_paths = [File.join('core', 'main', 'ar-migrations')] - context = ActiveRecord::MigrationContext.new(ActiveRecord::Migrator.migrations_paths) - ActiveRecord::Migrator.new(:up, context.migrations, context.schema_migration, context.internal_metadata).migrate if context.needs_migration? - + MUTEX.synchronize do + context = ActiveRecord::MigrationContext.new(ActiveRecord::Migrator.migrations_paths) + if context.needs_migration? + ActiveRecord::Migrator.new(:up, context.migrations, context.schema_migration, context.internal_metadata).migrate + end + end + BeEF::Core::Migration.instance.update_db! # add AutoRunEngine rule diff --git a/spec/beef/extensions/websocket_hooked_browser_spec.rb b/spec/beef/extensions/websocket_hooked_browser_spec.rb index fa5f668ba..2e9d242e2 100644 --- a/spec/beef/extensions/websocket_hooked_browser_spec.rb +++ b/spec/beef/extensions/websocket_hooked_browser_spec.rb @@ -20,7 +20,11 @@ RSpec.describe 'Browser hooking with Websockets', run_on_browserstack: true do print_info 'Loading database' db_file = @config.get('beef.database.file') print_info 'Resetting the database for BeEF.' - File.delete(db_file) if File.exist?(db_file) + + if ENV['RESET_DB'] + File.delete(db_file) if File.exist?(db_file) + end + @config.set('beef.credentials.user', 'beef') @config.set('beef.credentials.passwd', 'beef') @config.set('beef.http.websocket.secure', false) From 31decb81b3f8d42e5115120cbd8dd508b9b39beb Mon Sep 17 00:00:00 2001 From: zinduolis Date: Sun, 7 Sep 2025 19:23:26 +1000 Subject: [PATCH 22/22] Centralised the initiation of MUTEX --- spec/beef/core/main/autorun_engine/autorun_engine_spec.rb | 2 -- spec/beef/core/main/handlers/browser_details_handler_spec.rb | 2 -- spec/beef/extensions/websocket_hooked_browser_spec.rb | 2 -- spec/beef/modules/debug/test_beef_debugs_spec.rb | 2 -- spec/spec_helper.rb | 2 ++ 5 files changed, 2 insertions(+), 8 deletions(-) diff --git a/spec/beef/core/main/autorun_engine/autorun_engine_spec.rb b/spec/beef/core/main/autorun_engine/autorun_engine_spec.rb index 4c7ce1f94..02258042d 100644 --- a/spec/beef/core/main/autorun_engine/autorun_engine_spec.rb +++ b/spec/beef/core/main/autorun_engine/autorun_engine_spec.rb @@ -10,8 +10,6 @@ require_relative '../../../../spec_helper' require_relative '../../../../support/constants' require_relative '../../../../support/beef_test' -MUTEX = Mutex.new - RSpec.describe 'AutoRunEngine Test', run_on_browserstack: true do before(:all) do @config = BeEF::Core::Configuration.instance diff --git a/spec/beef/core/main/handlers/browser_details_handler_spec.rb b/spec/beef/core/main/handlers/browser_details_handler_spec.rb index 33497330b..c0c5fe9d7 100644 --- a/spec/beef/core/main/handlers/browser_details_handler_spec.rb +++ b/spec/beef/core/main/handlers/browser_details_handler_spec.rb @@ -10,8 +10,6 @@ require_relative '../../../../spec_helper' require_relative '../../../../support/constants' require_relative '../../../../support/beef_test' -MUTEX = Mutex.new - RSpec.describe 'Browser Details Handler', run_on_browserstack: true do before(:all) do diff --git a/spec/beef/extensions/websocket_hooked_browser_spec.rb b/spec/beef/extensions/websocket_hooked_browser_spec.rb index 2e9d242e2..34156645e 100644 --- a/spec/beef/extensions/websocket_hooked_browser_spec.rb +++ b/spec/beef/extensions/websocket_hooked_browser_spec.rb @@ -11,8 +11,6 @@ require_relative '../../support/beef_test' require 'core/main/network_stack/websocket/websocket' require 'websocket-client-simple' -MUTEX = Mutex.new - RSpec.describe 'Browser hooking with Websockets', run_on_browserstack: true do before(:all) do @config = BeEF::Core::Configuration.instance diff --git a/spec/beef/modules/debug/test_beef_debugs_spec.rb b/spec/beef/modules/debug/test_beef_debugs_spec.rb index 0821f8478..075d13490 100644 --- a/spec/beef/modules/debug/test_beef_debugs_spec.rb +++ b/spec/beef/modules/debug/test_beef_debugs_spec.rb @@ -10,8 +10,6 @@ require_relative '../../../spec_helper' require_relative '../../../support/constants' require_relative '../../../support/beef_test' -MUTEX = Mutex.new - RSpec.describe 'BeEF Debug Command Modules:', run_on_browserstack: true do before(:all) do # Grab config and set creds in variables for ease of access diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index f011e4fa1..aa23cbfbb 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -25,6 +25,8 @@ require 'selenium-webdriver' require 'browserstack/local' require 'byebug' +MUTEX ||= Mutex.new + # Require supports Dir['spec/support/*.rb'].each do |f| require f