Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
96 changes: 96 additions & 0 deletions .github/workflows/finalize-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
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:
workflow_run:
workflows: [Docker, CLI]
types: [completed]
branches: [main]
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Outdated

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 != 'pull_request'
&& startsWith(github.event.workflow_run.head_branch, 'v')
Comment thread
coderabbitai[bot] marked this conversation as resolved.
runs-on: ubuntu-latest
permissions:
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 }}
WORKFLOW_NAME: ${{ github.event.workflow.name }}
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Outdated
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" --limit 1 \
--status completed \
--json conclusion --jq '.[0].conclusion // "pending"')

DOCKER_CONCLUSION=$(gh run list --repo "$GITHUB_REPOSITORY" \
--workflow docker.yml --branch "$TAG" --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