Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
8bf0f9f
Add security and auditing gems to Gemfile for enhanced application sa…
rsmoke Apr 24, 2025
c408981
Add Dependabot configuration for automated dependency updates
rsmoke Apr 24, 2025
faaad33
Add GitHub Actions workflow for security and test checks
rsmoke Apr 24, 2025
3863c1b
Add SecurityProtection concern for request validation and logging
rsmoke Apr 24, 2025
66e80a6
Add Rack::Attack initializer for enhanced security measures
rsmoke Apr 24, 2025
44dce2c
Integrate SecurityProtection concern into ApplicationController for e…
rsmoke Apr 24, 2025
94cfc73
Add Rake tasks for security audits and dependency updates
rsmoke Apr 24, 2025
c4551ed
Update GitHub Actions workflow to use MySQL 8.0 and enhance database …
rsmoke Apr 24, 2025
b2f2bad
Refactor database configuration for improved environment variable usage
rsmoke Apr 24, 2025
21727fd
Update GitHub Actions workflow to streamline environment variable con…
rsmoke Apr 24, 2025
0b11363
Enhance database configuration by standardizing environment variable …
rsmoke Apr 24, 2025
09a421c
Refactor database configuration for improved security and consistency
rsmoke Apr 24, 2025
bd4d141
Update database configuration to ensure consistent password handling
rsmoke Apr 24, 2025
ff78a5c
Add database environment variables to GitHub workflow
rsmoke Apr 24, 2025
10aae03
Ensure test environment for database setup in GitHub Actions
rsmoke Apr 24, 2025
3391f96
test check
rsmoke Apr 24, 2025
926b5e5
Add explicit DATABASE_URL for test environment
rsmoke Apr 24, 2025
c766c23
Add test-specific database configuration
rsmoke Apr 24, 2025
4cea877
Refactor controller access control to use policy scopes
rsmoke Apr 24, 2025
4c78bfc
Refactor views to use partials for rendering
rsmoke Apr 24, 2025
d1c4ad9
Update GitHub Actions security workflow to use bundle exec for bundle…
rsmoke Apr 24, 2025
d22552d
Refactor RSpec configuration for cleaner syntax
rsmoke Apr 24, 2025
89d2d47
Update user role policy spec to create user role instance
rsmoke Apr 24, 2025
6605bdb
Refactor security protection concern to use safe parameter handling
rsmoke Apr 24, 2025
be5eab9
Update routes to exclude root path from error handling
rsmoke Apr 24, 2025
4efd428
Refactor exception handling in ApplicationController for improved cla…
rsmoke Apr 24, 2025
4a6ac1c
Enhance authorization handling in ContestInstancesController
rsmoke Apr 24, 2025
89773ce
Update unauthorized action message in ApplicationController for clarity
rsmoke Apr 24, 2025
da59b5b
Refactor authorization handling in ContestInstancesController
rsmoke Apr 24, 2025
2abf9c9
Enhance entry authorization handling in EntriesController
rsmoke Apr 24, 2025
b1deb68
Refactor entry authorization logic in EntryPolicy for clarity
rsmoke Apr 24, 2025
acceb35
Add unauthorized view template for user feedback
rsmoke Apr 24, 2025
f8f67a9
Refactor judge management system tests for clarity and consistency
rsmoke Apr 24, 2025
3dd96e8
Enhance system test configuration with increased wait time and debugg…
rsmoke Apr 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
version: 2
updates:
- package-ecosystem: "bundler"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
time: "09:00"
timezone: "America/New_York"
open-pull-requests-limit: 10
reviewers:
- "rsmoke"
assignees:
- "rsmoke"
labels:
- "dependencies"
- "security"
commit-message:
prefix: "chore"
include: "scope"
rebase-strategy: "auto"
versioning-strategy: "auto"
77 changes: 77 additions & 0 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
name: Security and Test Checks

on:
push:
branches: [ main, staging ]
pull_request:
branches: [ main, staging ]
schedule:
- cron: '0 0 * * 1' # Run weekly on Monday at midnight UTC *

jobs:
security_and_tests:
runs-on: ubuntu-latest

services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: lsa_evaluate_test
ports:
- 3306:3306
options: >-
--health-cmd="mysqladmin ping"
--health-interval=10s
--health-timeout=5s
--health-retries=3

env:
RAILS_ENV: test
RACK_ENV: test
LOCAL_MYSQL_DATABASE_PASSWORD: password
DATABASE_USERNAME: root
DATABASE_URL: mysql2://root:password@127.0.0.1:3306/lsa_evaluate_test

steps:
- uses: actions/checkout@v4

- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.3.4'
bundler-cache: true

- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y mysql-client libmysqlclient-dev
bundle install

- name: Wait for MySQL
run: |
while ! mysqladmin ping -h"127.0.0.1" -P"3306" --silent; do
sleep 1
done

- name: Set up database
env:
RAILS_ENV: test
DATABASE_URL: mysql2://root:password@127.0.0.1:3306/lsa_evaluate_test
DISABLE_DATABASE_ENVIRONMENT_CHECK: 1
run: |
cp config/database.test.yml config/database.yml
bundle exec rails db:create
bundle exec rails db:schema:load

- name: Run security audit
run: bundle exec rake security:audit

- name: Run Brakeman
run: bundle exec brakeman -A

- name: Run bundle audit
run: bundle exec bundle-audit check --update

- name: Run tests
run: bundle exec rspec
4 changes: 4 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ gem 'omniauth-saml', '~> 2.1'
gem 'pagy', '~> 6.4'
gem 'puma'
gem 'pundit'
gem 'rack-attack'
gem 'redis', '~> 5.0'
gem 'sentry-ruby'
gem 'sentry-rails'
Expand Down Expand Up @@ -59,6 +60,8 @@ group :development, :staging do
end

group :development, :test do
gem 'brakeman'
gem 'bundler-audit'
gem 'capybara'
gem 'debug', platforms: %i[mri mingw x64_mingw]
gem 'pry-byebug'
Expand All @@ -72,6 +75,7 @@ group :development, :test do
gem 'rubocop-rails-omakase', require: false
gem 'rubocop-rspec', require: false
gem 'rubocop-rspec_rails', require: false
gem 'safety_net'
end

group :development, :staging, :test do
Expand Down
12 changes: 12 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,12 @@ GEM
debug_inspector (>= 1.2.0)
bootsnap (1.18.4)
msgpack (~> 1.2)
brakeman (7.0.2)
racc
builder (3.3.0)
bundler-audit (0.9.2)
bundler (>= 1.2.0, < 3)
thor (~> 1.0)
byebug (11.1.3)
capistrano (3.19.1)
airbrussh (>= 1.0.0)
Expand Down Expand Up @@ -318,6 +323,8 @@ GEM
rack (3.1.12)
rack-accept (0.4.5)
rack (>= 0.4)
rack-attack (6.7.0)
rack (>= 1.0, < 4)
rack-protection (4.1.1)
base64 (>= 0.1.0)
logger (>= 1.6.0)
Expand Down Expand Up @@ -445,6 +452,7 @@ GEM
ffi (~> 1.12)
logger
rubyzip (2.3.2)
safety_net (1.0.1)
sassc (2.4.0)
ffi (~> 1.9)
sassc-rails (2.1.2)
Expand Down Expand Up @@ -551,6 +559,8 @@ DEPENDENCIES
better_errors
binding_of_caller
bootsnap
brakeman
bundler-audit
capistrano (~> 3.17)
capistrano-asdf
capistrano-rails (~> 1.6, >= 1.6.1)
Expand Down Expand Up @@ -578,6 +588,7 @@ DEPENDENCIES
puma
pundit
pundit-matchers
rack-attack
rails (~> 7.2)
rails-controller-testing
redis (~> 5.0)
Expand All @@ -589,6 +600,7 @@ DEPENDENCIES
rubocop-rails-omakase
rubocop-rspec
rubocop-rspec_rails
safety_net
sassc-rails
selenium-webdriver
sentry-rails
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/addresses_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def destroy
private

def set_address
@address = Address.find(params[:id])
@address = Address.accessible_by(current_ability).find(params[:id])
end

def address_params
Expand Down
16 changes: 5 additions & 11 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ class ApplicationController < ActionController::Base
include Pundit::Authorization
include ApplicationHelper
include Pagy::Backend
include SecurityProtection
before_action :authenticate_user!

# Custom flash logging (optional)
Expand Down Expand Up @@ -39,24 +40,17 @@ def after_sign_out_path_for(resource_or_scope)
def handle_exceptions
yield
rescue Pundit::NotAuthorizedError => exception
# Rails.logger.info('!!!! Handling Pundit::NotAuthorizedError in ApplicationController')
user_not_authorized(exception)
rescue ActiveRecord::RecordNotFound => exception
Rails.logger.info('!!!!!!! Handling ActiveRecord::RecordNotFound in ApplicationController')
redirect_to not_found_path
end

# Private method for handling Pundit not authorized errors
def user_not_authorized(exception)
logger.info('!!!!!!! Handling Pundit::NotAuthorizedError in ApplicationController')
policy_name = exception.policy.class.to_s.underscore
message = '!!! Not authorized !!!'

flash[:alert] = message
Rails.logger.error("#!#!#!# Pundit error: #{message} - User: #{current_user&.id}, Action: #{exception.query}, Policy: #{policy_name.humanize}")

# Redirect back or to root if referer is not available
redirect_to(request.referer || root_path)
redirect_to root_path
rescue ActiveRecord::RecordNotFound => exception
Rails.logger.info('!!!!!!! Handling ActiveRecord::RecordNotFound in ApplicationController')
redirect_to not_found_path
end

# Log exceptions in detail
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/assignments_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def destroy
private

def set_container
@container = Container.find(params[:container_id])
@container = policy_scope(Container).find(params[:container_id])
end

def assignment_params
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/bulk_contest_instances_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def create
private

def set_container
@container = Container.find(params[:container_id])
@container = policy_scope(Container).find(params[:container_id])
end

def authorize_container_access
Expand Down
94 changes: 94 additions & 0 deletions app/controllers/concerns/security_protection.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# frozen_string_literal: true

module SecurityProtection
extend ActiveSupport::Concern

included do
before_action :check_request_security
end

private

def check_request_security
# Check for invalid UTF-8 sequences
unless valid_utf8_request?
Rails.logger.warn("[Security] Invalid UTF-8 sequence detected from IP: #{request.remote_ip}")
Sentry.capture_message("Invalid UTF-8 sequence detected",
level: 'warning',
extra: {
ip: request.remote_ip,
path: request.path,
user_agent: request.user_agent
}
)
return render_404
end

# Check for PHP-style attacks
if php_attack_attempt?
Rails.logger.warn("[Security] PHP probe attempt detected from IP: #{request.remote_ip}")
Sentry.capture_message("PHP probe attempt detected",
level: 'warning',
extra: {
ip: request.remote_ip,
path: request.path,
user_agent: request.user_agent,
params: request.params.to_h
}
)
return render_403
end
end

def valid_utf8_request?
# Validate path
path = request.path.dup
path.force_encoding('UTF-8')
return false unless path.valid_encoding?

# Validate query parameters
request.query_parameters.each do |key, value|
next if value.nil?
str = value.to_s.dup
str.force_encoding('UTF-8')
return false unless str.valid_encoding?
end

true
end

def php_attack_attempt?
# Check for common PHP attack patterns
path = request.path.downcase
return true if path.end_with?('.php')
return true if path.include?('php')
return true if request.params.values.any? { |v| v.to_s.include?('<?php') }

# Check for common PHP vulnerability probe patterns
suspicious_patterns = [
'eval(', 'system(', 'exec(',
'phpinfo', 'base64_decode',
'.php', '<?php'
]

suspicious_patterns.any? do |pattern|
request.params.values.any? { |v| v.to_s.downcase.include?(pattern) }
end
end

def render_404
respond_to do |format|
format.html { render 'errors/not_found', status: :not_found }
format.json { render json: { error: 'Not Found' }, status: :not_found }
format.any { head :not_found }
end
end

def render_403
respond_to do |format|
format.html { render 'errors/forbidden', status: :forbidden }
format.json { render json: { error: 'Forbidden' }, status: :forbidden }
format.any { head :forbidden }
end
end
end
2 changes: 1 addition & 1 deletion app/controllers/containers_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ def authorize_container
end

def set_container
@container = Container.find(params[:id])
@container = policy_scope(Container).find(params[:id])
end

def authorize_index
Expand Down
4 changes: 2 additions & 2 deletions app/controllers/contest_descriptions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,11 @@ def handle_save(success, action)
end

def set_container
@container = Container.find(params[:container_id])
@container = policy_scope(Container).find(params[:container_id])
end

def set_contest_description
@contest_description = ContestDescription.find(params[:id])
@contest_description = policy_scope(ContestDescription).find(params[:id])
end

def contest_description_params
Expand Down
Loading
Loading