Skip to content

Commit b0ae590

Browse files
fix: improve trigger.pipeline validation with expression checks and better errors (#189, #188) (#196)
Add ADO expression rejection (${{, $(, $[) to triggers.pipeline.name, triggers.pipeline.project, and triggers.pipeline.branches entries, matching the existing checks on name/description fields. Include the offending branch value in newline validation error messages to aid debugging when multiple branches are configured. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 8087f15 commit b0ae590

File tree

1 file changed

+78
-7
lines changed

1 file changed

+78
-7
lines changed

src/compile/common.rs

Lines changed: 78 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -185,16 +185,34 @@ pub fn validate_front_matter_identity(front_matter: &FrontMatter) -> Result<()>
185185
}
186186
}
187187

188-
// Validate trigger.pipeline fields for newlines
188+
// Validate trigger.pipeline fields for newlines and ADO expressions
189189
if let Some(trigger_config) = &front_matter.triggers {
190190
if let Some(pipeline) = &trigger_config.pipeline {
191-
if pipeline.name.contains('\n') || pipeline.name.contains('\r') {
192-
anyhow::bail!(
193-
"Front matter 'triggers.pipeline.name' must be a single line (no newlines). \
194-
Multi-line values could inject YAML structure into the generated pipeline.",
195-
);
191+
for (field, value) in [("triggers.pipeline.name", pipeline.name.as_str())] {
192+
if value.contains("${{") || value.contains("$(") || value.contains("$[") {
193+
anyhow::bail!(
194+
"Front matter '{}' contains an ADO expression ('${{{{', '$(', or '$[') which is not allowed. \
195+
Use literal values only. Found: '{}'",
196+
field,
197+
value,
198+
);
199+
}
200+
if value.contains('\n') || value.contains('\r') {
201+
anyhow::bail!(
202+
"Front matter '{}' must be a single line (no newlines). \
203+
Multi-line values could inject YAML structure into the generated pipeline.",
204+
field,
205+
);
206+
}
196207
}
197208
if let Some(project) = &pipeline.project {
209+
if project.contains("${{") || project.contains("$(") || project.contains("$[") {
210+
anyhow::bail!(
211+
"Front matter 'triggers.pipeline.project' contains an ADO expression ('${{{{', '$(', or '$[') which is not allowed. \
212+
Use literal values only. Found: '{}'",
213+
project,
214+
);
215+
}
198216
if project.contains('\n') || project.contains('\r') {
199217
anyhow::bail!(
200218
"Front matter 'triggers.pipeline.project' must be a single line (no newlines). \
@@ -203,10 +221,18 @@ pub fn validate_front_matter_identity(front_matter: &FrontMatter) -> Result<()>
203221
}
204222
}
205223
for branch in &pipeline.branches {
224+
if branch.contains("${{") || branch.contains("$(") || branch.contains("$[") {
225+
anyhow::bail!(
226+
"Front matter 'triggers.pipeline.branches' entry {:?} contains an ADO expression ('${{{{', '$(', or '$[') \
227+
which is not allowed. Use literal values only.",
228+
branch,
229+
);
230+
}
206231
if branch.contains('\n') || branch.contains('\r') {
207232
anyhow::bail!(
208-
"Front matter 'triggers.pipeline.branches' entries must be single line (no newlines). \
233+
"Front matter 'triggers.pipeline.branches' entry {:?} must be single line (no newlines). \
209234
Multi-line values could inject YAML structure into the generated pipeline.",
235+
branch,
210236
);
211237
}
212238
}
@@ -2430,6 +2456,51 @@ mod tests {
24302456
assert!(result.unwrap_err().to_string().contains("ADO expression"));
24312457
}
24322458

2459+
#[test]
2460+
fn test_validate_front_matter_identity_rejects_ado_expression_in_trigger_pipeline_name() {
2461+
let mut fm = minimal_front_matter();
2462+
fm.triggers = Some(TriggerConfig {
2463+
pipeline: Some(crate::compile::types::PipelineTrigger {
2464+
name: "Build $(System.AccessToken)".to_string(),
2465+
project: None,
2466+
branches: vec![],
2467+
}),
2468+
});
2469+
let result = validate_front_matter_identity(&fm);
2470+
assert!(result.is_err());
2471+
assert!(result.unwrap_err().to_string().contains("ADO expression"));
2472+
}
2473+
2474+
#[test]
2475+
fn test_validate_front_matter_identity_rejects_ado_expression_in_trigger_pipeline_project() {
2476+
let mut fm = minimal_front_matter();
2477+
fm.triggers = Some(TriggerConfig {
2478+
pipeline: Some(crate::compile::types::PipelineTrigger {
2479+
name: "Build Pipeline".to_string(),
2480+
project: Some("$(System.AccessToken)".to_string()),
2481+
branches: vec![],
2482+
}),
2483+
});
2484+
let result = validate_front_matter_identity(&fm);
2485+
assert!(result.is_err());
2486+
assert!(result.unwrap_err().to_string().contains("ADO expression"));
2487+
}
2488+
2489+
#[test]
2490+
fn test_validate_front_matter_identity_rejects_ado_expression_in_trigger_pipeline_branch() {
2491+
let mut fm = minimal_front_matter();
2492+
fm.triggers = Some(TriggerConfig {
2493+
pipeline: Some(crate::compile::types::PipelineTrigger {
2494+
name: "Build Pipeline".to_string(),
2495+
project: None,
2496+
branches: vec!["$[variables['token']]".to_string()],
2497+
}),
2498+
});
2499+
let result = validate_front_matter_identity(&fm);
2500+
assert!(result.is_err());
2501+
assert!(result.unwrap_err().to_string().contains("ADO expression"));
2502+
}
2503+
24332504
#[test]
24342505
fn test_pipeline_resources_escapes_single_quotes() {
24352506
let triggers = Some(TriggerConfig {

0 commit comments

Comments
 (0)