OWASP RailsGoat

github.com/OWASP/railsgoat — Rails training app focused on mass-assignment, SQLi, and open-redirect.

Results

toolfindingswall-clock
rastray112.0 s
semgrep2227.8 s
gitleaks12.6 s
banditN/A
gosecN/A
eslint-securityN/A

What rastray fires on

codecountwhat it catches
RSTR-INJ-0093params[...].constantize / .classify — mobile_controller, benefit_forms
RSTR-INJ-0032eval
RSTR-CRY-0052Math.random (vendored JS)
RSTR-INJ-0081User.where("id = '#{params[:user][:id]}'") — users_controller
RSTR-ORM-0051params.require(:user).permit! — users_controller
RSTR-REDOS-0011Catastrophic backtracking
RSTR-DES-0051Ruby Marshal.load

What changed in v0.11.0

Rastray's Rails coverage grew from 6 findings to 11 on RailsGoat between v0.10.0 and v0.11.0 — almost doubling the catch rate on this target. The new rules added in this release:

  • RSTR-INJ-008 — Rails .where with string interpolation of params. Fires on users_controller.rb:29: User.where("id = '#{params[:user][:id]}'").
  • RSTR-INJ-009params[...].constantize / .classify / .safe_constantize. Fires on the mobile-API pattern of "deserialise whatever class the client names."
  • RSTR-INJ-010render inline: / text: with #{params[...]} interpolation (SSTI).
  • RSTR-ORM-005params.require(:x).permit! open-permit. Fires on users_controller.rb:50.
  • RSTR-RDR-004redirect_to params[...] open-redirect. Does not fire on RailsGoat because the sample uses the indirect form path = params[:url]; redirect_to path — same one-step taint scope the rest of rastray uses.

The remaining gap vs Semgrep is mostly the same "indirect flow" pattern as on DVWA: RailsGoat consistently assigns params[...] to a local first, then uses the local. Semgrep's rule pack tracks that single assignment; rastray deliberately does not. For codebases that match rastray's direct-sink scope, the new rules close most of the real-world gap.

Reproduce

powershell -File scripts/benchmarks/run.ps1 -Target railsgoat