---
id: w7/m7/t003
title: CI guard that fails on any reintroduced cluster-wide unrestricted secrets read
worker: worker7
status: todo
estimate: 30m
depends_on: [w7/m7/t001, w7/m7/t002]
---

## Objective

Make the least-privilege posture from t001/t002 a regression guard, not a one-time cleanup. Add a cluster-less CI check that fails if any platform RBAC manifest reintroduces a cluster-wide unrestricted Secret read (a ClusterRole rule granting `get`/`list`/`watch` on `secrets` with no namespace scope / `resourceNames`), for either the operator or bex-api.

## Context

- `scripts/gitops-validate.sh` (run in `.github/workflows/gitops.yml`) already does cluster-less manifest assertions (e.g. platform namespaces carry default-deny-ingress) — the natural home for this check, mirroring the tenant-isolation guard from w6/m6.
- The operator role is kubebuilder-generated (`lego/operator/config/rbac/role.yaml`); bex-api's RBAC lives in `deploy/gitops/` — the guard should cover both manifest sources.
- Keep the check precise: it must pass for the scoped grants t001/t002 leave in place and fail only on a genuine cluster-wide secrets broadening.

## Steps

1. Add an assertion (to `gitops-validate.sh` or a sibling test) that scans the RBAC manifests for a ClusterRole rule over `secrets` with read verbs and no scoping, and fails if found.
2. Confirm it passes on the current (post-t001/t002) manifests and fails on a deliberately re-broadened test fixture.
3. Wire it into the CI workflow that runs the validator (if not already covered).

## Files

- `scripts/gitops-validate.sh` (or a new sibling assertion)
- `.github/workflows/gitops.yml` (if a new step is needed)

## Acceptance criteria

- [ ] The guard fails on a ClusterRole granting cluster-wide unrestricted `secrets` read and passes on the scoped post-t001/t002 manifests.
- [ ] It runs in CI (cluster-less) and covers both the operator and bex-api RBAC sources.

## Out of scope

- The scoping itself (t001/t002); broader RBAC-drift linting beyond the secrets-read case.
