name: secrets-guard # Fail the build if any infra/deploy/secrets/*.yaml is committed UNENCRYPTED. # SOPS stores a `sops:` metadata block and wraps every value in ENC[...]; a # plaintext leak has neither. This is the backstop against the classic # "committed the decrypted file by accident" incident. .sops.yaml (the # plaintext config) and README.md are not *.yaml under infra/deploy/secrets/, # so they're correctly ignored. # # Deliberately NOT path-filtered: it's a ~1s grep, and a backstop that only # runs when you expect it to isn't a backstop. on: # workflow_call so pr-build.yml can fold this into the single required `gate` # status. This is the one that mattered most: branch protection requires only # `gate`, so a commit adding a PLAINTEXT secrets file failed this check as a # separate status and was still mergeable. workflow_call: {} pull_request: push: branches: [dev, main, release] jobs: encrypted: runs-on: docker steps: - uses: actions/checkout@v4 - name: Assert infra/deploy/secrets/*.yaml are SOPS-encrypted run: | set -euo pipefail shopt -s nullglob files=(infra/deploy/secrets/*.yaml) if [ ${#files[@]} -eq 0 ]; then echo "no infra/deploy/secrets/*.yaml yet — nothing to check" exit 0 fi fail=0 for f in "${files[@]}"; do if grep -q '^sops:' "$f" && grep -q 'ENC\[AES256_GCM' "$f"; then echo "ok: $f is SOPS-encrypted" else echo "::error file=$f::NOT SOPS-encrypted — refusing to accept a plaintext secrets file" fail=1 fi done exit $fail