name: Workflow Sanity on: pull_request: paths-ignore: - "CHANGELOG.md" push: branches: [main] paths-ignore: - "CHANGELOG.md" workflow_dispatch: permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" jobs: no-tabs: if: github.event_name != 'workflow_dispatch' runs-on: ubuntu-24.04 steps: - name: Checkout env: CHECKOUT_REPO: ${{ github.repository }} CHECKOUT_SHA: ${{ github.sha }} run: | set -euo pipefail git init "$GITHUB_WORKSPACE" git -C "$GITHUB_WORKSPACE" config gc.auto 0 git -C "$GITHUB_WORKSPACE" remote add origin "https://github.com/${CHECKOUT_REPO}.git" timeout --signal=TERM --kill-after=10s 30s git -C "$GITHUB_WORKSPACE" \ -c protocol.version=2 \ fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \ "+${CHECKOUT_SHA}:refs/remotes/origin/checkout" git -C "$GITHUB_WORKSPACE" checkout --detach refs/remotes/origin/checkout - name: Fail on tabs in workflow files run: | python - <<'PY' from __future__ import annotations import pathlib import sys root = pathlib.Path(".github/workflows") bad: list[str] = [] for path in sorted(root.rglob("*.yml")): if b"\t" in path.read_bytes(): bad.append(str(path)) for path in sorted(root.rglob("*.yaml")): if b"\t" in path.read_bytes(): bad.append(str(path)) if bad: print("Tabs found in workflow file(s):") for path in bad: print(f"- {path}") sys.exit(1) PY actionlint: if: github.event_name != 'workflow_dispatch' runs-on: ubuntu-24.04 steps: - name: Checkout env: CHECKOUT_REPO: ${{ github.repository }} CHECKOUT_SHA: ${{ github.sha }} run: | set -euo pipefail git init "$GITHUB_WORKSPACE" git -C "$GITHUB_WORKSPACE" config gc.auto 0 git -C "$GITHUB_WORKSPACE" remote add origin "https://github.com/${CHECKOUT_REPO}.git" timeout --signal=TERM --kill-after=10s 30s git -C "$GITHUB_WORKSPACE" \ -c protocol.version=2 \ fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \ "+${CHECKOUT_SHA}:refs/remotes/origin/checkout" git -C "$GITHUB_WORKSPACE" checkout --detach refs/remotes/origin/checkout - name: Setup Python uses: actions/setup-python@v6 with: python-version: "3.12" - name: Prepare trusted workflow audit configs if: github.event_name == 'pull_request' env: BASE_REF: ${{ github.event.pull_request.base.ref }} BASE_SHA: ${{ github.event.pull_request.base.sha }} run: | set -euo pipefail trusted_config="$RUNNER_TEMP/pre-commit-base.yaml" trusted_zizmor_config="$RUNNER_TEMP/zizmor-base.yml" if ! git cat-file -e "${BASE_SHA}^{commit}" 2>/dev/null; then timeout --signal=TERM --kill-after=10s 30s git fetch --no-tags --depth=1 origin \ "+${BASE_SHA}:refs/remotes/origin/security-base" || timeout --signal=TERM --kill-after=10s 30s git fetch --no-tags --depth=1 origin \ "+refs/heads/${BASE_REF}:refs/remotes/origin/${BASE_REF}" fi if git cat-file -e "${BASE_SHA}:.pre-commit-config.yaml" 2>/dev/null; then git show "${BASE_SHA}:.pre-commit-config.yaml" > "$trusted_config" elif git show "refs/remotes/origin/${BASE_REF}:.pre-commit-config.yaml" \ > "$trusted_config" 2>/dev/null; then echo "Base SHA ${BASE_SHA} does not expose .pre-commit-config.yaml; using origin/${BASE_REF} instead." else echo "::error title=trusted pre-commit config unavailable::Could not read .pre-commit-config.yaml from ${BASE_SHA} or origin/${BASE_REF}." exit 1 fi if git cat-file -e "${BASE_SHA}:.github/zizmor.yml" 2>/dev/null; then git show "${BASE_SHA}:.github/zizmor.yml" > "$trusted_zizmor_config" elif git show "refs/remotes/origin/${BASE_REF}:.github/zizmor.yml" \ > "$trusted_zizmor_config" 2>/dev/null; then echo "Base SHA ${BASE_SHA} does not expose .github/zizmor.yml; using origin/${BASE_REF} instead." else echo "::error title=trusted zizmor config unavailable::Could not read .github/zizmor.yml from ${BASE_SHA} or origin/${BASE_REF}." exit 1 fi python3 - "$trusted_config" "$trusted_zizmor_config" <<'PY' from pathlib import Path import sys config_path = Path(sys.argv[1]) zizmor_config_path = sys.argv[2] text = config_path.read_text() if ".github/zizmor.yml" not in text: raise SystemExit("trusted pre-commit config does not reference .github/zizmor.yml") config_path.write_text(text.replace(".github/zizmor.yml", zizmor_config_path)) PY echo "PRE_COMMIT_CONFIG_PATH=$trusted_config" >> "$GITHUB_ENV" - name: Install pre-commit run: python -m pip install --disable-pip-version-check pre-commit==4.2.0 - name: Install actionlint shell: bash run: | set -euo pipefail ACTIONLINT_VERSION="1.7.11" archive="actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz" base_url="https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}" # GitHub release downloads occasionally return transient 5xx responses. # Retry all curl errors here so workflow-sanity does not fail closed on # a one-off release edge outage. curl --retry 5 --retry-delay 2 --retry-all-errors -sSfL -o "${archive}" "${base_url}/${archive}" curl --retry 5 --retry-delay 2 --retry-all-errors -sSfL -o checksums.txt "${base_url}/actionlint_${ACTIONLINT_VERSION}_checksums.txt" grep " ${archive}\$" checksums.txt | sha256sum -c - tar -xzf "${archive}" actionlint sudo install -m 0755 actionlint /usr/local/bin/actionlint - name: Lint workflows run: actionlint - name: Audit all workflows with zizmor shell: bash run: | set -euo pipefail mapfile -t workflow_files < <( find .github/workflows -maxdepth 1 -type f \( -name '*.yml' -o -name '*.yaml' \) | sort ) pre-commit run --config "${PRE_COMMIT_CONFIG_PATH:-.pre-commit-config.yaml}" zizmor --files "${workflow_files[@]}" - name: Disallow direct inputs interpolation in composite run blocks run: python3 scripts/check-composite-action-input-interpolation.py - name: Disallow tracked merge conflict markers run: node scripts/check-no-conflict-markers.mjs generated-doc-baselines: if: github.event_name == 'workflow_dispatch' runs-on: ubuntu-24.04 steps: - name: Checkout env: CHECKOUT_REPO: ${{ github.repository }} CHECKOUT_SHA: ${{ github.sha }} run: | set -euo pipefail git init "$GITHUB_WORKSPACE" git -C "$GITHUB_WORKSPACE" config gc.auto 0 git -C "$GITHUB_WORKSPACE" remote add origin "https://github.com/${CHECKOUT_REPO}.git" timeout --signal=TERM --kill-after=10s 30s git -C "$GITHUB_WORKSPACE" \ -c protocol.version=2 \ fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \ "+${CHECKOUT_SHA}:refs/remotes/origin/checkout" git -C "$GITHUB_WORKSPACE" checkout --detach refs/remotes/origin/checkout - name: Setup Node environment uses: ./.github/actions/setup-node-env with: install-bun: "false" - name: Check config docs drift statefile run: pnpm config:docs:check - name: Check plugin SDK API baseline drift run: pnpm plugin-sdk:api:check