rails-security — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited rails-security (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
This skill is the Rails-specific layer on top of secure-coding. Convention over configuration covers a lot, but anyone going against the grain (string interpolation in .where, .html_safe on user input, CSRF off because "it was hard") gets vulnerabilities back.
Triggers on:
Gemfile, config/application.rb, config/environments/production.rb, config/routes.rb, Devise setup under config/initializers/devise.rb, Pundit/CanCanCan policies.html_safe, raw, .where("... #{x} ..."), skip_before_action :verify_authenticity_token, or skip_forgery_protection.security-review where Rails is in the stack.secure-coding.api-security) → that skill for OWASP API Top 10, here for Rails-specific implementation.sast-orchestrator. Brakeman is Rails-specific and belongs here.cve-triage (via bundle audit or OSV-scanner).container-hardening / k8s-security.config/master.key or credentials.yml.enc on disk → secrets-scanner. This skill only covers the Rails credentials model.Six phases. Brakeman does the heavy static-analysis lifting; this skill adds human context to what Brakeman outputs and catches what it misses.
Brakeman (OSS, Rails-specific, since 2011). Static analysis that knows Rails idioms. Default tool for this skill.
bundle add brakeman --group=development
bundle exec brakeman --no-pager -o brakeman-report.json -f jsonCI integration via the Brakeman action or plain bundle exec brakeman --exit-on-warn. Fail on new warnings via a baseline workflow (--compare against a previous run).
Triage Brakeman warnings with the same discipline as sast-orchestrator phase 5: baseline at introduction, suppress with rule ID plus reason (ignore.json), revisit periodically.
bundler-audit (OSS) for Gemfile.lock vulns; pass results on to cve-triage. Dependabot/Renovate for auto-updates.
The classic Rails vulnerability. Pre-Rails-4 the whitelist was opt-in; since then it has been required via strong_parameters.
params.require(:user).permit(:name, :email). Only those fields are accepted by @user.update(user_params).params.require(:user).permit! — the bang variant permits everything, effectively mass-assignment without a whitelist. Search for .permit! in every review.permit(addresses_attributes: [:street, :city]) — explicit per level.permit(preferences: {}) is an empty whitelist and accepts an entire hash without key checking. Limit to known keys.is_admin, role, stripe_customer_id — hardcode in the controller, do not allow via params.ActiveRecord parameterizes via hash syntax and positional placeholders. Issues arise with string interpolation.
Safe (all parameterized):
User.where(name: name)
User.where("name = ?", name)
User.where("name = :n AND age > :a", n: name, a: 18)Unsafe:
User.where("name = '#{name}'") # string interpolation
User.where("name = '" + name + "'") # concatenation
User.order(params[:sort]) # order-by user input = vulnerable
User.find_by_sql("SELECT ... #{query}") # raw SQLSpecific sub-patterns:
User.order(params[:sort].presence_in(%w[name created_at]) || :id).Rails escapes by default in ERB (<%= %>). XSS arises where you opt out.
escape: false — variants.html_safe are a common bug source. def my_helper(input); "<b>#{input}</b>".html_safe; end is broken if input is user input. Fix: content_tag(:b, input).protect_from_forgery with: :exception (default since Rails 5.2). skip_before_action :verify_authenticity_token is a security-review trigger. API controllers extending ActionController::API do not need CSRF (stateless); mixed controllers (web + JSON) do.:database_authenticatable, :registerable, :recoverable, :rememberable, :validatable. Turn on :confirmable for email verification, :lockable for brute-force mitigation, :timeoutable for inactive sessions.devise-two-factor for TOTP MFA.reset_password_within reasonably short) and enumeration-safe (same response for existing/non-existing email).config.session_store :cookie_store, key: '_app_session', secure: Rails.env.production?, httponly: true, same_site: :lax. expire_after reasonable (e.g. 2 hours of inactivity).config/initializers/content_security_policy.rb. Build the default policy without 'unsafe-inline' or 'unsafe-eval'; nonce-based if inline is needed.config.force_ssl = true in production.rb. Or at the reverse-proxy level, not both redundantly.Rails/Rack CVEs to know:
serialize :column. Fixed in 5.2.8.1, 6.0.5.1, 6.1.6.1, 7.0.3.1. Search code for serialize :col without a type argument; that triggers the legacy YAML path.[verify against https://github.com/rack/rack/security/advisories] for current status.[verify against https://rubyonrails.org/security/] for Rails-specific, [verify against https://github.com/rack/rack/security/advisories] for Rack.File uploads and Active Storage:
has_one_attached :avatar — content type is stored via content_type on the blob, but the MIME check on upload is the controller's responsibility. Use validates :avatar, content_type: [...] via the active_storage_validations gem.ImageMagick backend: a CVE-intensive library. Prefer vips as the backend.ActiveStorage::Current.url_options); do not share blob URLs directly without a signed URL plus expiry.Verification-loop: Layer 1 scope (Gemfile versions, production.rb settings, routes + controller permits all walked through?), assumptions (protect_from_forgery active on all web controllers?). Layer 2 (CVE IDs verified against rubyonrails.org/security, Brakeman rule IDs are real, no invented Devise modules).
Rails security review — <app>
Rails: <x.y.z> | Ruby: <x.y.z> | Gems with vuln: <N via cve-triage>
Brakeman:
Warnings total: N (baseline: M, new: K)
High-confidence: <list>
Grouped by class
Code patterns:
skip_before_action :verify_authenticity_token: <list>
.permit!, mass-assignment risks: <list>
String interpolation in .where/.order: <list>
html_safe/raw on user input: <list>
Devise:
Modules present: <confirmable, lockable, timeoutable?>
MFA active: <devise-two-factor | none>
stretches: <N>
enumeration-safe: <yes/no on reset flow>
Deploy settings:
config.force_ssl: <true | false>
session-cookie flags: <secure, httponly, same_site>
CSP: <default policy, no unsafe-inline?>
Active Storage (if applicable):
MIME validation: <present | missing>
ImageMagick vs vips: <backend>
Version check:
Rails security releases: <current | N months behind>
Rack CVE scope: <verified>
Findings (severity-sorted, follow security-review format)
Verification-loop: ...~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.