diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 48da3aa66..7d357bdf0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,4 +20,6 @@ jobs: permissions: contents: read uses: ./.github/workflows/quality.yml + secrets: + HF_TOKEN_READ_PUBLIC_ONLY: ${{ secrets.HF_TOKEN_READ_PUBLIC_ONLY }} diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index b0a153dc0..0e4e04156 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -60,6 +60,8 @@ jobs: permissions: contents: read uses: ./.github/workflows/quality.yml + secrets: + HF_TOKEN_READ_PUBLIC_ONLY: ${{ secrets.HF_TOKEN_READ_PUBLIC_ONLY }} pre-release-check: permissions: contents: read diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 1760a7fb2..06ea98d18 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -2,6 +2,10 @@ name: Verify Code Quality on: workflow_call: + secrets: + HF_TOKEN_READ_PUBLIC_ONLY: + description: "Hugging Face Hub READ-ONLY token (public repos only) for authenticated model/dataset access during tests." + required: false concurrency: group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.pull_request.number || github.ref_name }} @@ -29,6 +33,12 @@ jobs: strategy: matrix: python-version: ["3.11", "3.12", "3.13"] + # The GitHub secret HF_TOKEN_READ_PUBLIC_ONLY must be a READ-ONLY token on public + # repositories: same-repo PR runs have access to this secret, so a write-scope + # token could be exfiltrated by a malicious workflow change. It is exposed as the + # env var HF_TOKEN (the name huggingface_hub picks up automatically) only on the + # specific steps that need it, to limit exposure to unrelated steps like the + # Ollama installer. Rotate via repo Settings -> Secrets and variables. steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: @@ -48,6 +58,33 @@ jobs: key: pre-commit|${{ env.PY }}|${{ hashFiles('.pre-commit-config.yaml', 'uv.lock') }} - name: Install dependencies run: uv sync --frozen --all-extras --group dev + - name: Check HF_TOKEN + continue-on-error: true + env: + # read-only public-repo HF token; environment gating not warranted + HF_TOKEN: ${{ secrets.HF_TOKEN_READ_PUBLIC_ONLY }} # zizmor: ignore[secrets-outside-env] + run: | + if [ -z "${HF_TOKEN:-}" ]; then + echo "::warning::HF_TOKEN is NOT set — Hugging Face Hub calls will be anonymous." + exit 0 + fi + echo "HF_TOKEN is set; verifying with the Hugging Face Hub API..." + { + uv run python - <<'PY' + import os, sys + try: + from huggingface_hub import HfApi + except ImportError: + print("::warning::huggingface_hub not installed in this env; skipping HF_TOKEN verification.") + sys.exit(0) + try: + info = HfApi().whoami(token=os.environ["HF_TOKEN"]) + name = info.get("name") or info.get("fullname") or "" + print(f"::notice::HF_TOKEN is valid (user: {name}).") + except Exception as e: + print(f"::warning::HF_TOKEN is set but verification failed — token may be invalid or expired: {e}") + PY + } || echo "::warning::HF_TOKEN verification step could not run." - name: Check style and run tests id: precommit run: uv run pre-commit run --all-files @@ -65,6 +102,9 @@ jobs: ollama pull granite4.1:3b - name: Run Tests id: tests + env: + # read-only public-repo HF token; environment gating not warranted + HF_TOKEN: ${{ secrets.HF_TOKEN_READ_PUBLIC_ONLY }} # zizmor: ignore[secrets-outside-env] run: uv run -m pytest -v --junit-xml=/tmp/pytest-results.xml test - name: Send failure message tests if: failure() # This step will only run if a previous step failed