Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 61 additions & 4 deletions .github/workflows/template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,20 @@ on:
- '.github/workflows/template.yml'
branches:
- main
workflow_dispatch:

permissions:
contents: read

jobs:
buildAndPublish:
# The DockerHub image is cluster-independent, so it is only rebuilt on pushes.
buildAndPushImage:
if: ${{ github.event_name == 'push' }}
defaults:
run:
working-directory: ./template

name: Build and Push Images
name: Build and Push Image to DockerHub
runs-on: ubuntu-22.04
steps:
- name: Checkout repository
Expand Down Expand Up @@ -58,7 +61,61 @@ jobs:
--push \
--tag ${{ secrets.DOCKERHUB_USERNAME }}/desktop:latest -f - files

# The template is built on every production cluster. Clusters are separate
# tenancies with separate template registries, so each needs its own build.
buildTemplate:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Agentic Security Review

Severity: MEDIUM

Description: buildTemplate runs on workflow_dispatch for whatever ref the caller selects, with production cluster API keys in the job environment and no GitHub Environment. GitHub executes the workflow file and commit from that ref, then poetry run python build_prod.py publishes alias desktop. A repository writer can push a non-main branch and dispatch it to read those keys and replace the production desktop template.

Impact: Push-triggered runs of this workflow are already limited to main, and buildAndPushImage refuses every non-push event. workflow_dispatch drops that gate. A writer who cannot land unreviewed code on main can still run branch-controlled code with E2B_API_KEY, E2B_JULIETT_API_KEY, and E2B_TANGO_API_KEY, exfiltrate them, and Template.build alias desktop onto e2b.dev, e2b-juliett.dev, and e2b-tango.dev. Because the selected ref's workflow file is what runs, that writer can also drop the DockerHub job's push-only guard and read DOCKERHUB_USERNAME and DOCKERHUB_TOKEN. Fork contributors without write cannot dispatch. An in-file if: github.ref == 'refs/heads/main' does not hold: the attacker deletes it on the branch. Reachability: exposure internal (repo write required), automatable yes (branch push plus the workflow dispatch API, secret names are in this file), technical_impact total (production credentials).

Remediation: Store the three cluster API keys in a GitHub Environment whose deployment branch policy allows only the protected main ref, and set environment: on buildTemplate so a non-main ref cannot receive them. Keep them out of repository-level secrets. Do not rely on if: github.ref == 'refs/heads/main' in this file: workflow_dispatch runs the workflow from the selected ref, so a writer can remove that condition. Removing workflow_dispatch also closes the path. Apply the same environment gate to DockerHub credentials if a dispatched ref must not be able to name them.

name: Build E2B template (${{ matrix.cluster }})
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
include:
- cluster: foxtrot
domain: e2b.dev
- cluster: juliett
domain: e2b-juliett.dev
- cluster: tango
domain: e2b-tango.dev
# Builds on the same cluster serialize; different clusters run concurrently.
concurrency:
group: Template-${{ github.ref }}-${{ matrix.cluster }}
cancel-in-progress: false
defaults:
run:
working-directory: ./template
env:
E2B_DOMAIN: ${{ matrix.domain }}
steps:
- name: Checkout repository
uses: actions/checkout@a37ce9120846195fa4ece8f58b268e6043cb2f26 # v3.7.0

- name: Parse .tool-versions
uses: wistia/parse-tool-versions@32f568a4ffd4bfa7720ebf93f171597d1ebc979a # v2.1.1
id: tool-versions
with:
filename: '.tool-versions'
uppercase: 'true'
prefix: 'tool_version_'

- name: Install uv
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
with:
version: '${{ env.TOOL_VERSION_UV }}'
python-version: '${{ env.TOOL_VERSION_PYTHON }}'
working-directory: ./template

- name: Install dependencies
run: uv sync --locked

- name: Build E2B template
run: uv run python build_prod.py
env:
E2B_API_KEY: ${{ secrets.E2B_API_KEY }}
CLUSTER: ${{ matrix.cluster }}
# Static secret references: a dynamic secrets[...] index would hand this
# job every organization and repository secret instead of one cluster key.
E2B_API_KEY: ${{ (matrix.cluster == 'foxtrot' && secrets.E2B_API_KEY) || (matrix.cluster == 'juliett' && secrets.E2B_JULIETT_API_KEY) || (matrix.cluster == 'tango' && secrets.E2B_TANGO_API_KEY) || '' }}
run: |
if [ -z "$E2B_API_KEY" ]; then
echo "::error::Missing API key secret for cluster: $CLUSTER" >&2
exit 1
fi
uv run python build_prod.py
Loading