Supply-chain hardening for the CI pipeline. 23 workflow files
modified, 59 mutable-tag refs replaced with commit SHAs.
The risk
Every `uses:` reference in .github/workflows/*.yml was pinned to a
mutable tag (e.g., `actions/checkout@v4`). A maintainer of an
action — or a compromised maintainer account — can repoint that
tag to malicious code, and our pipelines silently pull it on the
next run. The tj-actions/changed-files compromise of March 2025 is
the canonical example: maintainer credential leak, attacker
repointed several `@v<N>` tags to a payload that exfiltrated
repository secrets. Repos that pinned to SHAs were unaffected.
The fix
Replace each `@v<N>` with `@<commit-sha> # v<N>`. The trailing
comment preserves human readability ("ah, this is v4"); the SHA
makes the reference immutable.
Actions covered (10 distinct):
actions/{checkout,setup-go,setup-python,setup-node,upload-artifact,github-script}
docker/{login-action,setup-buildx-action,build-push-action}
github/codeql-action/{init,autobuild,analyze}
dorny/paths-filter
imjasonh/setup-crane
pnpm/action-setup (already pinned in molecule-app, listed here for completeness)
Excluded:
Molecule-AI/molecule-ci/.github/workflows/disable-auto-merge-on-push.yml@main
— internal org reusable workflow; we control its repo, threat model
is different from third-party actions. Conventional to pin to @main
rather than SHA for internal reusables.
The maintenance cost
SHA pinning means upstream fixes require manual SHA bumps. Without
automation, pinned SHAs go stale. So this PR also enables Dependabot
across four ecosystems:
- github-actions (workflows)
- gomod (workspace-server)
- npm (canvas)
- pip (workspace runtime requirements)
Weekly cadence — the supply-chain attack window is "minutes between
repoint and pull"; weekly auto-bumps don't help with zero-days
regardless. The point is to pull in non-zero-day fixes without
operator effort.
Aligns with user-stated principle: "long-term, robust, fully-
automated, eliminate human error."
Companion PR: Molecule-AI/molecule-controlplane#308 (same pattern,
smaller surface).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
106 lines
4.1 KiB
YAML
106 lines
4.1 KiB
YAML
name: publish-canvas-image
|
|
|
|
# Builds and pushes the canvas Docker image to GHCR whenever a commit lands
|
|
# on main that touches canvas code. Previously canvas changes were visible in
|
|
# CI (npm run build passed) but the live container was never updated —
|
|
# operators had to manually run `docker compose build canvas` each time.
|
|
#
|
|
# Mirror of publish-platform-image.yml, adapted for the Next.js canvas layer.
|
|
# See that workflow for inline notes on macOS Keychain isolation and QEMU.
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
paths:
|
|
# Only rebuild when canvas source changes — saves GHA minutes on
|
|
# platform-only / docs-only / MCP-only merges.
|
|
- 'canvas/**'
|
|
- '.github/workflows/publish-canvas-image.yml'
|
|
# Manual trigger: use after a non-canvas merge that still needs a fresh
|
|
# image (e.g. a Dockerfile change lives outside the canvas/ tree).
|
|
workflow_dispatch:
|
|
inputs:
|
|
platform_url:
|
|
description: 'NEXT_PUBLIC_PLATFORM_URL baked into the bundle (default: http://localhost:8080)'
|
|
required: false
|
|
default: ''
|
|
ws_url:
|
|
description: 'NEXT_PUBLIC_WS_URL baked into the bundle (default: ws://localhost:8080/ws)'
|
|
required: false
|
|
default: ''
|
|
|
|
permissions:
|
|
contents: read
|
|
packages: write # required to push to ghcr.io/${{ github.repository_owner }}/*
|
|
|
|
env:
|
|
IMAGE_NAME: ghcr.io/molecule-ai/canvas
|
|
|
|
jobs:
|
|
build-and-push:
|
|
name: Build & push canvas image
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
|
|
- name: Log in to GHCR
|
|
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
|
|
with:
|
|
registry: ghcr.io
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Set up Docker Buildx
|
|
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
|
|
|
|
- name: Compute tags
|
|
id: tags
|
|
shell: bash
|
|
run: |
|
|
echo "sha=${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Resolve build args
|
|
id: build_args
|
|
# Priority: workflow_dispatch input > repo secret > hardcoded default.
|
|
# NEXT_PUBLIC_* env vars are baked into the JS bundle at build time by
|
|
# Next.js — they cannot be changed at runtime without a full rebuild.
|
|
# For local docker-compose deployments the defaults (localhost:8080)
|
|
# work as-is; production deployments should set CANVAS_PLATFORM_URL
|
|
# and CANVAS_WS_URL as repository secrets.
|
|
#
|
|
# Inputs are passed via env vars (not direct ${{ }} interpolation) to
|
|
# prevent shell injection from workflow_dispatch string inputs.
|
|
shell: bash
|
|
env:
|
|
INPUT_PLATFORM_URL: ${{ github.event.inputs.platform_url }}
|
|
SECRET_PLATFORM_URL: ${{ secrets.CANVAS_PLATFORM_URL }}
|
|
INPUT_WS_URL: ${{ github.event.inputs.ws_url }}
|
|
SECRET_WS_URL: ${{ secrets.CANVAS_WS_URL }}
|
|
run: |
|
|
PLATFORM_URL="${INPUT_PLATFORM_URL:-${SECRET_PLATFORM_URL:-http://localhost:8080}}"
|
|
WS_URL="${INPUT_WS_URL:-${SECRET_WS_URL:-ws://localhost:8080/ws}}"
|
|
|
|
echo "platform_url=${PLATFORM_URL}" >> "$GITHUB_OUTPUT"
|
|
echo "ws_url=${WS_URL}" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Build & push canvas image to GHCR
|
|
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
|
|
with:
|
|
context: ./canvas
|
|
file: ./canvas/Dockerfile
|
|
platforms: linux/amd64
|
|
push: true
|
|
build-args: |
|
|
NEXT_PUBLIC_PLATFORM_URL=${{ steps.build_args.outputs.platform_url }}
|
|
NEXT_PUBLIC_WS_URL=${{ steps.build_args.outputs.ws_url }}
|
|
tags: |
|
|
${{ env.IMAGE_NAME }}:latest
|
|
${{ env.IMAGE_NAME }}:sha-${{ steps.tags.outputs.sha }}
|
|
cache-from: type=gha
|
|
cache-to: type=gha,mode=max
|
|
labels: |
|
|
org.opencontainers.image.source=https://github.com/${{ github.repository }}
|
|
org.opencontainers.image.revision=${{ github.sha }}
|
|
org.opencontainers.image.description=Molecule AI canvas (Next.js 15 + React Flow)
|