agentic-actions-auditor · v1.0.0 · 2026-07-28 · sha256 516e93b83886edba
agentic-actions-auditor v1.0.0A
Immutable. This exact content never changes and is served at /api/v1/blob/516e93b83886edba.
---
name: agentic-actions-auditor
id: agentic-actions-auditor
version: "1.0.0"
title: "Agentic Actions Auditor"
type: skill
description: "Audit CI/CD workflows that invoke LLM agents for dangerous trigger, checkout, token-scope, and prompt-construction patterns — flags the injection and blast-radius surface without shipping a working exploit"
status: experimental
owners:
- "@GSA-TTS/agentic-coding-team"
primary_personas:
- developers
- security
requires:
anchors: []
output:
format: markdown
contract:
required_sections:
- "Summary"
- "Findings"
prohibited_content:
- "Secrets"
- "Real PII"
- "Real CUI"
- "Internal URLs"
- "Approved Autonomy For Irreversible Actions"
- "Real Tokens Or Tool Credentials"
quality_gates:
readability_max_grade: 10
citations_required: false
triggers:
- "pull_request_target"
- "issue_comment"
- "workflow_run"
- "GITHUB_TOKEN"
- "agent in CI"
- "LLM in workflow"
- "prompt injection CI"
- "permissions write-all"
tags:
- "security"
- "ci-cd"
- "github-actions"
- "prompt-injection"
- "least-privilege"
- "agentic"
categories:
- "security"
- "review"
- "supply-chain"
risk_tier: high
human_review_required: true
allowed_tools: []
network_policy: deny
write_policy: deny
script_policy: deny
compliance:
frameworks:
- "OWASP Top 10 Agentic 2026"
- "OWASP Top 10 LLM 2025"
- "NIST SP 800-53"
nist_controls:
- "AC-6"
- "AU-2"
- "CM-3"
portability:
opencode: true
cursor: true
claude_projects: true
chatgpt: true
generic_llm: true
scope:
intended_use:
- "Audit CI/CD workflows that invoke an LLM agent for dangerous triggers"
- "Flag untrusted PR-head checkout that runs with repo secrets in scope"
- "Flag agent prompts built from attacker-controlled text (PR title/body, comments)"
- "Flag over-scoped GITHUB_TOKEN in agent jobs and missing human-approval gates"
exclusions:
- "Does NOT cover general permission-scope review — see least-privilege-review"
- "Does NOT audit the agent's model or its prompt-safety logic itself"
- "Not a substitute for a full supply-chain or dependency audit"
- "Does not ship or run a working exploit; defers policy to the playbook"
source_inspiration: [] # OWASP-inspired categories only; no text, config, or prompt copied. Add an intake record if a public source is used.
changelog:
- version: "1.0.0"
date: "2026-07-01"
change_type: minor
summary: "Initial version — audits agent-invoking CI workflows for dangerous trigger/checkout/token/prompt patterns and missing approval gates; describes fixes, ships no working exploit."
collection: security
routing:
task_types:
- "analyze"
- "review"
input_artifacts:
- "ci-workflow"
- "source-code"
output_artifacts:
- "security-review"
prefer_when:
- "the target is a CI/CD workflow that invokes an LLM agent"
avoid_when:
- "the question is about permission minimality"
- "permissions minimal"
- "token scope"
delegates:
- pattern: least-privilege-review
when: "permissions minimal token scope minimality"
aliases:
- "workflow audit"
- "github actions security"
- "ci agent audit"
---
# Skill: Agentic Actions Auditor
Audit CI/CD workflows that **invoke an LLM agent** for the patterns that turn a
pipeline into an attacker-reachable action surface: dangerous event triggers,
untrusted checkout of pull-request code with secrets in scope, over-scoped
`GITHUB_TOKEN`, agent prompts built from attacker-controlled text, and missing
human-approval gates before the agent writes or takes irreversible actions.
> **This skill is self-limiting.** It audits agent-invoking CI for dangerous
> trigger, checkout, permission, and prompt-construction patterns; it describes
> each pattern and its fix but ships **no working end-to-end exploit**. Minimal
> YAML snippets that show a dangerous trigger are config patterns, not payloads,
> and each is paired with the safer form. Authoritative policy lives in the
> [agentic-coding-playbook](https://github.com/GSA-TTS/agentic-coding-playbook);
> this skill defers all policy there and never restates it.
## When to Use
- Reviewing a GitHub Actions (or similar CI) workflow that calls an LLM agent —
a code reviewer bot, an issue triager, an auto-fixer, or a chat-ops agent
- A workflow reads PR titles, bodies, or issue comments and feeds them to a model
- Before enabling an agent bot on a public or fork-friendly repository
- When a workflow uses `pull_request_target`, `issue_comment`, `issues`, or
`workflow_run` and also has access to secrets
## When NOT to Use
- For general permission-scope review of any workflow — use `least-privilege-review`
- To evaluate the agent's own prompt-safety or model choice
- As a full supply-chain / dependency audit
- To *grant* autonomy for irreversible actions — this skill only flags its absence
## Prerequisites
- Read access to the workflow files (usually `.github/workflows/*.yml`)
- Knowledge of which step invokes the LLM agent (an action, a CLI, or an API call)
- Understanding of the repo's trust boundary (does it accept fork PRs?)
## Procedure
### 1. Identify agent-invoking workflows
Scan workflow files for the step that calls an LLM agent: a marketplace action,
a model API call (`curl`/SDK to an LLM endpoint), or an agent CLI. If no step
invokes an agent, this skill does not apply — stop and note it.
### 2. Check the trigger
Inspect the `on:` block. These triggers run with **repository secrets** on
**untrusted content** and are high-risk for agent jobs:
| Trigger | Why high-risk |
|---------|---------------|
| `pull_request_target` | Runs in the base-repo context **with secrets**, but the PR is attacker-authored |
| `issue_comment` / `issues` | Fires on attacker-controlled comment/issue text |
| `workflow_run` | Can inherit secrets while processing artifacts from an untrusted run |
Flag any agent job on these triggers. `pull_request` (no `_target`) runs without
base secrets on fork PRs and is the safer default.
### 3. Check for untrusted checkout with secrets in scope
Look for a checkout of the **PR head** (`ref: ${{ github.event.pull_request.head.sha }}`
or `head.ref`) in a job that also holds secrets. This gives attacker-controlled
code a runner that can read those secrets. Flag it, especially under
`pull_request_target`.
### 4. Check the GITHUB_TOKEN scope in the agent job
Inspect the job- or workflow-level `permissions:` block. Flag:
- `permissions: write-all` or a missing `permissions:` block (defaults may be broad)
- Any `write` scope the agent step does not actually need (e.g. `contents: write`
on a read-only reviewer)
The blast radius of an injected instruction equals the token's scope.
### 5. Check whether the prompt is built from attacker-controlled fields
Trace what text flows into the agent's prompt. Flag any prompt assembled from
attacker-controlled fields: `github.event.pull_request.title` / `.body`,
`github.event.comment.body`, `github.event.issue.title` / `.body`, or branch
names. These are **data**, not instructions — treating them as instructions is
prompt injection.
### 6. Check for a human-approval gate
Determine whether the agent can take an **irreversible or writing** action
(merge, push, comment, deploy, call a tool) without a human gate. Flag the
absence of an `environment:` with required reviewers, a manual approval step, or
a dry-run/propose-only mode for such actions.
### 7. Report with severity
Produce the output below. Each finding cites the trigger or line and names the
safer pattern. Rank by blast radius: untrusted-checkout-with-secrets and
write-scoped injection paths are highest.
## Output Contract
```markdown
## Summary
<1-3 sentences: workflows audited, findings count, highest-severity issue.>
## Findings
For each finding:
- **Workflow / job:** <file · job name>
- **Location:** <trigger or line>
- **Pattern:** dangerous-trigger | untrusted-checkout | over-scoped-token | prompt-from-untrusted-input | missing-approval-gate
- **Severity:** high | medium | low
- **Why flagged:** <one sentence>
- **Safer pattern:** <use pull_request not pull_request_target | drop PR-head checkout from secret job | least-privilege token | treat PR/issue text as data | add environment: with required reviewers>
## Notes
- This audit flags patterns only; it does not grant autonomy for irreversible actions. See the playbook for policy.
```
## Verification
- Every finding cites the trigger or line **and** names a concrete safer pattern
- The report proposes no working exploit and endorses no unattended irreversible
agent action (check against the `Approved Autonomy For Irreversible Actions`
prohibited-content rule)
- No real tokens, secrets, or tool credentials appear in the output
- Safer-pattern advice matches the finding: `pull_request` over
`pull_request_target`; `environment:` + required reviewers for writes;
least-privilege token; PR/issue text handled as data
## Examples
### Example 1 — pull_request_target + untrusted checkout + agent step (flag)
Dangerous config (trigger pattern only, paired with fix):
```yaml
on: pull_request_target # runs WITH base-repo secrets
jobs:
review:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }} # attacker's code
- run: run-llm-reviewer # agent step now sees secrets + hostile code
```
Finding:
- **Pattern:** untrusted-checkout (with dangerous-trigger)
- **Severity:** high
- **Why:** `pull_request_target` exposes secrets while checking out attacker code.
- **Safer pattern:** switch to `on: pull_request` (no base secrets on fork PRs);
if base context is required, do **not** check out or execute PR head in that job.
### Example 2 — agent prompt built from PR/issue text (flag)
```yaml
- run: run-llm-agent --prompt "Review this PR: ${{ github.event.pull_request.body }}"
```
Finding:
- **Pattern:** prompt-from-untrusted-input
- **Severity:** high
- **Why:** the PR body is attacker-controlled and is injected as instructions.
- **Safer pattern:** pass PR text to the model as clearly delimited **data**, not
instructions; keep the trusted instruction template separate from user content.
### Example 3 — over-scoped token in an agent job (boundary/edge, flag)
```yaml
permissions: write-all # agent job needs only read + PR comment
```
Finding:
- **Pattern:** over-scoped-token
- **Severity:** medium
- **Why:** an injected instruction inherits full write scope (push, merge, releases).
- **Safer pattern:** set least privilege, e.g. `contents: read` plus only the
narrow write the step needs (`pull-requests: write` for a comment).
### Example 4 — safe workflow (not flagged)
```yaml
on: pull_request # no base secrets on fork PRs
permissions:
contents: read # least privilege
jobs:
review:
steps:
- uses: actions/checkout@v4 # base ref, no PR-head execution with secrets
- run: run-llm-reviewer --input-as-data pr-diff.txt # PR content treated as data
```
Finding: none — safer trigger, least-privilege token, PR content handled as data,
and no irreversible write without a gate.
## References
- Authoritative policy (never restated here): playbook
[`AGENTS.md`](https://github.com/GSA-TTS/agentic-coding-playbook/blob/main/AGENTS.md)
§11 (prompt injection defense), §3 (authorization and least privilege), §7 (supply chain security)
- OWASP Top 10 for Agentic Applications (2026) and OWASP Top 10 for LLM
Applications (2025) — prompt injection and excessive-agency categories
- GitHub Actions security hardening for untrusted input, `pull_request_target`,
and `GITHUB_TOKEN` permissions (see GitHub's official docs)
- Governance model for security skills:
[`docs/security-skill-governance.md`](../../docs/security-skill-governance.md)