Skip to content

Gate ecr.yml workflow_dispatch on protected-branch reachability or org membership #3412

@bdchatham

Description

@bdchatham

Problem

The .github/workflows/ecr.yml workflow's workflow_dispatch trigger accepts an arbitrary ref input and builds + publishes a <full-sha>-tagged image to the production 189176372795.dkr.ecr.us-east-2.amazonaws.com/sei/sei-chain ECR repo. The dispatch runs in the trusted target-repo context (with access to repo secrets and the OIDC role's trust policy), regardless of whether the input SHA is reachable from a protected branch or whether the dispatching actor has org membership.

This was surfaced in the harbor-dev skill bugbash as Item 2 (Critical). See docs/bugbash/harbor-dev.md in sei-protocol/Tide for the full finding context.

Impact

Supply-chain compromise path with low attacker effort:

  1. Attacker forks sei-protocol/sei-chain.
  2. Pushes a commit containing arbitrary Dockerfile / Makefile content to their fork.
  3. Opens a fork PR.
  4. Convinces an engineer (or platform team member) with gh write access to dispatch a build against the fork-PR head SHA — the harbor-dev skill's image-resolution.md reference explicitly documents this as the workaround when fork-PR auto-builds are gated: "Manual dispatch on the head SHA still works."
  5. The build runs in trusted context, the resulting image lands in the production ECR repo tagged with the fork commit's full SHA.

From there, anyone pulling by SHA gets the attacker's binary:

  • Other engineers running ad-hoc benches.
  • The nightly autobake orchestrator.
  • In principle, pacific-1/seid-node consumers if any pull by SHA.

No signature/provenance/Sigstore verification anywhere downstream. No commit-signature gate.

Proposed change

Layered defenses, in order of leverage:

1. Gate workflow_dispatch on protected-branch reachability

Refuse to build a SHA that's not an ancestor of main or any release/** branch. In the workflow:

```yaml

  • name: Verify SHA is on a protected branch
    run: |
    SHA=${{ github.event.inputs.ref }}
    if ! git merge-base --is-ancestor "${SHA}" origin/main \
    && ! git branch -r --contains "${SHA}" | grep -E 'origin/release/'; then
    echo "::error::SHA ${SHA} is not reachable from main or release/** — refusing to build"
    exit 1
    fi
    ```

2. Gate workflow_dispatch on org membership of the actor

Require the dispatching actor (github.actor) be a member of sei-protocol org. Use actions/checkout + gh api orgs/sei-protocol/members/${ACTOR} to verify:

```yaml

  • name: Verify actor is org member
    env:
    GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    run: |
    if ! gh api "orgs/sei-protocol/members/${{ github.actor }}" >/dev/null 2>&1; then
    echo "::error::dispatching actor ${{ github.actor }} is not a member of sei-protocol"
    exit 1
    fi
    ```

3. Sigstore signing on push (defense in depth)

Sign every published image with cosign keyless OIDC; downstream pulls verify. Larger scope; separate decision.

The first two together close the immediate hole; the third hardens the long tail.

Acceptance criteria

  • workflow_dispatch of ecr.yml against a fork-PR head SHA fails the workflow run with a clear error before any image is built.
  • workflow_dispatch against a SHA reachable from main or release/** succeeds (no regression for the legitimate use case — building a teammate's PR before merge for testing).
  • An external (non-org-member) actor with no fork PR cannot dispatch the workflow via API call (assuming repo write access exists somehow).
  • sei-protocol/Tide's harbor-dev image-resolution.md halt-condition note ("Manual dispatch on the head SHA still works") gets updated in a separate Tide PR to reflect the new behavior.

Out of scope

  • Sigstore signing of published images (a follow-up; substantial separate workstream).
  • Image admission policy on harbor (Kyverno / OPA gates pulling by SHA from untrusted commits).
  • Tightening the trust policy of the OIDC role used by the workflow (separate platform discussion).

References

  • sei-protocol/Tide bugbash artifact: docs/bugbash/harbor-dev.md Item 2
  • sei-protocol/Tide skill: .claude/skills/harbor-dev/references/image-resolution.md (lines 60-70, 115-122, 163 — the documented dispatch path)
  • sei-protocol/platform#464 — adjacent IAM scoping work (different attack surface, similar tenancy-isolation theme)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions