@@ -7,9 +7,10 @@ use serde::{Deserialize, Serialize};
77
88use super :: PATH_SEGMENT ;
99use ado_aw_derive:: SanitizeConfig ;
10- use crate :: sanitize:: { SanitizeContent , sanitize as sanitize_text} ;
10+ use crate :: sanitize:: { SanitizeContent , sanitize as sanitize_text, sanitize_config } ;
1111use crate :: tool_result;
1212use crate :: safeoutputs:: { ExecutionContext , ExecutionResult , Executor , Validate } ;
13+ use crate :: validate:: reject_pipeline_injection;
1314use anyhow:: { Context , ensure} ;
1415
1516/// Parameters for adding a comment thread on a pull request
@@ -89,6 +90,7 @@ impl Validate for AddPrCommentParams {
8990 if let Some ( fp) = & self . file_path {
9091 validate_file_path ( fp) ?;
9192 }
93+ reject_pipeline_injection ( & self . repository , "repository" ) ?;
9294 Ok ( ( ) )
9395 }
9496}
@@ -112,8 +114,8 @@ tool_result! {
112114impl SanitizeContent for AddPrCommentResult {
113115 fn sanitize_content_fields ( & mut self ) {
114116 self . content = sanitize_text ( & self . content ) ;
115- // Strip control characters from structural fields for defense-in-depth
116- self . repository = self . repository . chars ( ) . filter ( |c| !c . is_control ( ) ) . collect ( ) ;
117+ self . repository = sanitize_config ( & self . repository ) ;
118+ // Strip control characters from remaining structural fields for defense-in-depth
117119 self . status = self . status . chars ( ) . filter ( |c| !c. is_control ( ) ) . collect ( ) ;
118120 self . file_path = self . file_path . as_ref ( ) . map ( |fp| {
119121 fp. chars ( ) . filter ( |c| !c. is_control ( ) ) . collect ( )
@@ -463,6 +465,21 @@ mod tests {
463465 assert ! ( result. is_err( ) ) ;
464466 }
465467
468+ #[ test]
469+ fn test_validation_rejects_repository_pipeline_command ( ) {
470+ let params = AddPrCommentParams {
471+ pull_request_id : 42 ,
472+ content : "This is a valid comment body text." . to_string ( ) ,
473+ repository : "##vso[task.setvariable variable=x]y" . to_string ( ) ,
474+ file_path : None ,
475+ start_line : None ,
476+ line : None ,
477+ status : "active" . to_string ( ) ,
478+ } ;
479+ let result: Result < AddPrCommentResult , _ > = params. try_into ( ) ;
480+ assert ! ( result. is_err( ) ) ;
481+ }
482+
466483 #[ test]
467484 fn test_validation_rejects_line_without_file_path ( ) {
468485 let params = AddPrCommentParams {
@@ -632,4 +649,33 @@ allowed-statuses:
632649 "uppercase 'Active' should match config value 'active'"
633650 ) ;
634651 }
652+
653+ #[ test]
654+ fn test_sanitize_content_neutralizes_repository_pipeline_command ( ) {
655+ let params = AddPrCommentParams {
656+ pull_request_id : 42 ,
657+ content : "This is a valid comment body text." . to_string ( ) ,
658+ repository : "##vso[task.setvariable variable=x]y" . to_string ( ) ,
659+ file_path : None ,
660+ start_line : None ,
661+ line : None ,
662+ status : "active" . to_string ( ) ,
663+ } ;
664+ let mut result = AddPrCommentResult {
665+ name : "add-pr-comment" . to_string ( ) ,
666+ pull_request_id : params. pull_request_id ,
667+ content : params. content ,
668+ repository : params. repository ,
669+ file_path : params. file_path ,
670+ start_line : params. start_line ,
671+ line : params. line ,
672+ status : params. status ,
673+ } ;
674+ result. sanitize_content_fields ( ) ;
675+ assert ! (
676+ result. repository. contains( "`##vso[`" ) ,
677+ "repository pipeline command should be neutralized with backticks: {}" ,
678+ result. repository
679+ ) ;
680+ }
635681}
0 commit comments