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:
- Attacker forks
sei-protocol/sei-chain.
- Pushes a commit containing arbitrary
Dockerfile / Makefile content to their fork.
- Opens a fork PR.
- 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."
- 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
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)
Problem
The
.github/workflows/ecr.ymlworkflow'sworkflow_dispatchtrigger accepts an arbitraryrefinput and builds + publishes a<full-sha>-tagged image to the production189176372795.dkr.ecr.us-east-2.amazonaws.com/sei/sei-chainECR 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.mdin sei-protocol/Tide for the full finding context.Impact
Supply-chain compromise path with low attacker effort:
sei-protocol/sei-chain.Dockerfile/Makefilecontent to their fork.ghwrite access to dispatch a build against the fork-PR head SHA — the harbor-dev skill'simage-resolution.mdreference explicitly documents this as the workaround when fork-PR auto-builds are gated: "Manual dispatch on the head SHA still works."From there, anyone pulling by SHA gets the attacker's binary:
pacific-1/seid-nodeconsumers 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_dispatchon protected-branch reachabilityRefuse to build a SHA that's not an ancestor of
mainor anyrelease/**branch. In the workflow:```yaml
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_dispatchon org membership of the actorRequire the dispatching actor (
github.actor) be a member ofsei-protocolorg. Useactions/checkout+gh api orgs/sei-protocol/members/${ACTOR}to verify:```yaml
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_dispatchofecr.ymlagainst a fork-PR head SHA fails the workflow run with a clear error before any image is built.workflow_dispatchagainst a SHA reachable frommainorrelease/**succeeds (no regression for the legitimate use case — building a teammate's PR before merge for testing).image-resolution.mdhalt-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
References
docs/bugbash/harbor-dev.mdItem 2.claude/skills/harbor-dev/references/image-resolution.md(lines 60-70, 115-122, 163 — the documented dispatch path)