"""AIOS Secret Manager V2 reference-only Candidate adapter.""" from __future__ import annotations from dataclasses import dataclass from datetime import datetime, timezone from typing import Any, Mapping, Protocol, Sequence from .errors import CredentialUnavailable from .models import CredentialRequest PROVIDER_REFERENCES = { "openclaw": "systemd-credential:openclaw-gateway-candidate", "codex": "systemd-credential:codex-app-server-candidate", "native_adapter_control": "systemd-credential:session-native-adapter-control-candidate", "gitea": "systemd-credential:gitea-admin-api-candidate", } FORBIDDEN_RESPONSE_FIELDS = { "api_key", "token", "password", "cookie", "private_key", "credential_body", "plaintext", } class SecretManagerTransport(Protocol): def exchange(self, operation: str, payload: Mapping[str, Any]) -> Mapping[str, Any]: ... @dataclass(frozen=True, slots=True) class SecretProjectionReceipt: task_id: str credential_id: str reference: str provider: str audience: str consumer_service: str state: str expires_at: datetime plaintext_secret_returned: bool = False class SecretManagerV2: """Issue, project, inspect and revoke without receiving authentication material.""" def __init__(self, transport: SecretManagerTransport) -> None: self.transport = transport @staticmethod def _assert_safe(value: Any) -> None: if isinstance(value, Mapping): if FORBIDDEN_RESPONSE_FIELDS.intersection(str(key).lower() for key in value): raise CredentialUnavailable( "SECRET_MANAGER_PLAINTEXT_REJECTED", "Secret Manager response included a forbidden plaintext field", ) for item in value.values(): SecretManagerV2._assert_safe(item) elif isinstance(value, (list, tuple)): for item in value: SecretManagerV2._assert_safe(item) def _exchange(self, operation: str, payload: Mapping[str, Any]) -> Mapping[str, Any]: try: response = self.transport.exchange(operation, payload) except CredentialUnavailable: raise except Exception as error: raise CredentialUnavailable( "SECRET_MANAGER_UNAVAILABLE", "Secret Manager V2 operation failed" ) from error if not isinstance(response, Mapping): raise CredentialUnavailable( "SECRET_MANAGER_RESPONSE_INVALID", "Secret Manager V2 returned a non-object" ) self._assert_safe(response) if response.get("plaintext_secret_returned") is not False: raise CredentialUnavailable( "SECRET_MANAGER_PLAINTEXT_REJECTED", "Secret Manager did not attest plaintext_secret_returned=false", ) return response @staticmethod def _payload(request: CredentialRequest) -> dict[str, Any]: return { "approval_plan_id": request.approval_plan_id, "task_id": request.task_id, "request_id": request.request_id, "session_id": request.session_id, "worker_id": request.worker_id, "provider": request.provider, "audience": request.audience, "consumer_service": request.consumer_service, "runtime_user": request.runtime_user, "repository": request.binding.repository, "branch": request.binding.branch, "branch_oid": request.binding.base_oid, "allowed_paths": list(request.binding.allowed_paths), "operations": list(request.operations), "not_before": request.not_before.astimezone(timezone.utc).isoformat(), "expires_at": request.expires_at.astimezone(timezone.utc).isoformat(), "nonce": request.nonce, "ttl_seconds": request.ttl_seconds, "max_uses": request.max_uses, } def issue( self, request: CredentialRequest, now: datetime | None = None ) -> SecretProjectionReceipt: now = now or datetime.now(timezone.utc) if now < request.not_before or now >= request.expires_at: raise CredentialUnavailable( "SECRET_MANAGER_APPROVAL_WINDOW_INVALID", "credential request is not currently valid" ) expected_reference = PROVIDER_REFERENCES.get(request.provider) if expected_reference is None: raise CredentialUnavailable( "SECRET_MANAGER_PROVIDER_DENIED", "provider is not Candidate-approved" ) response = self._exchange("issue", self._payload(request)) credential_id = response.get("credential_id") if ( response.get("task_id") != request.task_id or not isinstance(credential_id, str) or not credential_id or response.get("credential_reference") != expected_reference or response.get("state") != "ISSUED" ): raise CredentialUnavailable( "SECRET_MANAGER_BINDING_MISMATCH", "issued projection does not match the request" ) expires_at = datetime.fromisoformat(str(response.get("expires_at"))) if expires_at > request.expires_at or expires_at <= now: raise CredentialUnavailable( "SECRET_MANAGER_EXPIRY_INVALID", "issued projection expiry is outside approval" ) return SecretProjectionReceipt( task_id=request.task_id, credential_id=credential_id, reference=expected_reference, provider=request.provider, audience=request.audience, consumer_service=request.consumer_service, state="ISSUED", expires_at=expires_at, plaintext_secret_returned=False, ) @staticmethod def _receipt_payload(receipt: SecretProjectionReceipt) -> dict[str, Any]: return { "task_id": receipt.task_id, "credential_id": receipt.credential_id, "credential_reference": receipt.reference, "provider": receipt.provider, "audience": receipt.audience, "consumer_service": receipt.consumer_service, } def _state_operation( self, operation: str, receipt: SecretProjectionReceipt, expected_state: str ) -> Mapping[str, Any]: response = self._exchange(operation, self._receipt_payload(receipt)) if ( response.get("task_id") != receipt.task_id or response.get("credential_id") != receipt.credential_id or response.get("state") != expected_state ): raise CredentialUnavailable( "SECRET_MANAGER_STATE_MISMATCH", f"{operation} response is not task-bound" ) return response def project(self, receipt: SecretProjectionReceipt) -> None: self._state_operation("project", receipt, "PROJECTED") def status(self, receipt: SecretProjectionReceipt) -> str: return str(self._state_operation("status", receipt, "PROJECTED")["state"]) def revoke(self, receipt: SecretProjectionReceipt) -> None: self._state_operation("revoke", receipt, "REVOKED") def verify_revocation(self, receipt: SecretProjectionReceipt) -> None: response = self._state_operation("verify_revocation", receipt, "REVOKED") if response.get("post_revocation_auth") != "REJECTED": raise CredentialUnavailable( "SECRET_MANAGER_REVOCATION_UNVERIFIED", "post-revocation authentication was not rejected", ) def audit(self, receipt: SecretProjectionReceipt) -> Sequence[Mapping[str, Any]]: response = self._exchange("audit", self._receipt_payload(receipt)) if ( response.get("task_id") != receipt.task_id or response.get("credential_id") != receipt.credential_id ): raise CredentialUnavailable( "SECRET_MANAGER_AUDIT_MISMATCH", "audit response is not task-bound" ) events = response.get("events") if not isinstance(events, list) or any( not isinstance(event, Mapping) or event.get("secrets_included") is not False for event in events ): raise CredentialUnavailable( "SECRET_MANAGER_AUDIT_INVALID", "audit events are absent or unsafe" ) return events