Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
6.1 KiB
Browser authentication and Approval Engine transport
Implemented under INFD-WP-0001-T08. The service supplies sign-in and sign-out;
explicit owner configuration enables the protected memo and human-response
routes described in protected-browser-review.md.
Install and exercise from the repository:
uv venv
uv pip install -e '.[dev]'
uv run python -m pytest -q
INFD_APPROVAL_ENGINE_SOURCE=/home/worsch/approval-engine uv run python -m pytest -q
uv run python tools/smoke_browser_shell.py
INFD_KEYCAPE_ISSUER=https://kc.coulomb.social uv run informed-decision-web
The component checks require the explicit Approval Engine source path. They use
its actual JWT verifier and API with locally signed synthetic identity fixtures.
They are not native human authentication or custody proof. The smoke script
needs free loopback port 8080, checks the installed entrypoint and stops it. It
never follows the authorization redirect or contacts KeyCape. --receipt PATH
writes metadata-only results.
The entrypoint listens on 127.0.0.1:8080 for a local reverse proxy. The public
origin/callback is fixed at https://decisions.coulomb.social/auth/callback;
Host, forwarded headers and return URLs cannot replace it. A development HTTP
listener does not replace this HTTPS callback registration. Deployment and
registration rollout remain pending. /healthz checks this process; /readyz
returns 503 without owner review configuration and recent audit delivery health.
Readiness is not a declaration of native policy/login/custody admission.
Identity boundary
KeyCapeLogin uses a configured HTTPS issuer with its /authorize, /token
and /jwks endpoints. No discovery or JWT header can substitute an endpoint.
It requests only openid approval:read approval:approve, with public-client
S256 PKCE. State, a separate browser cookie and nonce are random; the callback
consumes its five-minute pending state before token exchange. The access token
never enters a browser cookie, HTML, URL or error message.
The verifier accepts RS256 only and checks both tokens against the issuer JWKS:
ID-token audience is the client, access-token audience is approval-engine.
Subject, tenant, provenance and assurance must agree. Nonce, time bounds, exact
scopes, human principal, platform tenant and the published KeyCape MFA facts are
required. JWKS refresh failure refuses login; expired cached keys are not a
fallback. Login does not decide entitlement to view or approve anything.
KeyCape source f9812ab3b2bfe8f0817185f44071e612264ec3ee, specifically
src/internal/server/oidc/token.go, is the provenance contract reviewed here:
the code handler looks up the authenticated user and emits human; client
credentials emit service. tenant_source=directory and registration stay
distinct. Unknown/default provenance is refused in this flow. A verified
registration-supplied tenant remains the bounded GH-DEC-2026-013 gap.
MFA at is preserved as authentication time. A successful login does not assert
that MFA meets an action-specific freshness policy; the policy integration must
use these facts for the action. No local approval verdict is inferred.
Sessions last at most fifteen minutes and never outlive either token. They are
bounded to 1,024 entries, protected by a process lock, rotated on sign-in and
deleted on sign-out/expiry. Restart requires reauthentication. This is a
single-process design; replicas need a separate shared-session design. These
sessions contain no approval current state or evidence. Cookies are Secure,
HttpOnly, SameSite=Lax with the __Host- prefix. Sign-out requires POST, the
exact origin and a session CSRF token. Responses prohibit caching and framing.
The proxy must omit callback query strings, bearer headers and cookies from
access logs. Waitress has no request-target access log enabled by this entrypoint.
Approval transport boundary
ApprovalHTTPClient implements get-by-id and add-entry using the session's
access token. Each read reaches the engine. It requires exact
binding.human_control=true for this first factory integration and preserves
the native sha256:<64 hex> act digest. A pre-entry read rejects an undeclared
object before mutation. Identifiers cannot inject extra paths or query strings.
The default origin is HTTPS. The current in-cluster service can be wired with
explicit allow_internal_http=True and a fixed .svc/.svc.cluster.local
origin; that option also permits numeric loopback for component tests. Public
HTTP origins are refused. Redirects are never followed, response bodies are
limited to 256 KiB, and socket operations use a five-second timeout. No ambient
proxy credentials are inherited.
POST bodies are empty objects: Approval Engine derives identity and evidence
from its verified token and discards caller content. The adapter extracts
(approval_id, subject_id, approved_at) from the matching actual human entry,
never updated_at or local time. A 409 duplicate_approver triggers a GET of
that original entry and returns duplicate=True. The controller preserves
the original presentation correlation; it cannot attach a new presentation to
an old entry just because a duplicate exists. Other conflicts remain refusals.
A transport failure after POST is ambiguous: the engine may have committed. There is no automatic POST retry. Durable disposition processing must reconcile the current engine entry and its correlation before reporting success or offering a retry. A failed dependency must never be recorded as a human decline.
The client is an internal seam. review.py now connects fresh PDP entitlement,
durable presentations, actor/version/acknowledgment guards, local responses,
the transactional outbox and the original-entry recovery rules. runtime.py
schedules bounded delivery and reconciliation. No consume route exists.
The legacy evidence.Outbox is an in-memory test double; Store supplies
durable atomic state/evidence. See durable-review-evidence.md.
Native policy/caller assignment, registered human login, deployment and
independent custody remain in T08. Disposable browser success does not close them.