For AI agents (and the people driving them)
Increasingly the “reader” of authorization docs is an AI coding agent. These prompts encode the foot-guns this documentation already knows about, so an agent gets the integration right on the first try. Copy, adjust the names, paste.
There is also an llms.txt index of this site, and every page here links to its canonical in-repo source — point your agent at those for depth.
Install CurrentScope in this app
Add the current_scope gem to this Rails app (Rails >= 8.1, < 9 required).
1. Add `gem "current_scope"` to the Gemfile, then run:
bin/rails generate current_scope:install
bin/rails current_scope:install:migrations && bin/rails db:migrate
2. In ApplicationController, include CurrentScope::Context and
CurrentScope::Guard (in that order). Authentication must be wired
BEFORE these concerns run — Context reads current_user when its
callback fires, so an auth callback registered after these includes
means the gate denies before authentication happens.
3. The gate is fail-closed and covers EVERY action. You MUST add
`skip_before_action :current_scope_check!` to controllers where
authorization does not apply — sign-in/sessions (or nobody can log in),
webhooks, health checks. A skipped controller is unprotected by the
permission gate: keep or add the app's own auth there. If the app has
act-as/impersonation, ALSO add
`skip_before_action :current_scope_mutation_guard!` on sign-in,
sign-out, and the stop-impersonation action — that guard is a separate
callback that survives the gate skip, and without its skip an
impersonating admin cannot sign out or stop impersonating.
4. If this app already has users and traffic, set
`config.enforcement = :report` in the initializer BEFORE deploying,
run the test suite, then run `bin/rails current_scope:report` and seed
the roles it names. Flip back to :enforce only when re-exercising the
app adds no NEW access.would_deny rows (the report reads the
append-only ledger — historical rows never clear) and any
access.sod_blind_spot rows are resolved. Report mode must not be the
final state.
5. Bootstrap the first admin (the management UI only admits full-access
subjects): `bin/rails current_scope:grant SUBJECT_ID=<id>` — or, in
seeds, `CurrentScope.grant!(user)` (it creates the default
Owner/Member roles if missing; Member starts with zero permissions).
6. Declare record hooks as PRIVATE controller methods. For member actions:
`def current_scope_record = (set_thing if request.path_parameters[:id])`
— key off request.path_parameters, never params, so a ?id= query string
cannot smuggle a record into collection actions. Use the route's actual
member key: a route declared with a custom param (e.g. :slug) must
check request.path_parameters[:slug], or the hook returns nil and the
SoD veto is silently skipped on that action.
7. Run the full test suite. Expect controller tests to 403 until grants are
seeded — use the test helpers (grant_role!/grant_scoped_role!) from
current_scope/test_helpers, not stubs.
Before finishing, read docs/SECURITY-CHECKLIST.md in the gem repo
(https://github.com/davidteren/current_scope/blob/main/docs/SECURITY-CHECKLIST.md)
and verify each item that applies.
Enable separation of duties on an approve flow
Enable CurrentScope separation of duties so an initiator can never approve
their own record. SoD is OFF by default — config.sod_actions = [] means the
veto never runs.
1. In config/initializers/current_scope.rb set
`config.sod_actions = %w[approve]` (action NAMES, not full keys).
2. On the model, define `def current_scope_initiator = <initiator assoc>`.
If an SoD action reaches a record whose class lacks this hook, the
resolver raises ConfigurationError — that is intended (fail loud).
3. CRITICAL: the controller's `current_scope_record` hook MUST return the
record on the SoD member action. If it returns nil there, the veto is
silently SKIPPED and the initiator can approve. This is the load-bearing
control — verify it, don't assume it.
4. Write the verification test and run it:
- grant the initiator a role that ticks approve
- POST the approve action as the initiator
- assert response :forbidden AND
response.headers["X-Current-Scope-Reason"] == "sod_veto"
If the reason is "no_grant" instead, SoD is not examining this action.
5. Do NOT add bypass logic unless explicitly asked. If a conditional
self-approval is a real requirement, use the engine's break-glass
(allow_sod_bypass) — never a hand-rolled branch, because break-glass
records the sod.bypassed audit event a hand-rolled branch forgets.
That recording requires config.audit enabled (the default); if an
unaudited bypass must be impossible, set config.audit = :strict.
Debug a CurrentScope 403
A request is being denied. Diagnose it from the engine's own signals; do not
guess or bypass the gate.
1. Read the X-Current-Scope-Reason response header (or the INFO log line
"[CurrentScope] denied <key> (<reason>) → 403"). Map the reason:
- no_grant → nothing grants this controller#action key; check
the role grid / seed the grant
- sod_veto → the subject initiated this record; that denial is
the anti-fraud control working — do not "fix" it
- model_undeclared → the controller needs `def current_scope_model = <Model>`
- model_invalid → current_scope_model returned something that is not
a concrete ActiveRecord class
- impersonation_gate→ non-GET/HEAD while impersonating; sessions are
read-only by design
- not_full_access → the management UI; only full-access subjects enter
2. In development/test, check the log for the engine's nudges (nil SoD
record, inert scoped grant, cross-controller key derivation) — each
names its one-line fix.
3. If there is no header and no log line, a host rescue_from may have
replaced the denial handler, or the controller never included Guard —
run `bin/rails current_scope:ungated` to check the gated surface.
Planned agent surfaces
These are tracked but not shipped — do not prompt an agent to use them yet:
- Exposing the subject’s abilities to a separate JS front-end (React/Next): #96, Inertia props: #97.
- Assisted migration from Pundit / CanCanCan / Action Policy: #45. Until then, the manual path is the adoption guide.