# Credential separation — reviewed change package 2026-09-28. Prepared under the existing CUST-WP-0073 tasks. The initial admission rollout was rolled back, then corrected and re-enabled; see the [rollout receipt](../../evidence/2026-09-28-secret-annotation-rollout.md). The founder selected agent-specific supervised/autopilot modes in [the decision record](../../agent-autonomy-decision.md). The concrete supervised execution path and account cutover remain unimplemented. ## Verified current state `ssh railiance01` runs as `tegwick` (uid 1000), with `(ALL) NOPASSWD: ALL`. `/etc/rancher/k3s/k3s.yaml` is `644 root root`. `kubectl auth whoami` reports `system:admin`, `system:masters`. No credential contents were read. The public principal inventory in `railiance-infra/ansible/inventory/ssh_principals.yaml` maps both `agt-*` and `adm-full` to `tegwick`. ops-warden issues certificates; railiance-infra owns host principal mapping. A different principal on this same account does not separate privilege. ## Supervised starting identity and later scoped promotion (T01/T02) Use separate agent and admin OS identities on both the workstation and server. Agents must not inherit the admin SSH key/certificate, SSH agent socket, sudo, container-runtime socket, kubeconfig, OpenBao token or admin home directory. Moving a file or changing the default context under the same unrestricted account is insufficient. Keep an independently verified attended admin session open during cutover; verify recovery before withdrawing the old agent path. The supervised starting Kubernetes identity is outside `system:masters`, with an explicit reviewed observation allowlist. Do not simply bind built-in `view`: ConfigMaps, pod specs and logs can themselves contain credentials. Do not grant workload edits, arbitrary ConfigMap writes, exec/attach/portforward, proxy, logs, Secret verbs, token issuance, impersonation, RBAC writes or CSR approval. Broader action authority is a per-agent autopilot grant earned through evidence, with cost and risk limits in EUR; it is not unrestricted credential access. Deployment create/patch authority allows code or mounts to extract credentials. This is an upstream documented escalation route, not a missing deny rule: [Kubernetes RBAC good practices](https://kubernetes.io/docs/concepts/security/rbac-good-practices/). Agents prepare exact changes; in supervised-mode the supervisor approves or runs them through the privileged path. An agent-controlled GitOps write path must obey that same gate. Later autopilot may execute scoped actions without individual approval only through verified policy and budget/risk enforcement. Otherwise it recreates the bypass. Mode, supervisor, proposal dispositions and verified outcomes belong to the identified agent's existing records and receipts. Set k3s's persistent kubeconfig mode to `600` in railiance-enablement and fix the existing file, but do not mistake that step for OS-account separation. The final selected launcher/account setup must prove agents cannot regain the old admin account, including through workstation/Windows interoperability. No new credential broker or harness implementation is proposed. Acceptance uses the actual agent process/account, not only admin impersonation: whoami without masters; denied Secret get/list/watch; denied pod exec, workload writes, logs, token issuance and impersonation; denied admin-file reads and sudo; no accessible admin socket/key/token; approved observation succeeds; attended admin recovery succeeds. Record authorization booleans and file access results, never credential contents. Negative checks must not attempt to print an actual secret if access unexpectedly succeeds. ## Secret annotation policy (T03) `reject-secret-last-applied.yaml` contains a native v1 policy and Deny binding. It rejects the annotation key even when its value is empty, on CREATE/UPDATE, cluster-wide. It introduces no controller or workload. Server dry-run on the verified v1.35.1+k3s1 cluster accepted both objects on September 28: ```text validatingadmissionpolicy.admissionregistration.k8s.io/reject-secret-last-applied serverside-applied (server dry run) validatingadmissionpolicybinding.admissionregistration.k8s.io/reject-secret-last-applied serverside-applied (server dry run) ``` Subsequent live native tests passed, but actual ESO refresh failed and required rollback. Enforcement is now enabled after explicit metadata fixes and successful fresh refreshes of all 39 ExternalSecrets. The platform owner's pinned revision `7daf7e9` is authoritative; the original proposal file is retained for history. Reference: [ValidatingAdmissionPolicy](https://kubernetes.io/docs/reference/access-authn-authz/validating-admission-policy/). Before any retry, fix and verify the 31 ESO declarations described in the rollout receipt, then in one attended railiance-platform window: remove existing last-applied annotations without logging values; persist the manifest in that owner's deployment path; dry-run and diff; install policy and binding. Use only a synthetic, non-credential Secret in a scratch namespace to verify clean create and update succeed, annotated create/update (including empty annotation) fail, and client-side apply fails. Verify the policy type-check status, then remove the fixture. Record only names, booleans and counts. Do not use dry-run output of real Secret objects or print admission errors containing real values. If the policy interrupts a required write, the attended admin can delete its binding, fix the writer to use server-side apply/replace, and rebind. This temporarily reopens annotation leakage and must be recorded. The policy does not revoke any credential or stop other ways of reading secrets. ## Guidance and deferred rotation (T04/T05) The September 24 standing notice exists. The raw presence template is now withdrawn: absent annotations can trigger a dump of the full Secret. Use only the maintenance helper, which captures and suppresses kubectl output on errors. Claude's recorded guard remains a stopgap. No equivalent Codex/Grok read-denial hook has been established by this work; this session has broad kubectl/SSH permissions, so instructions are the current protection. No claim is made that every Grok installation was inspected. OpenBao/Vault secret reads belong inside the same attended boundary, regardless of preapproved command prefixes. Rotation remains in existing T05 with the founder's September 24 trigger-based deferral. Do not silently cancel it or open another plan. At final closure, either execute the rotation in its attended window or explicitly resolve its existing disposition under the work-record rules. No rotation was performed. ## Bounded implementation evidence The existing sandbox mechanism passed the synthetic checks in [the sandbox receipt](../../evidence/2026-09-28-supervised-sandbox-proof.json). It does not prove interactive agent migration. The per-agent record at `.kaizen/agents/custodian-codex/supervision.json` keeps this agent supervised, with no autopilot grant or EUR limits assigned. The descriptive summarizer `scripts/summarize_agent_supervision.py` cannot authorize or promote an agent. No eligible scoring sample exists; the failed annotation rollout and recovery are retained visibly rather than counted as unchanged success. Current T03 outcome: nine admission checks pass; all 39 ExternalSecrets refreshed successfully under Deny; the guard is Synced/Healthy. Source fixes and deployment receipts are in the linked rollout record. The absent-namespace orphan Secret was deleted after explicit founder approval, using its exact UID precondition; absence and unchanged 3 GiB PVC were verified. `orphan-secret-deletion.json` now contains the execution disposition. T03 is complete. Agent-runtime migration remains a separate unfinished task.