From 516bb49f0d2a340ef1b6e9f44d5022e0fb437e86 Mon Sep 17 00:00:00 2001 From: Rodrigo Delduca Date: Mon, 20 Jul 2026 16:43:32 -0300 Subject: [PATCH 1/5] ci: add release dry-run deployment --- .github/workflows/ci.yml | 8 +++-- .github/workflows/release.yml | 63 ++++++++++++++++++++++++++++++----- 2 files changed, 61 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2d47713..05efc72 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,9 @@ jobs: name: Wippy lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Install Wippy run: | curl -fsSL https://hub.wippy.ai/install.sh -o /tmp/wippy-install.sh @@ -46,7 +48,9 @@ jobs: --health-timeout 5s --health-retries 12 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Install static-check dependency run: sudo apt-get update && sudo apt-get install -y ripgrep - name: Install Wippy diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f6b2b71..a5115c2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,12 +1,10 @@ -name: Release +name: Release Please on: push: branches: [master] -permissions: - contents: write - pull-requests: write +permissions: {} concurrency: group: release-${{ github.ref }} @@ -14,11 +12,60 @@ concurrency: jobs: checks: + permissions: + contents: read uses: ./.github/workflows/ci.yml release: needs: checks - uses: wippyai/template-module/.github/workflows/reusable-release.yml@v1 - with: - publish: true - secrets: inherit + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + outputs: + created: ${{ steps.release.outputs.release_created }} + version: ${{ steps.release.outputs.version }} + steps: + - name: Run Release Please + id: release + uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0 + with: + token: ${{ github.token }} + config-file: release-please-config.json + manifest-file: .release-please-manifest.json + + deploy-dry-run: + name: Deploy dry run + needs: release + if: ${{ needs.release.outputs.created == 'true' }} + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: v${{ needs.release.outputs.version }} + persist-credentials: false + + - name: Install Wippy runtime + env: + GH_TOKEN: ${{ github.token }} + WIPPY_VERSION: v0.3.24a + run: | + set -euo pipefail + tmp="$(mktemp -d)" + gh release download "$WIPPY_VERSION" --repo wippyai/runtime \ + --pattern wippy-linux-amd64 --pattern SHA256SUMS --dir "$tmp" + (cd "$tmp" && sha256sum --ignore-missing -c SHA256SUMS) + install -Dm0755 "$tmp/wippy-linux-amd64" "$HOME/.wippy/bin/wippy" + "$HOME/.wippy/bin/wippy" version + + - name: Deploy dry run + env: + RELEASE_VERSION: ${{ needs.release.outputs.version }} + WIPPY_TOKEN: ${{ secrets.WIPPY_TOKEN }} + run: | + set -euo pipefail + manifest_version="$(awk '/^version:/ { print $2; exit }' wippy.yaml)" + test "$manifest_version" = "$RELEASE_VERSION" + "$HOME/.wippy/bin/wippy" publish --version "$RELEASE_VERSION" --dry-run From 139479b4466334175cc50bd7b1fa9e13570467a1 Mon Sep 17 00:00:00 2001 From: Rodrigo Delduca Date: Mon, 20 Jul 2026 16:52:04 -0300 Subject: [PATCH 2/5] ci: resolve latest wippy runtime --- .github/workflows/release.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a5115c2..3abf204 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -47,14 +47,15 @@ jobs: ref: v${{ needs.release.outputs.version }} persist-credentials: false - - name: Install Wippy runtime + - name: Install latest Wippy runtime env: GH_TOKEN: ${{ github.token }} - WIPPY_VERSION: v0.3.24a run: | set -euo pipefail + wippy_version="$(gh api repos/wippyai/runtime/releases/latest --jq .tag_name)" + test -n "$wippy_version" tmp="$(mktemp -d)" - gh release download "$WIPPY_VERSION" --repo wippyai/runtime \ + gh release download "$wippy_version" --repo wippyai/runtime \ --pattern wippy-linux-amd64 --pattern SHA256SUMS --dir "$tmp" (cd "$tmp" && sha256sum --ignore-missing -c SHA256SUMS) install -Dm0755 "$tmp/wippy-linux-amd64" "$HOME/.wippy/bin/wippy" From 91b8b1dbf71f21e5ed3a8a60537f811accc475ce Mon Sep 17 00:00:00 2001 From: Rodrigo Delduca Date: Mon, 20 Jul 2026 17:37:23 -0300 Subject: [PATCH 3/5] ci: harden dataflow quality gates --- .github/CODEOWNERS | 4 +- .github/actions/setup-wippy/action.yml | 43 ++++++++++ .github/actions/setup-wippy/go.mod | 3 + .github/actions/setup-wippy/verify-release.go | 69 ++++++++++++++++ .../setup-wippy/verify-release_test.go | 53 +++++++++++++ .../setup-wippy/wippy-runtime-release.pub | 1 + .github/dependabot.yml | 11 +++ .github/workflows/ci.yml | 63 +++++++++++---- .github/workflows/release.yml | 22 ++---- .github/workflows/security.yml | 79 +++++++++++++++++++ .gitleaks.toml | 13 +++ Makefile | 32 ++++++-- 12 files changed, 354 insertions(+), 39 deletions(-) create mode 100644 .github/actions/setup-wippy/action.yml create mode 100644 .github/actions/setup-wippy/go.mod create mode 100644 .github/actions/setup-wippy/verify-release.go create mode 100644 .github/actions/setup-wippy/verify-release_test.go create mode 100644 .github/actions/setup-wippy/wippy-runtime-release.pub create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/security.yml create mode 100644 .gitleaks.toml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 41e587e..6431d6b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -6,4 +6,6 @@ * @wippyai/framework # Specific code owners for certain files or directories. -.github/* @wippyai/github +.github/* @wippyai/github +.github/actions/ @wippyai/github +.github/workflows/ @wippyai/github diff --git a/.github/actions/setup-wippy/action.yml b/.github/actions/setup-wippy/action.yml new file mode 100644 index 0000000..949b090 --- /dev/null +++ b/.github/actions/setup-wippy/action.yml @@ -0,0 +1,43 @@ +name: Setup verified latest Wippy runtime +description: Installs the latest stable Wippy runtime after signature verification. + +outputs: + path: + description: Absolute path to the verified Wippy executable. + value: ${{ steps.install.outputs.path }} + version: + description: Installed Wippy release tag. + value: ${{ steps.install.outputs.version }} + +runs: + using: composite + steps: + - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 + with: + go-version: 1.26.5 + check-latest: false + - id: install + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + wippy_version="$(gh api repos/wippyai/runtime/releases/latest --jq .tag_name)" + test -n "$wippy_version" + + tmp="$(mktemp -d)" + trap 'rm -rf "$tmp"' EXIT + gh release download "$wippy_version" --repo wippyai/runtime --dir "$tmp" \ + --pattern wippy-linux-amd64 --pattern wippy-linux-amd64.sig + go run "$GITHUB_ACTION_PATH/verify-release.go" \ + "$GITHUB_ACTION_PATH/wippy-runtime-release.pub" \ + "$tmp/wippy-linux-amd64" \ + "$tmp/wippy-linux-amd64.sig" + + wippy_path="$HOME/.wippy/bin/wippy" + install -Dm0755 "$tmp/wippy-linux-amd64" "$wippy_path" + "$wippy_path" version + { + echo "path=$wippy_path" + echo "version=$wippy_version" + } >> "$GITHUB_OUTPUT" diff --git a/.github/actions/setup-wippy/go.mod b/.github/actions/setup-wippy/go.mod new file mode 100644 index 0000000..b1a29d2 --- /dev/null +++ b/.github/actions/setup-wippy/go.mod @@ -0,0 +1,3 @@ +module github.com/wippyai/dataflow/setup-wippy + +go 1.26.0 diff --git a/.github/actions/setup-wippy/verify-release.go b/.github/actions/setup-wippy/verify-release.go new file mode 100644 index 0000000..6067b9b --- /dev/null +++ b/.github/actions/setup-wippy/verify-release.go @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "crypto/ed25519" + "encoding/base64" + "fmt" + "os" + "strings" +) + +func readBase64(path string) ([]byte, error) { + encoded, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("read %q: %w", path, err) + } + + decoded, err := base64.StdEncoding.DecodeString(strings.TrimSpace(string(encoded))) + if err != nil { + return nil, fmt.Errorf("decode base64 in %q: %w", path, err) + } + + return decoded, nil +} + +func verify(publicKey, artifact, signature []byte) error { + if len(publicKey) != ed25519.PublicKeySize { + return fmt.Errorf("public key is %d bytes, want %d", len(publicKey), ed25519.PublicKeySize) + } + if len(signature) != ed25519.SignatureSize { + return fmt.Errorf("signature is %d bytes, want %d", len(signature), ed25519.SignatureSize) + } + if !ed25519.Verify(ed25519.PublicKey(publicKey), artifact, signature) { + return fmt.Errorf("runtime signature verification failed") + } + + return nil +} + +func main() { + if len(os.Args) != 4 { + fmt.Fprintln(os.Stderr, "usage: verify-release ") + os.Exit(2) + } + + publicKey, err := readBase64(os.Args[1]) + if err != nil { + fmt.Fprintf(os.Stderr, "read public key: %v\n", err) + os.Exit(1) + } + + artifact, err := os.ReadFile(os.Args[2]) + if err != nil { + fmt.Fprintf(os.Stderr, "read artifact: %v\n", err) + os.Exit(1) + } + + signature, err := readBase64(os.Args[3]) + if err != nil { + fmt.Fprintf(os.Stderr, "read signature: %v\n", err) + os.Exit(1) + } + + if err := verify(publicKey, artifact, signature); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} diff --git a/.github/actions/setup-wippy/verify-release_test.go b/.github/actions/setup-wippy/verify-release_test.go new file mode 100644 index 0000000..afb4ef1 --- /dev/null +++ b/.github/actions/setup-wippy/verify-release_test.go @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "crypto/ed25519" + "crypto/rand" + "encoding/base64" + "os" + "path/filepath" + "testing" +) + +func TestVerify(t *testing.T) { + publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + t.Fatal(err) + } + + artifact := []byte("verified runtime artifact") + signature := ed25519.Sign(privateKey, artifact) + if err := verify(publicKey, artifact, signature); err != nil { + t.Fatalf("verify valid signature: %v", err) + } + + signature[0] ^= 1 + if err := verify(publicKey, artifact, signature); err == nil { + t.Fatal("verify accepted a tampered signature") + } +} + +func TestReadBase64(t *testing.T) { + path := filepath.Join(t.TempDir(), "key") + want := []byte("test value") + if err := os.WriteFile(path, []byte(base64.StdEncoding.EncodeToString(want)), 0o600); err != nil { + t.Fatal(err) + } + + got, err := readBase64(path) + if err != nil { + t.Fatalf("read base64: %v", err) + } + if string(got) != string(want) { + t.Fatalf("read base64 = %q, want %q", got, want) + } + + if err := os.WriteFile(path, []byte("not base64!"), 0o600); err != nil { + t.Fatal(err) + } + if _, err := readBase64(path); err == nil { + t.Fatal("readBase64 accepted invalid input") + } +} diff --git a/.github/actions/setup-wippy/wippy-runtime-release.pub b/.github/actions/setup-wippy/wippy-runtime-release.pub new file mode 100644 index 0000000..47ef0df --- /dev/null +++ b/.github/actions/setup-wippy/wippy-runtime-release.pub @@ -0,0 +1 @@ +xcLLvYAfv/BCYborznp5nrm6qdIU/o9c2xJi0tr0fhw= diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..b614fc4 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + cooldown: + default-days: 7 + groups: + github-actions: + patterns: ['*'] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 05efc72..71d6b06 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,34 +8,43 @@ on: permissions: contents: read +concurrency: + group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + jobs: lint: name: Wippy lint - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 + timeout-minutes: 10 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - name: Install Wippy - run: | - curl -fsSL https://hub.wippy.ai/install.sh -o /tmp/wippy-install.sh - . /tmp/wippy-install.sh - echo "$HOME/.wippy/bin" >> "$GITHUB_PATH" + - id: wippy + uses: ./.github/actions/setup-wippy - name: Install module dependencies - run: make install + env: + WIPPY: ${{ steps.wippy.outputs.path }} + run: | + make install + make verify-lock - name: Lint + env: + WIPPY: ${{ steps.wippy.outputs.path }} run: make lint test: name: Tests (${{ matrix.database }}) - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 + timeout-minutes: 20 strategy: fail-fast: false matrix: database: [sqlite, postgres] services: postgres: - image: postgres:17-alpine + image: postgres@sha256:af194ccf3e2d7fe367012c7b88ce8b816c5c889b18a5b316799a1f0d7eac746a # 17-alpine, linux/amd64 env: POSTGRES_DB: dataflow_test POSTGRES_USER: dataflow @@ -53,12 +62,36 @@ jobs: persist-credentials: false - name: Install static-check dependency run: sudo apt-get update && sudo apt-get install -y ripgrep - - name: Install Wippy - run: | - curl -fsSL https://hub.wippy.ai/install.sh -o /tmp/wippy-install.sh - . /tmp/wippy-install.sh - echo "$HOME/.wippy/bin" >> "$GITHUB_PATH" + - id: wippy + uses: ./.github/actions/setup-wippy - name: Install module dependencies - run: make install + env: + WIPPY: ${{ steps.wippy.outputs.path }} + run: | + make install + make verify-lock - name: Run ${{ matrix.database }} tests + env: + WIPPY: ${{ steps.wippy.outputs.path }} run: make test-${{ matrix.database }} + + package: + name: Package dry run + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - id: wippy + uses: ./.github/actions/setup-wippy + - name: Install module dependencies + env: + WIPPY: ${{ steps.wippy.outputs.path }} + run: | + make install + make verify-lock + - name: Validate package + env: + WIPPY: ${{ steps.wippy.outputs.path }} + run: make verify-package diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3abf204..1472f91 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,7 @@ jobs: release: needs: checks - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: contents: write pull-requests: write @@ -38,7 +38,7 @@ jobs: name: Deploy dry run needs: release if: ${{ needs.release.outputs.created == 'true' }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: contents: read steps: @@ -47,26 +47,16 @@ jobs: ref: v${{ needs.release.outputs.version }} persist-credentials: false - - name: Install latest Wippy runtime - env: - GH_TOKEN: ${{ github.token }} - run: | - set -euo pipefail - wippy_version="$(gh api repos/wippyai/runtime/releases/latest --jq .tag_name)" - test -n "$wippy_version" - tmp="$(mktemp -d)" - gh release download "$wippy_version" --repo wippyai/runtime \ - --pattern wippy-linux-amd64 --pattern SHA256SUMS --dir "$tmp" - (cd "$tmp" && sha256sum --ignore-missing -c SHA256SUMS) - install -Dm0755 "$tmp/wippy-linux-amd64" "$HOME/.wippy/bin/wippy" - "$HOME/.wippy/bin/wippy" version + - id: wippy + uses: ./.github/actions/setup-wippy - name: Deploy dry run env: RELEASE_VERSION: ${{ needs.release.outputs.version }} + WIPPY: ${{ steps.wippy.outputs.path }} WIPPY_TOKEN: ${{ secrets.WIPPY_TOKEN }} run: | set -euo pipefail manifest_version="$(awk '/^version:/ { print $2; exit }' wippy.yaml)" test "$manifest_version" = "$RELEASE_VERSION" - "$HOME/.wippy/bin/wippy" publish --version "$RELEASE_VERSION" --dry-run + "$WIPPY" publish --version "$RELEASE_VERSION" --dry-run diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml new file mode 100644 index 0000000..532575e --- /dev/null +++ b/.github/workflows/security.yml @@ -0,0 +1,79 @@ +name: Security checks + +on: + pull_request: + push: + branches: [master] + schedule: + - cron: '17 4 * * 1' + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: security-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + workflow-syntax: + name: Workflow syntax + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 + with: + go-version: 1.26.5 + check-latest: false + - name: Test runtime signature verifier + run: (cd .github/actions/setup-wippy && go test .) + - name: Validate GitHub Actions workflows + run: go run github.com/rhysd/actionlint/cmd/actionlint@v1.7.12 .github/workflows/*.yml + + secrets: + name: Secrets + runs-on: ubuntu-24.04 + timeout-minutes: 15 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 + with: + go-version: 1.26.5 + check-latest: false + - name: Scan repository history for secrets + run: go run github.com/zricethezav/gitleaks/v8@v8.30.1 detect --config .gitleaks.toml --source . --no-banner --redact + + actions-security: + name: Actions security + runs-on: ubuntu-24.04 + timeout-minutes: 15 + permissions: + actions: read + contents: read + security-events: write + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - id: zizmor + uses: zizmorcore/zizmor-action@6599ee8b7a49aef6a770f63d261d214911a7ce02 # v0.6.0 + with: + advanced-security: true + inputs: .github + online-audits: true + persona: pedantic + version: v1.27.0 + color: false + - name: Fail if workflow risks are found + env: + SARIF_FILE: ${{ steps.zizmor.outputs.output-file }} + run: | + set -euo pipefail + test -f "$SARIF_FILE" + test "$(jq '[.runs[].results[]?] | length' "$SARIF_FILE")" -eq 0 diff --git a/.gitleaks.toml b/.gitleaks.toml new file mode 100644 index 0000000..f08285c --- /dev/null +++ b/.gitleaks.toml @@ -0,0 +1,13 @@ +title = "Dataflow secret scanning" + +[extend] +useDefault = true + +# These commits introduced reviewed test fixtures, not production credentials. +# Keep this list commit-specific so new secrets are never silently allowlisted. +[[allowlists]] +description = "Reviewed test fixture false positives" +commits = [ + "701ce4fe8033ea8c69006142dd0cad5d1b908c8d", + "87682078c81dbc2795840cb0439a96b65d2ff292", +] diff --git a/Makefile b/Makefile index b6aaa53..bdf8fe1 100644 --- a/Makefile +++ b/Makefile @@ -13,16 +13,18 @@ DATAFLOW_PG_PORT ?= 5432 DATAFLOW_PG_DATABASE ?= dataflow_test DATAFLOW_PG_USERNAME ?= dataflow DATAFLOW_PG_PASSWORD ?= dataflow +WIPPY ?= wippy +PUBLISH_DRY_RUN_TOKEN ?= wpy_ci_dry_run_0123456789abcdef0123456789abcdef -.PHONY: test test-sqlite test-postgres test-static lint install clean +.PHONY: test test-sqlite test-postgres test-static lint install verify-lock verify-package clean test: test-sqlite test-sqlite: clean test-static - cd $(TEST_DIR) && wippy run test --profile sqlite + cd $(TEST_DIR) && $(WIPPY) run test --profile sqlite test-postgres: test-static - cd $(TEST_DIR) && wippy run test --profile postgres \ + cd $(TEST_DIR) && $(WIPPY) run test --profile postgres \ --set "vars.postgres_host=$(DATAFLOW_PG_HOST)" \ --set "vars.postgres_port=$(DATAFLOW_PG_PORT)" \ --set "vars.postgres_database=$(DATAFLOW_PG_DATABASE)" \ @@ -44,21 +46,37 @@ test-static: rg --files-without-match "test\.run_cases\(" src --glob '*_test.lua'; \ exit 1; \ fi + @for test_file in $$(rg --files src -g '*_test.lua'); do \ + test_name="$${test_file##*/}"; \ + test_index="$${test_file%/*}/_index.yaml"; \ + if ! test -f "$$test_index" || ! rg -q --fixed-strings "source: file://$$test_name" "$$test_index"; then \ + echo "source test is not registered in its own index: $$test_file"; \ + exit 1; \ + fi; \ + done @if rg -n "name: (wake|sweeper)_security_scope" src/_index.yaml; then \ echo "wake service authority must be module-owned, not consumer-injected"; \ exit 1; \ fi - @awk '''BEGIN { in_service=0; has_root_scope=0 } \ + @awk 'BEGIN { in_service=0; has_root_scope=0 } \ /^ - name: wake_process\.service$$/ { in_service=1; next } \ /^ - name: / { in_service=0 } \ in_service && /userspace\.dataflow\.security:root/ { has_root_scope=1 } \ - END { if (!has_root_scope) { print "wake service must use the canonical root process scope"; exit 1 } }''' src/runner/_index.yaml + END { if (!has_root_scope) { print "wake service must use the canonical root process scope"; exit 1 } }' src/runner/_index.yaml lint: - cd $(TEST_DIR) && wippy lint --profile sqlite --ns app --ns 'userspace.dataflow.**' + cd $(TEST_DIR) && $(WIPPY) lint --profile sqlite --ns app --ns 'userspace.dataflow.**' install: - cd $(TEST_DIR) && wippy install + cd $(TEST_DIR) && $(WIPPY) install + +verify-lock: + git diff --exit-code -- test/wippy.lock + +verify-package: + @version="$$(awk '/^version:/ { print $$2; exit }' wippy.yaml)"; \ + test -n "$$version"; \ + WIPPY_TOKEN="$(PUBLISH_DRY_RUN_TOKEN)" $(WIPPY) publish --version "$$version" --dry-run clean: cd $(TEST_DIR) && rm -f $(TEST_DB) $(TEST_DB)-wal $(TEST_DB)-shm From e28ee6c951aeb9a5da596bcde14aecf45a351c87 Mon Sep 17 00:00:00 2001 From: Rodrigo Delduca Date: Mon, 20 Jul 2026 17:44:20 -0300 Subject: [PATCH 4/5] ci: enforce workflow security findings --- .github/actions/setup-wippy/action.yml | 4 +++- .github/workflows/ci.yml | 4 +++- .github/workflows/release.yml | 4 ++++ .github/workflows/security.yml | 4 ++++ 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/actions/setup-wippy/action.yml b/.github/actions/setup-wippy/action.yml index 949b090..946183a 100644 --- a/.github/actions/setup-wippy/action.yml +++ b/.github/actions/setup-wippy/action.yml @@ -16,7 +16,9 @@ runs: with: go-version: 1.26.5 check-latest: false - - id: install + cache: false + - name: Install and verify Wippy runtime + id: install shell: bash env: GH_TOKEN: ${{ github.token }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 71d6b06..56ee703 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,6 +38,8 @@ jobs: name: Tests (${{ matrix.database }}) runs-on: ubuntu-24.04 timeout-minutes: 20 + env: + MAKE_TARGET: test-${{ matrix.database }} strategy: fail-fast: false matrix: @@ -73,7 +75,7 @@ jobs: - name: Run ${{ matrix.database }} tests env: WIPPY: ${{ steps.wippy.outputs.path }} - run: make test-${{ matrix.database }} + run: make "$MAKE_TARGET" package: name: Package dry run diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1472f91..49338fe 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,15 +12,19 @@ concurrency: jobs: checks: + name: Checks permissions: contents: read uses: ./.github/workflows/ci.yml release: + name: Release Please needs: checks runs-on: ubuntu-24.04 permissions: + # Required for Release Please to create tags and GitHub releases. contents: write + # Required for Release Please to create and update release pull requests. pull-requests: write outputs: created: ${{ steps.release.outputs.release_created }} diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 532575e..fe20f05 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -28,6 +28,7 @@ jobs: with: go-version: 1.26.5 check-latest: false + cache: false - name: Test runtime signature verifier run: (cd .github/actions/setup-wippy && go test .) - name: Validate GitHub Actions workflows @@ -46,6 +47,7 @@ jobs: with: go-version: 1.26.5 check-latest: false + cache: false - name: Scan repository history for secrets run: go run github.com/zricethezav/gitleaks/v8@v8.30.1 detect --config .gitleaks.toml --source . --no-banner --redact @@ -54,8 +56,10 @@ jobs: runs-on: ubuntu-24.04 timeout-minutes: 15 permissions: + # Required for Zizmor online audits of referenced GitHub Actions. actions: read contents: read + # Required to upload the Zizmor SARIF report for code scanning. security-events: write steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 From 14c507e8a84964032e262bb447e845eabbea2caf Mon Sep 17 00:00:00 2001 From: Rodrigo Delduca Date: Mon, 20 Jul 2026 17:49:16 -0300 Subject: [PATCH 5/5] ci: document elevated workflow permissions --- .github/workflows/release.yml | 6 ++---- .github/workflows/security.yml | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 49338fe..bd43221 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,10 +22,8 @@ jobs: needs: checks runs-on: ubuntu-24.04 permissions: - # Required for Release Please to create tags and GitHub releases. - contents: write - # Required for Release Please to create and update release pull requests. - pull-requests: write + contents: write # Required for Release Please to create tags and GitHub releases. + pull-requests: write # Required for Release Please to create and update release pull requests. outputs: created: ${{ steps.release.outputs.release_created }} version: ${{ steps.release.outputs.version }} diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index fe20f05..5b432a5 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -56,11 +56,9 @@ jobs: runs-on: ubuntu-24.04 timeout-minutes: 15 permissions: - # Required for Zizmor online audits of referenced GitHub Actions. - actions: read + actions: read # Required for Zizmor online audits of referenced GitHub Actions. contents: read - # Required to upload the Zizmor SARIF report for code scanning. - security-events: write + security-events: write # Required to upload the Zizmor SARIF report for code scanning. steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: