Auto-PR Workflow Security Model
This document describes the security model for the auto-pr GitHub Actions workflows. It addresses CWE-829 (Improper Control of a Resource Through its Lifetime) and related supply-chain concerns.
Threat Model
Section titled “Threat Model”When a workflow runs on a push to an ai/** branch, the pushed code may be from any collaborator or fork. An attacker could:
- Push malicious code that runs during the workflow (e.g. modified
package.jsonscripts, build scripts) - Poison artifacts that a privileged job later consumes
- Exfiltrate secrets if untrusted code runs with access to them
The goal is to never execute untrusted code in a privileged context (secrets, pull-requests: write).
Two-Phase Design
Section titled “Two-Phase Design”The auto-pr flow is split into two reusable workflows:
| Workflow | Checkout | Permissions | Secrets |
|---|---|---|---|
generate (auto-pr-generate-reusable.yml) | Branch (github.ref_name) | contents: read, models: read | GH_TOKEN from caller (secrets.GH_TOKEN or github.token) for GitHub Models |
create (auto-pr-create-reusable.yml) | Default branch only | contents: read, pull-requests: write | APP_ID, APP_PRIVATE_KEY |
The entry workflow (auto-pr.yml) must also include models: read in its top-level permissions when it calls the generate reusable workflow. Nested jobs cannot request broader GITHUB_TOKEN permissions than the caller grants (reusable workflows and permissions).
Generate (Unprivileged)
Section titled “Generate (Unprivileged)”- Checkout: The pushed branch — untrusted, but acceptable because the workflow has no privileged permissions.
- Runs:
auto-pr-generate-content(AI), artifact preparation. - Output: Artifact
pr-content(title, body, branch, default_branch). - Risk: Limited. It cannot write to the repo. The job receives a token for GitHub Models as passed from the caller (repo
GH_TOKENsecret or defaultgithub.token), not the App install secrets (APP_ID/APP_PRIVATE_KEY).
Create (Privileged, Trusted Checkout Only)
Section titled “Create (Privileged, Trusted Checkout Only)”- Checkout:
github.event.repository.default_branch— trusted. - Input: Artifact from generate job.
- Runs: GitHub App token generation,
gh pr createorgh pr edit. - Risk: Mitigated. No untrusted code is checked out or executed. Artifact content is treated as data, not code.
Artifact Handling
Section titled “Artifact Handling”The create workflow downloads the artifact produced by generate. Artifacts from unprivileged jobs are considered untrusted data:
- Extraction: Artifact is downloaded to
${{ runner.temp }}/pr-artifact(not workspace) to avoid overwriting trusted files. - Usage: Artifact files (title.txt, body.md, branch.txt, default_branch.txt) are read as data and passed to
gh. No scripts from the artifact are executed. - Validation: The create-or-update-pr CLI validates inputs before calling
gh.
CodeQL and Suppression
Section titled “CodeQL and Suppression”CodeQL flags “Checkout of untrusted code in trusted context” (CWE-829) when a workflow checks out potentially attacker-controlled refs while having privileged permissions. Our two-phase design satisfies the security intent:
- Generate: untrusted checkout, unprivileged context
- Create: trusted checkout only, privileged context
CodeQL does not fully model cross-workflow permission separation, so it may report false positives on reusable workflows. We exclude the untrusted-checkout queries via .github/codeql/codeql-config.yml; the security model above is preserved.
Related
Section titled “Related”- ADR 0002: Two-Phase Auto-PR Workflow — Design decision and alternatives
- docs/CI.md — Workflow overview
- GitHub: Keeping your GitHub Actions and workflows secure