Fix IDOR and missing authorization in Ruby on Rails
===================================================

Load records through a scope built from the signed-in user, never by ID alone:
visible_invoices(current_user).find(params[:id]), where the scope applies the
tenant to everyone and the owner to members. Use the same scope for show,
index and export, and let find raise RecordNotFound, so outside the scope and
missing both answer 404.

Page: https://softwaresecurity.com/fix/missing-authorization/ruby-rails/

What is in this archive
-----------------------
missing-authorization/ruby-rails/   the project: three implementations of the same code and one test suite
  app/access/invoice_access/vulnerable.rb   (vulnerable)
  app/access/invoice_access/fixed.rb   (fixed)
  app/access/invoice_access/wrong.rb   (a plausible fix that still fails)
run-all.mjs   runs the tests in all three modes and checks the results (Node.js and Docker)
LICENSE-CODE.txt

Run it
------
  Tested with Ruby 4.0.7. run-all.mjs needs Node.js 22 or later. Supported
  versions: https://softwaresecurity.com/fix/versions/

  From the missing-authorization/ruby-rails folder, install the dependencies
  once (this needs network access):

    BUNDLE_PATH=vendor/bundle BUNDLE_FROZEN=true bundle install

  Then run the tests. IMPL chooses the implementation the tests load:

    # every test passes
    BUNDLE_PATH=vendor/bundle IMPL=fixed bundle exec ruby -Itest test/invoices_test.rb
    # an exploit test fails
    BUNDLE_PATH=vendor/bundle IMPL=vulnerable bundle exec ruby -Itest test/invoices_test.rb
    # a test fails
    BUNDLE_PATH=vendor/bundle IMPL=wrong bundle exec ruby -Itest test/invoices_test.rb

  Without Ruby installed, Docker runs the same commands in the image the card
  was verified in, pinned by its digest (run them from the
  missing-authorization/ruby-rails folder; the named volumes keep the dependency
  and build caches between runs, so the test runs need no network):

    docker run --rm --init \
      -v "$PWD":/card -w /card \
      -e BUNDLE_PATH=vendor/bundle \
      ruby:4.0.7-trixie@sha256:0192d5244fa068587325c3ef4f886fdb2d6775689757d598f9a5261d0f86d40e \
      sh -c 'BUNDLE_PATH=vendor/bundle BUNDLE_FROZEN=true bundle install'

    # every test passes
    docker run --rm --init --network=none \
      -v "$PWD":/card -w /card \
      -e IMPL=fixed -e BUNDLE_PATH=vendor/bundle \
      ruby:4.0.7-trixie@sha256:0192d5244fa068587325c3ef4f886fdb2d6775689757d598f9a5261d0f86d40e \
      sh -c 'bundle exec ruby -Itest test/invoices_test.rb'
    # an exploit test fails
    docker run --rm --init --network=none \
      -v "$PWD":/card -w /card \
      -e IMPL=vulnerable -e BUNDLE_PATH=vendor/bundle \
      ruby:4.0.7-trixie@sha256:0192d5244fa068587325c3ef4f886fdb2d6775689757d598f9a5261d0f86d40e \
      sh -c 'bundle exec ruby -Itest test/invoices_test.rb'
    # a test fails
    docker run --rm --init --network=none \
      -v "$PWD":/card -w /card \
      -e IMPL=wrong -e BUNDLE_PATH=vendor/bundle \
      ruby:4.0.7-trixie@sha256:0192d5244fa068587325c3ef4f886fdb2d6775689757d598f9a5261d0f86d40e \
      sh -c 'bundle exec ruby -Itest test/invoices_test.rb'

  The JUnit report is written to reports/TEST-*.xml. To run all three modes and
  check the results in one step, run the harness from this top folder (it needs
  only Node.js and Docker: it runs the commands in the container; --install runs
  the install step first):

    node run-all.mjs --install missing-authorization/ruby-rails
    node run-all.mjs missing-authorization/ruby-rails

Verified
--------
  11 October 2026 on Debian 13 (trixie) in Docker, on macOS 27.0 (Darwin 27.0.0,
  arm64), with ruby 4.0.7 (ruby:4.0.7-trixie image, linux/arm64), bundler
  4.0.20, rails 8.1.4, minitest 6.0.6, minitest-reporters 1.8.0, sqlite3 2.9.6
  (SQLite 3.53.2), csv 3.3.6, semgrep 1.180.0, brakeman 8.1.0. Versions and
  results hold for that date: run the tests with your own versions before
  relying on the fix.

Licence
-------
  The code is MIT licensed (LICENSE-CODE.txt). The page text is CC BY 4.0.
