slash_console 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eb63a5b059ae9d711cdaf34435c86829ada9cb92a50b0599d8b571efae6dc1ff
4
- data.tar.gz: 596977091e038ecb3a9da8dcc74d278da2fe30f2fd569e5896a50965091634cf
3
+ metadata.gz: 00dad9f1dd2b860398d627127e7bd3df9b5596b0e9fc8c8e915de93c7229ceed
4
+ data.tar.gz: 2f451ca16d04c7e89a008b1533b9ffcc355326689c3664fedeb8054fd37bfa85
5
5
  SHA512:
6
- metadata.gz: 35d2c6cf00fda4a2e950fd04c925e463d74df9331d0559a65080ddf6702838344628e3f255b8ef06f9e15c6562088d80502fbf666996ff7ebeac9015b435f067
7
- data.tar.gz: f666530036a635c0b35f7e842929ffa57989bce870dfde848c21c289225e89f722c1bc7555485cc6b4cc159a83aec9258e44def4cac4bfe7e81a99630eb1f717
6
+ metadata.gz: af3ca9a32035629be4bcdc38b7e8808bc2a4b7f92635359a7781a4e622a566b09d68a4963855ac614f18590d124e0fb953e731c92cc8d5670357f6a20804ebdb
7
+ data.tar.gz: 824164461e81532a821fdda656ff611e8ff6d662dc2fb8451fc030e7d1f79ee3adcf5f33230a475ab937318037e489937f48853cb40ab595150c8af567b237e9
data/CHANGELOG.md CHANGED
@@ -7,6 +7,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.1.6] - 2026-07-10
11
+
12
+ ### Security
13
+ - Production Basic authentication moved from the controller into a Rack
14
+ middleware inserted in front of `WebConsole::Middleware`, so it now covers
15
+ web-console's evaluator endpoints (`/__web_console/repl_sessions/:id`) as
16
+ well as the console page. Previously a console session ID acted as an
17
+ unauthenticated bearer token for code execution until process restart.
18
+
19
+ ### Fixed
20
+ - The console renders under a strict nonce-based Content Security Policy.
21
+ The page stylesheet carries Rails' per-request nonce, and requesting the
22
+ nonce during rendering makes it available to web-console's injected
23
+ scripts even when the app hoists the CSP middleware above web-console
24
+ (previously such apps got a nonempty header nonce but `nonce=""` scripts,
25
+ i.e. a blank console).
26
+ - Console input evaluates in a fresh top-level binding, so constants
27
+ resolve the same way as in `bin/rails console` instead of inside the
28
+ engine's namespace (`ApplicationController` no longer resolves to
29
+ `SlashConsole::ApplicationController`, and app constants no longer need a
30
+ leading `::`). Local variables persist within a console session but not
31
+ across sessions.
32
+ - Removed the duplicate `WebConsole::Middleware` the engine inserted
33
+ alongside the copy web-console's own railtie already adds.
34
+
35
+ ## [0.1.5] - 2025-09-15
36
+
37
+ ### Fixed
38
+ - Load web-console extensions explicitly to fix blank page in production
39
+ - Add full-page CSS for console element to ensure proper display
40
+ - Use modern viewport units (100svh) for better mobile compatibility
41
+
10
42
  ## [0.1.4] - 2025-09-15
11
43
 
12
44
  ### Fixed
@@ -46,5 +78,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
46
78
  - Development mode with no authentication required
47
79
  - Standard Ruby style guide compliance
48
80
 
49
- [Unreleased]: https://github.com/firstdraft/slash_console/compare/v0.1.0...HEAD
50
- [0.1.0]: https://github.com/firstdraft/slash_console/releases/tag/v0.1.0
81
+ [Unreleased]: https://github.com/firstdraft/slash_console/compare/v0.1.6...HEAD
82
+ [0.1.6]: https://github.com/firstdraft/slash_console/compare/v0.1.0...v0.1.6
83
+ [0.1.0]: https://github.com/firstdraft/slash_console/releases/tag/v0.1.0
data/README.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  A Rails engine that provides a web-based console interface at `/rails/console`, allowing easy access to a Rails console in both development and production environments.
4
4
 
5
+ ⚠️ **Security Warning**: This gem provides direct access to the `rails console`. Anyone who accesses it can run arbitrary code on the server: stealing/deleting all your data, planting bugs, sending scam emails, mining cryptocurrency, etc. So, be careful.
6
+
7
+ - Only use this gem in applications where the security trade-offs are acceptable. Basically, only for toy apps/proofs-of-concept/portfolio projects that contain only sample data. Never use this gem when real user data is at risk.
8
+ - For serious apps, SSH into the server and run `rails console` at the command-line. This may require upgrading your hosting from free to paid, but you should be doing that anyway if you have real users.
9
+ - Make up a strong, unique `ADMIN_PASSWORD` for each app.
10
+
5
11
  ## Installation
6
12
 
7
13
  Add this line to your application's Gemfile:
@@ -10,7 +16,7 @@ Add this line to your application's Gemfile:
10
16
  gem "slash_console"
11
17
  ```
12
18
 
13
- And then execute:
19
+ And then:
14
20
 
15
21
  ```bash
16
22
  bundle install
@@ -22,15 +28,11 @@ That's it! Navigate to `/rails/console` in your browser.
22
28
 
23
29
  ### Development
24
30
 
25
- In development, no authentication is required. Simply visit:
26
-
27
- ```
28
- http://localhost:3000/rails/console
29
- ```
31
+ In development, no authentication is required. Simply visit `/rails/console`.
30
32
 
31
33
  ### Production
32
34
 
33
- For production use, authentication is **required**. Set these environment variables:
35
+ For production use, authentication is required. Set these environment variables:
34
36
 
35
37
  ```bash
36
38
  ADMIN_USERNAME="choose_your_own_username"
@@ -39,30 +41,29 @@ ADMIN_PASSWORD="choose_your_own_strong_password"
39
41
 
40
42
  Without these environment variables, you'll see an error message explaining what needs to be configured.
41
43
 
42
- ⚠️ **Security Warning**: This gem provides direct access to your Rails console. In production:
43
-
44
- - Only use for applications where the security trade-offs are acceptable. Basically, only for toy apps; never where real user data is at risk. For serious apps, SSH into the server and run `rails console` at the command-line.
45
- - Make up a strong, unique `ADMIN_PASSWORD` for each app.
46
- - In production, the gem allows console access from all IP addresses (0.0.0.0/0) when authenticated. The authentication layer is your only protection.
47
-
48
44
  ## How It Works
49
45
 
50
- SlashConsole is a lightweight wrapper around the excellent `web-console` gem. It:
46
+ SlashConsole is a lightweight wrapper around [the excellent `web-console` gem](https://github.com/rails/web-console). It:
51
47
 
52
48
  1. Provides a dedicated route for console access (instead of only on error pages).
53
- 2. Renders a full-page console interface.
54
- 3. Uses basic authentication when in production.
49
+ 2. Renders a full-page console interface, including in apps that enforce a strict nonce-based Content Security Policy.
50
+ 3. In production, requires basic authentication via a Rack middleware that protects both the console page and web-console's code-evaluation endpoints (`/__web_console/repl_sessions/:id`).
51
+ 4. Evaluates console input at the top level, so constants resolve the same way as in `bin/rails console`.
55
52
 
56
53
  ## Development
57
54
 
58
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests.
55
+ After checking out the repo, run `bundle install` to install dependencies.
59
56
 
60
57
  To install this gem onto your local machine, run `bundle exec rake install`.
61
58
 
62
59
  ### Running Tests
63
60
 
61
+ The test suite needs a local PostgreSQL server. Prepare the test database once, then:
62
+
64
63
  ```bash
65
- bundle exec rake test
64
+ bundle exec rake app:db:test:prepare
65
+ bundle exec rake test # integration + unit tests
66
+ bundle exec rake test:system # browser smoke tests (needs Chrome)
66
67
  ```
67
68
 
68
69
  ### Linting
data/Rakefile CHANGED
@@ -6,3 +6,19 @@ load "rails/tasks/engine.rake"
6
6
  load "rails/tasks/statistics.rake"
7
7
 
8
8
  require "bundler/gem_tasks"
9
+
10
+ require "rake/testtask"
11
+
12
+ Rake::TestTask.new(:test) do |t|
13
+ t.libs << "test"
14
+ t.test_files = FileList["test/**/*_test.rb"].exclude("test/system/**/*", "test/dummy/**/*")
15
+ t.verbose = false
16
+ end
17
+
18
+ Rake::TestTask.new("test:system") do |t|
19
+ t.libs << "test"
20
+ t.test_files = FileList["test/system/**/*_test.rb"]
21
+ t.verbose = false
22
+ end
23
+
24
+ task default: :test
@@ -4,27 +4,30 @@ module SlashConsole
4
4
 
5
5
  skip_before_action :verify_authenticity_token, if: -> { defined?(verify_authenticity_token) }
6
6
 
7
+ # BasicAuthMiddleware is the primary guard: it protects both this page
8
+ # and web-console's evaluator endpoints, but only at the engine's
9
+ # standard /rails mount point. These filters remain as a second layer
10
+ # so the page stays protected if an application mounts the engine at a
11
+ # custom path.
7
12
  before_action :ensure_credentials_configured, if: -> { Rails.env.production? }
8
13
  before_action :authenticate_user, if: -> { Rails.env.production? }
9
14
 
10
15
  def index
11
- console
16
+ console(SlashConsole.console_binding)
12
17
  render :index
13
18
  end
14
19
 
15
20
  private
16
21
 
17
22
  def ensure_credentials_configured
18
- if ENV["ADMIN_USERNAME"].blank? || ENV["ADMIN_PASSWORD"].blank?
19
- render plain: 'Before you can access the console, you must set environment variables called "ADMIN_USERNAME" and "ADMIN_PASSWORD".',
20
- status: :service_unavailable
23
+ unless BasicAuthMiddleware.credentials_configured?
24
+ render plain: BasicAuthMiddleware::CREDENTIALS_MESSAGE, status: :service_unavailable
21
25
  end
22
26
  end
23
27
 
24
28
  def authenticate_user
25
- authenticate_or_request_with_http_basic("Rails Console") do |username, password|
26
- ActiveSupport::SecurityUtils.secure_compare(username, ENV["ADMIN_USERNAME"]) &&
27
- ActiveSupport::SecurityUtils.secure_compare(password, ENV["ADMIN_PASSWORD"])
29
+ authenticate_or_request_with_http_basic(BasicAuthMiddleware::REALM) do |username, password|
30
+ BasicAuthMiddleware.authorized?(username, password)
28
31
  end
29
32
  end
30
33
  end
@@ -1,36 +1,23 @@
1
1
  <!DOCTYPE html>
2
2
  <html>
3
- <head>
4
- <title>Rails Console</title>
5
- <meta name="viewport" content="width=device-width,initial-scale=1">
6
- <style>
7
- html, body {
8
- height: 100%;
9
- margin: 0;
10
- padding: 0;
11
- overflow: hidden;
12
- }
13
-
14
- /* Hide all content - console will fill the page */
15
- body > * {
16
- display: none !important;
17
- }
18
-
19
- /* Ensure the web-console div fills the entire viewport */
20
- #console {
21
- display: block !important;
22
- position: fixed !important;
23
- top: 0 !important;
24
- left: 0 !important;
25
- right: 0 !important;
26
- bottom: 0 !important;
27
- width: 100vw !important;
28
- height: 100vh !important;
29
- z-index: 999999 !important;
30
- }
31
- </style>
32
- </head>
33
- <body>
34
- <!-- The console helper will inject the web-console here -->
35
- </body>
36
- </html>
3
+ <head>
4
+ <title>Rails Console</title>
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <%# Asking Rails for the nonce here also makes it available to the
7
+ web-console middleware when it injects the console scripts below. %>
8
+ <style nonce="<%= content_security_policy_nonce %>">
9
+ body, html {
10
+ margin: 0;
11
+ padding: 0;
12
+ height: 100svh;
13
+ overflow: hidden;
14
+ }
15
+ #console {
16
+ height: 100svh !important;
17
+ }
18
+ </style>
19
+ </head>
20
+ <body>
21
+ <!-- Console will be injected here by web-console middleware -->
22
+ </body>
23
+ </html>
@@ -0,0 +1,71 @@
1
+ module SlashConsole
2
+ # Basic authentication for the console in production.
3
+ #
4
+ # Authentication must happen in the middleware stack, not in
5
+ # ConsoleController: web-console's evaluator endpoints (PUT/POST
6
+ # <mount_point>/repl_sessions/:id) are served directly by
7
+ # WebConsole::Middleware and never reach the router, so a controller
8
+ # filter cannot protect them. This middleware is inserted before
9
+ # WebConsole::Middleware and guards both the console page and the
10
+ # evaluator endpoints with the same credentials.
11
+ class BasicAuthMiddleware
12
+ REALM = "Rails Console"
13
+ CREDENTIALS_MESSAGE = 'Before you can access the console, you must set environment variables called "ADMIN_USERNAME" and "ADMIN_PASSWORD".'
14
+
15
+ # The paths the engine serves at its standard mount point: the root
16
+ # (/rails), the console page, and either with a format suffix or
17
+ # trailing slash. Deliberately does not cover other /rails/* paths --
18
+ # unmatched requests cascade past the engine to the host app, which
19
+ # owns paths like /rails/active_storage/*.
20
+ CONSOLE_PATHS = %r{\A/rails(?:/(?:console)?(?:\.[^/]*)?/?)?\z}
21
+
22
+ class << self
23
+ def credentials_configured?
24
+ ENV["ADMIN_USERNAME"].present? && ENV["ADMIN_PASSWORD"].present?
25
+ end
26
+
27
+ def authorized?(username, password)
28
+ ActiveSupport::SecurityUtils.secure_compare(username.to_s, ENV["ADMIN_USERNAME"].to_s) &&
29
+ ActiveSupport::SecurityUtils.secure_compare(password.to_s, ENV["ADMIN_PASSWORD"].to_s)
30
+ end
31
+ end
32
+
33
+ def initialize(app)
34
+ @app = app
35
+ end
36
+
37
+ def call(env)
38
+ return @app.call(env) unless Rails.env.production? && protects?(env[Rack::PATH_INFO].to_s)
39
+
40
+ unless self.class.credentials_configured?
41
+ return [503, {"content-type" => "text/plain"}, [CREDENTIALS_MESSAGE]]
42
+ end
43
+
44
+ auth = Rack::Auth::Basic::Request.new(env)
45
+ if auth.provided? && auth.basic? && self.class.authorized?(*auth.credentials)
46
+ @app.call(env)
47
+ else
48
+ unauthorized
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def protects?(path)
55
+ CONSOLE_PATHS.match?(path) || evaluator_path?(path)
56
+ end
57
+
58
+ def evaluator_path?(path)
59
+ mount_point = WebConsole::Middleware.mount_point
60
+ path == mount_point || path.start_with?("#{mount_point}/")
61
+ end
62
+
63
+ def unauthorized
64
+ headers = {
65
+ "content-type" => "text/plain",
66
+ "www-authenticate" => %(Basic realm="#{REALM}")
67
+ }
68
+ [401, headers, ["HTTP Basic: Access denied.\n"]]
69
+ end
70
+ end
71
+ end
@@ -5,10 +5,21 @@ module SlashConsole
5
5
  config.before_initialize do
6
6
  Rails.application.config.web_console ||= ActiveSupport::OrderedOptions.new
7
7
  Rails.application.config.web_console.development_only = false
8
+ Rails.application.config.web_console.allowed_ips = "0.0.0.0/0"
9
+ end
8
10
 
9
- if Rails.env.production?
10
- Rails.application.config.web_console.allowed_ips = "0.0.0.0/0"
11
- end
11
+ initializer "slash_console.load_web_console_extensions", before: :load_config_initializers do
12
+ require "bindex"
13
+ require "web_console/extensions"
14
+ end
15
+
16
+ # web-console's own railtie already inserts WebConsole::Middleware
17
+ # (before ActionDispatch::DebugExceptions), so the engine must not
18
+ # insert a second copy. It only adds the authentication middleware,
19
+ # which has to sit in front of WebConsole::Middleware because the
20
+ # evaluator endpoints are served there and never reach the router.
21
+ initializer "slash_console.insert_basic_auth_middleware", after: "web_console.insert_middleware" do |app|
22
+ app.config.middleware.insert_before WebConsole::Middleware, SlashConsole::BasicAuthMiddleware
12
23
  end
13
24
 
14
25
  initializer "slash_console.mount_engine" do |app|
@@ -1,3 +1,3 @@
1
1
  module SlashConsole
2
- VERSION = "0.1.4"
2
+ VERSION = "0.1.6"
3
3
  end
data/lib/slash_console.rb CHANGED
@@ -1,6 +1,15 @@
1
1
  require "slash_console/version"
2
+ require "slash_console/basic_auth_middleware"
2
3
  require "slash_console/engine"
3
4
  require "web-console"
4
5
 
5
6
  module SlashConsole
7
+ # A fresh binding at the top level, so that console input sees the same
8
+ # +self+ and constant resolution as bin/rails console, rather than the
9
+ # engine's lexical namespace. Each call returns a new binding: local
10
+ # variables persist across evaluations within a console session but do
11
+ # not leak between sessions or into TOPLEVEL_BINDING itself.
12
+ def self.console_binding
13
+ TOPLEVEL_BINDING.eval("binding")
14
+ end
6
15
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: slash_console
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Raghu Betina
@@ -70,6 +70,7 @@ files:
70
70
  - app/views/slash_console/console/index.html.erb
71
71
  - config/routes.rb
72
72
  - lib/slash_console.rb
73
+ - lib/slash_console/basic_auth_middleware.rb
73
74
  - lib/slash_console/engine.rb
74
75
  - lib/slash_console/version.rb
75
76
  - lib/tasks/slash_console_tasks.rake
@@ -96,7 +97,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
96
97
  - !ruby/object:Gem::Version
97
98
  version: '0'
98
99
  requirements: []
99
- rubygems_version: 3.7.1
100
+ rubygems_version: 4.0.9
100
101
  specification_version: 4
101
102
  summary: Mountable Rails console at /rails/console
102
103
  test_files: []