Skip to content
Merged
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions .github/release-please-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"changelog-path": ".github/CHANGELOG.md",
"bump-minor-pre-major": true,
"bump-patch-for-minor-pre-major": true,
"draft": true,
"changelog-sections": [
{ "type": "feat", "section": "Features" },
{ "type": "fix", "section": "Bug Fixes" },
Expand Down
101 changes: 101 additions & 0 deletions .github/workflows/finalize-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
name: Finalize Release

# Publish draft releases once both CLI and Docker workflows succeed.
# Release Please creates draft releases (immutable once published),
# then CLI (GoReleaser) and Docker (GHCR) attach assets to the draft.
# This workflow fires on each completion and publishes when both are done.

on:
# zizmor: ignore[dangerous-triggers] — safe: no checkout, no untrusted code
# execution; only queries workflow status via gh CLI and publishes a draft
# release. Guarded by event != 'pull_request' and startsWith(head_branch, 'v').
workflow_run:
workflows: [Docker, CLI]
types: [completed]

permissions: {}

jobs:
publish:
name: Publish Draft Release
# Only process tag-triggered release builds, not PR-triggered runs.
# The event != 'pull_request' guard prevents a PR that modifies the
# Docker/CLI workflows from reaching this privileged publish step.
if: >-
github.event.workflow_run.event == 'push'
&& github.event.workflow_run.head_repository.full_name == github.repository
&& startsWith(github.event.workflow_run.head_branch, 'v')
Comment thread
coderabbitai[bot] marked this conversation as resolved.
runs-on: ubuntu-latest
permissions:
actions: read
contents: write
Comment thread
coderabbitai[bot] marked this conversation as resolved.
steps:
- name: Check both workflows and publish draft
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ github.event.workflow_run.head_branch }}
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
WORKFLOW_NAME: ${{ github.event.workflow_run.name }}
WORKFLOW_CONCLUSION: ${{ github.event.workflow_run.conclusion }}
run: |
set -euo pipefail

echo "Triggered by: $WORKFLOW_NAME"
echo "Tag: $TAG"
echo "Triggering run conclusion: $WORKFLOW_CONCLUSION"

# If the triggering workflow failed, no point checking the other
if [ "$WORKFLOW_CONCLUSION" != "success" ]; then
echo "Triggering workflow failed — skipping."
exit 0
fi

# Check status of both workflows for this tag.
# gh run list --branch works for both branches and tags.
# --status completed filters out in-progress re-triggered runs.
CLI_CONCLUSION=$(gh run list --repo "$GITHUB_REPOSITORY" \
--workflow cli.yml --branch "$TAG" --event push --commit "$HEAD_SHA" \
--limit 1 --status completed \
--json conclusion --jq '.[0].conclusion // "pending"')

DOCKER_CONCLUSION=$(gh run list --repo "$GITHUB_REPOSITORY" \
--workflow docker.yml --branch "$TAG" --event push --commit "$HEAD_SHA" \
--limit 1 --status completed \
--json conclusion --jq '.[0].conclusion // "pending"')
Comment thread
greptile-apps[bot] marked this conversation as resolved.

echo "CLI: $CLI_CONCLUSION"
echo "Docker: $DOCKER_CONCLUSION"

if [ "$CLI_CONCLUSION" != "success" ] || [ "$DOCKER_CONCLUSION" != "success" ]; then
echo "Not all workflows succeeded yet (CLI=$CLI_CONCLUSION, Docker=$DOCKER_CONCLUSION)."
echo "The other workflow's completion will trigger another attempt."
exit 0
fi

# Both succeeded — check if release exists and is still a draft
if ! IS_DRAFT=$(gh release view "$TAG" --repo "$GITHUB_REPOSITORY" \
--json isDraft --jq '.isDraft' 2>/dev/null); then
echo "Release $TAG not found — nothing to publish."
exit 0
fi

if [ "$IS_DRAFT" != "true" ]; then
echo "Release $TAG is not a draft (isDraft=$IS_DRAFT). Already published."
exit 0
fi

# Publish the draft release (makes it immutable).
# Handle TOCTOU race: if a concurrent run published first, the second
# call may fail with 422 (immutable). Re-check and exit cleanly.
if gh release edit "$TAG" --repo "$GITHUB_REPOSITORY" --draft=false; then
echo "Release $TAG published successfully."
else
IS_DRAFT_NOW=$(gh release view "$TAG" --repo "$GITHUB_REPOSITORY" \
--json isDraft --jq '.isDraft' 2>/dev/null || echo "unknown")
if [ "$IS_DRAFT_NOW" = "false" ]; then
echo "Release $TAG was already published by a concurrent run — OK."
else
echo "::error::Failed to publish release $TAG (isDraft=$IS_DRAFT_NOW)"
exit 1
fi
fi
5 changes: 3 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ site/ # Astro landing page (synthorg.io)
- Concurrency group cancels stale builds on rapid pushes
- **Docker**: `.github/workflows/docker.yml` — builds backend + web images, pushes to GHCR, signs with cosign. SLSA L3 provenance attestations via `actions/attest-build-provenance` (SHA-pinned, Sigstore-signed, pushed to registry). Scans: Trivy (CRITICAL = hard fail, HIGH = warn-only) + Grype (critical cutoff). CVE triage via `.github/.trivyignore.yaml` and `.github/.grype.yaml`. Images only pushed after scans pass. Triggers on push to main and version tags (`v*`).
- **Matrix**: Python 3.14
- **CLI**: `.github/workflows/cli.yml` — Go lint (`golangci-lint` + `go vet`) + test (`-race -coverprofile`) + build (cross-compile matrix: linux/darwin/windows × amd64/arm64) + vulnerability check (`govulncheck`) + fuzz testing (main-only, 30s/target, `continue-on-error`, matrix over 4 packages) on `cli/**` changes. `cli-pass` gate includes fuzz result as informational warning. GoReleaser release on `v*` tags (attaches assets to existing Release Please release). SLSA L3 provenance attestations via `actions/attest-build-provenance` (SHA-pinned, Sigstore-signed). Post-release step appends install instructions + checksum table + provenance verification instructions to GitHub Release notes.
- **CLI**: `.github/workflows/cli.yml` — Go lint (`golangci-lint` + `go vet`) + test (`-race -coverprofile`) + build (cross-compile matrix: linux/darwin/windows × amd64/arm64) + vulnerability check (`govulncheck`) + fuzz testing (main-only, 30s/target, `continue-on-error`, matrix over 4 packages) on `cli/**` changes. `cli-pass` gate includes fuzz result as informational warning. GoReleaser release on `v*` tags (attaches assets to the draft Release Please release). SLSA L3 provenance attestations via `actions/attest-build-provenance` (SHA-pinned, Sigstore-signed). Post-release step appends install instructions + checksum table + provenance verification instructions to the draft release notes (while still in draft — before finalize-release publishes).
- **Dependabot**: daily uv + github-actions + npm + pre-commit + docker + gomod updates, grouped minor/patch, no auto-merge. Use `/review-dep-pr` to review Dependabot PRs before merging
- **Python audit**: `.github/workflows/python-audit.yml` — weekly pip-audit scan for Python dependency vulnerabilities (also runs per-PR via `python-audit` job in ci.yml)
- **Dockerfile lint**: hadolint lints all 3 Dockerfiles (backend, web, sandbox) in CI via `dockerfile-lint` job + hadolint-docker pre-commit hook locally
Expand All @@ -272,7 +272,8 @@ site/ # Astro landing page (synthorg.io)
- **DAST**: `.github/workflows/dast.yml` — ZAP API scan against the backend OpenAPI spec on push to main + weekly schedule. Builds backend image locally, starts container, runs ZAP. Results available as workflow artifacts (no SARIF — action v0.10.0 lacks native SARIF output). Not on PRs (too slow).
- **Socket.dev**: GitHub App — supply chain attack detection on PRs (typosquatting, malware, suspicious ownership changes, obfuscated code). No config file needed, auto-comments on PRs.
- **CLA**: `.github/workflows/cla.yml` — Contributor License Agreement signature check on PRs via `contributor-assistant/github-action`. Triggers on `pull_request_target` and `issue_comment`. Skips Dependabot. Signatures stored in `.github/cla-signatures.json` on the `cla-signatures` branch (unprotected, so the action can commit directly).
- **Release**: `.github/workflows/release.yml` — Release Please (Google) auto-creates a release PR on every push to main. Merging the release PR creates a git tag (`vX.Y.Z`) + GitHub Release with changelog. Tag push triggers the Docker workflow to build version-tagged images. Uses `RELEASE_PLEASE_TOKEN` secret (PAT/GitHub App token) so tag creation triggers downstream workflows (GITHUB_TOKEN cannot). Config in `.github/release-please-config.json` and `.github/.release-please-manifest.json`. After creating/updating a release PR, auto-updates the BSL Change Date in LICENSE to 3 years ahead.
- **Release**: `.github/workflows/release.yml` — Release Please (Google) auto-creates a release PR on every push to main. Merging the release PR creates a git tag (`vX.Y.Z`) + **draft** GitHub Release with changelog. Tag push triggers Docker and CLI workflows to attach assets to the draft. Uses `RELEASE_PLEASE_TOKEN` secret (PAT/GitHub App token) so tag creation triggers downstream workflows (GITHUB_TOKEN cannot). Config in `.github/release-please-config.json` (`"draft": true`) and `.github/.release-please-manifest.json`. After creating/updating a release PR, auto-updates the BSL Change Date in LICENSE to 3 years ahead.
- **Finalize Release**: `.github/workflows/finalize-release.yml` — publishes draft releases created by Release Please. Triggers on `workflow_run` completion of Docker and CLI workflows. Verifies that both workflows succeeded for the associated tag before publishing the draft. Guards against PR-triggered runs (`event != 'pull_request'`). Handles TOCTOU races (concurrent publish attempts exit cleanly). Immutable releases are enabled on the repo — once published, release assets and body cannot be modified.

## Dependencies

Expand Down
1 change: 1 addition & 0 deletions docs/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ Images are **only pushed to GHCR after both scanners pass**.
- **CLI binaries**: SLSA Level 3 provenance attestations (verify via `gh attestation verify`)
- **Git commits**: GPG/SSH signed (enforced by branch protection ruleset)
- **GitHub Actions**: All actions pinned by full SHA commit hash
- **GitHub Releases**: Immutable releases enabled — once published, assets and body cannot be modified (prevents supply chain tampering). Releases are created as drafts by Release Please, finalized after all assets are attached.

---

Expand Down
Loading