makit 0.0.168 → 0.0.169

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