net-kingdom/security-case-types/oidc-browser-relying-party
tegwick 630ebfdd61
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Identity provider journey acceptance / provider (push) Successful in 9s
Build and Publish identity-provisioner / build-and-push (push) Successful in 9s
NK-WP-0048-T08: identity-provisioner service-access groups ensure/revoke
POST /v1/groups/ensure and /v1/groups/revoke: reserved prefixes refused, no
user creation, validate-before-mutate, read-back before success, delete only
empty groups on request. The oidc-browser-relying-party type now names them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5457@bnt-lap001
Assistant-Session: 792bf84a-3783-4f5d-a811-cded61dd2012
2026-10-08 22:58:36 +02:00
..
examples NK-WP-0048-T04: SecurityCaseType oidc-browser-relying-party 1.0 (proposed) 2026-10-08 22:45:31 +02:00
tests NK-WP-0048-T04: SecurityCaseType oidc-browser-relying-party 1.0 (proposed) 2026-10-08 22:45:31 +02:00
README.md NK-WP-0048-T08: identity-provisioner service-access groups ensure/revoke 2026-10-08 22:58:36 +02:00
type.yaml NK-WP-0048-T08: identity-provisioner service-access groups ensure/revoke 2026-10-08 22:58:36 +02:00

oidc-browser-relying-party 1.0

An internal web service signs its human users in through NetKingdom (KeyCape) with an OIDC authorization-code client. This type covers the client registration, the LLDAP groups that gate sign-in, and, for confidential clients, the client secret, minted straight into OpenBao custody and readable only by KeyCape and the consuming service.

Canon: canon/standards/security-case-types_v0.1.md. Status: proposed; it awaits acknowledgment through informed-decision (NK-WP-0048-T05).

Purpose

Until now every browser client was registered by its own script in sso-mfa/k8s/keycape/ (register-coulomb-social.py, portal-client-rollout.py, register-user-engine-portal.py, vergabe-client-rollout.py, openbao-client-config.py), each with its own test and an attended founder run. The scripts share one pattern: a non-exporting merge into the live client registry, with inspect, dry-run, apply and remove modes. key-cape now declares that pattern once as the playbook capability key-cape.oidc-client-registration. This type composes it.

Reference instances and fit

Client Fits 1.0 Why not, and what would cover it
user-engine portal yes —
vergabe-demo-company yes —
Forgejo (pending, KEY-WP-0038) yes first confidential case and the reference case for implemented
coulomb.social partly also registers plain-http loopback redirects for local development
openbao-admin no a CLI client with plain-http loopback callbacks; candidate type oidc-cli-loopback-client
informed-decision-approver no carries a resource audience, a registration-bound tenant and custom scopes; candidate 1.1 parameters or a separate type

Adopting an existing client as a case does not change its live registration (canon section 9).

Threat model

Assets: the users' sessions at the consuming service; the confidential client secret; group memberships that decide who may sign in and with what rights.

Threat Control in this type
Authorization code or token sent to an attacker-controlled URL exact redirect_uris, https only, no wildcards, validated by the type and enforced by KeyCape
Anyone with a NetKingdom account gains access to the service access_groups gate sign-in at the consumer; membership is explicit (initial_members), never implied
Stolen password grants admin rights mfa_required: "true" is pinned on the client when the service grants administrative or owner rights, not left to the deployment default
Client secret leaks through git, chat or a case file the secret never appears in a case; it is minted into secret_ref by the credential broker, and only two policies can read it
Secret readable by a broad identity verification includes a negative check that a default-only identity cannot read secret_ref
Over-broad scopes leak profile data allowed_scopes is a closed list; openid first
Silent drift between hand-built clients one capability, one type, one rollback; per-client scripts retire as clients are adopted

Out of scope: the consuming service's own authorization model (how groups map to roles there), token lifetime policy (KeyCape's), and federation (C5).

Alternatives considered

  • Keep per-client scripts. Rejected by the founder (2026-10-08): too much manual work, and every script is a separate review surface.
  • Wildcard or prefix redirect URIs. Rejected: the classic open-redirect weakness; exact strings cost nothing.
  • Auto-create Forgejo/consumer accounts for any NetKingdom user. Rejected: sign-in is gated by explicit groups; the consumer decides account creation (Forgejo: link existing accounts, no automatic creation).
  • Public clients everywhere (PKCE only). Allowed for SPAs and native apps; server-side services use confidential clients, so that a stolen authorization code alone is not enough.
  • Put the client secret in the case for convenience. Rejected: cases carry custody references only (canon section 9).

Attended steps

register-client is attended. KeyCape's live client registry is a Kubernetes Secret, and agents do not read or write Secrets. ops-warden groups every attended step of a run into one operator session (WARDEN-WP-0044). The way to remove this step is a KeyCape change: load clients from a non-secret registry and reference confidential secrets from custody. That would be a 1.1 or 2.0 of this type.

Known limits

  • No loopback or plain-http redirects (CLI and local development clients).
  • No resource audience, registration-bound tenant or custom scopes.
  • No service (client_credentials) clients: that is the oidc-service-client type.
  • Groups are service-access groups only: the identity-provisioner refuses tenant*, netkingdom-* and lldap_* names on this path, and never creates users (members must already exist).
  • custody uses the railiance-platform credential broker; that owner has not yet declared a c3.runtime-secrets capability, so mint-secret is a follow-through step rather than a composed capability. When it is declared, compose it.

Verification and rollback

Verification proves the registration with the authorization endpoint (accepts each registered redirect, refuses an unregistered one), checks group existence, and for confidential clients checks custody by capabilities only: the default identity is denied, the consumer's policy is allowed. Nothing reads a secret value.

Rollback is the exact inverse, in reverse order. The hand-over is removed first, then the registration, then the secret and its policies, then the case's group memberships. Groups are deleted only if this case created them and no other case uses them.