Skip to content

Security/CI/release hardening for v1.0 + continuous PyPI: enforce gates server-side, SHA-pin actions, OIDC trusted publishing #331

@MartinCastroAlvarez

Description

@MartinCastroAlvarez

Security/CI/release hardening for the v1.0 → continuous-PyPI → production transition. Filed by the Security & Compliance Lead from a workflow/release audit (2026-05-27). Respects the current v0.x postureSECURITY.md §6 ("CI is intentionally absent — repo-owner direction") and §8 ("Static analysis local-only, no CI in v0.x") are deliberate, documented decisions. This issue is the forward recommendation, to be enabled deliberately (owner sign-off) as the project moves toward production + continuous publication. Cross-refs #144 (hardening tracker) and #234 (v1.0 readiness).

The gap (becomes material at v1.0 / continuous PyPI)

Today the only CI is codeql.yml (static analysis). The security invariants are enforced only locally via scripts/lint.sh / pre-commit — which an external contributor or an agent that skips pre-commit install will not run. Nothing server-side enforces, on a PR:

  • the pygrep security rules (no @csrf_exempt, no Model.objects.all/filter in api/, no partial-token redactions, no user.has_perm in api code, the @dar/api import-boundary),
  • bandit / ruff -S,
  • the test suite (pytest, incl. tests/test_security.py — the cross-cutting security regressions),
  • the frontend typecheck + the new vitest suite.

GitHub-native secret scanning + push protection are on (verified) and CodeQL runs — so dataflow + secret leakage are covered server-side. But the package-specific security rules and the regression suite are not. For a repo that is continuously published to PyPI and deployed to production, "a green PR" should mean those gates passed.

Recommended hardening (gated behind the v1.0 transition)

  1. CI gate workflow (.github/workflows/ci.yml, Tier 5) running on PRs: poetry install && ./scripts/lint.sh (which already runs ruff/black/isort/bandit/pylint/mypy/pytest/pre-commit gitleaks+pygrep) + cd frontend && pnpm -r typecheck && pnpm -r lint && pnpm test. Least-privilege permissions: { contents: read }. This makes the existing local gates enforced, not optional.
  2. Wire the per-file coverage + mypy gates (the T-2 thresholds, META: v1.0.0 release readiness — gates + what's left #234 §C; mypy clean, chore: mypy drifted to 3 errors since #260 — fix + wire into the lint gate #316) into that workflow so they can't silently drift.
  3. SHA-pin the GitHub Actions in codeql.yml (and any new workflow): actions/checkout, github/codeql-action/* are tag-pinned (@v4/@v3) — pin to full commit SHAs per GitHub's supply-chain hardening guidance (a tag can be moved; a SHA can't).
  4. PyPI Trusted Publishing (OIDC) for release integrity: the current publish is a local, long-lived-token flow (POETRY_PYPI_TOKEN_PYPI, SECURITY.md §7). A gated GitHub release workflow using OIDC trusted publishing (id-token: write, no stored token) removes the long-lived secret and ties artifacts to a verifiable build — the standard for continuous PyPI publication. Keep it human-triggered (Tier 6).

Acceptance

  • Owner decision: keep local-only through which version, flip at v1.0?
  • CI gate workflow enforcing scripts/lint.sh + frontend checks on PRs.
  • Actions SHA-pinned.
  • Trusted Publishing evaluated for the release flow.
  • SECURITY.md §6/§8 updated to reflect whichever posture is chosen.

Each item is its own follow-up PR; all Tier 5/6 (workflows / release) → human-gated. Not a v0.x blocker; a v1.0/production-readiness gate.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions