makit 0.0.140 → 0.0.142

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.
Files changed (153) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +41 -41
  3. data/exe/makit +5 -5
  4. data/lib/makit/apache.rb +28 -28
  5. data/lib/makit/auto.rb +48 -48
  6. data/lib/makit/cli/build_commands.rb +500 -500
  7. data/lib/makit/cli/generators/base_generator.rb +74 -74
  8. data/lib/makit/cli/generators/dotnet_generator.rb +50 -50
  9. data/lib/makit/cli/generators/generator_factory.rb +49 -49
  10. data/lib/makit/cli/generators/node_generator.rb +50 -50
  11. data/lib/makit/cli/generators/ruby_generator.rb +77 -77
  12. data/lib/makit/cli/generators/rust_generator.rb +50 -50
  13. data/lib/makit/cli/generators/templates/dotnet_templates.rb +167 -167
  14. data/lib/makit/cli/generators/templates/node_templates.rb +161 -161
  15. data/lib/makit/cli/generators/templates/ruby/gemfile.rb +26 -26
  16. data/lib/makit/cli/generators/templates/ruby/gemspec.rb +40 -40
  17. data/lib/makit/cli/generators/templates/ruby/main_lib.rb +33 -33
  18. data/lib/makit/cli/generators/templates/ruby/rakefile.rb +35 -35
  19. data/lib/makit/cli/generators/templates/ruby/readme.rb +63 -63
  20. data/lib/makit/cli/generators/templates/ruby/test.rb +39 -39
  21. data/lib/makit/cli/generators/templates/ruby/test_helper.rb +29 -29
  22. data/lib/makit/cli/generators/templates/ruby/version.rb +29 -29
  23. data/lib/makit/cli/generators/templates/rust_templates.rb +128 -128
  24. data/lib/makit/cli/main.rb +69 -69
  25. data/lib/makit/cli/project_commands.rb +868 -868
  26. data/lib/makit/cli/repository_commands.rb +661 -661
  27. data/lib/makit/cli/strategy_commands.rb +203 -203
  28. data/lib/makit/cli/utility_commands.rb +521 -521
  29. data/lib/makit/commands/factory.rb +359 -359
  30. data/lib/makit/commands/middleware/base.rb +73 -73
  31. data/lib/makit/commands/middleware/cache.rb +248 -248
  32. data/lib/makit/commands/middleware/command_logger.rb +312 -312
  33. data/lib/makit/commands/middleware/validator.rb +269 -269
  34. data/lib/makit/commands/request.rb +316 -316
  35. data/lib/makit/commands/result.rb +323 -323
  36. data/lib/makit/commands/runner.rb +388 -385
  37. data/lib/makit/commands/strategies/base.rb +171 -171
  38. data/lib/makit/commands/strategies/child_process.rb +165 -165
  39. data/lib/makit/commands/strategies/factory.rb +136 -136
  40. data/lib/makit/commands/strategies/synchronous.rb +139 -139
  41. data/lib/makit/commands.rb +50 -50
  42. data/lib/makit/configuration/dotnet_project.rb +12 -12
  43. data/lib/makit/configuration/gitlab_helper.rb +58 -58
  44. data/lib/makit/configuration/project.rb +168 -168
  45. data/lib/makit/configuration/rakefile_helper.rb +43 -43
  46. data/lib/makit/configuration/step.rb +34 -34
  47. data/lib/makit/configuration/timeout.rb +74 -74
  48. data/lib/makit/configuration.rb +15 -15
  49. data/lib/makit/content/default_gitignore.rb +7 -7
  50. data/lib/makit/content/default_gitignore.txt +225 -225
  51. data/lib/makit/content/default_rakefile.rb +13 -13
  52. data/lib/makit/content/gem_rakefile.rb +16 -16
  53. data/lib/makit/context.rb +1 -1
  54. data/lib/makit/data.rb +49 -49
  55. data/lib/makit/directories.rb +140 -140
  56. data/lib/makit/directory.rb +262 -262
  57. data/lib/makit/docs/files.rb +89 -89
  58. data/lib/makit/docs/rake.rb +102 -102
  59. data/lib/makit/dotnet/cli.rb +69 -69
  60. data/lib/makit/dotnet/project.rb +217 -217
  61. data/lib/makit/dotnet/solution.rb +38 -38
  62. data/lib/makit/dotnet/solution_classlib.rb +239 -239
  63. data/lib/makit/dotnet/solution_console.rb +264 -264
  64. data/lib/makit/dotnet/solution_maui.rb +354 -354
  65. data/lib/makit/dotnet/solution_wasm.rb +275 -275
  66. data/lib/makit/dotnet/solution_wpf.rb +304 -304
  67. data/lib/makit/dotnet.rb +102 -102
  68. data/lib/makit/email.rb +90 -90
  69. data/lib/makit/environment.rb +142 -142
  70. data/lib/makit/examples/runner.rb +370 -370
  71. data/lib/makit/exceptions.rb +45 -45
  72. data/lib/makit/fileinfo.rb +24 -24
  73. data/lib/makit/files.rb +43 -43
  74. data/lib/makit/gems.rb +40 -40
  75. data/lib/makit/git/cli.rb +54 -54
  76. data/lib/makit/git/repository.rb +90 -90
  77. data/lib/makit/git.rb +98 -98
  78. data/lib/makit/gitlab_runner.rb +59 -59
  79. data/lib/makit/humanize.rb +137 -137
  80. data/lib/makit/indexer.rb +47 -47
  81. data/lib/makit/logging/configuration.rb +308 -308
  82. data/lib/makit/logging/format_registry.rb +84 -84
  83. data/lib/makit/logging/formatters/base.rb +39 -39
  84. data/lib/makit/logging/formatters/console_formatter.rb +140 -140
  85. data/lib/makit/logging/formatters/json_formatter.rb +65 -65
  86. data/lib/makit/logging/formatters/plain_text_formatter.rb +71 -71
  87. data/lib/makit/logging/formatters/text_formatter.rb +64 -64
  88. data/lib/makit/logging/log_request.rb +119 -119
  89. data/lib/makit/logging/logger.rb +199 -199
  90. data/lib/makit/logging/sinks/base.rb +91 -91
  91. data/lib/makit/logging/sinks/console.rb +72 -72
  92. data/lib/makit/logging/sinks/file_sink.rb +92 -92
  93. data/lib/makit/logging/sinks/structured.rb +123 -123
  94. data/lib/makit/logging/sinks/unified_file_sink.rb +296 -296
  95. data/lib/makit/logging.rb +565 -565
  96. data/lib/makit/markdown.rb +75 -75
  97. data/lib/makit/mp/basic_object_mp.rb +17 -17
  98. data/lib/makit/mp/command_mp.rb +13 -13
  99. data/lib/makit/mp/command_request.mp.rb +17 -17
  100. data/lib/makit/mp/project_mp.rb +199 -199
  101. data/lib/makit/mp/string_mp.rb +199 -191
  102. data/lib/makit/nuget.rb +74 -74
  103. data/lib/makit/port.rb +32 -32
  104. data/lib/makit/process.rb +163 -163
  105. data/lib/makit/protoc.rb +107 -107
  106. data/lib/makit/rake/cli.rb +196 -196
  107. data/lib/makit/rake/trace_controller.rb +173 -173
  108. data/lib/makit/rake.rb +80 -80
  109. data/lib/makit/ruby/cli.rb +185 -185
  110. data/lib/makit/ruby.rb +25 -25
  111. data/lib/makit/secrets.rb +51 -51
  112. data/lib/makit/serializer.rb +130 -130
  113. data/lib/makit/services/builder.rb +186 -186
  114. data/lib/makit/services/error_handler.rb +226 -226
  115. data/lib/makit/services/repository_manager.rb +231 -231
  116. data/lib/makit/services/validator.rb +112 -112
  117. data/lib/makit/setup/classlib.rb +101 -101
  118. data/lib/makit/setup/gem.rb +268 -268
  119. data/lib/makit/setup/razorclasslib.rb +101 -101
  120. data/lib/makit/setup/runner.rb +54 -54
  121. data/lib/makit/setup.rb +5 -5
  122. data/lib/makit/show.rb +110 -110
  123. data/lib/makit/storage.rb +126 -126
  124. data/lib/makit/symbols.rb +170 -170
  125. data/lib/makit/task_info.rb +130 -130
  126. data/lib/makit/tasks/at_exit.rb +15 -15
  127. data/lib/makit/tasks/build.rb +22 -22
  128. data/lib/makit/tasks/clean.rb +13 -13
  129. data/lib/makit/tasks/configure.rb +10 -10
  130. data/lib/makit/tasks/format.rb +10 -10
  131. data/lib/makit/tasks/hook_manager.rb +443 -443
  132. data/lib/makit/tasks/init.rb +49 -49
  133. data/lib/makit/tasks/integrate.rb +29 -29
  134. data/lib/makit/tasks/pull_incoming.rb +13 -13
  135. data/lib/makit/tasks/setup.rb +13 -13
  136. data/lib/makit/tasks/sync.rb +17 -17
  137. data/lib/makit/tasks/tag.rb +16 -16
  138. data/lib/makit/tasks/task_monkey_patch.rb +81 -81
  139. data/lib/makit/tasks/test.rb +22 -22
  140. data/lib/makit/tasks/update.rb +18 -18
  141. data/lib/makit/tasks.rb +20 -20
  142. data/lib/makit/test_cache.rb +239 -239
  143. data/lib/makit/tree.rb +37 -37
  144. data/lib/makit/v1/makit.v1_pb.rb +35 -35
  145. data/lib/makit/v1/makit.v1_services_pb.rb +27 -27
  146. data/lib/makit/version.rb +99 -99
  147. data/lib/makit/version_util.rb +21 -21
  148. data/lib/makit/wix.rb +95 -95
  149. data/lib/makit/yaml.rb +29 -29
  150. data/lib/makit/zip.rb +17 -17
  151. data/lib/makit copy.rb +44 -44
  152. data/lib/makit.rb +43 -42
  153. metadata +2 -2
@@ -1,269 +1,269 @@
1
- # frozen_string_literal: true
2
-
3
- require "shellwords"
4
- require "uri"
5
-
6
- module Makit
7
- module Commands
8
- # Custom exception for security-related command validation failures
9
- class SecurityError < StandardError; end
10
-
11
- module Middleware
12
- # Validation middleware that provides input sanitization and security checks
13
- # for command execution contexts.
14
- #
15
- # This middleware validates and sanitizes:
16
- # - Command strings for shell injection attacks
17
- # - File paths for directory traversal
18
- # - URLs for malicious schemes
19
- # - Environment variables for sensitive data
20
- # - Arguments for dangerous patterns
21
- #
22
- # @example Basic usage
23
- # validator = Commands::Middleware::Validator.new
24
- # context = Commands::Context.new(command: 'ls -la')
25
- # validated_context = validator.call(context)
26
- #
27
- # @example With custom validation rules
28
- # validator = Commands::Middleware::Validator.new(
29
- # allow_shell_operators: false,
30
- # blocked_commands: %w[rm sudo],
31
- # max_command_length: 500
32
- # )
33
- class Validator
34
- # Default blocked commands that pose security risks
35
- DEFAULT_BLOCKED_COMMANDS = %w[
36
- rm rmdir
37
- sudo su
38
- chmod chown
39
- wget curl
40
- eval exec
41
- python ruby node
42
- ssh scp rsync
43
- dd fdisk mount umount
44
- iptables ufw
45
- systemctl service
46
- crontab
47
- passwd
48
- ].freeze
49
-
50
- # Dangerous shell operators and patterns
51
- DANGEROUS_PATTERNS = [
52
- /[;&|`$(){}]/, # Shell operators and command substitution
53
- %r{\.\./}, # Directory traversal
54
- %r{/etc/|/proc/|/sys/}, # Sensitive system directories
55
- /\$\{[^}]*\}/, # Variable expansion
56
- %r{~[^/\s]*}, # User home directory expansion
57
- %r{\bfile://|ftp://|https?://[^\s]*\.(sh|py|rb|js|exe|bat)\b}i, # Executable URLs
58
- ].freeze
59
-
60
- # Maximum allowed lengths for various inputs
61
- DEFAULT_LIMITS = {
62
- command_length: 1000,
63
- argument_length: 500,
64
- path_length: 1000,
65
- env_var_value_length: 2000,
66
- }.freeze
67
-
68
- attr_reader :options
69
-
70
- # Initialize the validator with security options
71
- #
72
- # @param options [Hash] Configuration options
73
- # @option options [Boolean] :allow_shell_operators (false) Allow shell operators like ;, |, &
74
- # @option options [Array<String>] :blocked_commands Commands to block
75
- # @option options [Hash] :limits Length limits for various inputs
76
- # @option options [Boolean] :strict_mode (true) Enable strict validation
77
- # @option options [Array<String>] :allowed_schemes URL schemes to allow
78
- def initialize(options = {})
79
- @options = {
80
- allow_shell_operators: false,
81
- blocked_commands: DEFAULT_BLOCKED_COMMANDS,
82
- limits: DEFAULT_LIMITS,
83
- strict_mode: true,
84
- allowed_schemes: %w[http https file],
85
- allow_environment_access: false,
86
- }.merge(options)
87
- end
88
-
89
- # Validate and sanitize the command context
90
- #
91
- # @param context [Commands::Context] The command execution context
92
- # @return [Commands::Context] Validated context
93
- # @raise [Commands::SecurityError] If validation fails
94
- def call(context)
95
- validate_command!(context.command) if context.command
96
- validate_arguments!(context.arguments) if context.arguments
97
- validate_environment!(context.environment) if context.environment
98
- validate_working_directory!(context.working_directory) if context.working_directory
99
-
100
- context
101
- rescue StandardError => e
102
- raise Commands::SecurityError, "Validation failed: #{e.message}"
103
- end
104
-
105
- private
106
-
107
- # Validate the main command string
108
- def validate_command!(command)
109
- raise Commands::SecurityError, "Empty command" if command.nil? || command.strip.empty?
110
-
111
- if command.length > limits[:command_length]
112
- raise Commands::SecurityError,
113
- "Command too long (#{command.length} > #{limits[:command_length]})"
114
- end
115
-
116
- # Check for blocked commands
117
- command_parts = Shellwords.split(command)
118
- base_command = File.basename(command_parts.first || "")
119
-
120
- raise Commands::SecurityError, "Blocked command: #{base_command}" if blocked_commands.include?(base_command)
121
-
122
- # Check for dangerous patterns
123
- unless options[:allow_shell_operators]
124
- DANGEROUS_PATTERNS.each do |pattern|
125
- if command.match?(pattern)
126
- raise Commands::SecurityError, "Dangerous pattern detected in command: #{pattern.inspect}"
127
- end
128
- end
129
- end
130
-
131
- # Validate shell escaping
132
- begin
133
- Shellwords.split(command)
134
- rescue ArgumentError => e
135
- raise Commands::SecurityError, "Invalid shell command syntax: #{e.message}"
136
- end
137
- end
138
-
139
- # Validate command arguments
140
- def validate_arguments!(arguments)
141
- return unless arguments
142
-
143
- arguments.each_with_index do |arg, index|
144
- next unless arg
145
-
146
- if arg.length > limits[:argument_length]
147
- raise Commands::SecurityError, "Argument #{index} too long (#{arg.length} > #{limits[:argument_length]})"
148
- end
149
-
150
- # Check for dangerous patterns in arguments
151
- if options[:strict_mode]
152
- DANGEROUS_PATTERNS.each do |pattern|
153
- if arg.match?(pattern)
154
- raise Commands::SecurityError, "Dangerous pattern in argument #{index}: #{pattern.inspect}"
155
- end
156
- end
157
- end
158
-
159
- # Validate file paths in arguments
160
- validate_path!(arg) if looks_like_path?(arg)
161
-
162
- # Validate URLs in arguments
163
- validate_url!(arg) if looks_like_url?(arg)
164
- end
165
- end
166
-
167
- # Validate environment variables
168
- def validate_environment!(environment)
169
- return unless environment
170
-
171
- environment.each do |key, value|
172
- next unless value
173
-
174
- if value.to_s.length > limits[:env_var_value_length]
175
- raise Commands::SecurityError, "Environment variable #{key} value too long"
176
- end
177
-
178
- # Check for sensitive environment variables
179
- if sensitive_env_var?(key) && !options[:allow_environment_access]
180
- raise Commands::SecurityError, "Access to sensitive environment variable #{key} not allowed"
181
- end
182
-
183
- # Validate environment variable values
184
- if options[:strict_mode] && contains_dangerous_content?(value.to_s)
185
- raise Commands::SecurityError, "Dangerous content in environment variable #{key}"
186
- end
187
- end
188
- end
189
-
190
- # Validate working directory path
191
- def validate_working_directory!(path)
192
- validate_path!(path, "working directory")
193
- end
194
-
195
- # Validate file system paths
196
- def validate_path!(path, context = "path")
197
- return if path.nil? || path.empty?
198
-
199
- if path.length > limits[:path_length]
200
- raise Commands::SecurityError, "#{context.capitalize} too long (#{path.length} > #{limits[:path_length]})"
201
- end
202
-
203
- # Check for directory traversal
204
- normalized_path = File.expand_path(path)
205
- if path.include?("..") && options[:strict_mode]
206
- raise Commands::SecurityError, "Directory traversal detected in #{context}: #{path}"
207
- end
208
-
209
- # Check for access to sensitive directories
210
- sensitive_dirs = %w[/etc /proc /sys /dev /boot /root]
211
- return unless sensitive_dirs.any? { |dir| normalized_path.start_with?(dir) } && options[:strict_mode]
212
-
213
- raise Commands::SecurityError, "Access to sensitive directory in #{context}: #{path}"
214
- end
215
-
216
- # Validate URLs
217
- def validate_url!(url)
218
- uri = URI.parse(url)
219
-
220
- unless options[:allowed_schemes].include?(uri.scheme)
221
- raise Commands::SecurityError, "Disallowed URL scheme: #{uri.scheme}"
222
- end
223
-
224
- # Check for suspicious hosts
225
- if uri.host&.match?(/localhost|127\.0\.0\.1|0\.0\.0\.0/)
226
- raise Commands::SecurityError, "Local network access not allowed: #{uri.host}"
227
- end
228
- rescue URI::InvalidURIError => e
229
- raise Commands::SecurityError, "Invalid URL format: #{e.message}"
230
- end
231
-
232
- # Check if string looks like a file path
233
- def looks_like_path?(str)
234
- str.start_with?("/") || str.start_with?("./") || str.start_with?("../") || str.include?(File::SEPARATOR)
235
- end
236
-
237
- # Check if string looks like a URL
238
- def looks_like_url?(str)
239
- str.match?(/\A[a-z][a-z0-9+.-]*:/i)
240
- end
241
-
242
- # Check if environment variable is sensitive
243
- def sensitive_env_var?(key)
244
- sensitive_patterns = %w[
245
- PASSWORD SECRET TOKEN KEY API PRIVATE
246
- AWS GCP AZURE GITHUB GITLAB
247
- DATABASE_URL DB_PASSWORD
248
- RAILS_MASTER_KEY
249
- ]
250
-
251
- sensitive_patterns.any? { |pattern| key.to_s.upcase.include?(pattern) }
252
- end
253
-
254
- # Check if content contains dangerous patterns
255
- def contains_dangerous_content?(content)
256
- DANGEROUS_PATTERNS.any? { |pattern| content.match?(pattern) }
257
- end
258
-
259
- def limits
260
- options[:limits]
261
- end
262
-
263
- def blocked_commands
264
- options[:blocked_commands]
265
- end
266
- end
267
- end
268
- end
269
- end
1
+ # frozen_string_literal: true
2
+
3
+ require "shellwords"
4
+ require "uri"
5
+
6
+ module Makit
7
+ module Commands
8
+ # Custom exception for security-related command validation failures
9
+ class SecurityError < StandardError; end
10
+
11
+ module Middleware
12
+ # Validation middleware that provides input sanitization and security checks
13
+ # for command execution contexts.
14
+ #
15
+ # This middleware validates and sanitizes:
16
+ # - Command strings for shell injection attacks
17
+ # - File paths for directory traversal
18
+ # - URLs for malicious schemes
19
+ # - Environment variables for sensitive data
20
+ # - Arguments for dangerous patterns
21
+ #
22
+ # @example Basic usage
23
+ # validator = Commands::Middleware::Validator.new
24
+ # context = Commands::Context.new(command: 'ls -la')
25
+ # validated_context = validator.call(context)
26
+ #
27
+ # @example With custom validation rules
28
+ # validator = Commands::Middleware::Validator.new(
29
+ # allow_shell_operators: false,
30
+ # blocked_commands: %w[rm sudo],
31
+ # max_command_length: 500
32
+ # )
33
+ class Validator
34
+ # Default blocked commands that pose security risks
35
+ DEFAULT_BLOCKED_COMMANDS = %w[
36
+ rm rmdir
37
+ sudo su
38
+ chmod chown
39
+ wget curl
40
+ eval exec
41
+ python ruby node
42
+ ssh scp rsync
43
+ dd fdisk mount umount
44
+ iptables ufw
45
+ systemctl service
46
+ crontab
47
+ passwd
48
+ ].freeze
49
+
50
+ # Dangerous shell operators and patterns
51
+ DANGEROUS_PATTERNS = [
52
+ /[;&|`$(){}]/, # Shell operators and command substitution
53
+ %r{\.\./}, # Directory traversal
54
+ %r{/etc/|/proc/|/sys/}, # Sensitive system directories
55
+ /\$\{[^}]*\}/, # Variable expansion
56
+ %r{~[^/\s]*}, # User home directory expansion
57
+ %r{\bfile://|ftp://|https?://[^\s]*\.(sh|py|rb|js|exe|bat)\b}i, # Executable URLs
58
+ ].freeze
59
+
60
+ # Maximum allowed lengths for various inputs
61
+ DEFAULT_LIMITS = {
62
+ command_length: 1000,
63
+ argument_length: 500,
64
+ path_length: 1000,
65
+ env_var_value_length: 2000,
66
+ }.freeze
67
+
68
+ attr_reader :options
69
+
70
+ # Initialize the validator with security options
71
+ #
72
+ # @param options [Hash] Configuration options
73
+ # @option options [Boolean] :allow_shell_operators (false) Allow shell operators like ;, |, &
74
+ # @option options [Array<String>] :blocked_commands Commands to block
75
+ # @option options [Hash] :limits Length limits for various inputs
76
+ # @option options [Boolean] :strict_mode (true) Enable strict validation
77
+ # @option options [Array<String>] :allowed_schemes URL schemes to allow
78
+ def initialize(options = {})
79
+ @options = {
80
+ allow_shell_operators: false,
81
+ blocked_commands: DEFAULT_BLOCKED_COMMANDS,
82
+ limits: DEFAULT_LIMITS,
83
+ strict_mode: true,
84
+ allowed_schemes: %w[http https file],
85
+ allow_environment_access: false,
86
+ }.merge(options)
87
+ end
88
+
89
+ # Validate and sanitize the command context
90
+ #
91
+ # @param context [Commands::Context] The command execution context
92
+ # @return [Commands::Context] Validated context
93
+ # @raise [Commands::SecurityError] If validation fails
94
+ def call(context)
95
+ validate_command!(context.command) if context.command
96
+ validate_arguments!(context.arguments) if context.arguments
97
+ validate_environment!(context.environment) if context.environment
98
+ validate_working_directory!(context.working_directory) if context.working_directory
99
+
100
+ context
101
+ rescue StandardError => e
102
+ raise Commands::SecurityError, "Validation failed: #{e.message}"
103
+ end
104
+
105
+ private
106
+
107
+ # Validate the main command string
108
+ def validate_command!(command)
109
+ raise Commands::SecurityError, "Empty command" if command.nil? || command.strip.empty?
110
+
111
+ if command.length > limits[:command_length]
112
+ raise Commands::SecurityError,
113
+ "Command too long (#{command.length} > #{limits[:command_length]})"
114
+ end
115
+
116
+ # Check for blocked commands
117
+ command_parts = Shellwords.split(command)
118
+ base_command = File.basename(command_parts.first || "")
119
+
120
+ raise Commands::SecurityError, "Blocked command: #{base_command}" if blocked_commands.include?(base_command)
121
+
122
+ # Check for dangerous patterns
123
+ unless options[:allow_shell_operators]
124
+ DANGEROUS_PATTERNS.each do |pattern|
125
+ if command.match?(pattern)
126
+ raise Commands::SecurityError, "Dangerous pattern detected in command: #{pattern.inspect}"
127
+ end
128
+ end
129
+ end
130
+
131
+ # Validate shell escaping
132
+ begin
133
+ Shellwords.split(command)
134
+ rescue ArgumentError => e
135
+ raise Commands::SecurityError, "Invalid shell command syntax: #{e.message}"
136
+ end
137
+ end
138
+
139
+ # Validate command arguments
140
+ def validate_arguments!(arguments)
141
+ return unless arguments
142
+
143
+ arguments.each_with_index do |arg, index|
144
+ next unless arg
145
+
146
+ if arg.length > limits[:argument_length]
147
+ raise Commands::SecurityError, "Argument #{index} too long (#{arg.length} > #{limits[:argument_length]})"
148
+ end
149
+
150
+ # Check for dangerous patterns in arguments
151
+ if options[:strict_mode]
152
+ DANGEROUS_PATTERNS.each do |pattern|
153
+ if arg.match?(pattern)
154
+ raise Commands::SecurityError, "Dangerous pattern in argument #{index}: #{pattern.inspect}"
155
+ end
156
+ end
157
+ end
158
+
159
+ # Validate file paths in arguments
160
+ validate_path!(arg) if looks_like_path?(arg)
161
+
162
+ # Validate URLs in arguments
163
+ validate_url!(arg) if looks_like_url?(arg)
164
+ end
165
+ end
166
+
167
+ # Validate environment variables
168
+ def validate_environment!(environment)
169
+ return unless environment
170
+
171
+ environment.each do |key, value|
172
+ next unless value
173
+
174
+ if value.to_s.length > limits[:env_var_value_length]
175
+ raise Commands::SecurityError, "Environment variable #{key} value too long"
176
+ end
177
+
178
+ # Check for sensitive environment variables
179
+ if sensitive_env_var?(key) && !options[:allow_environment_access]
180
+ raise Commands::SecurityError, "Access to sensitive environment variable #{key} not allowed"
181
+ end
182
+
183
+ # Validate environment variable values
184
+ if options[:strict_mode] && contains_dangerous_content?(value.to_s)
185
+ raise Commands::SecurityError, "Dangerous content in environment variable #{key}"
186
+ end
187
+ end
188
+ end
189
+
190
+ # Validate working directory path
191
+ def validate_working_directory!(path)
192
+ validate_path!(path, "working directory")
193
+ end
194
+
195
+ # Validate file system paths
196
+ def validate_path!(path, context = "path")
197
+ return if path.nil? || path.empty?
198
+
199
+ if path.length > limits[:path_length]
200
+ raise Commands::SecurityError, "#{context.capitalize} too long (#{path.length} > #{limits[:path_length]})"
201
+ end
202
+
203
+ # Check for directory traversal
204
+ normalized_path = File.expand_path(path)
205
+ if path.include?("..") && options[:strict_mode]
206
+ raise Commands::SecurityError, "Directory traversal detected in #{context}: #{path}"
207
+ end
208
+
209
+ # Check for access to sensitive directories
210
+ sensitive_dirs = %w[/etc /proc /sys /dev /boot /root]
211
+ return unless sensitive_dirs.any? { |dir| normalized_path.start_with?(dir) } && options[:strict_mode]
212
+
213
+ raise Commands::SecurityError, "Access to sensitive directory in #{context}: #{path}"
214
+ end
215
+
216
+ # Validate URLs
217
+ def validate_url!(url)
218
+ uri = URI.parse(url)
219
+
220
+ unless options[:allowed_schemes].include?(uri.scheme)
221
+ raise Commands::SecurityError, "Disallowed URL scheme: #{uri.scheme}"
222
+ end
223
+
224
+ # Check for suspicious hosts
225
+ if uri.host&.match?(/localhost|127\.0\.0\.1|0\.0\.0\.0/)
226
+ raise Commands::SecurityError, "Local network access not allowed: #{uri.host}"
227
+ end
228
+ rescue URI::InvalidURIError => e
229
+ raise Commands::SecurityError, "Invalid URL format: #{e.message}"
230
+ end
231
+
232
+ # Check if string looks like a file path
233
+ def looks_like_path?(str)
234
+ str.start_with?("/") || str.start_with?("./") || str.start_with?("../") || str.include?(File::SEPARATOR)
235
+ end
236
+
237
+ # Check if string looks like a URL
238
+ def looks_like_url?(str)
239
+ str.match?(/\A[a-z][a-z0-9+.-]*:/i)
240
+ end
241
+
242
+ # Check if environment variable is sensitive
243
+ def sensitive_env_var?(key)
244
+ sensitive_patterns = %w[
245
+ PASSWORD SECRET TOKEN KEY API PRIVATE
246
+ AWS GCP AZURE GITHUB GITLAB
247
+ DATABASE_URL DB_PASSWORD
248
+ RAILS_MASTER_KEY
249
+ ]
250
+
251
+ sensitive_patterns.any? { |pattern| key.to_s.upcase.include?(pattern) }
252
+ end
253
+
254
+ # Check if content contains dangerous patterns
255
+ def contains_dangerous_content?(content)
256
+ DANGEROUS_PATTERNS.any? { |pattern| content.match?(pattern) }
257
+ end
258
+
259
+ def limits
260
+ options[:limits]
261
+ end
262
+
263
+ def blocked_commands
264
+ options[:blocked_commands]
265
+ end
266
+ end
267
+ end
268
+ end
269
+ end