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 +4 -4
- data/CHANGELOG.md +35 -2
- data/README.md +19 -18
- data/Rakefile +16 -0
- data/app/controllers/slash_console/console_controller.rb +10 -7
- data/app/views/slash_console/console/index.html.erb +21 -34
- data/lib/slash_console/basic_auth_middleware.rb +71 -0
- data/lib/slash_console/engine.rb +14 -3
- data/lib/slash_console/version.rb +1 -1
- data/lib/slash_console.rb +9 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 00dad9f1dd2b860398d627127e7bd3df9b5596b0e9fc8c8e915de93c7229ceed
|
|
4
|
+
data.tar.gz: 2f451ca16d04c7e89a008b1533b9ffcc355326689c3664fedeb8054fd37bfa85
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
50
|
-
[0.1.
|
|
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
|
|
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
|
|
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.
|
|
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 `
|
|
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
|
-
|
|
19
|
-
render plain:
|
|
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(
|
|
26
|
-
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
data/lib/slash_console/engine.rb
CHANGED
|
@@ -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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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|
|
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
|
+
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:
|
|
100
|
+
rubygems_version: 4.0.9
|
|
100
101
|
specification_version: 4
|
|
101
102
|
summary: Mountable Rails console at /rails/console
|
|
102
103
|
test_files: []
|