Module: RailsConsoleAi::BuiltinGuards

Defined in:
lib/rails_console_ai/safety_guards.rb

Overview

Built-in guard: database write prevention Works on all Rails versions (5+) and all database adapters. Prepends a write-intercepting module once, controlled by a thread-local flag.

Defined Under Namespace

Modules: EngineCallBlocker, HttpBlocker, InProcessRequestBlocker, WriteBlocker

Class Method Summary collapse

Class Method Details

.check_in_process_request!(http_method, path) ⇒ Object



390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
# File 'lib/rails_console_ai/safety_guards.rb', line 390

def self.check_in_process_request!(http_method, path)
  return unless Thread.current[:rails_console_ai_block_in_process_requests]
  return if Thread.current[:rails_console_ai_bypass_guards]

  key = path.to_s
  guards = RailsConsoleAi.configuration.safety_guards
  return if !key.empty? && guards.allowed?(:in_process_requests, key)

  label = [http_method.to_s.upcase, key].reject(&:empty?).join(' ')
  raise RailsConsoleAi::SafetyError.new(
    "In-process HTTP request blocked (#{label.empty? ? 'app dispatch' : label}). " \
    "Dispatching a request through the app's own middleware stack from this session " \
    "can hang the process indefinitely, even for GET. Do not retry via another route " \
    "or Rack — call the controller's underlying service or model code directly instead.",
    guard: :in_process_requests,
    blocked_key: key.empty? ? nil : key
  )
end

.database_writesObject



295
296
297
298
299
300
301
302
303
304
305
# File 'lib/rails_console_ai/safety_guards.rb', line 295

def self.database_writes
  ->(& block) {
    ensure_write_blocker_installed!
    Thread.current[:rails_console_ai_block_writes] = true
    begin
      block.call
    ensure
      Thread.current[:rails_console_ai_block_writes] = false
    end
  }
end

.ensure_http_blocker_installed!Object



442
443
444
445
446
447
448
449
450
# File 'lib/rails_console_ai/safety_guards.rb', line 442

def self.ensure_http_blocker_installed!
  return if @http_blocker_installed

  require 'net/http'
  unless Net::HTTP.ancestors.include?(HttpBlocker)
    Net::HTTP.prepend(HttpBlocker)
  end
  @http_blocker_installed = true
end

.ensure_in_process_blocker_installed!Object



422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
# File 'lib/rails_console_ai/safety_guards.rb', line 422

def self.ensure_in_process_blocker_installed!
  return if @in_process_blocker_installed

  begin
    require 'action_dispatch'
    require 'action_dispatch/testing/integration'
  rescue LoadError, NameError
    nil # actionpack not (fully) available — the Engine backstop may still apply
  end

  if defined?(ActionDispatch::Integration::Session) &&
     !ActionDispatch::Integration::Session.ancestors.include?(InProcessRequestBlocker)
    ActionDispatch::Integration::Session.prepend(InProcessRequestBlocker)
  end
  if defined?(Rails::Engine) && !Rails::Engine.ancestors.include?(EngineCallBlocker)
    Rails::Engine.prepend(EngineCallBlocker)
  end
  @in_process_blocker_installed = true
end

.ensure_write_blocker_installed!Object



307
308
309
310
311
312
313
314
315
# File 'lib/rails_console_ai/safety_guards.rb', line 307

def self.ensure_write_blocker_installed!
  return if @write_blocker_installed

  connection = ActiveRecord::Base.connection
  unless connection.class.ancestors.include?(WriteBlocker)
    connection.class.prepend(WriteBlocker)
  end
  @write_blocker_installed = true
end

.http_mutationsObject



341
342
343
344
345
346
347
348
349
350
351
# File 'lib/rails_console_ai/safety_guards.rb', line 341

def self.http_mutations
  ->(&block) {
    ensure_http_blocker_installed!
    Thread.current[:rails_console_ai_block_http] = true
    begin
      block.call
    ensure
      Thread.current[:rails_console_ai_block_http] = false
    end
  }
end

.in_process_requestsObject



409
410
411
412
413
414
415
416
417
418
419
420
# File 'lib/rails_console_ai/safety_guards.rb', line 409

def self.in_process_requests
  ->(&block) {
    ensure_in_process_blocker_installed!
    prev = Thread.current[:rails_console_ai_block_in_process_requests]
    Thread.current[:rails_console_ai_block_in_process_requests] = true
    begin
      block.call
    ensure
      Thread.current[:rails_console_ai_block_in_process_requests] = prev
    end
  }
end

.mailersObject



353
354
355
356
357
358
359
360
361
362
363
364
365
# File 'lib/rails_console_ai/safety_guards.rb', line 353

def self.mailers
  ->(&block) {
    return block.call if Thread.current[:rails_console_ai_bypass_guards]

    old_value = ActionMailer::Base.perform_deliveries
    ActionMailer::Base.perform_deliveries = false
    begin
      block.call
    ensure
      ActionMailer::Base.perform_deliveries = old_value
    end
  }
end