@@ -11,6 +11,7 @@ use tracing::{error, info, warn};
1111use crate :: agent:: config:: Config ;
1212use crate :: commands:: executor:: CommandExecutor ;
1313use crate :: commands:: firewall:: FirewallPolicy ;
14+ use crate :: commands:: validator:: CommandValidator ;
1415use crate :: commands:: TimeoutStrategy ;
1516use crate :: monitoring:: { spawn_heartbeat, MetricsCollector , MetricsSnapshot , MetricsStore } ;
1617use crate :: transport:: { http_polling, CommandResult } ;
@@ -216,36 +217,54 @@ async fn execute_and_report(
216217 }
217218 }
218219 Ok ( None ) => {
219- // Not a stacker command, fall back to shell execution
220- info ! (
221- command_id = %cmd. command_id,
222- command_name = %cmd. name,
223- "executing as shell command"
224- ) ;
225- let strategy = TimeoutStrategy :: backup_strategy ( ctx. command_timeout ) ;
226- let exec_result = executor. execute ( & cmd, strategy) . await ;
227-
228- match exec_result {
229- Ok ( output) => CommandResult {
230- command_id : cmd. command_id . clone ( ) ,
231- status : "success" . to_string ( ) ,
232- result : Some ( json ! ( {
233- "stdout" : output. stdout,
234- "stderr" : output. stderr,
235- "exit_code" : output. exit_code,
236- } ) ) ,
237- error : None ,
238- completed_at : Utc :: now ( ) . to_rfc3339_opts ( SecondsFormat :: Secs , true ) ,
239- ..CommandResult :: default ( )
240- } ,
241- Err ( e) => CommandResult {
220+ // Not a stacker command — validate before shell execution
221+ let validator = CommandValidator :: default_secure ( ) ;
222+ if let Err ( e) = validator. validate ( & cmd) {
223+ error ! (
224+ command_id = %cmd. command_id,
225+ command_name = %cmd. name,
226+ error = %e,
227+ "shell command rejected by validator"
228+ ) ;
229+ CommandResult {
242230 command_id : cmd. command_id . clone ( ) ,
243231 status : "failed" . to_string ( ) ,
244232 result : None ,
245- error : Some ( e . to_string ( ) ) ,
233+ error : Some ( format ! ( "Command validation failed: {}" , e ) ) ,
246234 completed_at : Utc :: now ( ) . to_rfc3339_opts ( SecondsFormat :: Secs , true ) ,
247235 ..CommandResult :: default ( )
248- } ,
236+ }
237+ } else {
238+ info ! (
239+ command_id = %cmd. command_id,
240+ command_name = %cmd. name,
241+ "executing validated shell command"
242+ ) ;
243+ let strategy = TimeoutStrategy :: backup_strategy ( ctx. command_timeout ) ;
244+ let exec_result = executor. execute ( & cmd, strategy) . await ;
245+
246+ match exec_result {
247+ Ok ( output) => CommandResult {
248+ command_id : cmd. command_id . clone ( ) ,
249+ status : "success" . to_string ( ) ,
250+ result : Some ( json ! ( {
251+ "stdout" : output. stdout,
252+ "stderr" : output. stderr,
253+ "exit_code" : output. exit_code,
254+ } ) ) ,
255+ error : None ,
256+ completed_at : Utc :: now ( ) . to_rfc3339_opts ( SecondsFormat :: Secs , true ) ,
257+ ..CommandResult :: default ( )
258+ } ,
259+ Err ( e) => CommandResult {
260+ command_id : cmd. command_id . clone ( ) ,
261+ status : "failed" . to_string ( ) ,
262+ result : None ,
263+ error : Some ( e. to_string ( ) ) ,
264+ completed_at : Utc :: now ( ) . to_rfc3339_opts ( SecondsFormat :: Secs , true ) ,
265+ ..CommandResult :: default ( )
266+ } ,
267+ }
249268 }
250269 }
251270 Err ( e) => {
0 commit comments