Context
We currently provide a remote Rails console at /rails/console with a single shared HTTP Basic credential in production and no auth in development. The README is upfront that this is "toy apps only." After comparing the gem to console1984 (Basecamp's IRB-based audit/protection layer), there are two changes that would meaningfully reduce the recklessness of using slash_console without abandoning its "drop-in convenience" positioning.
Why not just depend on console1984?
We considered it. It's a bad fit:
console1984 installs its protections inside Rails' console do ... end block, which fires when bin/rails console boots IRB. slash_console doesn't use IRB — web-console evaluates input by eval-ing strings inside a stored Binding. The IRB::Context prepend, the decrypt!/encrypt! commands, and the TracePoint-based forbidden-method shield all have nothing to attach to in a /rails/console request.
- It carries real weight: hard Rails 7 + Active Record Encryption requirement, four migration tables, eager loading on session start, a
parser dep, monkeypatches into TCPSocket/SSLSocket/all three AR adapters, and Refrigerator#freeze_all which freezes classes process-wide. That conflicts with our "toy apps only" framing — and if you're willing to take all that on, you should be SSHing in and running rails console the normal way.
The ideas transfer cleanly, though. The two below are the high-value, low-cost subset.
Proposal
1. A real user concept (prerequisite for #2)
Today there is no notion of "who is using the console" — ADMIN_USERNAME is a shared label and dev mode has no auth at all. Before audit logging can mean anything, we need attribution.
Add a configurable resolver with a sensible fallback:
SlashConsole.configure do |c|
c.user_resolver = -> { Current.user&.email } # host app injects identity
end
- If configured, the result is stamped on every logged command.
- If not configured, fall back to the Basic auth username (production) or a one-time per-session "your name" prompt stored in the session cookie (development). Unverified, but at least an auditable string — same posture as
console1984's unverified CONSOLE_USER.
This is small, composes with whatever auth the host app already has, and avoids slash_console pretending to be an auth system.
2. Session reason prompt + per-command audit log
Borrowing the most valuable part of console1984:
- On first access in a session, ask the user why they're opening the console. Store with
created_at + resolved user.
- Persist every submitted command and its rendered output to a DB table. Encrypt at rest with Active Record Encryption if the host app has it configured; plain text otherwise (with a README warning).
- Suggested schema (mirrors
console1984 but flatter — no separate users table, no sensitive-access concept):
slash_console_sessions: id, user, reason, ip, user_agent, created_at
slash_console_commands: id, session_id, statements (encrypted), output (encrypted), created_at
web-console already hands us the input string in the controller, so this is straightforward — no IRB plumbing needed.
Explicitly out of scope
Refrigerator-style class freezing. A web-console attacker can Object.send(:remove_const, ...) or otherwise bypass; selling a defense we can't deliver is worse than not selling it.
decrypt! / encrypt! two-mode model. Awkward in a web form; if we ever want it, do it as a per-request flag, not a stateful mode toggle.
- Socket-level egress blocking to
protected_urls. Too invasive for this gem's positioning.
README update
Add a section pointing readers to console1984 + audits1984 + SSH for serious use cases, and reframe slash_console as: convenience for low-stakes apps, with attribution and an audit trail so misuse is at least traceable.
Open question
Should the audit log be opt-in via a generator (rails generate slash_console:install) or auto-create on first boot? Leaning opt-in to keep the "just add the gem, it works" path unchanged for toy apps that don't want a DB migration.
Context
We currently provide a remote Rails console at
/rails/consolewith a single shared HTTP Basic credential in production and no auth in development. The README is upfront that this is "toy apps only." After comparing the gem toconsole1984(Basecamp's IRB-based audit/protection layer), there are two changes that would meaningfully reduce the recklessness of usingslash_consolewithout abandoning its "drop-in convenience" positioning.Why not just depend on
console1984?We considered it. It's a bad fit:
console1984installs its protections inside Rails'console do ... endblock, which fires whenbin/rails consoleboots IRB.slash_consoledoesn't use IRB —web-consoleevaluates input byeval-ing strings inside a storedBinding. The IRB::Context prepend, thedecrypt!/encrypt!commands, and the TracePoint-based forbidden-method shield all have nothing to attach to in a/rails/consolerequest.parserdep, monkeypatches intoTCPSocket/SSLSocket/all three AR adapters, andRefrigerator#freeze_allwhich freezes classes process-wide. That conflicts with our "toy apps only" framing — and if you're willing to take all that on, you should be SSHing in and runningrails consolethe normal way.The ideas transfer cleanly, though. The two below are the high-value, low-cost subset.
Proposal
1. A real user concept (prerequisite for #2)
Today there is no notion of "who is using the console" —
ADMIN_USERNAMEis a shared label and dev mode has no auth at all. Before audit logging can mean anything, we need attribution.Add a configurable resolver with a sensible fallback:
console1984's unverifiedCONSOLE_USER.This is small, composes with whatever auth the host app already has, and avoids
slash_consolepretending to be an auth system.2. Session reason prompt + per-command audit log
Borrowing the most valuable part of
console1984:created_at+ resolved user.console1984but flatter — no separate users table, no sensitive-access concept):slash_console_sessions:id,user,reason,ip,user_agent,created_atslash_console_commands:id,session_id,statements(encrypted),output(encrypted),created_atweb-consolealready hands us the input string in the controller, so this is straightforward — no IRB plumbing needed.Explicitly out of scope
Refrigerator-style class freezing. Aweb-consoleattacker canObject.send(:remove_const, ...)or otherwise bypass; selling a defense we can't deliver is worse than not selling it.decrypt!/encrypt!two-mode model. Awkward in a web form; if we ever want it, do it as a per-request flag, not a stateful mode toggle.protected_urls. Too invasive for this gem's positioning.README update
Add a section pointing readers to
console1984+audits1984+ SSH for serious use cases, and reframeslash_consoleas: convenience for low-stakes apps, with attribution and an audit trail so misuse is at least traceable.Open question
Should the audit log be opt-in via a generator (
rails generate slash_console:install) or auto-create on first boot? Leaning opt-in to keep the "just add the gem, it works" path unchanged for toy apps that don't want a DB migration.