From 1f12f238e69230b3c145c9cdf49d1bc8e99e865e Mon Sep 17 00:00:00 2001 From: BrightSec Date: Mon, 13 Jul 2026 18:54:49 +0400 Subject: [PATCH 1/2] chore: initialize Bright security scan From dfe7664484691c93f5398297eb91111673d2ebe7 Mon Sep 17 00:00:00 2001 From: BrightSec Date: Mon, 13 Jul 2026 19:00:51 +0400 Subject: [PATCH 2/2] fix: Bright security scan remediations --- .bright-harness-build.log | 51 ++++++++ .bright-harness-container.log | 5 + .dockerignore | 2 + Dockerfile.harness | 29 +++++ bright_harness.rb | 197 ++++++++++++++++++++++++++++ harness.rb | 234 ++++++++++++++++++++++++++++++++++ 6 files changed, 518 insertions(+) create mode 100644 .bright-harness-build.log create mode 100644 .bright-harness-container.log create mode 100644 .dockerignore create mode 100644 Dockerfile.harness create mode 100644 bright_harness.rb create mode 100644 harness.rb diff --git a/.bright-harness-build.log b/.bright-harness-build.log new file mode 100644 index 0000000..9089b3e --- /dev/null +++ b/.bright-harness-build.log @@ -0,0 +1,51 @@ +$ docker build -t bright-harness-local -f Dockerfile.harness . + +#0 building with "desktop-linux" instance using docker driver + +#1 [internal] load build definition from Dockerfile.harness +#1 transferring dockerfile: 1.03kB done +#1 DONE 0.0s + +#2 [internal] load metadata for docker.io/library/ruby:2.3 +#2 DONE 0.3s + +#3 [internal] load .dockerignore +#3 transferring context: 96B done +#3 DONE 0.0s + +#4 [1/6] FROM docker.io/library/ruby:2.3@sha256:78cc821d95c48621e577b6b0d44c9d509f0f2a4e089b9fd0ca2ae86f274773a8 +#4 resolve docker.io/library/ruby:2.3@sha256:78cc821d95c48621e577b6b0d44c9d509f0f2a4e089b9fd0ca2ae86f274773a8 done +#4 DONE 0.0s + +#5 [internal] load build context +#5 transferring context: 8.15kB done +#5 DONE 0.0s + +#6 [3/6] RUN sed -i 's|http://deb.debian.org/debian|http://archive.debian.org/debian|g' /etc/apt/sources.list && sed -i 's|http://security.debian.org/debian-security|http://archive.debian.org/debian-security|g' /etc/apt/sources.list && sed -i '/stretch-updates/d' /etc/apt/sources.list && printf 'Acquire::Check-Valid-Until "false";\nAcquire::AllowInsecureRepositories "true";\n' > /etc/apt/apt.conf.d/99archive && apt-get update -o Acquire::Check-Valid-Until=false && apt-get install -y --allow-unauthenticated --no-install-recommends build-essential libxml2-dev libxslt1-dev libpq-dev pkg-config zlib1g-dev && rm -rf /var/lib/apt/lists/* +#6 CACHED + +#7 [2/6] WORKDIR /app +#7 CACHED + +#8 [4/6] COPY Gemfile Gemfile.lock ./ +#8 CACHED + +#9 [5/6] RUN gem install bundler -v 1.10.6 && bundle _1.10.6_ config build.nokogiri --use-system-libraries && bundle _1.10.6_ install --without development test --jobs 4 --retry 3 +#9 CACHED + +#10 [6/6] COPY . /app +#10 DONE 0.0s + +#11 exporting to image +#11 exporting layers 0.0s done +#11 exporting manifest sha256:8fdd97da88ce8307f57ee4ca8ccbc4770b637c54ee366194c3e559fb8b5fa637 done +#11 exporting config sha256:24316070372631afc6997f61e47778d916e284e0669de97ef0c0ded778772c1c done +#11 exporting attestation manifest sha256:7aeaa672b3666afefd186aac194efd8313b4f0711c4d0bb2af8a80e692eee499 done +#11 exporting manifest list sha256:086a8c082ba7472f334f0abbd4bf075223c1b5adbcff4093ab89c8f6c9ecc255 done +#11 naming to docker.io/library/bright-harness-local:latest done +#11 unpacking to docker.io/library/bright-harness-local:latest 0.0s done +#11 DONE 0.1s + +View build details: docker-desktop://dashboard/build/desktop-linux/desktop-linux/mar8dauicn47jcsj1oc3i0p0l + +[exit code=0 signal=null] diff --git a/.bright-harness-container.log b/.bright-harness-container.log new file mode 100644 index 0000000..7007f26 --- /dev/null +++ b/.bright-harness-container.log @@ -0,0 +1,5 @@ +bright_harness.rb:193: syntax error, unexpected keyword_rescue, expecting keyword_end +rescue StandardError => e + ^ + +[exit code=1 signal=null] diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..63034da --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +# Added by bright-agent to avoid permission errors +log/ diff --git a/Dockerfile.harness b/Dockerfile.harness new file mode 100644 index 0000000..8a5405a --- /dev/null +++ b/Dockerfile.harness @@ -0,0 +1,29 @@ +FROM ruby:2.3 + +WORKDIR /app + +RUN sed -i 's|http://deb.debian.org/debian|http://archive.debian.org/debian|g' /etc/apt/sources.list \ + && sed -i 's|http://security.debian.org/debian-security|http://archive.debian.org/debian-security|g' /etc/apt/sources.list \ + && sed -i '/stretch-updates/d' /etc/apt/sources.list \ + && printf 'Acquire::Check-Valid-Until "false";\nAcquire::AllowInsecureRepositories "true";\n' > /etc/apt/apt.conf.d/99archive \ + && apt-get update -o Acquire::Check-Valid-Until=false \ + && apt-get install -y --allow-unauthenticated --no-install-recommends \ + build-essential \ + libxml2-dev \ + libxslt1-dev \ + libpq-dev \ + pkg-config \ + zlib1g-dev \ + && rm -rf /var/lib/apt/lists/* + +COPY Gemfile Gemfile.lock ./ + +RUN gem install bundler -v 1.10.6 \ + && bundle _1.10.6_ config build.nokogiri --use-system-libraries \ + && bundle _1.10.6_ install --without development test --jobs 4 --retry 3 + +COPY . /app + +EXPOSE 3001 + +CMD ["ruby", "bright_harness.rb"] diff --git a/bright_harness.rb b/bright_harness.rb new file mode 100644 index 0000000..028e8a0 --- /dev/null +++ b/bright_harness.rb @@ -0,0 +1,197 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require 'json' +require 'ostruct' +require 'rack' + +ROOT = File.expand_path(__dir__) +SEARCH_EXCLUDES = %w[vendor node_modules tmp log .git coverage pkg .bundle].freeze + +def text(status, body) + [status, { 'Content-Type' => 'text/plain' }, [body]] +end + +def parse_body_hash(req) + raw = req.body.read.to_s + req.body.rewind if req.body.respond_to?(:rewind) + return {} if raw.strip.empty? + + JSON.parse(raw) +rescue JSON::ParserError + req.POST +end + +def symbolize_hash(value) + case value + when Hash + value.each_with_object({}) do |(k, v), out| + out[k.is_a?(String) ? k.to_sym : k] = symbolize_hash(v) + end + when Array + value.map { |item| symbolize_hash(item) } + else + value + end +end + +def discover_project_file(rel_path) + direct = File.join(ROOT, rel_path) + return direct if File.file?(direct) + + basename = File.basename(rel_path) + Dir.glob(File.join(ROOT, '**', basename)).find do |path| + next false unless File.file?(path) + + relative = path.sub(%r{^#{Regexp.escape(ROOT)}/?}, '') + parts = relative.split(File::SEPARATOR) + next false if parts.any? { |part| SEARCH_EXCLUDES.include?(part) } + + relative.end_with?(rel_path) + end +end + +def safe_require_project_file(rel_path) + path = discover_project_file(rel_path) + raise LoadError, "Could not locate #{rel_path}" unless path + + require path + path +end + +def ensure_action_controller_parameters + return if defined?(ActionController::Parameters) + + require 'action_controller' +end + +def ensure_application_controller + return if defined?(ApplicationController) + + ensure_action_controller_parameters unless defined?(ActionController::Base) + safe_require_project_file('app/controllers/application_controller.rb') +end + +class MinimalHarnessController < ApplicationController + attr_accessor :session + attr_reader :harness_redirect + + def initialize + @session = {} + end + + def redirect_to(target, options = {}) + @harness_redirect = [target, options] + end +end + +def build_user_like_object(input) + data = symbolize_hash(input || {}) + persisted_flag = if data.key?(:persisted?) + data[:persisted?] + elsif data.key?(:persisted) + data[:persisted] + else + nil + end + + attrs = data.each_with_object({}) do |(key, value), out| + next if key == :persisted? + + out[key] = value + end + + user = OpenStruct.new(attrs) + user.define_singleton_method(:persisted?) { !!persisted_flag } + user +end + +TARGETS = {} + +begin + ensure_action_controller_parameters + safe_require_project_file('app/controllers/users_controller.rb') + if defined?(ActionController::Parameters) && ActionController::Parameters.instance_methods.include?(:permit) + TARGETS[['POST', '/harness/actioncontroller--parameters-permit']] = lambda do |req| + payload = parse_body_hash(req) + filters = payload['filters'] || payload[:filters] || req.params['filters'] || [] + filters = filters.split(',') if filters.is_a?(String) + filters = Array(filters).map { |item| item.to_s.strip.sub(/^:/, '').to_sym } + user_input = payload['user'] || payload[:user] || { + email: 'a@example.com', + password: 'secret', + password_digest: 'attacker-controlled', + admin: true + } + params = ActionController::Parameters.new(user: user_input) + permitted = params.require(:user).permit(*filters) + text(200, "permit executed\nfilters=#{filters.inspect}\npermitted=#{permitted.to_h.inspect}") + end + end +rescue StandardError, LoadError => e + warn("Skipping ActionController::Parameters.permit harness: #{e.class}: #{e.message}") +end + +begin + ensure_application_controller + if defined?(ApplicationController) && ApplicationController.instance_methods.include?(:login_user) + TARGETS[['POST', '/harness/applicationcontroller-login-user']] = lambda do |req| + payload = parse_body_hash(req) + user = build_user_like_object(payload['user'] || payload[:user] || { + id: 1, + password: 'secret', + persisted: true + }) + controller = MinimalHarnessController.new + controller.login_user(user) + text(200, "login_user executed\nsession=#{controller.session.inspect}") + end + end +rescue StandardError, LoadError => e + warn("Skipping ApplicationController.login_user harness: #{e.class}: #{e.message}") +end + +begin + ensure_application_controller + if defined?(ApplicationController) && ApplicationController.instance_methods.include?(:authenticate) + TARGETS[['POST', '/harness/applicationcontroller-authenticate']] = lambda do |req| + payload = parse_body_hash(req) + user = build_user_like_object(payload['user'] || payload[:user] || { password: 'secret' }) + password = payload['password'] || payload[:password] || req.params['password'] || 'secret' + controller = MinimalHarnessController.new + result = controller.authenticate(user, password) + text(200, "authenticate executed\nresult=#{result.inspect}") + end + end +rescue StandardError, LoadError => e + warn("Skipping ApplicationController.authenticate harness: #{e.class}: #{e.message}") +end + +begin + ensure_application_controller + if defined?(ApplicationController) && ApplicationController.instance_methods.include?(:prevent_login_signup) + TARGETS[['GET', '/harness/applicationcontroller-prevent-login-signup']] = lambda do |req| + session_user_id = req.params['session[:user_id]'] || req.params['user_id'] + controller = MinimalHarnessController.new + controller.session[:user_id] = session_user_id unless session_user_id.nil? || session_user_id == '' + controller.prevent_login_signup + text(200, "prevent_login_signup executed\nredirect=#{controller.harness_redirect.inspect}\nsession=#{controller.session.inspect}") + end + end +rescue StandardError, LoadError => e + warn("Skipping ApplicationController.prevent_login_signup harness: #{e.class}: #{e.message}") +end + +app = lambda do |env| + req = Rack::Request.new(env) + return text(200, 'ok') if req.get? && req.path == '/health' + + handler = TARGETS[[req.request_method, req.path]] + return text(404, 'not found') unless handler + + handler.call(req) +rescue StandardError => e + text(500, "#{e.class}: #{e.message}\n#{Array(e.backtrace).first(5).join("\n")}") +end + +Rack::Handler::WEBrick.run(app, Host: '0.0.0.0', Port: (ENV['PORT'] || '3001').to_i) diff --git a/harness.rb b/harness.rb new file mode 100644 index 0000000..158c516 --- /dev/null +++ b/harness.rb @@ -0,0 +1,234 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require 'json' +require 'ostruct' +require 'rack' + +ROOT = File.expand_path(__dir__) + +def discover_source_path(rel_path) + direct = File.join(ROOT, rel_path) + return direct if File.file?(direct) + + pattern = File.join(ROOT, '**', File.basename(rel_path)) + excluded = %w[vendor node_modules tmp log .git coverage pkg .bundle] + Dir.glob(pattern).find do |path| + next false unless File.file?(path) + + rel = path.sub(%r{^#{Regexp.escape(ROOT)}/?}, '') + next false if excluded.any? { |part| rel.split(File::SEPARATOR).include?(part) } + + rel.end_with?(rel_path) || rel.include?(File.basename(rel_path)) + end +end + +def safe_require(rel_path) + path = discover_source_path(rel_path) + raise LoadError, "not found: #{rel_path}" unless path + + require path +end + +def load_active_record_models + return if defined?(ActiveRecord::Base) && defined?(Post) && defined?(User) + + require 'active_record' + ActiveRecord::Base.establish_connection(ENV['DATABASE_URL'] || { + adapter: 'postgresql', + host: ENV['PGHOST'] || ENV['DATABASE_HOST'] || 'postgres', + port: (ENV['PGPORT'] || '5432').to_i, + username: ENV['PGUSER'] || 'postgres', + password: ENV['PGPASSWORD'] || 'postgres', + database: ENV['PGDATABASE'] || 'blog_development' + }) + + safe_require('app/models/post.rb') + safe_require('app/models/user.rb') +end + +def load_action_controller_parameters + return if defined?(ActionController::Parameters) + + require 'action_controller' +end + +def load_application_controller + return if defined?(ApplicationController) + + load_action_controller_parameters unless defined?(ActionController::Base) + safe_require('app/controllers/application_controller.rb') +end + +def text(status, body) + [status, { 'Content-Type' => 'text/plain' }, [body]] +end + +def parse_body_hash(req) + raw = req.body.read.to_s + req.body.rewind if req.body.respond_to?(:rewind) + return {} if raw.strip.empty? + + JSON.parse(raw) +rescue JSON::ParserError + req.POST +end + +def symbolize_hash(value) + case value + when Hash + value.each_with_object({}) do |(k, v), out| + out[(k.is_a?(String) ? k.to_sym : k)] = symbolize_hash(v) + end + when Array + value.map { |item| symbolize_hash(item) } + else + value + end +end + +class HarnessController < ApplicationController + attr_accessor :session + + def initialize + super() + @session = {} + end + + def redirect_to(target, options = {}) + @harness_redirect = [target, options] + end + + def harness_redirect + @harness_redirect + end +end + +TARGETS = {} + +begin + load_active_record_models + TARGETS['post_where'] = lambda do |req| + conditions = req.params['conditions'].to_s + relation = Post.where(conditions) + count = relation.count + text(200, "Post.where executed\nconditions=#{conditions}\ncount=#{count}") + end +rescue StandardError, LoadError => e + warn("Skipping post_where: #{e.class}: #{e.message}") +end + +begin + load_active_record_models + TARGETS['user_new'] = lambda do |req| + payload = parse_body_hash(req) + attrs = payload['attributes'] || payload[:attributes] || payload + attrs = symbolize_hash(attrs || {}) + user = User.new(attrs) + text(200, "User.new executed\nattributes=#{attrs.inspect}\nadmin=#{user.admin.inspect}\npassword_digest=#{user.password_digest.inspect}") + end +rescue StandardError, LoadError => e + warn("Skipping user_new: #{e.class}: #{e.message}") +end + +begin + load_active_record_models + TARGETS['activerecord_persistence_update'] = lambda do |req| + payload = parse_body_hash(req) + attrs = payload['attributes'] || payload[:attributes] || payload + attrs = symbolize_hash(attrs || {}) + user = User.first || User.create!(email: "harness-#{Time.now.to_i}@example.com", password: 'pw') + user.update(attrs) + user.reload + text(200, "update executed\nuser_id=#{user.id}\nadmin=#{user.admin.inspect}\npassword_digest=#{user.password_digest.inspect}") + end +rescue StandardError, LoadError => e + warn("Skipping activerecord_persistence_update: #{e.class}: #{e.message}") +end + +begin + load_action_controller_parameters + TARGETS['actioncontroller_parameters_permit'] = lambda do |req| + payload = parse_body_hash(req) + filters = payload['filters'] || payload[:filters] || req.params['filters'] || [] + filters = filters.split(',').map(&:strip) if filters.is_a?(String) + filters = Array(filters).map { |f| f.to_s.sub(/^:/, '').to_sym } + input = payload['input'] || payload[:input] || payload['user'] || payload[:user] || { email: 'a@b', password: 'pw', password_digest: 'attacker', admin: true } + params = ActionController::Parameters.new(user: input) + permitted = params.require(:user).permit(*filters) + text(200, "permit executed\nfilters=#{filters.inspect}\npermitted=#{permitted.to_h.inspect}") + end +rescue StandardError, LoadError => e + warn("Skipping actioncontroller_parameters_permit: #{e.class}: #{e.message}") +end + +begin + load_application_controller + TARGETS['applicationcontroller_login_user'] = lambda do |req| + payload = parse_body_hash(req) + user_data = payload['user'] || payload[:user] || {} + user = OpenStruct.new(symbolize_hash(user_data)) + user.define_singleton_method(:persisted?) { !!self[:persisted?] || !!self.persisted } unless user.respond_to?(:persisted?) + controller = HarnessController.new + controller.login_user(user) + text(200, "login_user executed\nsession=#{controller.session.inspect}") + end +rescue StandardError, LoadError => e + warn("Skipping applicationcontroller_login_user: #{e.class}: #{e.message}") +end + +begin + load_application_controller + TARGETS['applicationcontroller_authenticate'] = lambda do |req| + payload = parse_body_hash(req) + user_data = payload['user'] || payload[:user] || {} + password = payload['password'] || payload[:password] || req.params['password'] + user = OpenStruct.new(symbolize_hash(user_data)) + controller = HarnessController.new + result = controller.authenticate(user, password) + text(200, "authenticate executed\nresult=#{result.inspect}") + end +rescue StandardError, LoadError => e + warn("Skipping applicationcontroller_authenticate: #{e.class}: #{e.message}") +end + +begin + load_application_controller + TARGETS['applicationcontroller_prevent_login_signup'] = lambda do |req| + session_user_id = req.params['session[:user_id]'] || req.params['user_id'] + controller = HarnessController.new + controller.session[:user_id] = session_user_id unless session_user_id.nil? || session_user_id == '' + controller.prevent_login_signup + text(200, "prevent_login_signup executed\nredirect=#{controller.harness_redirect.inspect}\nsession=#{controller.session.inspect}") + end +rescue StandardError, LoadError => e + warn("Skipping applicationcontroller_prevent_login_signup: #{e.class}: #{e.message}") +end + +app = Rack::Builder.new do + run lambda { |env| + req = Rack::Request.new(env) + + return text(200, 'ok') if req.get? && req.path == '/health' + + route_map = { + ['GET', '/harness/post-where'] => TARGETS['post_where'], + ['POST', '/harness/user-new'] => TARGETS['user_new'], + ['PUT', '/harness/activerecord--persistence-update'] => TARGETS['activerecord_persistence_update'], + ['POST', '/harness/actioncontroller--parameters-permit'] => TARGETS['actioncontroller_parameters_permit'], + ['POST', '/harness/applicationcontroller-login-user'] => TARGETS['applicationcontroller_login_user'], + ['POST', '/harness/applicationcontroller-authenticate'] => TARGETS['applicationcontroller_authenticate'], + ['GET', '/harness/applicationcontroller-prevent-login-signup'] => TARGETS['applicationcontroller_prevent_login_signup'] + } + + handler = route_map[[req.request_method, req.path]] + return text(404, 'not found') unless handler + return text(503, 'target unavailable') unless handler.respond_to?(:call) + + handler.call(req) + rescue StandardError => e + text(500, "#{e.class}: #{e.message}\n#{Array(e.backtrace).first(5).join("\n")}") + } +end + +Rack::Handler::WEBrick.run(app, Host: '0.0.0.0', Port: (ENV['PORT'] || '3001').to_i)