Rails has many security features built-in - ensure they’re properly configured for Heroku deployment.
Expedited WAF for Rails
Expedited WAF adds network level request filtering, preventing malicious attacks from ever touching your application. This enables true defense in depth for your Rails app—even if your framework has a vulnerability or is improperly configured, Expedited WAF dynamically blocks threats before they reach your code.
From experience, we’ve seen many applications where attacks technically didn’t succeed, but burned through so many server resources that the application went down anyway. By filtering malicious requests at the network edge, Expedited WAF protects both your security and your availability.
New Security Features for Your Rails App
These capabilities aren’t available out of the box with Rails, but Expedited WAF adds them instantly:
- Block IP Addresses - Stop malicious actors by IP or CIDR range
- Block User Agents - Filter out bad bots and scrapers
- Block by Geolocation - Restrict traffic by country or region
- DDoS Protection - Stop attacks with CAPTCHA challenges
- Block Anonymous Proxies - Prevent traffic from VPNs and proxy networks
Rails Core Security
-
Configure SECRET_KEY_BASE securely
- Store in Heroku config:
heroku config:set SECRET_KEY_BASE=$(rake secret) - Load from environment in config/secrets.yml or credentials
- Never commit to version control
- Rotate periodically
- Store in Heroku config:
-
Enable force_ssl in production
# config/environments/production.rb config.force_ssl = true config.ssl_options = { hsts: { expires: 1.year, subdomains: true, preload: true } } -
Configure secure cookie settings
# config/initializers/session_store.rb Rails.application.config.session_store :cookie_store, key: '_app_session', secure: Rails.env.production?, httponly: true, same_site: :lax -
Enable CSRF protection
- Enabled by default - keep it!
- Verify
protect_from_forgery with: :exceptionin ApplicationController - Use
form_authenticity_tokenin forms - Set
config.action_controller.default_protect_from_forgery = true
-
Set allowed hosts
# config/environments/production.rb config.hosts << "yourapp.herokuapp.com" config.hosts << "yourdomain.com"
Database Security
-
Use Rails 6+ encrypted credentials
EDITOR=vim rails credentials:edit --environment production- Store database passwords, API keys
- Deploy master.key via Heroku config
-
Configure Heroku Postgres properly
# config/database.yml production: <<: *default url: <%= ENV['DATABASE_URL'] %> pool: <%= ENV['DB_POOL'] || 5 %> sslmode: require -
Use parameterized queries
- Rails ActiveRecord does this by default
- Avoid raw SQL with string interpolation
- Use
where("name = ?", params[:name])notwhere("name = '#{params[:name]}'")
-
Enable query logging carefully
- Set
config.active_record.log_level = :info(not :debug in production) - Don’t log sensitive data
- Set
Authentication & Authorization
-
Use Devise for authentication
- Configure secure password requirements
- Enable account locking after failed attempts
- Implement email confirmations
- Use secure remember_me tokens
-
Implement authorization
- Use Pundit or CanCanCan
- Never rely on params for authorization
- Use
authorize @resourcein controllers
-
Configure strong parameters
def user_params params.require(:user).permit(:email, :name) # Never permit all end -
Implement rate limiting
- Use Rack::Attack
# config/initializers/rack_attack.rb Rack::Attack.throttle('req/ip', limit: 300, period: 5.minutes) do |req| req.ip end Rack::Attack.throttle('logins/email', limit: 5, period: 20.seconds) do |req| req.params['email'] if req.path == '/login' && req.post? end
File Uploads & Assets
-
Use ActiveStorage with S3
# config/storage.yml amazon: service: S3 access_key_id: <%= ENV['AWS_ACCESS_KEY_ID'] %> secret_access_key: <%= ENV['AWS_SECRET_ACCESS_KEY'] %> region: us-east-1 bucket: your-bucket # config/environments/production.rb config.active_storage.service = :amazon -
Validate file uploads
validates :avatar, content_type: ['image/png', 'image/jpg', 'image/jpeg'], size: { less_than: 5.megabytes } -
Configure asset pipeline security
# config/environments/production.rb config.public_file_server.enabled = false # Let CDN/nginx serve config.assets.compile = false config.assets.digest = true
Security Headers
-
Configure security headers
# config/initializers/content_security_policy.rb Rails.application.config.content_security_policy do |policy| policy.default_src :self policy.script_src :self policy.style_src :self end # config/initializers/permissions_policy.rb Rails.application.config.permissions_policy do |f| f.camera :none f.gyroscope :none f.microphone :none end -
Set X-Frame-Options
- Enabled by default:
config.action_dispatch.default_headers['X-Frame-Options'] = 'SAMEORIGIN'
- Enabled by default:
Dependency Management
-
Keep Rails and gems updated
bundle update rails bundle update bundle audit check --update -
Use bundle audit
gem install bundler-audit bundle audit- Add to CI/CD pipeline
- Fix vulnerabilities promptly
-
Pin gem versions
- Use Gemfile.lock
- Review dependencies before updating
Environment & Configuration
-
Store secrets in Heroku config
heroku config:set SECRET_KEY_BASE='...' heroku config:set DATABASE_URL='...' heroku config:set RAILS_MASTER_KEY='...' -
Configure production environment properly
# config/environments/production.rb config.log_level = :info config.consider_all_requests_local = false config.action_controller.perform_caching = true config.cache_classes = true config.eager_load = true -
Never commit secrets
- Add config/master.key to .gitignore
- Use encrypted credentials for sensitive data
Logging & Monitoring
-
Configure production logging
# config/environments/production.rb config.log_level = :info config.log_tags = [:request_id] config.logger = ActiveSupport::Logger.new(STDOUT) -
Don’t log sensitive parameters
# config/initializers/filter_parameter_logging.rb Rails.application.config.filter_parameters += [ :password, :password_confirmation, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn, :cvv ] -
Set up error tracking
- Use Sentry, Rollbar, or Honeybadger
- Configure in production only
Heroku Deployment
-
Use Puma web server
# config/puma.rb workers Integer(ENV['WEB_CONCURRENCY'] || 2) threads_count = Integer(ENV['RAILS_MAX_THREADS'] || 5) threads threads_count, threads_count preload_app! on_worker_boot do ActiveRecord::Base.establish_connection end# Procfile web: bundle exec puma -C config/puma.rb -
Specify Ruby version
# Gemfile ruby '3.2.2' -
Configure buildpacks if needed
heroku buildpacks:add heroku/ruby heroku buildpacks:add heroku/nodejs # If using Webpacker
API Security (Rails API mode)
-
Use Rails API mode securely
class ApplicationController < ActionController::API include ActionController::HttpAuthentication::Token::ControllerMethods before_action :authenticate private def authenticate authenticate_or_request_with_http_token do |token, options| ActiveSupport::SecurityUtils.secure_compare(token, ENV['API_TOKEN']) end end end -
Implement CORS properly
# Gemfile gem 'rack-cors' # config/initializers/cors.rb Rails.application.config.middleware.insert_before 0, Rack::Cors do allow do origins 'yourdomain.com' resource '/api/*', headers: :any, methods: [:get, :post] end end -
Use JWTs for API authentication
- gem ‘jwt’
- Implement token expiration
- Refresh tokens securely
Testing
-
Write security tests
- Test authentication/authorization
- Test CSRF protection
- Test input validation
-
Test on Heroku staging
heroku create myapp-staging git push staging main
Pre-Deployment Checklist
-
Run brakeman security scanner
gem install brakeman brakeman -
Review Rails security guide