From 82c9d4f55f70801e9d4e7b7255ca747a301113ce Mon Sep 17 00:00:00 2001 From: "Xin.Zh" Date: Mon, 14 Sep 2026 04:47:43 +0800 Subject: [PATCH] ci: harden workflow reproducibility and evidence gates --- .github/actions/setup-python-env/action.yml | 4 +- .github/workflows/build-artifacts.yml | 9 +-- .github/workflows/build-docker.yml | 11 +-- .github/workflows/deploy-docs.yml | 13 ++-- .github/workflows/e2e-harness.yml | 80 +++++++++++++++++---- .github/workflows/license-check.yml | 9 ++- .github/workflows/master.yml | 52 ++++++++++---- .github/workflows/native-service.yml | 28 ++++++-- .github/workflows/release-verify.yml | 7 +- .github/workflows/release.yml | 33 ++++++--- 10 files changed, 183 insertions(+), 63 deletions(-) diff --git a/.github/actions/setup-python-env/action.yml b/.github/actions/setup-python-env/action.yml index a7e5557844..116eb9e9ce 100644 --- a/.github/actions/setup-python-env/action.yml +++ b/.github/actions/setup-python-env/action.yml @@ -14,12 +14,12 @@ inputs: runs: using: "composite" steps: - - uses: actions/setup-python@v7 + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 with: python-version: ${{ inputs.python-version }} - name: Install uv - uses: astral-sh/setup-uv@v10.0.0 + uses: astral-sh/setup-uv@ae62891fec2bb8e7d6c99fc78c9fec3a63790f8d # v10.0.0 with: version: ${{ inputs.uv-version }} enable-cache: 'true' diff --git a/.github/workflows/build-artifacts.yml b/.github/workflows/build-artifacts.yml index 0cb70ce0e1..ba1128dc71 100644 --- a/.github/workflows/build-artifacts.yml +++ b/.github/workflows/build-artifacts.yml @@ -43,18 +43,19 @@ jobs: steps: - name: Check out requested source - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: ref: ${{ inputs.ref || github.sha }} fetch-depth: 0 + persist-credentials: false - name: Set up Python - uses: actions/setup-python@v7 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 with: python-version: ${{ env.PYTHON_VERSION }} - name: Set up uv - uses: astral-sh/setup-uv@v10.0.0 + uses: astral-sh/setup-uv@ae62891fec2bb8e7d6c99fc78c9fec3a63790f8d # v10.0.0 with: version: ${{ env.UV_VERSION }} enable-cache: true @@ -400,7 +401,7 @@ jobs: > "build/output/${ARTIFACT_NAME}.tar.gz.sha256" - name: Upload customer artifact - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: ${{ steps.metadata.outputs.artifact_name }} path: | diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 61d2430c01..879ee4aea7 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -21,21 +21,22 @@ jobs: steps: - name: Check out - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: fetch-depth: 0 + persist-credentials: false - name: Set up uv - uses: astral-sh/setup-uv@v10.0.0 + uses: astral-sh/setup-uv@ae62891fec2bb8e7d6c99fc78c9fec3a63790f8d # v10.0.0 with: version: "0.10.12" enable-cache: true - name: Set up QEMU - uses: docker/setup-qemu-action@v4 + uses: docker/setup-qemu-action@1f40c72289eff860ee54a304f1438e3cff362e0a # v4 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4 + uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4 - name: Resolve image metadata id: metadata @@ -116,7 +117,7 @@ jobs: exit 1 - name: Upload Docker image archives - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: ${{ steps.metadata.outputs.artifact_name }} path: docker-images/*.tar diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 6540d15b8d..9642cfb94b 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -20,19 +20,22 @@ jobs: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest + timeout-minutes: 20 env: NEXT_PUBLIC_BASE_PATH: '' NEXT_PUBLIC_SITE_URL: https://powercontext.oceanbase.io NEXT_PUBLIC_REPOSITORY_URL: https://github.com/oceanbase/powercontext steps: - name: Check out - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false - name: Set up the environment uses: ./.github/actions/setup-python-env - name: Set up website tools - uses: jdx/mise-action@v2 + uses: jdx/mise-action@c37c93293d6b742fc901e1406b8f764f6fb19dac # v2 - name: Build website run: make docs-build @@ -40,13 +43,13 @@ jobs: GITHUB_TOKEN: ${{ github.token }} - name: Configure GitHub Pages - uses: actions/configure-pages@v5 + uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5 - name: Upload website artifact - uses: actions/upload-pages-artifact@v5 + uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5 with: path: website/out - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v5 + uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5 diff --git a/.github/workflows/e2e-harness.yml b/.github/workflows/e2e-harness.yml index 30a6b6bc8e..0fb30ba099 100644 --- a/.github/workflows/e2e-harness.yml +++ b/.github/workflows/e2e-harness.yml @@ -12,7 +12,7 @@ permissions: contents: read env: - TRUFFLEHOG_IMAGE: ghcr.io/trufflesecurity/trufflehog:3.96.0 + TRUFFLEHOG_IMAGE: ghcr.io/trufflesecurity/trufflehog@sha256:aa821cf4ace8861c7d096d83818cdf7bb9719028a52d37a52eaad44086a52577 # v3.96.0 UV_VERSION: "0.10.12" concurrency: @@ -22,14 +22,16 @@ concurrency: jobs: validate: runs-on: ubuntu-latest + timeout-minutes: 15 steps: - name: Check out - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: fetch-depth: 0 + persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@v10.0.0 + uses: astral-sh/setup-uv@ae62891fec2bb8e7d6c99fc78c9fec3a63790f8d # v10.0.0 with: version: ${{ env.UV_VERSION }} @@ -50,16 +52,18 @@ jobs: name: Acceptance (${{ matrix.database }}) steps: - name: Check out - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: fetch-depth: 0 + persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@v10.0.0 + uses: astral-sh/setup-uv@ae62891fec2bb8e7d6c99fc78c9fec3a63790f8d # v10.0.0 with: version: ${{ env.UV_VERSION }} - name: Run deterministic scenarios + id: acceptance_scenarios env: POWERCONTEXT_E2E_DATABASE: ${{ matrix.database }} POWERCONTEXT_E2E_OUTPUT: ${{ github.workspace }}/.powercontext-e2e/${{ matrix.database }}/acceptance @@ -68,16 +72,42 @@ jobs: - name: Scan replay evidence id: evidence_scan if: always() - continue-on-error: true env: + DATABASE: ${{ matrix.database }} + DIAGNOSTICS_PATH: ${{ runner.temp }}/powercontext-e2e-diagnostics-${{ matrix.database }} EVIDENCE_PATH: ${{ github.workspace }}/.powercontext-e2e/${{ matrix.database }}/acceptance + SCENARIO_OUTCOME: ${{ steps.acceptance_scenarios.outcome }} run: | - test -d "${EVIDENCE_PATH}" - if ! find "${EVIDENCE_PATH}" -type f -print -quit | grep -q .; then - echo "No replay evidence was produced." >&2 + set -eu + mkdir -p "${DIAGNOSTICS_PATH}" + summary="${DIAGNOSTICS_PATH}/summary.txt" + { + echo "database=${DATABASE}" + echo "diagnostic_format=powercontext-e2e-v1" + echo "scenario_outcome=${SCENARIO_OUTCOME}" + echo "scanner_image=${TRUFFLEHOG_IMAGE}" + } > "$summary" + + if ! test -d "${EVIDENCE_PATH}"; then + echo "evidence_status=missing" >> "$summary" + echo "Replay evidence directory was not produced." >&2 + exit 1 + fi + + file_count="$(find "${EVIDENCE_PATH}" -type f | wc -l | tr -d '[:space:]')" + if test "$file_count" = 0; then + echo "evidence_status=empty" >> "$summary" + echo "Replay evidence directory was empty." >&2 exit 1 fi + { + echo "evidence_status=present" + echo "evidence_file_count=${file_count}" + } >> "$summary" + + set +e docker run --rm \ + --network none \ --volume "${EVIDENCE_PATH}:/evidence:ro" \ "${TRUFFLEHOG_IMAGE}" \ filesystem /evidence \ @@ -86,15 +116,31 @@ jobs: --fail \ --fail-on-scan-errors \ --no-update \ - --json > "${RUNNER_TEMP}/trufflehog-acceptance-${{ matrix.database }}.jsonl" + --json > /dev/null 2>&1 + scanner_status=$? + set -e + if test "$scanner_status" -ne 0; then + { + echo "scan_status=failed" + echo "scanner_exit_code=${scanner_status}" + } >> "$summary" + echo "Replay evidence failed secret scanning; raw evidence will not be uploaded." >&2 + exit 1 + fi + echo "scan_status=clean" >> "$summary" - - name: Report suppressed replay evidence - if: always() && steps.evidence_scan.outcome != 'success' - run: echo "::warning::Replay evidence was missing or secret scanning did not complete cleanly." + - name: Upload sanitized replay diagnostics + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: e2e-acceptance-diagnostics-${{ matrix.database }}-${{ github.sha }} + path: ${{ runner.temp }}/powercontext-e2e-diagnostics-${{ matrix.database }}/summary.txt + if-no-files-found: error + retention-days: 14 - name: Upload replay evidence if: always() && steps.evidence_scan.outcome == 'success' - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: e2e-acceptance-${{ matrix.database }}-${{ github.sha }} path: .powercontext-e2e/${{ matrix.database }}/acceptance @@ -107,3 +153,9 @@ jobs: env: POWERCONTEXT_E2E_DATABASE: ${{ matrix.database }} run: make harness-compose-down + + - name: Enforce replay evidence policy + if: always() && steps.evidence_scan.outcome != 'success' + run: | + echo "Replay evidence is missing or did not pass secret scanning." >&2 + exit 1 diff --git a/.github/workflows/license-check.yml b/.github/workflows/license-check.yml index f124b1e7d6..102c7b9486 100644 --- a/.github/workflows/license-check.yml +++ b/.github/workflows/license-check.yml @@ -5,6 +5,9 @@ on: push: branches: [master] +permissions: + contents: read + concurrency: group: license-check-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true @@ -15,10 +18,12 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 10 steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false - name: Check License Header - uses: apache/skywalking-eyes/header@v0.8.0 + uses: apache/skywalking-eyes/header@61275cc80d0798a405cb070f7d3a8aaf7cf2c2c1 # v0.8.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index d093536692..fbdb52b6a5 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -7,14 +7,24 @@ on: pull_request: types: [opened, synchronize, reopened, ready_for_review] +permissions: + contents: read + +concurrency: + group: master-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: quality: + timeout-minutes: 20 runs-on: ubuntu-latest steps: - name: Check out - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false - - uses: actions/cache@v6 + - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 with: path: ~/.cache/prek key: prek-${{ hashFiles('.pre-commit-config.yaml') }} @@ -29,6 +39,7 @@ jobs: run: make contract-test tests: + timeout-minutes: 40 runs-on: ubuntu-latest strategy: matrix: @@ -39,7 +50,9 @@ jobs: shell: bash steps: - name: Check out - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false - name: Set up the environment uses: ./.github/actions/setup-python-env @@ -53,16 +66,19 @@ jobs: run: make e2e-test dsh-package: + timeout-minutes: 30 runs-on: ubuntu-latest steps: - name: Check out - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false - name: Set up the Python environment uses: ./.github/actions/setup-python-env - name: Set up project tools - uses: jdx/mise-action@v2 + uses: jdx/mise-action@c37c93293d6b742fc901e1406b8f764f6fb19dac # v2 - name: Test DSH package run: make js-test @@ -71,22 +87,28 @@ jobs: run: make dsh-runtime-test pi-package: + timeout-minutes: 20 runs-on: ubuntu-latest steps: - name: Check out - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false - name: Set up project tools - uses: jdx/mise-action@v2 + uses: jdx/mise-action@c37c93293d6b742fc901e1406b8f764f6fb19dac # v2 - name: Test Pi package run: make pi-test opendal-package: + timeout-minutes: 20 runs-on: ubuntu-latest steps: - name: Check out - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false - name: Set up the Python environment uses: ./.github/actions/setup-python-env @@ -98,13 +120,16 @@ jobs: website-link-validation-windows: + timeout-minutes: 20 runs-on: windows-latest steps: - name: Check out - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false - name: Set up website tools - uses: jdx/mise-action@v2 + uses: jdx/mise-action@c37c93293d6b742fc901e1406b8f764f6fb19dac # v2 - name: Install website dependencies run: pnpm --dir website install --frozen-lockfile @@ -113,16 +138,19 @@ jobs: run: pnpm --dir website test:links check-website: + timeout-minutes: 20 runs-on: ubuntu-latest steps: - name: Check out - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false - name: Set up the environment uses: ./.github/actions/setup-python-env - name: Set up website tools - uses: jdx/mise-action@v2 + uses: jdx/mise-action@c37c93293d6b742fc901e1406b8f764f6fb19dac # v2 - name: Check website run: make docs-test diff --git a/.github/workflows/native-service.yml b/.github/workflows/native-service.yml index 8abc588932..6c24c572ea 100644 --- a/.github/workflows/native-service.yml +++ b/.github/workflows/native-service.yml @@ -7,15 +7,25 @@ on: pull_request: types: [opened, synchronize, reopened, ready_for_review] +permissions: + contents: read + +concurrency: + group: native-service-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: linux-systemd-user: runs-on: ubuntu-latest + timeout-minutes: 30 env: POWERCONTEXT_RUN_NATIVE_SERVICE_TESTS: "1" POWERCONTEXT_NATIVE_SERVICE_IDENTIFIER: powercontext-native-${{ github.run_id }}-${{ github.run_attempt }}.service steps: - name: Check out - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false - name: Set up the environment uses: ./.github/actions/setup-python-env @@ -57,7 +67,7 @@ jobs: - name: Upload Linux native-service logs after failure if: failure() - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: linux-native-service-logs path: ${{ runner.temp }}/powercontext-native-tests/**/logs/* @@ -65,12 +75,15 @@ jobs: macos-launchagent: runs-on: macos-latest + timeout-minutes: 30 env: POWERCONTEXT_RUN_NATIVE_SERVICE_TESTS: "1" POWERCONTEXT_NATIVE_SERVICE_IDENTIFIER: com.oceanbase.powercontext.native.${{ github.run_id }}.${{ github.run_attempt }} steps: - name: Check out - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false - name: Set up the environment uses: ./.github/actions/setup-python-env @@ -103,7 +116,7 @@ jobs: - name: Upload macOS native-service logs after failure if: failure() - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: macos-native-service-logs path: ${{ runner.temp }}/powercontext-native-tests/**/logs/* @@ -111,12 +124,15 @@ jobs: windows-task-scheduler: runs-on: windows-latest + timeout-minutes: 30 env: POWERCONTEXT_RUN_NATIVE_SERVICE_TESTS: "1" POWERCONTEXT_NATIVE_SERVICE_IDENTIFIER: PowerContext-Native-${{ github.run_id }}-${{ github.run_attempt }} steps: - name: Check out - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false - name: Set up the environment uses: ./.github/actions/setup-python-env @@ -177,7 +193,7 @@ jobs: - name: Upload Windows native-service logs after failure if: failure() - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: windows-native-service-logs path: ${{ runner.temp }}/powercontext-native-tests/**/logs/* diff --git a/.github/workflows/release-verify.yml b/.github/workflows/release-verify.yml index b545a5212a..315a8cda10 100644 --- a/.github/workflows/release-verify.yml +++ b/.github/workflows/release-verify.yml @@ -34,12 +34,15 @@ permissions: jobs: verify: runs-on: ubuntu-24.04 + timeout-minutes: 30 steps: - name: Check out verification scripts - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false - name: Set up Python - uses: actions/setup-python@v7 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 with: python-version: "3.12" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 46c046047e..d51a87df80 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,14 +7,20 @@ on: permissions: contents: read +concurrency: + group: release-${{ github.event.release.tag_name || github.ref }} + cancel-in-progress: false + jobs: release-build: runs-on: ubuntu-24.04 + timeout-minutes: 20 steps: - name: Check out - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: fetch-depth: 0 + persist-credentials: false - name: Set up the environment uses: ./.github/actions/setup-python-env @@ -55,7 +61,7 @@ jobs: uvx --from twine twine check dist/* - name: Upload release distributions - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: release-dists path: dist/* @@ -64,33 +70,35 @@ jobs: pypi-publish: runs-on: ubuntu-latest + timeout-minutes: 15 needs: release-build permissions: id-token: write steps: - name: Download release distributions - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: release-dists path: dist/ - name: Publish release distributions to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 + uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1 release-assets: runs-on: ubuntu-latest + timeout-minutes: 15 needs: release-build permissions: contents: write steps: - name: Download release distributions - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: release-dists path: dist/ - name: Upload GitHub Release assets - uses: softprops/action-gh-release@v3 + uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3 with: tag_name: ${{ github.event.release.tag_name }} files: dist/* @@ -115,15 +123,18 @@ jobs: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest + timeout-minutes: 20 steps: - name: Check out - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false - name: Set up the environment uses: ./.github/actions/setup-python-env - name: Set up website tools - uses: jdx/mise-action@v2 + uses: jdx/mise-action@c37c93293d6b742fc901e1406b8f764f6fb19dac # v2 - name: Build website run: make docs-build @@ -131,13 +142,13 @@ jobs: GITHUB_TOKEN: ${{ github.token }} - name: Configure GitHub Pages - uses: actions/configure-pages@v5 + uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5 - name: Upload website artifact - uses: actions/upload-pages-artifact@v5 + uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5 with: path: website/out - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v5 + uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5