keycape-client.py --check-config runs KeyCape's startup checks (key-cape c528daa) on the live and proposed registry via stdin; apply refuses a registry KeyCape would refuse at start. Type 1.1 patch names the option. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 736651@bnt-lap001 Assistant-Session: f6189624-fd64-4109-b53e-f8a59ca8a2d8 |
||
|---|---|---|
| .. | ||
| decision | ||
| examples | ||
| tests | ||
| README.md | ||
| type.yaml | ||
oidc-browser-relying-party 1.1
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: acknowledged
(2026-10-09, informed-decision memo nk-wp-0048-t05-oidc-browser-relying-party-r4,
hub decision e418263e). It becomes implemented once its first case, Forgejo,
is live and verified (NK-WP-0048-T06).
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. net-kingdom now declares
that pattern once as the playbook capability net-kingdom.keycape-client-registration,
implemented by sso-mfa/k8s/keycape/keycape-client.py. 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 by the credential broker into platform/workloads/keycape/{client_id}, a path the type derives, and only two read policies (read on that one data path) can reach it, one of them through the ESO role |
| Secret readable by a broad identity | verification includes a negative check that a default-only identity cannot read the custody path |
| 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).
- Let each case choose its custody path. Rejected (ops-mason review,
MASON-WP-0010-T01): the type derives
platform/workloads/keycape/{client_id}and reuses theworkload-kv-read-keycape-<client>-<reader>policy family andexternal-secrets-keycape-<client>role from the live CCR-2026-0033 placement, so a case cannot add a new layout or policy family.
Attended steps
register-client is attended: KeyCape's live client registry is a Kubernetes
Secret, and agents do not read or write Secrets. For confidential clients,
bind-env is attended as well, being a change to the identity provider's
Deployment. KeyCape resolves a confidential client's secret from an environment
variable at start, so the binding (and deliver-secret before it) must exist
before registration. ensure-groups is attended too (see Lanes below). ops-warden
groups the attended steps 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.
Lanes, value handling and gates
Every step ops-warden executes or groups names its catalog lane
(keycape-client-secret-generate is new and must exist before the first case
runs; the attended steps use the existing key-cape-oidc-login). "ops-warden executes" means ops-warden invokes the owner's
entry point through its caller proxy, under the caller's identity: it holds no
credential (canon section 3). mint-secret generates the value inside the
broker and custody (value_handling: generated_in_custody), so no value passes
through the run. deliver-secret gates bind-env with a status-only Ready check.
ensure-groups adds exactly the memberships recorded in the accepted case. It is
attended, in the same session as register-client, and runs inside the
provisioner pod: the provisioner's only HTTP credential is user-engine's runtime
token, so a caller-proxy lane would act as user-engine (ops-warden review R8). It
can become unattended once the provisioner has an operator credential of its own.
Known limits
- No loopback or plain-http redirects (CLI and local development clients).
- No resource
audience, registration-boundtenantor custom scopes. - No service (
client_credentials) clients: that is theoidc-service-clienttype. - Groups are service-access groups only: the identity-provisioner refuses
tenant*,netkingdom-*andlldap_*names on this path, and never creates users (members must already exist). custodyuses the railiance-platform credential broker; that owner has not yet declared ac3.runtime-secretscapability, somint-secretis 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.
History
- 1.1 patch (2026-10-10):
register-clientrunskeycape check-config(key-cape c528daa) throughkeycape-client.py --check-configon the live and the proposed registry; apply refuses a registry KeyCape would refuse at start. - 1.1 (2026-10-10, minor): verification
consumer-enforces-groups, and hand-over requires the consumer to enforceaccess_groups, because KeyCape has no per-client group gate (key-cape type review R2, key-cape 6038483). No new acknowledgment: the risk class is unchanged. The Forgejo case was re-pinned before any apply, on the founder's go. - 1.0 patch (2026-10-10): R1 redirect URIs refuse
?and#; R2groupsscope always required; R3/R4 documentation (net-kingdom61ab6bc).{client_id:env}adopted inbind-envand its rollback (9a9b8e3). - 1.0 (2026-10-09): acknowledged via informed-decision memo r4 (hub decision
e418263e).