Skip to content

fix: secure CI against fork PRs using pull_request + workflow_run split#18

Merged
yajith merged 3 commits into
developfrom
fix/fork-pr-ci-security
Apr 22, 2026
Merged

fix: secure CI against fork PRs using pull_request + workflow_run split#18
yajith merged 3 commits into
developfrom
fix/fork-pr-ci-security

Conversation

@yajith
Copy link
Copy Markdown
Member

@yajith yajith commented Apr 17, 2026

Problem

Fork PRs cannot access repository secrets (DOCKER_USERNAME, DOCKER_PASSWORD), causing the Docker push step to fail silently. The previous workflow also held pull-requests: write permission in the same context as the build, which is unnecessary exposure.

Solution

Split the single CI workflow into two, following the pattern recommended by GitHub Security Lab:

  • ci.yaml — triggered by pull_request. Runs with contents: read only, no secrets. Builds a multi-arch OCI tarball and uploads it as an artifact. Fork contributors can never access secrets here regardless of what their code does.
  • ci-publish.yaml — triggered by workflow_run after ci succeeds. Always executes from the develop branch, so its code is never fork-controlled. Downloads the pre-built artifact, validates the image tag with a strict regex, logs in with registry credentials, and pushes via docker buildx imagetools create. Posts a PR comment with the published image reference.

For non-PR events (push to develop, workflow_dispatch), ci.yaml builds and pushes directly as before — no change to that path.

Security properties

  • Fork-contributed code only ever runs in the sandboxed, secrets-less ci workflow
  • The privileged ci-publish workflow never executes any code from the fork
  • Image tag read from the artifact is validated against ^pr-[0-9]+$ before use, preventing injection

Fork PRs previously couldn't access DOCKER_USERNAME/DOCKER_PASSWORD secrets,
causing the push step to silently fail. This implements the pull_request +
workflow_run split pattern recommended by GitHub Security Lab to safely push
PR images even from fork contributors.

- ci.yaml: runs on pull_request with no secrets; builds a multi-arch OCI
  tarball and uploads it as an artifact. For non-PR events (push, dispatch)
  it logs in and pushes directly as before.
- ci-publish.yaml: runs on workflow_run after ci succeeds; always executes
  from the default branch so its code is never fork-controlled. Downloads
  the pre-built tarball, validates the image tag with a strict regex, and
  uses docker buildx imagetools create to push to portainerci. Posts a PR
  comment with the published image tag.

The fork author's code is only ever executed in the sandboxed, secrets-less
ci workflow. Secrets are only accessed in ci-publish.yaml which never runs
untrusted code, preventing the "pwn request" supply chain attack vector.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@samdulam samdulam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@yajith yajith merged commit 0bbb3c0 into develop Apr 22, 2026
2 checks passed
@yajith yajith deleted the fix/fork-pr-ci-security branch April 22, 2026 05:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants