From 40a752f1d048bc6cdca99ffbd56254485b54f4e5 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Tue, 4 Aug 2026 15:51:31 -0700 Subject: [PATCH] Gate the deploy before validation, and match the bundle's directory mode The ref assertion ran beside validation rather than before it, so a mis-dispatched production deploy installed a toolchain and built a site before refusing. The upload cleared the setgid bit the bundle is assembled with, so the group would stop propagating below the first directory the transfer creates. A relative environment file is refused rather than resolved when it traverses, since a relative name is meant to reach the secrets directory. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/deploy-site-task.yml | 2 +- .github/workflows/deploy-site.yml | 19 ++++++++++--------- deploy/make-release.sh | 7 ++++++- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/.github/workflows/deploy-site-task.yml b/.github/workflows/deploy-site-task.yml index 8fd7f9e..3693b28 100644 --- a/.github/workflows/deploy-site-task.yml +++ b/.github/workflows/deploy-site-task.yml @@ -114,7 +114,7 @@ jobs: ENVIRONMENT: ${{ inputs.environment }} run: | set -Eeuo pipefail - rsync -az --mkpath --chmod=D755,F644 \ + rsync -az --mkpath --no-g --chmod=D2755,F644 \ --link-dest="/${ENVIRONMENT}/current/" \ -e "ssh -i ~/.ssh/deploy -o IdentitiesOnly=yes" \ "${RUNNER_TEMP}/bundle/releases/${RELEASE_ID}/" \ diff --git a/.github/workflows/deploy-site.yml b/.github/workflows/deploy-site.yml index f6e689c..773055d 100644 --- a/.github/workflows/deploy-site.yml +++ b/.github/workflows/deploy-site.yml @@ -18,15 +18,8 @@ concurrency: jobs: - # The same gate the pull request and a release run. - validate: - name: Validate sources job - uses: ./.github/workflows/validate-task.yml - permissions: - contents: read - # Staging deploys from any ref, since proving a branch before it merges is what staging is for. - # Asserted before Hugo is installed and before the key reaches the runner. + # First, so a mis-dispatched production deploy fails before anything is installed or written. assert-ref: name: Assert deploy ref job runs-on: ubuntu-latest @@ -39,9 +32,17 @@ jobs: exit 1 fi + # The same gate the pull request and a release run. + validate: + name: Validate sources job + needs: [ assert-ref ] + uses: ./.github/workflows/validate-task.yml + permissions: + contents: read + deploy: name: Deploy site job - needs: [ validate, assert-ref ] + needs: [ validate ] uses: ./.github/workflows/deploy-site-task.yml with: environment: ${{ inputs.environment }} diff --git a/deploy/make-release.sh b/deploy/make-release.sh index 63f9a43..d1e86d9 100755 --- a/deploy/make-release.sh +++ b/deploy/make-release.sh @@ -23,9 +23,14 @@ REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" # The first argument overrides the root, being read after this. DEFAULT_ENV_FILE="$REPO/secrets/.env" ENV_FILE="${ENV_FILE:-$DEFAULT_ENV_FILE}" -# Anchored under the repo, so a relative name resolves the same from any working directory. +# A relative name resolves against the repo, so it means the same from any working directory. +# Traversal is refused rather than resolved, since a relative name is meant to reach secrets/. case "$ENV_FILE" in /*) ;; +*..*) + echo "ENV_FILE must not traverse: $ENV_FILE" >&2 + exit 1 + ;; *) ENV_FILE="$REPO/$ENV_FILE" ;; esac if [ -f "$ENV_FILE" ]; then