Skip to content

Workflow security audit #2120

@enyst

Description

@enyst

I reviewed our GitHub Actions workflows for exposure to the attack chain described in https://adnanthekhan.com/posts/clinejection/ (LLM prompt injection → agent executes commands in CI → GitHub Actions cache poisoning → pivot into a more-privileged workflow to steal publish secrets).

What I reviewed

  • Enumerated all 23 workflows in .github/workflows/.
  • Flagged workflows using higher-risk triggers: issues, pull_request_target, workflow_run.
  • Looked for:
    • LLM/agent execution (OpenHands / Claude / LLM usage)
    • secret exposure (secrets.*)
    • checkout of untrusted refs (PR head SHA/ref in pull_request_target)
    • cache usage (explicit setup-uv enable-cache: true, or other caching in privileged contexts)
    • overly-broad permissions: (e.g., id-token: write, actions: write) in jobs that could run untrusted code

Key findings

High risk

  1. pr-review-by-openhands.yml + .github/actions/pr-review composite action
  • Triggered via pull_request_target on opened/ready_for_review/labeled/review_requested.
  • The composite action checked out:
    • the SDK tooling at sdk-version: ${{ github.event.pull_request.head.sha }} (i.e., untrusted PR code), and
    • the PR repo head.ref (untrusted),
      then ran the PR review agent with secrets available.
  • This is a direct untrusted-code-execution path in a secrets-bearing pull_request_target workflow.
  • It also enabled setup-uv GitHub Actions caching, which can contribute to cross-workflow cache poisoning/pivot patterns.

Mitigation: opened PR #2119 to harden this (details below).

Medium risk / defense-in-depth

  1. auto-label-issues.yml
  • Runs on issues: opened (public trigger) and used a long-lived PAT secret to add a label.
  • The workflow does not need a PAT; the ephemeral GITHUB_TOKEN with issues: write is sufficient.

Mitigation: included in PR #2119.

  1. integration-runner.yml
  • Uses pull_request_target and checks out PR head SHA for label-triggered runs (expected for integration tests).
  • setup-matrix previously ran on any label event (even non-test labels) and imported python code from the checked-out PR.
  • run-integration-tests granted id-token: write (OIDC) despite no usage.

Mitigation: included in PR #2119 (gate setup-matrix to relevant triggers + remove id-token: write).

  1. condenser-runner.yml
  • Similar label-triggered pull_request_target pattern. Also granted id-token: write without usage.

Mitigation: included in PR #2119.

PR with fixes

Summary of changes:

  • PR review now runs only on maintainer-applied review-this label.
  • Composite PR review action no longer checks out PR head code (uses trusted base commit + GitHub API diffs).
  • Disabled setup-uv Actions cache for the PR review action.
  • Switched issue auto-label to GITHUB_TOKEN.
  • Removed unused id-token: write from label-triggered pull_request_target jobs.

Recommended follow-ups (not included)

  • Consider additional guardrails for any CI LLM/agent runs:
    • run agents with a minimal tool allowlist (avoid unrestricted bash/network where possible)
    • use fine-grained tokens with least privilege for commenting/reviewing
    • consider GitHub environments + required reviewers for workflows that must use highly-privileged secrets
    • avoid using shared caches in any workflow that has publish credentials, or salt cache keys with a secret only available to publish workflows

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions