agentsclimarketplace

Rails security

Skill roodlicht/accans-sec-skills/skills/rails-security

Rails security review — Brakeman integration, mass-assignment via strong_parameters, SQL injection in ActiveRecord, template injection via html_safe/raw, Devise hardening, credentials.yml.enc, force_ssl and CSP config, recent Rails/Rack CVE patterns.From its SKILL.md

Install
npx -y skills add roodlicht/accans-sec-skills --skill rails-security

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 4 stars4 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.

SKILL.md

10.8 KB, ~2.7k tokens by cl100k_base, as published. Nobody here has run it

Rails Security

When to use

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:

  • A question like "review this Rails app", "Brakeman triage", "Devise hardening", "mass-assignment check", "is our CSP OK", "force SSL".
  • Presence of Gemfile, config/application.rb, config/environments/production.rb, config/routes.rb, Devise setup under config/initializers/devise.rb, Pundit/CanCanCan policies.
  • A PR that touches html_safe, raw, .where("... #{x} ..."), skip_before_action :verify_authenticity_token, or skip_forgery_protection.
  • A Brakeman report that needs triaging.
  • A handoff from security-review where Rails is in the stack.

When NOT (handoff)

  • General Ruby secure-coding → secure-coding.
  • API design (Rails API-only mode overlaps with api-security) → that skill for OWASP API Top 10, here for Rails-specific implementation.
  • SAST tool orchestration → sast-orchestrator. Brakeman is Rails-specific and belongs here.
  • Dep vulns in gems → cve-triage (via bundle audit or OSV-scanner).
  • Container/K8s deploy → container-hardening / k8s-security.
  • Secrets in config/master.key or credentials.yml.enc on disk → secrets-scanner. This skill only covers the Rails credentials model.

Approach

Six phases. Brakeman does the heavy static-analysis lifting; this skill adds human context to what Brakeman outputs and catches what it misses.

1. Brakeman and dependency scanning

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 json

CI 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.

2. Mass-assignment and strong_parameters

The classic Rails vulnerability. Pre-Rails-4 the whitelist was opt-in; since then it has been required via strong_parameters.

  • Controller idiom: params.require(:user).permit(:name, :email). Only those fields are accepted by @user.update(user_params).
  • Foot-gun: params.require(:user).permit! — the bang variant permits everything, effectively mass-assignment without a whitelist. Search for .permit! in every review.
  • Nested attributes: permit(addresses_attributes: [:street, :city]) — explicit per level.
  • Polymorphic / hash-type input: permit(preferences: {}) is an empty whitelist and accepts an entire hash without key checking. Limit to known keys.
  • Admin flags never in permit: is_admin, role, stripe_customer_id — hardcode in the controller, do not allow via params.

3. SQL injection in ActiveRecord

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 SQL

Specific sub-patterns:

  • order(params[:sort]) — allowlist of permitted column names. User.order(params[:sort].presence_in(%w[name created_at]) || :id).
  • group(...), having(...), pluck(...) — accept string fragments; do not compose from user input.
  • sanitize_sql_array/sanitize_sql_like for cases where you really do need raw SQL.
  • find_by_sql, connection.execute — always parameterized; Brakeman catches this.

4. XSS and template escape

Rails escapes by default in ERB (<%= %>). XSS arises where you opt out.

  • raw(user_input) — renders raw, no escaping.
  • <%= user_input.html_safe %> — same effect, method on string.
  • safe_concat, content_tag(..., user_input) with escape: false — variants.
  • sanitize(user_html, tags: ..., attributes: ...) — Rails' built-in HTML sanitizer; acceptable for user-generated HTML provided the allowlist is tight. Refer to the Rails docs for the default allowlist.
  • javascript_tag do ... end with user input in the body — JS-context XSS, not covered by HTML escape.
  • Custom helpers that return 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).

5. Auth: Devise, CSRF, sessions, CSP

  • CSRF: 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.
  • Devise hardening:
    • Modules: :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable. Turn on :confirmable for email verification, :lockable for brute-force mitigation, :timeoutable for inactive sessions.
    • Password hashing: Devise uses bcrypt; stretches >= 12 for prod.
    • devise-two-factor for TOTP MFA.
    • Password reset flow: verify the token is time-bound (reset_password_within reasonably short) and enumeration-safe (same response for existing/non-existing email).
  • Session config: 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).
  • Content Security Policy: since Rails 5.2 via config/initializers/content_security_policy.rb. Build the default policy without 'unsafe-inline' or 'unsafe-eval'; nonce-based if inline is needed.
  • force_ssl: config.force_ssl = true in production.rb. Or at the reverse-proxy level, not both redundantly.

6. CVE patterns and verification-loop

Rails/Rack CVEs to know:

  • CVE-2019-5418 ActionView file disclosure — Accept-header-based path traversal. Historical but canonical; every Rails version <5.2.2.1 / 4.2.11.1 is vulnerable. Reviewer reflex: is the Rails version recent enough?
  • CVE-2022-32224 ActiveRecord YAML deserialization RCE — via 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.
  • CVE-2023-22795 ActionDispatch ReDoS — header-parsing regex DoS.
  • Rack 3.x security stream (2024) — multiple CVEs in Rack's request parser. [verify against https://github.com/rack/rack/security/advisories] for current status.
  • Other: [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.
  • Variants with the ImageMagick backend: a CVE-intensive library. Prefer vips as the backend.
  • Private storage for non-public files (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).

Output

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: ...

References

Categories

  • appsec

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.