key-cape/workplans/KEY-WP-0028-browser-client-field-validation.md

80 lines
3.3 KiB
Markdown
Raw Normal View History

---
id: KEY-WP-0028
type: workplan
title: "Reject service-identity fields that a browser client silently ignores"
domain: infotech
repo: key-cape
status: finished
owner: claude
topic_slug: browser-client-field-validation
created: "2026-09-09"
updated: "2026-09-09"
state_hub_workstream_id: "6d0cb4fd-17da-5466-8011-5ade1ac46171"
---
`serviceSubject` and `roles` are read only on the `client_credentials` path. On a
browser client they are accepted at startup and then ignored: the subject and
roles come from the directory user. A registration that looks effective and is
not fails later, somewhere else, as a downstream rejection rather than as a
registration defect.
`tokenLifetime` was already rejected this way for non-service clients, so the
rule exists — it was just incomplete.
## Reject the ignored fields
```task
id: KEY-WP-0028-T01
status: done
priority: medium
state_hub_task_id: "e4bce8c7-0e60-55e8-ad32-a3a0c4e46044"
```
Config validation now rejects `serviceSubject` and `roles` on any client that
does not use `client_credentials`, naming where the value actually comes from.
Nothing in `config/dev-config.yaml`, the example fixture or the live deployment
sets either field on a browser client, so this breaks no existing configuration —
checked against all three rather than assumed.
Re-verified against `sso/keycape-config` on 2026-09-09, after a peer raised that
this validation fails closed at startup and a rollout is imminent, so a stray
field would surface as KeyCape failing to boot. The deployed config now holds six
clients: `demo-app`, `netkingdom-bootstrap-console` and `openbao-admin` are
browser clients carrying neither field, while `rapp-qonto-client`,
`secrets-engine-approval` and `approval-engine-operator` carry both legitimately
on the `client_credentials` path. The rule rejects none of them. Kept as errors
rather than downgraded to warnings on that evidence: a warning for a field that
does nothing is the state this change exists to end.
**`tenant` is deliberately excluded from the rule**, and a test pins that. A
browser client may declare a tenant; `humanTenant` resolves it against the
directory and refuses issuance when the two disagree (KEY-WP-0013-T05). An
earlier version of this change rejected `tenant` too, which would have made that
feature unusable.
## Correction
```task
id: KEY-WP-0028-T02
status: done
priority: medium
state_hub_task_id: "5ab951f8-2f9c-522c-b6ea-7f6d572cd618"
```
This work started from KEY-WP-0013-T05's blocker paragraph, which described a
human token as unable to carry `tenant:platform`. That was true when written and
had already been fixed in a concurrent session by the time this task began; the
paragraph was read as current state rather than as a dated record.
Two consequences, both corrected within the session:
- the `tenant` rejection was removed before it was committed, and a test now
asserts a browser client carrying `tenant: tenant:platform` validates cleanly;
- a hub decision (`0145ab57`) recording the tenant question as open and
unimplemented was withdrawn with a rationale pointing at the implementation.
Nothing was built on it.
The lesson worth keeping: in a repository where several sessions work at once,
blocker prose ages faster than the code it describes. Re-read the source before
acting on a workplan's description of it.