Test prompt injection defenses
Test an issue-triage agent against instructions embedded in public issues. The GitHub token has write access, but proposed GitHub writes require approval in Slack after the agent reads an issue. Wardrik enforces these permissions outside the model.
Requires a fine-grained GitHub token with write access to that repository's issues only, and a Slack app.
The files
Section titled “The files”mcp: github: url: https://api.githubcopilot.com/mcp/ headers: authorization: Bearer ${GITHUB_TOKEN} only: [list_issues, search_issues, issue_read, issue_write, add_issue_comment] gated: [issue_write, add_issue_comment]
channels: slack: type: slack token: ${SLACK_BOT_TOKEN} signing_secret: ${SLACK_SIGNING_SECRET} domains: {acme.com: default}
notify: targets: triage: {channel: slack, room: "#triage"}
roles: approver: rules: [{verbs: [read, events, decide], kinds: [agent], names: [issues]}]bindings: - {org: default, role: approver, subjects: [user:default/@maria]}- Both write tools are gated. After the agent reads an issue, gated calls need approval.
domainsmakes[email protected]in Slack the useruser:default/@maria. The binding allows that user to approve the agent's calls.
---description: Triages the issues anyone files on acme/widgets.model: anthropic:claude-sonnet-5tools: [github/*, files]context: [TRIAGE.md]triggers: - name: sweep every: 15m prompt: Triage the issues opened since your last sweep.policy: allow: [github/*, read_file, write_file(TRIAGE.md), edit_file(TRIAGE.md)] default: deny budget: {usd: 3.00, per: day, on_exhausted: deny}notify: triage---You triage the issues filed on github.com/acme/widgets.
For each issue opened since your last sweep, label it bug, feature or question, and comment on it:thank whoever filed it, and say what you would need from them to act on it. Keep the number of thelast issue you triaged in TRIAGE.md.default: denydenies every tool not matched byallow.- The budget stops further model calls after the spending threshold is reached. The call that crosses the threshold still completes.
Try it
Section titled “Try it”echo "$GITHUB_TOKEN" | wardrik vault put GITHUB_TOKEN --for headerecho "$SLACK_BOT_TOKEN" | wardrik vault put SLACK_BOT_TOKEN --for api_keyecho "$SLACK_SIGNING_SECRET" | wardrik vault put SLACK_SIGNING_SECRET --for signing_secretsystemctl --user restart wardrikwardrik agent show issuesOpen an issue with a prompt injection, then watch #triage:
issueswaits on a person to approvegithub__issue_write.{"issue_number":14,"labels":["bug"],"method":"update","owner":"acme","repo":"widgets"}
If an injection makes the model try to close every issue, each close is a pending call.
How it works
Section titled “How it works”- After reading untrusted content, every gated call needs approval until
wardrik agent clear issues. See Untrusted content. - Files written after reading untrusted content inherit the
outsidelabel. Reading those notes applies the same approval requirements to later turns. - The policy is defined outside the prompt, so no issue can change it. See Policy.
- Don't add
allow_after: {outside: [github/issue_write]}. The same tool both labels and closes.