activerecord-safer-query

activerecord-safer-query is a small static checker for Rails applications. It looks for class-level ActiveRecord lookups that may bypass tenant, organization, or user scopes.

This is an audit tool, not a proof engine. Findings should be reviewed by a human before treating them as vulnerabilities.

Usage

Run it from the target Rails repository:

exe/activerecord-safer-query
exe/activerecord-safer-query app/graphql app/controllers
exe/activerecord-safer-query --root /path/to/rails-app app/graphql
exe/activerecord-safer-query --fail-level HIGH app/graphql
exe/activerecord-safer-query --format json app/controllers/organizations
exe/activerecord-safer-query --whitelist config/safer-query-whitelist.yml app/graphql

When installed as a gem, run:

gem install activerecord-safer-query
activerecord-safer-query app/graphql app/controllers

Rules

  • GLOBAL_FIND_EXTERNAL_INPUT: class-level find / find_by with params, GraphQL input, args, session, cookies, headers, or request data.
  • GLOBAL_FIND_ID_VARIABLE: class-level find with a local id, *_id, *_ids, *_uuid, *_slug, or *_code variable.
  • GLOBAL_WHERE_EXTERNAL_IDS: class-level where(id: ...) with external input.
  • GLOBAL_NATURAL_KEY_LOOKUP: class-level lookup by email, uid, issuer, code, subdomain, slug, token, or similar natural keys.
  • UNSCOPED_DESTRUCTIVE_IDS: destructive operations driven by external/global IDs.
  • WITHOUT_TENANT_BOUNDARY: boundary code that calls ActsAsTenant.without_tenant.
  • DRAFT_COURSE_EXPOSURE: draft/closed course scopes in public-ish Rails boundaries.

Suppress a known-safe finding with:

Course.find(params[:id]) # active_record_safer_query: ignore

Suppress resolved findings in .activerecord-safer-query.yml:

whitelist:
  - path: app/graphql/mutations/active_user/select_curriculum.rb
    rule: GLOBAL_FIND_EXTERNAL_INPUT
    source: Curriculum.find
    reason: The caller already scopes available curriculum IDs.

Whitelist entries match all fields that are present. path, rule, and severity support glob patterns, line can be a number or list of numbers, and source matches a source-code fragment. reason is documentation-only and does not affect matching.

Development

bundle install
bundle exec rake

License

MIT.