makit 0.0.144 → 0.0.145

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 (165) 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/base.rb +17 -0
  7. data/lib/makit/cli/build_commands.rb +500 -500
  8. data/lib/makit/cli/generators/base_generator.rb +74 -74
  9. data/lib/makit/cli/generators/dotnet_generator.rb +50 -50
  10. data/lib/makit/cli/generators/generator_factory.rb +49 -49
  11. data/lib/makit/cli/generators/node_generator.rb +50 -50
  12. data/lib/makit/cli/generators/ruby_generator.rb +77 -77
  13. data/lib/makit/cli/generators/rust_generator.rb +50 -50
  14. data/lib/makit/cli/generators/templates/dotnet_templates.rb +167 -167
  15. data/lib/makit/cli/generators/templates/node_templates.rb +161 -161
  16. data/lib/makit/cli/generators/templates/ruby/gemfile.rb +26 -26
  17. data/lib/makit/cli/generators/templates/ruby/gemspec.rb +41 -40
  18. data/lib/makit/cli/generators/templates/ruby/main_lib.rb +33 -33
  19. data/lib/makit/cli/generators/templates/ruby/rakefile.rb +35 -35
  20. data/lib/makit/cli/generators/templates/ruby/readme.rb +63 -63
  21. data/lib/makit/cli/generators/templates/ruby/test.rb +39 -39
  22. data/lib/makit/cli/generators/templates/ruby/test_helper.rb +29 -29
  23. data/lib/makit/cli/generators/templates/ruby/version.rb +29 -29
  24. data/lib/makit/cli/generators/templates/rust_templates.rb +128 -128
  25. data/lib/makit/cli/main.rb +78 -69
  26. data/lib/makit/cli/pipeline_commands.rb +311 -0
  27. data/lib/makit/cli/project_commands.rb +868 -868
  28. data/lib/makit/cli/repository_commands.rb +661 -661
  29. data/lib/makit/cli/strategy_commands.rb +207 -212
  30. data/lib/makit/cli/utility_commands.rb +521 -521
  31. data/lib/makit/commands/factory.rb +359 -359
  32. data/lib/makit/commands/middleware/base.rb +73 -73
  33. data/lib/makit/commands/middleware/cache.rb +248 -248
  34. data/lib/makit/commands/middleware/command_logger.rb +312 -312
  35. data/lib/makit/commands/middleware/validator.rb +269 -269
  36. data/lib/makit/commands/request.rb +316 -316
  37. data/lib/makit/commands/result.rb +323 -323
  38. data/lib/makit/commands/runner.rb +386 -386
  39. data/lib/makit/commands/strategies/base.rb +171 -171
  40. data/lib/makit/commands/strategies/child_process.rb +162 -162
  41. data/lib/makit/commands/strategies/factory.rb +136 -136
  42. data/lib/makit/commands/strategies/synchronous.rb +139 -139
  43. data/lib/makit/commands.rb +50 -50
  44. data/lib/makit/configuration/dotnet_project.rb +48 -48
  45. data/lib/makit/configuration/gitlab_helper.rb +61 -58
  46. data/lib/makit/configuration/project.rb +446 -168
  47. data/lib/makit/configuration/rakefile_helper.rb +43 -43
  48. data/lib/makit/configuration/step.rb +34 -34
  49. data/lib/makit/configuration/timeout.rb +74 -74
  50. data/lib/makit/configuration.rb +21 -16
  51. data/lib/makit/content/default_gitignore.rb +7 -7
  52. data/lib/makit/content/default_gitignore.txt +225 -225
  53. data/lib/makit/content/default_rakefile.rb +13 -13
  54. data/lib/makit/content/gem_rakefile.rb +16 -16
  55. data/lib/makit/context.rb +1 -1
  56. data/lib/makit/data.rb +49 -49
  57. data/lib/makit/directories.rb +140 -140
  58. data/lib/makit/directory.rb +262 -262
  59. data/lib/makit/docs/files.rb +89 -89
  60. data/lib/makit/docs/rake.rb +102 -102
  61. data/lib/makit/dotnet/cli.rb +69 -69
  62. data/lib/makit/dotnet/project.rb +217 -217
  63. data/lib/makit/dotnet/solution.rb +38 -38
  64. data/lib/makit/dotnet/solution_classlib.rb +239 -239
  65. data/lib/makit/dotnet/solution_console.rb +264 -264
  66. data/lib/makit/dotnet/solution_maui.rb +354 -354
  67. data/lib/makit/dotnet/solution_wasm.rb +275 -275
  68. data/lib/makit/dotnet/solution_wpf.rb +304 -304
  69. data/lib/makit/dotnet.rb +102 -102
  70. data/lib/makit/email.rb +90 -90
  71. data/lib/makit/environment.rb +142 -142
  72. data/lib/makit/examples/runner.rb +370 -370
  73. data/lib/makit/exceptions.rb +45 -45
  74. data/lib/makit/fileinfo.rb +32 -24
  75. data/lib/makit/files.rb +43 -43
  76. data/lib/makit/gems.rb +40 -40
  77. data/lib/makit/git/cli.rb +54 -54
  78. data/lib/makit/git/repository.rb +266 -90
  79. data/lib/makit/git.rb +104 -98
  80. data/lib/makit/gitlab/pipeline.rb +857 -0
  81. data/lib/makit/gitlab/pipeline_service_impl.rb +1536 -0
  82. data/lib/makit/gitlab_runner.rb +59 -59
  83. data/lib/makit/humanize.rb +218 -137
  84. data/lib/makit/indexer.rb +47 -47
  85. data/lib/makit/io/filesystem.rb +111 -0
  86. data/lib/makit/io/filesystem_service_impl.rb +337 -0
  87. data/lib/makit/logging/configuration.rb +308 -308
  88. data/lib/makit/logging/format_registry.rb +84 -84
  89. data/lib/makit/logging/formatters/base.rb +39 -39
  90. data/lib/makit/logging/formatters/console_formatter.rb +140 -140
  91. data/lib/makit/logging/formatters/json_formatter.rb +65 -65
  92. data/lib/makit/logging/formatters/plain_text_formatter.rb +71 -71
  93. data/lib/makit/logging/formatters/text_formatter.rb +64 -64
  94. data/lib/makit/logging/log_request.rb +119 -119
  95. data/lib/makit/logging/logger.rb +199 -199
  96. data/lib/makit/logging/sinks/base.rb +91 -91
  97. data/lib/makit/logging/sinks/console.rb +72 -72
  98. data/lib/makit/logging/sinks/file_sink.rb +92 -92
  99. data/lib/makit/logging/sinks/structured.rb +123 -123
  100. data/lib/makit/logging/sinks/unified_file_sink.rb +296 -296
  101. data/lib/makit/logging.rb +565 -565
  102. data/lib/makit/markdown.rb +75 -75
  103. data/lib/makit/mp/basic_object_mp.rb +17 -17
  104. data/lib/makit/mp/command_mp.rb +13 -13
  105. data/lib/makit/mp/command_request.mp.rb +17 -17
  106. data/lib/makit/mp/project_mp.rb +199 -199
  107. data/lib/makit/mp/string_mp.rb +205 -199
  108. data/lib/makit/nuget.rb +74 -74
  109. data/lib/makit/podman/podman.rb +458 -0
  110. data/lib/makit/podman/podman_service_impl.rb +1081 -0
  111. data/lib/makit/port.rb +32 -32
  112. data/lib/makit/process.rb +377 -377
  113. data/lib/makit/protoc.rb +112 -107
  114. data/lib/makit/rake/cli.rb +196 -196
  115. data/lib/makit/rake/trace_controller.rb +174 -174
  116. data/lib/makit/rake.rb +81 -81
  117. data/lib/makit/ruby/cli.rb +185 -185
  118. data/lib/makit/ruby.rb +25 -25
  119. data/lib/makit/secrets.rb +51 -51
  120. data/lib/makit/serializer.rb +130 -130
  121. data/lib/makit/services/builder.rb +186 -186
  122. data/lib/makit/services/error_handler.rb +226 -226
  123. data/lib/makit/services/repository_manager.rb +367 -231
  124. data/lib/makit/services/validator.rb +112 -112
  125. data/lib/makit/setup/classlib.rb +101 -101
  126. data/lib/makit/setup/gem.rb +268 -268
  127. data/lib/makit/setup/pages.rb +11 -11
  128. data/lib/makit/setup/razorclasslib.rb +101 -101
  129. data/lib/makit/setup/runner.rb +54 -54
  130. data/lib/makit/setup.rb +5 -5
  131. data/lib/makit/show.rb +110 -110
  132. data/lib/makit/storage.rb +126 -126
  133. data/lib/makit/symbols.rb +175 -170
  134. data/lib/makit/task_info.rb +130 -130
  135. data/lib/makit/tasks/at_exit.rb +15 -15
  136. data/lib/makit/tasks/build.rb +22 -22
  137. data/lib/makit/tasks/clean.rb +13 -13
  138. data/lib/makit/tasks/configure.rb +10 -10
  139. data/lib/makit/tasks/format.rb +10 -10
  140. data/lib/makit/tasks/hook_manager.rb +443 -443
  141. data/lib/makit/tasks/init.rb +49 -49
  142. data/lib/makit/tasks/integrate.rb +29 -29
  143. data/lib/makit/tasks/pull_incoming.rb +13 -13
  144. data/lib/makit/tasks/setup.rb +16 -16
  145. data/lib/makit/tasks/sync.rb +17 -17
  146. data/lib/makit/tasks/tag.rb +16 -16
  147. data/lib/makit/tasks/task_monkey_patch.rb +81 -81
  148. data/lib/makit/tasks/test.rb +22 -22
  149. data/lib/makit/tasks/update.rb +18 -18
  150. data/lib/makit/tasks.rb +20 -20
  151. data/lib/makit/test_cache.rb +239 -239
  152. data/lib/makit/tree.rb +37 -37
  153. data/lib/makit/v1/configuration/project_service_impl.rb +371 -0
  154. data/lib/makit/v1/git/git_repository_service_impl.rb +295 -0
  155. data/lib/makit/v1/makit.v1_pb.rb +35 -35
  156. data/lib/makit/v1/makit.v1_services_pb.rb +27 -27
  157. data/lib/makit/v1/services/repository_manager_service_impl.rb +572 -0
  158. data/lib/makit/version.rb +100 -100
  159. data/lib/makit/version_util.rb +21 -21
  160. data/lib/makit/wix.rb +95 -95
  161. data/lib/makit/yaml.rb +29 -29
  162. data/lib/makit/zip.rb +17 -17
  163. data/lib/makit copy.rb +44 -44
  164. data/lib/makit.rb +111 -43
  165. metadata +61 -36
@@ -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