Skip to content

Commit

Permalink
Add config.disable_sandbox option to Rails console
Browse files Browse the repository at this point in the history
A long-running `rails console --sandbox` could cause a database server
to become out-of-memory as it's holding on to changes that happen on the
database.

Given that it's common for Ruby on Rails application with huge
traffic to have separate write database and read database, we should
allow the developers to disable this sandbox option to prevent someone
from accidentally causing the Denial-of-Service on their server.
  • Loading branch information
sikachu committed Mar 23, 2019
1 parent efb706d commit b271052
Show file tree
Hide file tree
Showing 7 changed files with 54 additions and 5 deletions.
2 changes: 2 additions & 0 deletions guides/source/configuring.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ application. Accepts a valid week day symbol (e.g. `:monday`).
end
```

* `config.disable_sandbox` controls whether or not someone could start a console in sandbox mode, as a long session of sandbox console could lead database server to run out of memory.

* `config.eager_load` when `true`, eager loads all registered `config.eager_load_namespaces`. This includes your application, engines, Rails frameworks, and any other registered namespace.

* `config.eager_load_namespaces` registers namespaces that are eager loaded when `config.eager_load` is `true`. All namespaces in the list must respond to the `eager_load!` method.
Expand Down
8 changes: 8 additions & 0 deletions railties/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
* Add `config.disable_sandbox` option to Rails console.

This setting will disable `rails console --sandbox` mode, preventing
developer from accidentally starting a sandbox console, left it inactive,
and cause the database server to run out of memory.

*Prem Sichanugrist*

* Add `-e/--environment` option to `rails initializers`.

*Yuji Yaginuma*
Expand Down
4 changes: 3 additions & 1 deletion railties/lib/rails/application/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ class Configuration < ::Rails::Engine::Configuration
:session_options, :time_zone, :reload_classes_only_on_change,
:beginning_of_week, :filter_redirect, :x, :enable_dependency_loading,
:read_encrypted_secrets, :log_level, :content_security_policy_report_only,
:content_security_policy_nonce_generator, :require_master_key, :credentials
:content_security_policy_nonce_generator, :require_master_key, :credentials,
:disable_sandbox

attr_reader :encoding, :api_only, :loaded_config_version, :autoloader

Expand Down Expand Up @@ -65,6 +66,7 @@ def initialize(*)
@credentials.content_path = default_credentials_content_path
@credentials.key_path = default_credentials_key_path
@autoloader = :classic
@disable_sandbox = false
end

def load_defaults(target_version)
Expand Down
6 changes: 6 additions & 0 deletions railties/lib/rails/commands/console/console_command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ def initialize(app, options = {})
@options = options

app.sandbox = sandbox?

if sandbox? && app.config.disable_sandbox
puts "Error: Unable to start console in sandbox mode as sandbox mode is disabled (config.disable_sandbox is true)."
exit 1
end

app.load_console

@console = app.config.console || IRB
Expand Down
16 changes: 16 additions & 0 deletions railties/test/application/configuration_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2476,6 +2476,22 @@ class MyLogger < ::Logger
assert_includes Rails.application.config.hosts, ".localhost"
end

test "disable_sandbox is false by default" do
app "development"

assert_equal false, Rails.configuration.disable_sandbox
end

test "disable_sandbox can be overridden" do
add_to_config <<-RUBY
config.disable_sandbox = true
RUBY

app "development"

assert Rails.configuration.disable_sandbox
end

private
def force_lazy_load_hooks
yield # Tasty clarifying sugar, homie! We only need to reference a constant to load it.
Expand Down
21 changes: 18 additions & 3 deletions railties/test/application/console_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -123,13 +123,17 @@ def write_prompt(command, expected_output = nil)
assert_output "> ", @primary
end

def spawn_console(options)
Process.spawn(
def spawn_console(options, wait_for_prompt: true)
pid = Process.spawn(
"#{app_path}/bin/rails console #{options}",
in: @replica, out: @replica, err: @replica
)

assert_output "> ", @primary, 30
if wait_for_prompt
assert_output "> ", @primary, 30
end

pid
end

def test_sandbox
Expand All @@ -148,6 +152,17 @@ def test_sandbox
@primary.puts "quit"
end

def test_sandbox_when_sandbox_is_disabled
add_to_config <<-RUBY
config.disable_sandbox = true
RUBY

output = `#{app_path}/bin/rails console --sandbox`

assert_includes output, "sandbox mode is disabled"
assert_equal 1, $?.exitstatus
end

def test_environment_option_and_irb_option
spawn_console("-e test -- --verbose")

Expand Down
2 changes: 1 addition & 1 deletion railties/test/commands/console_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ def app
def build_app(console)
mocked_console = Class.new do
attr_accessor :sandbox
attr_reader :console
attr_reader :console, :disable_sandbox

def initialize(console)
@console = console
Expand Down

0 comments on commit b271052

Please sign in to comment.