console1984 0.1.8 → 0.1.12

Sign up to get free protection for your applications and to get access to all the features.
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