safe_bro 0.1.0
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +215 -0
- data/Rakefile +3 -0
- data/app/controllers/safe_bro/application_controller.rb +50 -0
- data/app/controllers/safe_bro/audits_controller.rb +35 -0
- data/app/controllers/safe_bro/flagged_commands_controller.rb +14 -0
- data/app/controllers/safe_bro/rules_controller.rb +18 -0
- data/app/controllers/safe_bro/sessions_controller.rb +67 -0
- data/app/controllers/safe_bro/unlocks_controller.rb +14 -0
- data/app/helpers/safe_bro/application_helper.rb +59 -0
- data/app/jobs/safe_bro/purge_job.rb +10 -0
- data/app/models/safe_bro/application_record.rb +6 -0
- data/app/models/safe_bro/audit.rb +19 -0
- data/app/models/safe_bro/command.rb +20 -0
- data/app/models/safe_bro/sensitive_access.rb +13 -0
- data/app/models/safe_bro/session.rb +52 -0
- data/app/views/safe_bro/flagged_commands/index.html.erb +22 -0
- data/app/views/safe_bro/rules/show.html.erb +53 -0
- data/app/views/safe_bro/sessions/index.html.erb +51 -0
- data/app/views/safe_bro/sessions/show.html.erb +91 -0
- data/app/views/safe_bro/shared/forbidden.html.erb +3 -0
- data/app/views/safe_bro/unlocks/index.html.erb +25 -0
- data/config/routes.rb +11 -0
- data/db/migrate/20261114000001_create_safe_bro_tables.rb +52 -0
- data/lib/generators/safe_bro/install/install_generator.rb +35 -0
- data/lib/generators/safe_bro/install/templates/safe_bro.rb +45 -0
- data/lib/safe_bro/cipher.rb +45 -0
- data/lib/safe_bro/commands.rb +25 -0
- data/lib/safe_bro/configuration.rb +153 -0
- data/lib/safe_bro/encrypted_type.rb +16 -0
- data/lib/safe_bro/engine.rb +33 -0
- data/lib/safe_bro/errors.rb +41 -0
- data/lib/safe_bro/executor.rb +100 -0
- data/lib/safe_bro/guards/const_guard.rb +39 -0
- data/lib/safe_bro/guards/credentials_guard.rb +31 -0
- data/lib/safe_bro/guards/env_guard.rb +67 -0
- data/lib/safe_bro/guards/file_guard.rb +28 -0
- data/lib/safe_bro/guards/guarded.rb +43 -0
- data/lib/safe_bro/guards/irb_context.rb +23 -0
- data/lib/safe_bro/guards/method_guard.rb +31 -0
- data/lib/safe_bro/guards/network_guard.rb +98 -0
- data/lib/safe_bro/guards/sql_guard.rb +18 -0
- data/lib/safe_bro/parsed_command.rb +66 -0
- data/lib/safe_bro/recorder.rb +77 -0
- data/lib/safe_bro/shield.rb +83 -0
- data/lib/safe_bro/supervisor.rb +68 -0
- data/lib/safe_bro/terminal.rb +55 -0
- data/lib/safe_bro/validator.rb +74 -0
- data/lib/safe_bro/version.rb +3 -0
- data/lib/safe_bro.rb +98 -0
- data/lib/tasks/safe_bro.rake +7 -0
- metadata +141 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
module SafeBro
|
|
2
|
+
# The shield decides what is protected right now. Everything starts locked. unlock! asks for
|
|
3
|
+
# a justification, records it, and opens the requested resources until lock!.
|
|
4
|
+
#
|
|
5
|
+
# Resources:
|
|
6
|
+
# encrypted_data - Active Record encrypted attributes read as ciphertext
|
|
7
|
+
# credentials - Rails credentials, their keys, secret_key_base
|
|
8
|
+
# env - ENV values whose names look secret
|
|
9
|
+
# files - master.key, .env files
|
|
10
|
+
# network - connections to config.protected_urls
|
|
11
|
+
class Shield
|
|
12
|
+
RESOURCES = %i[ encrypted_data credentials env files network ].freeze
|
|
13
|
+
|
|
14
|
+
def initialize
|
|
15
|
+
@unlocked = Set.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def locked?
|
|
19
|
+
@unlocked.empty?
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def unlocked?(resource)
|
|
23
|
+
@unlocked.include?(:all) || @unlocked.include?(resource)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Is this resource protected from the console right now?
|
|
27
|
+
def protects?(resource)
|
|
28
|
+
SafeBro.config.protected_resources.include?(resource) && !unlocked?(resource)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def unlock(resources = [ :all ])
|
|
32
|
+
unknown = resources - RESOURCES - SafeBro.config.protected_resources - [ :all ]
|
|
33
|
+
raise ArgumentError, "Unknown resource #{unknown.first.inspect}. Choose from #{(RESOURCES | SafeBro.config.protected_resources).join(', ')}." if unknown.any?
|
|
34
|
+
|
|
35
|
+
SafeBro.executor.run_as_system do
|
|
36
|
+
justification = Terminal.ask(SafeBro.config.unlock_prompt)
|
|
37
|
+
SafeBro.recorder.start_sensitive_access(resources, justification)
|
|
38
|
+
@unlocked = Set.new(resources)
|
|
39
|
+
Terminal.say(SafeBro.config.unlocked_message, :yellow)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def lock(silent: false)
|
|
44
|
+
SafeBro.executor.run_as_system do
|
|
45
|
+
was_unlocked = !locked?
|
|
46
|
+
SafeBro.recorder.end_sensitive_access if SafeBro.recorder.session
|
|
47
|
+
@unlocked = Set.new
|
|
48
|
+
Terminal.say(SafeBro.config.locked_message, :yellow) if was_unlocked && !silent
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Runs the block with the protections that work by wrapping execution switched on.
|
|
53
|
+
def protect(&block)
|
|
54
|
+
if protects?(:encrypted_data) && encryption_available?
|
|
55
|
+
ActiveRecord::Encryption.protecting_encrypted_data(&block)
|
|
56
|
+
else
|
|
57
|
+
block.call
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def install
|
|
62
|
+
ActiveRecord::ConnectionAdapters::AbstractAdapter.prepend(Guards::SqlGuard)
|
|
63
|
+
Guards::NetworkGuard.install if SafeBro.config.protected_urls.any?
|
|
64
|
+
Guards::CredentialsGuard.install
|
|
65
|
+
Guards::EnvGuard.install
|
|
66
|
+
Guards::FileGuard.install
|
|
67
|
+
Guards::ConstGuard.install
|
|
68
|
+
Guards::MethodGuard.install
|
|
69
|
+
install_irb
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
def install_irb
|
|
74
|
+
IRB::Context.prepend(Guards::IrbContext)
|
|
75
|
+
IRB::Command.register(:unlock!, Commands::Unlock)
|
|
76
|
+
IRB::Command.register(:lock!, Commands::Lock)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def encryption_available?
|
|
80
|
+
defined?(ActiveRecord::Encryption) && ActiveRecord::Encryption.respond_to?(:protecting_encrypted_data)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
module SafeBro
|
|
2
|
+
# Entry point for a console (or runner) process: installs the protections once, then
|
|
3
|
+
# identifies the person, asks why, and opens a session.
|
|
4
|
+
class Supervisor
|
|
5
|
+
def run_console
|
|
6
|
+
install
|
|
7
|
+
start
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def run_runner
|
|
11
|
+
start(source: "runner", reason: runner_reason, username: runner_username, interactive: false)
|
|
12
|
+
SafeBro.recorder.record_command(runner_statements)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def install
|
|
16
|
+
return if @installed
|
|
17
|
+
|
|
18
|
+
# Loaded ahead of time: without this, the first command in a real console raised
|
|
19
|
+
# "uninitialized constant ActiveRecord::QueryMethods::Relation" (console1984 hit the same).
|
|
20
|
+
require "active_record/relation"
|
|
21
|
+
SafeBro.shield.install
|
|
22
|
+
SafeBro.lock_down! if SafeBro.config.freeze_internals
|
|
23
|
+
@installed = true
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def start(source: "console", reason: nil, username: nil, interactive: true)
|
|
27
|
+
SafeBro.shield.lock(silent: true)
|
|
28
|
+
Terminal.say(SafeBro.config.welcome_message, :yellow) if interactive
|
|
29
|
+
username ||= current_username
|
|
30
|
+
reason ||= session_reason(username)
|
|
31
|
+
SafeBro.recorder.start_session(username: username, reason: reason, source: source)
|
|
32
|
+
at_exit { stop }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def stop
|
|
36
|
+
SafeBro.recorder.finish_session
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def current_username
|
|
40
|
+
SafeBro.config.username_resolver.call.to_s.strip.presence ||
|
|
41
|
+
(SafeBro.config.ask_for_username_if_empty && Terminal.interactive? && Terminal.ask("Please, enter your name:")) ||
|
|
42
|
+
raise(MissingUsername)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
# CONSOLE_REASON answers without a terminal; otherwise ask, and refuse to continue without one.
|
|
47
|
+
def session_reason(username)
|
|
48
|
+
env_reason = ENV["CONSOLE_REASON"].to_s.squish.first(255).presence
|
|
49
|
+
return env_reason if env_reason
|
|
50
|
+
raise MissingReason unless Terminal.interactive?
|
|
51
|
+
|
|
52
|
+
Terminal.ask("#{username}, why are you using this console today?")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Runners are usually automation: don't refuse, but record that nobody said who or why.
|
|
56
|
+
def runner_reason
|
|
57
|
+
ENV["CONSOLE_REASON"].to_s.squish.first(255).presence || "(no reason given)"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def runner_username
|
|
61
|
+
SafeBro.config.username_resolver.call.to_s.strip.presence || "(unattributed)"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def runner_statements
|
|
65
|
+
"rails runner #{ARGV.join(' ')}".strip
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
module SafeBro
|
|
2
|
+
# Talking to the person at the console.
|
|
3
|
+
module Terminal
|
|
4
|
+
COLORS = { green: 32, yellow: 33, red: 31, blue: 34 }.freeze
|
|
5
|
+
|
|
6
|
+
class << self
|
|
7
|
+
# Tests silence console output.
|
|
8
|
+
attr_accessor :muted
|
|
9
|
+
|
|
10
|
+
def interactive?
|
|
11
|
+
$stdin.tty?
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def say(message, color = nil)
|
|
15
|
+
return if muted
|
|
16
|
+
|
|
17
|
+
text = "\n#{message}\n"
|
|
18
|
+
$stdout.puts(color && $stdout.tty? ? "\e[#{COLORS.fetch(color)}m#{text}\e[0m" : text)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Something worth the person's attention, on stderr so it doesn't mix with output they pipe.
|
|
22
|
+
def alert(message)
|
|
23
|
+
$stderr.puts("SafeBro: #{message}") unless muted
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Asks until it gets a non-blank answer. Raises NotInteractive when there's nobody to ask.
|
|
27
|
+
def ask(question)
|
|
28
|
+
raise NotInteractive unless interactive?
|
|
29
|
+
|
|
30
|
+
say(question, :green)
|
|
31
|
+
answer = nil
|
|
32
|
+
with_plain_output do
|
|
33
|
+
loop do
|
|
34
|
+
line = Reline.readline("> ", false)
|
|
35
|
+
raise NotInteractive, "No answer given." if line.nil?
|
|
36
|
+
|
|
37
|
+
answer = line.strip
|
|
38
|
+
break unless answer.empty?
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
answer
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
# IRB colorizes what you type; the answer should look like plain text.
|
|
46
|
+
def with_plain_output
|
|
47
|
+
original = Reline.output_modifier_proc
|
|
48
|
+
Reline.output_modifier_proc = nil
|
|
49
|
+
yield
|
|
50
|
+
ensure
|
|
51
|
+
Reline.output_modifier_proc = original
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
module SafeBro
|
|
2
|
+
# Static analysis of a console command, before it runs. Each validation raises
|
|
3
|
+
# ForbiddenCommand (blocked) or SuspiciousCommand (runs, but is flagged).
|
|
4
|
+
#
|
|
5
|
+
# This is a speed bump for honest mistakes and a tripwire for dishonest attempts. It is not
|
|
6
|
+
# a sandbox: Ruby is too dynamic for that. The runtime guards (see Guards) cover the most
|
|
7
|
+
# obvious ways around it, and the rest is what the audit trail is for.
|
|
8
|
+
class Validator
|
|
9
|
+
class Validation
|
|
10
|
+
def initialize(config)
|
|
11
|
+
@config = config
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
attr_reader :config
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# class Foo / module Foo for a protected Foo or Foo::Bar.
|
|
18
|
+
class ForbiddenReopening < Validation
|
|
19
|
+
def validate(command, locked:)
|
|
20
|
+
command.reopened_constants.each do |name|
|
|
21
|
+
if config.forbidden_reopening.any? { |forbidden| name == forbidden || name.start_with?("#{forbidden}::") }
|
|
22
|
+
raise ForbiddenCommand, "Reopening #{name} is not allowed."
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class ForbiddenConstantReference < Validation
|
|
29
|
+
def validate(command, locked:)
|
|
30
|
+
forbidden = config.forbidden_constants.fetch(:always, [])
|
|
31
|
+
forbidden += config.forbidden_constants.fetch(:locked, []) if locked
|
|
32
|
+
command.constant_paths.each do |path|
|
|
33
|
+
match = forbidden.find { |f| path == f || path.start_with?("#{f}::") }
|
|
34
|
+
raise ForbiddenCommand, "Referencing #{match} is not allowed#{' while locked' unless config.forbidden_constants.fetch(:always, []).include?(match)}." if match
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Method names from forbidden_methods ("Kernel#eval" -> eval), wherever they appear.
|
|
40
|
+
class ForbiddenMethods < Validation
|
|
41
|
+
def validate(command, locked:)
|
|
42
|
+
names = config.forbidden_methods.map { |entry| entry.split(/[#.]/, 2).last }.uniq
|
|
43
|
+
used = command.method_names & names
|
|
44
|
+
raise ForbiddenCommand, "Calling #{used.first} is not allowed." if used.any?
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
class SuspiciousTerms < Validation
|
|
49
|
+
def validate(command, locked:)
|
|
50
|
+
term = config.suspicious_terms.find { |t| command.source.include?(t) }
|
|
51
|
+
raise SuspiciousCommand, "Command mentions #{term}." if term
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
VALIDATIONS = [ ForbiddenReopening, ForbiddenConstantReference, ForbiddenMethods, SuspiciousTerms ].freeze
|
|
56
|
+
|
|
57
|
+
def initialize(config = SafeBro.config)
|
|
58
|
+
@validations = VALIDATIONS.map { |klass| klass.new(config) }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Raises on the first failing validation. Forbidden beats suspicious, so run every
|
|
62
|
+
# validation and only then raise a suspicion.
|
|
63
|
+
def validate(source, locked: true)
|
|
64
|
+
command = ParsedCommand.new(source)
|
|
65
|
+
suspicion = nil
|
|
66
|
+
@validations.each do |validation|
|
|
67
|
+
validation.validate(command, locked: locked)
|
|
68
|
+
rescue SuspiciousCommand => error
|
|
69
|
+
suspicion ||= error
|
|
70
|
+
end
|
|
71
|
+
raise suspicion if suspicion
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
data/lib/safe_bro.rb
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
require "irb"
|
|
2
|
+
require "reline"
|
|
3
|
+
require "set"
|
|
4
|
+
require "socket"
|
|
5
|
+
|
|
6
|
+
require "bro_hub"
|
|
7
|
+
require "safe_bro/version"
|
|
8
|
+
require "safe_bro/errors"
|
|
9
|
+
require "safe_bro/configuration"
|
|
10
|
+
require "safe_bro/cipher"
|
|
11
|
+
require "safe_bro/parsed_command"
|
|
12
|
+
require "safe_bro/validator"
|
|
13
|
+
require "safe_bro/terminal"
|
|
14
|
+
require "safe_bro/recorder"
|
|
15
|
+
require "safe_bro/executor"
|
|
16
|
+
require "safe_bro/guards/guarded"
|
|
17
|
+
require "safe_bro/guards/sql_guard"
|
|
18
|
+
require "safe_bro/guards/network_guard"
|
|
19
|
+
require "safe_bro/guards/credentials_guard"
|
|
20
|
+
require "safe_bro/guards/env_guard"
|
|
21
|
+
require "safe_bro/guards/file_guard"
|
|
22
|
+
require "safe_bro/guards/method_guard"
|
|
23
|
+
require "safe_bro/guards/const_guard"
|
|
24
|
+
require "safe_bro/guards/irb_context"
|
|
25
|
+
require "safe_bro/commands"
|
|
26
|
+
require "safe_bro/shield"
|
|
27
|
+
require "safe_bro/supervisor"
|
|
28
|
+
require "safe_bro/engine"
|
|
29
|
+
|
|
30
|
+
# = SafeBro
|
|
31
|
+
#
|
|
32
|
+
# Audited, guarded Rails console access.
|
|
33
|
+
#
|
|
34
|
+
# * Every console session is recorded: who, why, and every command (encrypted at rest).
|
|
35
|
+
# * Sensitive data (encrypted attributes, credentials, ENV secrets, protected systems) is
|
|
36
|
+
# locked until the person runs unlock! and explains why. Everything done while unlocked
|
|
37
|
+
# is linked to that justification.
|
|
38
|
+
# * Commands are checked before they run: forbidden ones are blocked, suspicious ones flagged.
|
|
39
|
+
# * The audit trail itself can't be changed from the console.
|
|
40
|
+
# * Auditors review sessions in a mountable engine.
|
|
41
|
+
#
|
|
42
|
+
# Access from the console is a deterrent plus an audit trail, not a sandbox. See the README.
|
|
43
|
+
module SafeBro
|
|
44
|
+
class << self
|
|
45
|
+
attr_accessor :debug
|
|
46
|
+
|
|
47
|
+
def config
|
|
48
|
+
@config ||= Configuration.new
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def configure
|
|
52
|
+
yield config
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def reset_config!
|
|
56
|
+
@config = nil
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def enabled?
|
|
60
|
+
config.enabled?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# The collaborators are created on first use and then shared.
|
|
64
|
+
def supervisor = @supervisor ||= Supervisor.new
|
|
65
|
+
def shield = @shield ||= Shield.new
|
|
66
|
+
def executor = @executor ||= Executor.new
|
|
67
|
+
def recorder = @recorder ||= Recorder.new
|
|
68
|
+
def validator = @validator ||= Validator.new(config)
|
|
69
|
+
|
|
70
|
+
# For gems that expose sensitive things (e.g. a credentials reveal): call this first.
|
|
71
|
+
# Raises ProtectedResource when running under a console command while the resource is
|
|
72
|
+
# locked. With `locations:` (caller_locations) it only raises for calls made directly
|
|
73
|
+
# from what the person typed.
|
|
74
|
+
def guard!(resource, detail = nil, locations: nil)
|
|
75
|
+
return unless executor.executing_user_command? && shield.protects?(resource)
|
|
76
|
+
return if locations && !executor.from_console?(locations)
|
|
77
|
+
|
|
78
|
+
raise ProtectedResource.new(resource, detail)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Subscribe with ActiveSupport::Notifications.subscribe(/\.safe_bro\z/) to alert people.
|
|
82
|
+
# Events: session_started, unlocked, command_flagged, session_flagged.
|
|
83
|
+
def notify(event, **payload)
|
|
84
|
+
ActiveSupport::Notifications.instrument("#{event}.safe_bro", payload)
|
|
85
|
+
rescue => error
|
|
86
|
+
Rails.logger&.error("SafeBro: notification #{event} failed: #{error.class}: #{error.message}")
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Freezes configuration and SafeBro's own classes so they can't be monkey patched from a console.
|
|
90
|
+
def lock_down!
|
|
91
|
+
config.lock_down!
|
|
92
|
+
[ Executor, Shield, Recorder, Supervisor, Validator, Validator::Validation, *Validator::VALIDATIONS,
|
|
93
|
+
ParsedCommand, Terminal, Commands::Unlock, Commands::Lock, Guards::Guarded, Guards::SqlGuard,
|
|
94
|
+
Guards::EnvGuard, Guards::FileGuard, Guards::MethodGuard, Guards::ConstGuard, Guards::CredentialsGuard, Guards::CredentialsGuard::Inspect,
|
|
95
|
+
Guards::IrbContext ].each(&:freeze)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
namespace :safe_bro do
|
|
2
|
+
desc "Delete console sessions older than config.retention (flagged ones are kept unless keep_flagged is off)"
|
|
3
|
+
task purge: :environment do
|
|
4
|
+
deleted = SafeBro::Session.purge!
|
|
5
|
+
puts "SafeBro: purged #{deleted} session#{'s' unless deleted == 1}"
|
|
6
|
+
end
|
|
7
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: safe_bro
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Ruby Dev SRL
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: rails
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '7.1'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '7.1'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: irb
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '1.13'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.13'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: bro_hub
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0.1'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0.1'
|
|
54
|
+
description: Records Rails console sessions (who, why, what), asks for a justification
|
|
55
|
+
before sensitive data is touched, blocks tampering and risky methods, and ships
|
|
56
|
+
a mountable engine where auditors review sessions. One gem instead of console1984
|
|
57
|
+
plus audits1984.
|
|
58
|
+
email:
|
|
59
|
+
- office@rubydev.ro
|
|
60
|
+
executables: []
|
|
61
|
+
extensions: []
|
|
62
|
+
extra_rdoc_files: []
|
|
63
|
+
files:
|
|
64
|
+
- MIT-LICENSE
|
|
65
|
+
- README.md
|
|
66
|
+
- Rakefile
|
|
67
|
+
- app/controllers/safe_bro/application_controller.rb
|
|
68
|
+
- app/controllers/safe_bro/audits_controller.rb
|
|
69
|
+
- app/controllers/safe_bro/flagged_commands_controller.rb
|
|
70
|
+
- app/controllers/safe_bro/rules_controller.rb
|
|
71
|
+
- app/controllers/safe_bro/sessions_controller.rb
|
|
72
|
+
- app/controllers/safe_bro/unlocks_controller.rb
|
|
73
|
+
- app/helpers/safe_bro/application_helper.rb
|
|
74
|
+
- app/jobs/safe_bro/purge_job.rb
|
|
75
|
+
- app/models/safe_bro/application_record.rb
|
|
76
|
+
- app/models/safe_bro/audit.rb
|
|
77
|
+
- app/models/safe_bro/command.rb
|
|
78
|
+
- app/models/safe_bro/sensitive_access.rb
|
|
79
|
+
- app/models/safe_bro/session.rb
|
|
80
|
+
- app/views/safe_bro/flagged_commands/index.html.erb
|
|
81
|
+
- app/views/safe_bro/rules/show.html.erb
|
|
82
|
+
- app/views/safe_bro/sessions/index.html.erb
|
|
83
|
+
- app/views/safe_bro/sessions/show.html.erb
|
|
84
|
+
- app/views/safe_bro/shared/forbidden.html.erb
|
|
85
|
+
- app/views/safe_bro/unlocks/index.html.erb
|
|
86
|
+
- config/routes.rb
|
|
87
|
+
- db/migrate/20261114000001_create_safe_bro_tables.rb
|
|
88
|
+
- lib/generators/safe_bro/install/install_generator.rb
|
|
89
|
+
- lib/generators/safe_bro/install/templates/safe_bro.rb
|
|
90
|
+
- lib/safe_bro.rb
|
|
91
|
+
- lib/safe_bro/cipher.rb
|
|
92
|
+
- lib/safe_bro/commands.rb
|
|
93
|
+
- lib/safe_bro/configuration.rb
|
|
94
|
+
- lib/safe_bro/encrypted_type.rb
|
|
95
|
+
- lib/safe_bro/engine.rb
|
|
96
|
+
- lib/safe_bro/errors.rb
|
|
97
|
+
- lib/safe_bro/executor.rb
|
|
98
|
+
- lib/safe_bro/guards/const_guard.rb
|
|
99
|
+
- lib/safe_bro/guards/credentials_guard.rb
|
|
100
|
+
- lib/safe_bro/guards/env_guard.rb
|
|
101
|
+
- lib/safe_bro/guards/file_guard.rb
|
|
102
|
+
- lib/safe_bro/guards/guarded.rb
|
|
103
|
+
- lib/safe_bro/guards/irb_context.rb
|
|
104
|
+
- lib/safe_bro/guards/method_guard.rb
|
|
105
|
+
- lib/safe_bro/guards/network_guard.rb
|
|
106
|
+
- lib/safe_bro/guards/sql_guard.rb
|
|
107
|
+
- lib/safe_bro/parsed_command.rb
|
|
108
|
+
- lib/safe_bro/recorder.rb
|
|
109
|
+
- lib/safe_bro/shield.rb
|
|
110
|
+
- lib/safe_bro/supervisor.rb
|
|
111
|
+
- lib/safe_bro/terminal.rb
|
|
112
|
+
- lib/safe_bro/validator.rb
|
|
113
|
+
- lib/safe_bro/version.rb
|
|
114
|
+
- lib/tasks/safe_bro.rake
|
|
115
|
+
homepage: https://www.rubydev.ro
|
|
116
|
+
licenses:
|
|
117
|
+
- MIT
|
|
118
|
+
metadata:
|
|
119
|
+
allowed_push_host: https://rubygems.org
|
|
120
|
+
rubygems_mfa_required: 'true'
|
|
121
|
+
homepage_uri: https://www.rubydev.ro
|
|
122
|
+
source_code_uri: https://github.com/rubydevro/safe_bro
|
|
123
|
+
bug_tracker_uri: https://github.com/rubydevro/safe_bro/issues
|
|
124
|
+
rdoc_options: []
|
|
125
|
+
require_paths:
|
|
126
|
+
- lib
|
|
127
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
128
|
+
requirements:
|
|
129
|
+
- - ">="
|
|
130
|
+
- !ruby/object:Gem::Version
|
|
131
|
+
version: '3.2'
|
|
132
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
133
|
+
requirements:
|
|
134
|
+
- - ">="
|
|
135
|
+
- !ruby/object:Gem::Version
|
|
136
|
+
version: '0'
|
|
137
|
+
requirements: []
|
|
138
|
+
rubygems_version: 4.0.10
|
|
139
|
+
specification_version: 4
|
|
140
|
+
summary: Audited, guarded Rails console access, with a built-in review UI.
|
|
141
|
+
test_files: []
|