console1984 0.1.8 → 0.1.12

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: bc64d037f2de5570292e0b09710b4543a68ba1af12759150cc68e7b7f4dd6e16
4
- data.tar.gz: c5929af5061393a32c4df38022535d53c62aaaabe951727798e340322fb0d950
3
+ metadata.gz: a3bfdacf3954fbc30c23046c89f7951d061c419533685fb5ecc2db6a6cf41a1d
4
+ data.tar.gz: 0b26c9effb7ffdd1df5ca51c344c53c3d9260bef1b02c4c2d9e6c8949dceb032
5
5
  SHA512:
6
- metadata.gz: b64f422bcdd421e7a874af965c9ee615f6d873e0d2a685d68667216fd1ee91d4e8e4815d4dd9e78ef7838a75763fb898be1f328c8466a137f1782a4121531da6
7
- data.tar.gz: ce7e1f60bfdb666abe3c85a02ddaa1ac25344c994a1471ec64d07cd15f8aac5735bcd0608497e71d9813cf111cdb76f414a2373eb8dbef14b983757ba3876812
6
+ metadata.gz: b24bf1fbfc353fdd8b5ac2194f4f30588281269748ea192def291cdedc19697b1bd75bff71baec0761e45ff08ec78aca78ff41d0b78c8ca6ae1cea4bdb43512e
7
+ data.tar.gz: 43628c0bd4f76662b65f1c343885618a4c54e2b8b40d536720f8c3eecd696ae51c03c78329810387598e68042622b6c6c15f6b015caf3b93d6fb3779bf349f95
@@ -1,12 +1,14 @@
1
- static_validations:
1
+ validations:
2
2
  forbidden_reopening:
3
3
  - ActiveRecord
4
4
  - Console1984
5
5
  - PG
6
6
  - Mysql2
7
+ - IRB
7
8
  forbidden_constant_reference:
8
9
  always:
9
10
  - Console1984
11
+ - IRB
10
12
  protected:
11
13
  - PG
12
14
  - Mysql2
@@ -16,15 +18,14 @@ static_validations:
16
18
  - Console1984
17
19
  - secret
18
20
  - credentials
21
+ - irb
19
22
  forbidden_methods:
20
- always:
21
- user:
22
- Kernel:
23
- - eval
24
- Object:
25
- - eval
26
- BasicObject:
27
- - eval
28
- - instance_eval
29
- Module:
30
- - class_eval
23
+ Kernel:
24
+ - eval
25
+ Object:
26
+ - eval
27
+ BasicObject:
28
+ - eval
29
+ - instance_eval
30
+ Module:
31
+ - class_eval
@@ -19,11 +19,15 @@ class Console1984::CommandExecutor
19
19
  run_as_system { session_logger.before_executing commands }
20
20
  validate_command commands
21
21
  execute_in_protected_mode(&block)
22
- rescue Console1984::Errors::ForbiddenCommand, FrozenError => e
22
+ rescue Console1984::Errors::ForbiddenCommandAttempted, FrozenError
23
23
  flag_suspicious(commands)
24
- rescue Console1984::Errors::SuspiciousCommand
24
+ rescue Console1984::Errors::SuspiciousCommandAttempted
25
25
  flag_suspicious(commands)
26
26
  execute_in_protected_mode(&block)
27
+ rescue Console1984::Errors::ForbiddenCommandExecuted
28
+ # We detected that a forbidden command was executed. We exit IRB right away.
29
+ flag_suspicious(commands)
30
+ Console1984.supervisor.exit_irb
27
31
  ensure
28
32
  run_as_system { session_logger.after_executing commands }
29
33
  end
@@ -65,13 +69,21 @@ class Console1984::CommandExecutor
65
69
  command_validator.validate(command)
66
70
  end
67
71
 
72
+ def from_irb?(backtrace)
73
+ executing_user_command? && backtrace.find do |line|
74
+ line_from_irb = line =~ /^[^\/]/
75
+ break if !(line =~ /console1984\/lib/ || line_from_irb)
76
+ line_from_irb
77
+ end
78
+ end
79
+
68
80
  private
69
81
  def command_validator
70
82
  @command_validator ||= build_command_validator
71
83
  end
72
84
 
73
85
  def build_command_validator
74
- Console1984::CommandValidator.from_config(Console1984.protections_config.static_validations)
86
+ Console1984::CommandValidator.from_config(Console1984.protections_config.validations)
75
87
  end
76
88
 
77
89
  def flag_suspicious(commands)
@@ -0,0 +1,69 @@
1
+ # Naming class with dot so that it doesn't get loaded eagerly by Zeitwork. We want to load
2
+ # only when a console session is started, when +parser+ is loaded.
3
+ #
4
+ # See +Console1984::Supervisor#require_dependencies+
5
+ class Console1984::CommandValidator::CommandParser < ::Parser::AST::Processor
6
+ include AST::Processor::Mixin
7
+ include Console1984::Freezeable
8
+
9
+ def initialize
10
+ @constants = []
11
+ @declared_classes_or_modules = []
12
+ @constant_assignments = []
13
+ end
14
+
15
+ # We define accessors to define lists without duplicates. We are not using a +SortedSet+ because we want
16
+ # to mutate strings in the list while the processing is happening. And we don't use metapgroamming to define the
17
+ # accessors to prevent having problems with freezable and its instance_variable* protection.
18
+
19
+ def constants
20
+ @constants.uniq
21
+ end
22
+
23
+ def declared_classes_or_modules
24
+ @declared_classes_or_modules.uniq
25
+ end
26
+
27
+ def constant_assignments
28
+ @constant_assignments.uniq
29
+ end
30
+
31
+ def on_class(node)
32
+ super
33
+ const_declaration, _, _ = *node
34
+ constant = extract_constants(const_declaration).first
35
+ @declared_classes_or_modules << constant if constant.present?
36
+ end
37
+
38
+ alias_method :on_module, :on_class
39
+
40
+ def on_const(node)
41
+ super
42
+ name, const_name = *node
43
+ const_name = const_name.to_s
44
+ last_constant = @constants.last
45
+
46
+ if name.nil? || (name && name.type == :cbase) # cbase = leading ::
47
+ if last_constant&.end_with?("::")
48
+ last_constant << const_name
49
+ else
50
+ @constants << const_name
51
+ end
52
+ elsif last_constant
53
+ last_constant << "::#{const_name}"
54
+ end
55
+ end
56
+
57
+ def on_casgn(node)
58
+ super
59
+ scope_node, name, value_node = *node
60
+ @constant_assignments.push(*extract_constants(value_node))
61
+ end
62
+
63
+ private
64
+ def extract_constants(node)
65
+ self.class.new.tap do |processor|
66
+ processor.process(node)
67
+ end.constants
68
+ end
69
+ end
@@ -14,11 +14,11 @@ class Console1984::CommandValidator::ForbiddenConstantReferenceValidation
14
14
  @constant_names_forbidden_in_protected_mode = config[:protected] || []
15
15
  end
16
16
 
17
- # Raises a Console1984::Errors::ForbiddenCommand if a banned constant is referenced.
17
+ # Raises a Console1984::Errors::ForbiddenCommandAttempted if a banned constant is referenced.
18
18
  def validate(parsed_command)
19
19
  if contains_invalid_const_reference?(parsed_command, @forbidden_constants_names) ||
20
20
  (@shield.protected_mode? && contains_invalid_const_reference?(parsed_command, @constant_names_forbidden_in_protected_mode))
21
- raise Console1984::Errors::ForbiddenCommand
21
+ raise Console1984::Errors::ForbiddenCommandAttempted
22
22
  end
23
23
  end
24
24
 
@@ -8,11 +8,11 @@ class Console1984::CommandValidator::ForbiddenReopeningValidation
8
8
  @banned_class_or_module_names = banned_classes_or_modules.collect(&:to_s)
9
9
  end
10
10
 
11
- # Raises a Console1984::Errors::ForbiddenCommand if an banned class or module reopening
11
+ # Raises a Console1984::Errors::ForbiddenCommandAttempted if an banned class or module reopening
12
12
  # is detected.
13
13
  def validate(parsed_command)
14
14
  if contains_invalid_class_or_module_declaration?(parsed_command)
15
- raise Console1984::Errors::ForbiddenCommand
15
+ raise Console1984::Errors::ForbiddenCommandAttempted
16
16
  end
17
17
  end
18
18
 
@@ -6,85 +6,19 @@ class Console1984::CommandValidator::ParsedCommand
6
6
 
7
7
  attr_reader :raw_command
8
8
 
9
- delegate :declared_classes_or_modules, :constants, :constant_assignments, to: :processed_ast
9
+ delegate :declared_classes_or_modules, :constants, :constant_assignments, to: :command_parser
10
10
 
11
11
  def initialize(raw_command)
12
12
  @raw_command = Array(raw_command).join("\n")
13
13
  end
14
14
 
15
15
  private
16
- def processed_ast
17
- @processed_ast ||= CommandProcessor.new.tap do |processor|
16
+ def command_parser
17
+ @command_parser ||= Console1984::CommandValidator::CommandParser.new.tap do |processor|
18
18
  ast = Parser::CurrentRuby.parse(raw_command)
19
19
  processor.process(ast)
20
20
  rescue Parser::SyntaxError
21
21
  # Fail open with syntax errors
22
22
  end
23
23
  end
24
-
25
- class CommandProcessor < ::Parser::AST::Processor
26
- include AST::Processor::Mixin
27
- include Console1984::Freezeable
28
-
29
- def initialize
30
- @constants = []
31
- @declared_classes_or_modules = []
32
- @constant_assignments = []
33
- end
34
-
35
- # We define accessors to define lists without duplicates. We are not using a +SortedSet+ because we want
36
- # to mutate strings in the list while the processing is happening. And we don't use metapgroamming to define the
37
- # accessors to prevent having problems with freezable and its instance_variable* protection.
38
-
39
- def constants
40
- @constants.uniq
41
- end
42
-
43
- def declared_classes_or_modules
44
- @declared_classes_or_modules.uniq
45
- end
46
-
47
- def constant_assignments
48
- @constant_assignments.uniq
49
- end
50
-
51
- def on_class(node)
52
- super
53
- const_declaration, _, _ = *node
54
- constant = extract_constants(const_declaration).first
55
- @declared_classes_or_modules << constant if constant.present?
56
- end
57
-
58
- alias_method :on_module, :on_class
59
-
60
- def on_const(node)
61
- super
62
- name, const_name = *node
63
- const_name = const_name.to_s
64
- last_constant = @constants.last
65
-
66
- if name.nil? || (name && name.type == :cbase) # cbase = leading ::
67
- if last_constant&.end_with?("::")
68
- last_constant << const_name
69
- else
70
- @constants << const_name
71
- end
72
- elsif last_constant
73
- last_constant << "::#{const_name}"
74
- end
75
- end
76
-
77
- def on_casgn(node)
78
- super
79
- scope_node, name, value_node = *node
80
- @constant_assignments.push(*extract_constants(value_node))
81
- end
82
-
83
- private
84
- def extract_constants(node)
85
- self.class.new.tap do |processor|
86
- processor.process(node)
87
- end.constants
88
- end
89
- end
90
24
  end
@@ -9,7 +9,7 @@ class Console1984::CommandValidator::SuspiciousTermsValidation
9
9
  # Raises a Console1984::Errors::SuspiciousCommand if the term is referenced.
10
10
  def validate(parsed_command)
11
11
  if contains_suspicious_term?(parsed_command)
12
- raise Console1984::Errors::SuspiciousCommand
12
+ raise Console1984::Errors::SuspiciousCommandAttempted
13
13
  end
14
14
  end
15
15
 
@@ -5,7 +5,7 @@
5
5
  #
6
6
  # The validation itself happens as a chain of validation objects. The system will invoke
7
7
  # each validation in order. Validations will raise an error if the validation fails (typically
8
- # a Console1984::Errors::ForbiddenCommand or Console1984::Errors::SuspiciousCommands).
8
+ # a Console1984::Errors::ForbiddenCommandAttempted or Console1984::Errors::SuspiciousCommands).
9
9
  #
10
10
  # Internally, validations will receive a Console1984::CommandValidator::ParsedCommand object. This
11
11
  # exposes parsed constructs in addition to the raw strings so that validations can use those.
@@ -10,11 +10,15 @@ module Console1984
10
10
 
11
11
  # Attempt to execute a command that is not allowed. The system won't
12
12
  # execute such commands and will flag them as sensitive.
13
- class ForbiddenCommand < StandardError; end
13
+ class ForbiddenCommandAttempted < StandardError; end
14
14
 
15
15
  # A suspicious command was executed. The command will be flagged but the system
16
16
  # will let it run.
17
- class SuspiciousCommand < StandardError; end
17
+ class SuspiciousCommandAttempted < StandardError; end
18
+
19
+ # A forbidden command was executed. The system will flag the command
20
+ # and exit.
21
+ class ForbiddenCommandExecuted < StandardError; end
18
22
 
19
23
  # Attempt to incinerate a session ahead of time as determined by
20
24
  # +config.console1984.incinerate_after+.
@@ -6,7 +6,7 @@ module Console1984::Ext::ActiveRecord::ProtectedAuditableTables
6
6
  define_method method do |*args, **kwargs|
7
7
  sql = args.first
8
8
  if Console1984.command_executor.executing_user_command? && sql =~ auditable_tables_regexp
9
- raise Console1984::Errors::ForbiddenCommand, "#{sql}"
9
+ raise Console1984::Errors::ForbiddenCommandAttempted, "#{sql}"
10
10
  else
11
11
  super(*args, **kwargs)
12
12
  end
@@ -7,9 +7,26 @@ module Console1984::Ext::Core::Module
7
7
 
8
8
  def instance_eval(*)
9
9
  if Console1984.command_executor.executing_user_command?
10
- raise Console1984::Errors::ForbiddenCommand
10
+ raise Console1984::Errors::ForbiddenCommandAttempted
11
11
  else
12
12
  super
13
13
  end
14
14
  end
15
+
16
+ def method_added(method)
17
+ if Console1984.command_executor.from_irb?(caller) && banned_for_reopening?
18
+ raise Console1984::Errors::ForbiddenCommandExecuted, "Trying to add method `#{method}` to #{self.name}"
19
+ end
20
+ end
21
+
22
+ private
23
+ def banned_for_reopening?
24
+ classes_and_modules_banned_for_reopening.find do |banned_class_or_module_name|
25
+ "#{self.name}::".start_with?("#{banned_class_or_module_name}::")
26
+ end
27
+ end
28
+
29
+ def classes_and_modules_banned_for_reopening
30
+ @classes_and_modules_banned_for_reopening ||= Console1984.protections_config.validations[:forbidden_reopening]
31
+ end
15
32
  end
@@ -25,7 +25,7 @@ module Console1984::Ext::Core::Object
25
25
  # See the list +forbidden_reopening+ in +config/command_protections.yml+.
26
26
  Console1984.command_executor.validate_command("class #{arguments.first}; end")
27
27
  super
28
- rescue Console1984::Errors::ForbiddenCommand
28
+ rescue Console1984::Errors::ForbiddenCommandAttempted
29
29
  raise
30
30
  rescue StandardError
31
31
  super
@@ -39,7 +39,7 @@ module Console1984::Freezeable
39
39
  private
40
40
  def prevent_sensitive_method(method_name)
41
41
  define_method method_name do |*arguments|
42
- raise Console1984::Errors::ForbiddenCommand, "You can't invoke #{method_name} on #{self}"
42
+ raise Console1984::Errors::ForbiddenCommandAttempted, "You can't invoke #{method_name} on #{self}"
43
43
  end
44
44
  end
45
45
  end
@@ -1,7 +1,7 @@
1
1
  class Console1984::ProtectionsConfig
2
2
  include Console1984::Freezeable
3
3
 
4
- delegate :static_validations, to: :instance
4
+ delegate :validations, to: :instance
5
5
 
6
6
  attr_reader :config
7
7
 
@@ -9,7 +9,7 @@ class Console1984::ProtectionsConfig
9
9
  @config = config
10
10
  end
11
11
 
12
- %i[ static_validations forbidden_methods ].each do |method_name|
12
+ %i[ validations forbidden_methods ].each do |method_name|
13
13
  define_method method_name do
14
14
  config[method_name].symbolize_keys
15
15
  end
@@ -11,14 +11,17 @@ class Console1984::Refrigerator
11
11
  end
12
12
 
13
13
  private
14
- EXTERNAL_MODULES_AND_CLASSES_TO_FREEZE = [Parser::CurrentRuby]
15
-
16
14
  def freeze_internal_instances
17
15
  Console1984.config.freeze unless Console1984.config.test_mode
18
16
  end
19
17
 
20
18
  def freeze_external_modules_and_classes
21
- EXTERNAL_MODULES_AND_CLASSES_TO_FREEZE.each { |klass| klass.include(Console1984::Freezeable) }
19
+ external_modules_and_classes_to_freeze.each { |klass| klass.include(Console1984::Freezeable) }
20
+ end
21
+
22
+ def external_modules_and_classes_to_freeze
23
+ # Not using a constant because we want this to run lazily (console-dependant dependencies might not be loaded).
24
+ [Parser::CurrentRuby]
22
25
  end
23
26
 
24
27
  def eager_load_all_classes
@@ -26,7 +29,3 @@ class Console1984::Refrigerator
26
29
  Console1984.class_loader.eager_load
27
30
  end
28
31
  end
29
-
30
- class Parser::Ruby27
31
- include Console1984::Freezeable
32
- end
@@ -3,22 +3,20 @@ class Console1984::Shield::MethodInvocationShell
3
3
  include Console1984::Freezeable
4
4
 
5
5
  class << self
6
- def install_for(config)
7
- Array(config[:user]).each { |invocation| self.new(invocation, only_for_user_commands: true).prevent_methods_invocation }
8
- Array(config[:system]).each { |invocation| self.new(invocation, only_for_user_commands: false).prevent_methods_invocation }
6
+ def install_for(invocations)
7
+ Array(invocations).each { |invocation| self.new(invocation).prevent_methods_invocation }
9
8
  end
10
9
  end
11
10
 
12
11
  attr_reader :class_name, :methods, :only_for_user_commands
13
12
 
14
- def initialize(invocation, only_for_user_commands:)
13
+ def initialize(invocation)
15
14
  @class_name, methods = invocation.to_a
16
15
  @methods = Array(methods)
17
- @only_for_user_commands = only_for_user_commands
18
16
  end
19
17
 
20
18
  def prevent_methods_invocation
21
- class_name.constantize.prepend build_protection_module
19
+ class_name.to_s.constantize.prepend build_protection_module
22
20
  end
23
21
 
24
22
  def build_protection_module
@@ -37,12 +35,8 @@ class Console1984::Shield::MethodInvocationShell
37
35
  def protected_method_invocation_source_for(method)
38
36
  <<~RUBY
39
37
  def #{method}(*args)
40
- if (!#{only_for_user_commands} || Console1984.command_executor.executing_user_command?) && caller.find do |line|
41
- line_from_irb = line =~ /^[^\\/]/
42
- break if !(line =~ /console1984\\/lib/ || line_from_irb)
43
- line_from_irb
44
- end
45
- raise Console1984::Errors::ForbiddenCommand
38
+ if Console1984.command_executor.from_irb?(caller)
39
+ raise Console1984::Errors::ForbiddenCommandAttempted
46
40
  else
47
41
  super
48
42
  end
@@ -30,12 +30,21 @@ class Console1984::Supervisor
30
30
  stop_session
31
31
  end
32
32
 
33
+ def exit_irb
34
+ stop
35
+ IRB.CurrentContext.exit
36
+ end
37
+
33
38
  private
34
39
  def require_dependencies
35
40
  Kernel.silence_warnings do
36
41
  require 'parser/current'
37
42
  end
38
43
  require 'colorized_string'
44
+
45
+ # Explicit lazy loading because it depends on +parser+, which we want to only load
46
+ # in console sessions.
47
+ require_relative "./command_validator/.command_parser"
39
48
  end
40
49
 
41
50
  def start_session
@@ -1,3 +1,3 @@
1
1
  module Console1984
2
- VERSION = '0.1.8'
2
+ VERSION = '0.1.12'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: console1984
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.8
4
+ version: 0.1.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jorge Manrubia
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-09-05 00:00:00.000000000 Z
11
+ date: 2021-09-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: colorize
@@ -200,6 +200,7 @@ files:
200
200
  - lib/console1984.rb
201
201
  - lib/console1984/command_executor.rb
202
202
  - lib/console1984/command_validator.rb
203
+ - lib/console1984/command_validator/.command_parser.rb
203
204
  - lib/console1984/command_validator/forbidden_constant_reference_validation.rb
204
205
  - lib/console1984/command_validator/forbidden_reopening_validation.rb
205
206
  - lib/console1984/command_validator/parsed_command.rb