Rails security
Skill hlsitechio/claude-skills-security/appsec-stack-pack/rails-security
Defensive security audit skills for Claude — tech-stack-keyed and audit-domain-keyed packs for SaaS apps.
npx -y skills add hlsitechio/claude-skills-security --skill rails-securityAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Security audit for Ruby on Rails applications including strong parameters / mass assignment, ActiveRecord SQL injection, ERB template safety, CSRF protection_from_forgery, Devise authentication, CanCanCan/Pundit authorization, secret_key_base, credentials.yml.enc, and Rails-specific patterns. Use this skill whenever the user mentions Ruby on Rails, Rails 6/7/8, ActiveRecord, ActiveAdmin, Devise, Pundit, CanCanCan, strong_parameters, ERB, Brakeman, or asks "audit my Rails app", "Rails security review", "Brakeman". Trigger when the codebase contains `Gemfile`, `config/application.rb`, or `rails` in dependencies.
SKILL.md
7.8 KB, as published. Nobody here has run it
Ruby on Rails Security Audit
Audit Rails applications (Rails 6, 7, 8).
When this skill applies
- Reviewing controllers, models, views
- Auditing strong parameters / mass assignment
- Reviewing ActiveRecord queries for injection
- Checking Devise / Pundit / CanCanCan setup
- Auditing secrets and credential management
Workflow
Follow ../_shared/audit-workflow.md.
Phase 1: Stack detection
grep -E "rails" Gemfile | head
bundle exec rails --version 2>/dev/null
Phase 2: Inventory
# Controllers
find app/controllers -name '*.rb' | head
# Models
find app/models -name '*.rb' | head
# Routes
cat config/routes.rb 2>/dev/null | head -100
# Initializers (security-relevant)
ls config/initializers/
# Brakeman recommended
which brakeman 2>/dev/null || echo "Install: gem install brakeman"
Phase 3: Detection — the checks
Strong parameters
- RLS-SP-1 Every controller action accepting params for create/update uses a
permitallowlist:def user_params params.require(:user).permit(:email, :name) # role, admin flags explicitly NOT in permit end - RLS-SP-2 No
params.permit!(allows everything — equivalent to no protection). - RLS-SP-3 Nested attributes use
permit(:foo, addresses_attributes: [:street, :city])notpermit!.
SQL injection (ActiveRecord)
- RLS-SQL-1
where("name = '#{params[:name]}'")is injection. Use placeholders:User.where("name = ?", params[:name]) User.where(name: params[:name]) - RLS-SQL-2 Dynamic ORDER BY:
User.order(params[:sort])— Rails 6+ raises on unknown columns, but the safe path isUser.order(sort_column => sort_direction)with allowlisted values. - RLS-SQL-3
find_by_sql("SELECT * WHERE id = #{params[:id]}")is injection. Use placeholders. - RLS-SQL-4 Raw SQL via
connection.executereviewed.
Template XSS (ERB)
- RLS-XSS-1 ERB auto-escapes
<%= %>.<%== %>(double equals) andraw(...),html_safe,.html_safeskip escaping. - RLS-XSS-2
sanitize(html, ...)used for partial HTML; safer thanraw. - RLS-XSS-3
link_to(text, params[:url])— Rails normalizesjavascript:URLs inlink_to? Confirm — depends on version; validate URLs server-side. - RLS-XSS-4
content_tag(:a, name, href: params[:url])does not validate URL.
CSRF
- RLS-CSRF-1
protect_from_forgery with: :exceptionin ApplicationController (Rails 5+ default; verify not removed). - RLS-CSRF-2 API-only controllers (
ActionController::API) skip CSRF by default — verify auth is token/JWT-based (not cookie). - RLS-CSRF-3 No
skip_before_action :verify_authenticity_tokenon state-changing endpoints unless replaced with equivalent (webhook signatures). - RLS-CSRF-4 Rails 7+
Origincheck by default for non-GET; verify config.
Authentication (Devise)
- RLS-AUTH-1 Devise modules appropriate:
:database_authenticatable,:registerable,:recoverable,:trackable,:lockable,:timeoutable,:validatable. - RLS-AUTH-2
:lockableenabled to prevent brute force. - RLS-AUTH-3 Password reset tokens single-use and time-limited (Devise defaults reasonable).
- RLS-AUTH-4 Email enumeration prevented: same response for "email sent" whether or not the email exists.
- RLS-AUTH-5
before_action :authenticate_user!on protected controllers.
Authorization (Pundit / CanCanCan)
- RLS-AZ-1 Every controller action uses
authorize @resource(Pundit) orload_and_authorize_resource(CanCanCan). - RLS-AZ-2 Pundit's
verify_authorizedandverify_policy_scopedinafter_actionto catch missed authz. - RLS-AZ-3 Policy scopes restrict index queries to current_user-visible.
- RLS-AZ-4 Default-deny: missing policies → 403.
class ApplicationController < ActionController::Base
include Pundit::Authorization
after_action :verify_authorized, except: :index
after_action :verify_policy_scoped, only: :index
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
private
def user_not_authorized
render plain: 'Forbidden', status: :forbidden
end
end
Mass assignment via attribute hash
- RLS-MA-1
User.new(params[:user])without strong params = mass assignment. - RLS-MA-2
User.update(params[:user])similarly. - RLS-MA-3
attr_accessible/attr_protected(deprecated; pre-Rails 4) not relied on.
Open redirects
- RLS-OR-1
redirect_to params[:return_to]— validate the URL against allowlist or useredirect_to params[:return_to], allow_other_host: false(Rails 7+ default for safety). - RLS-OR-2 Devise
after_sign_in_path_fordoesn't blindly usestored_location_for.
Insecure deserialization
- RLS-DES-1
Marshal.load(user_input)is RCE. Never deserialize untrusted data with Marshal. - RLS-DES-2
YAML.load(user_input)(not safe) → RCE. UseYAML.safe_load. - RLS-DES-3
JSON.loadinvokesObject.from_jsonand is unsafe; useJSON.parse.
Secrets and credentials
- RLS-SEC-1
config/credentials.yml.encencrypted;master.keynot committed (in.gitignore). - RLS-SEC-2
Rails.application.credentials.secret_key_baseset in production. - RLS-SEC-3 Environment-specific credentials (
config/credentials/production.yml.enc) used. - RLS-SEC-4 No secrets in
config/secrets.yml(deprecated) committed.
Cookies and sessions
- RLS-CK-1 Session store config secure:
Rails.application.config.session_store :cookie_store, key: ..., secure: true, httponly: true, same_site: :lax. - RLS-CK-2 Cookie store has size limit (4KB); if storing large data, use Redis/DB store.
- RLS-CK-3 Signed/encrypted cookies used for sensitive data (
cookies.signed[:foo],cookies.encrypted[:foo]).
File uploads (Active Storage, CarrierWave, Shrine)
- RLS-UP-1 Active Storage's allowlist of content types if exposing direct uploads.
- RLS-UP-2 Active Storage variants and previews not run on untrusted content (ImageMagick CVEs).
- RLS-UP-3 S3 / blob URLs not directly user-controllable.
Rails Admin / ActiveAdmin
- RLS-ADM-1 Admin gem authentication enforced separately from app auth.
- RLS-ADM-2 Admin actions audited (paper_trail, audited gem).
- RLS-ADM-3 Admin URL changed from default
/adminfor security through obscurity.
Headers
- RLS-HDR-1
config.force_ssl = truein production. - RLS-HDR-2 Custom CSP via
config.content_security_policyblock. - RLS-HDR-3
secure_headersgem or Rails defaults for X-Frame-Options, X-Content-Type-Options.
Brakeman
- RLS-BR-1 Brakeman run in CI.
brakeman --no-pager -Ashould pass with high/critical confidence findings = 0. - RLS-BR-2 Brakeman ignores (
config/brakeman.ignore) reviewed; false positives documented.
Dependencies
- RLS-DEP-1
bundle updaterecent; Gemfile.lock current. - RLS-DEP-2
bundle auditclean. - RLS-DEP-3 Rails version current LTS; old versions unpatched.
Phase 4: Triage
Critical: params.permit! on user model; YAML.load(user_input); raw SQL with string interpolation; CSRF disabled globally.
Phase 5: Report
Use ../_shared/findings-schema.md. Prefix IDs with RLS-.