KEY-WP-0038: Forgejo custody path derived by case type; propose KEY-WP-0039 negative-probe tokens

Forgejo's secret now lives at platform/workloads/keycape/forgejo, as derived by
oidc-browser-relying-party 1.0 (net-kingdom 074b3a0). KEY-WP-0039 answers
railiance-platform's CCR-2026-0038 question with one-mismatch probe registrations
instead of an issuer path that could mint arbitrary claims.

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

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 779409@bnt-lap001
Assistant-Session: c658ca06-19b5-468d-be64-2985dc20bfd3
This commit is contained in:
tegwick 2026-10-10 12:29:32 +02:00
parent f8ef4d418c
commit ab5cccdcf3
4 changed files with 103 additions and 4 deletions

View file

@ -210,4 +210,5 @@
| task | KEY-WP-0038-T01 | done | — | workplans/KEY-WP-0038-forgejo-oidc-client.md |
| task | KEY-WP-0038-T02 | wait | — | workplans/KEY-WP-0038-forgejo-oidc-client.md |
| task | KEY-WP-0038-T03 | wait | — | workplans/KEY-WP-0038-forgejo-oidc-client.md |
| task | KEY-WP-0038-T04 | cancel | — | workplans/KEY-WP-0038-forgejo-oidc-client.md |
| intake | KEY-IN-0001 | closed | — | intakes/intakes.md |

View file

@ -32,7 +32,12 @@ client:
custody:
# Minted straight into custody in T02; no value in git, State Hub or chat.
proposed_openbao_path: platform/workloads/forgejo/netkingdom-oidc-client
# Derived by SecurityCaseType oidc-browser-relying-party 1.0 as
# platform/workloads/keycape/{client_id}; a case may not set it (net-kingdom
# 074b3a0, after ops-mason MASON-WP-0010-T01). Replaces the earlier proposal
# platform/workloads/forgejo/netkingdom-oidc-client.
openbao_path: platform/workloads/keycape/forgejo
keycape_delivery: ESO Secret sso/keycape-forgejo-client (role external-secrets-keycape-forgejo)
field: CLIENT_SECRET
custody_owner: railiance-platform
keycape_environment: KEYCAPE_FORGEJO_CLIENT_SECRET

View file

@ -9,7 +9,7 @@ flavor: implementation
owner: claude-code
topic_slug: railiance
created: "2026-10-08"
updated: "2026-10-08"
updated: "2026-10-10"
depends_on: []
related_repos:
- prj-forgejo-org-refactor
@ -111,8 +111,9 @@ field rules and reviewing the type's composition.
The original intent below still describes what must result.
Register `forgejo` from the packet. Mint the client secret straight into custody
(proposed OpenBao path `platform/workloads/forgejo/netkingdom-oidc-client`,
custody owner railiance-platform, consumer Forgejo). Record a receipt with no
(OpenBao path `platform/workloads/keycape/forgejo`, field `CLIENT_SECRET`, derived
by the case type since net-kingdom 074b3a0; custody owner railiance-platform,
consumers KeyCape via ESO and Forgejo). Record a receipt with no
secret value.
Acceptance: discovery metadata lists the client's redirect URI. The secret is

View file

@ -0,0 +1,92 @@
---
id: KEY-WP-0039
type: workplan
title: "Negative-check tokens for the secrets-engine OpenBao JWT role"
domain: infotech
repo: key-cape
status: proposed
flavor: implementation
owner: claude-code
topic_slug: netkingdom
created: "2026-10-10"
updated: "2026-10-10"
depends_on: []
related_repos:
- railiance-platform
- secrets-engine
---
# KEY-WP-0039 - Negative-check tokens for the secrets-engine OpenBao JWT role
## Why
CCR-2026-0038 (railiance-platform RPF-WP-0035-T02) requires the OpenBao role
`secrets-engine-openbao` on `auth/keycape-services` to refuse JWTs with a wrong
`aud`, `sub`, `tenant`, `principal_type`, `roles` or `scope`, a coding-agent JWT
and an expired JWT. The positive login proof passed on 2026-10-10 (secrets-engine
`docs/evidence/2026-10-10-keycape-openbao-probe.json`). railiance-platform asked
whether KeyCape can issue the negative tokens without touching the
`secrets-engine` registration.
KeyCape signs only what a registration declares. It has no way to mint a token
with arbitrary claims, and adding one would create a forging path in the issuer.
So the negative tokens must come from real, separate registrations that differ
from `secrets-engine` in exactly one property each.
## Boundaries
- The `secrets-engine` registration is not changed.
- Probe clients are confidential `client_credentials` clients with the minimum
60-second lifetime, scopes that grant nothing, and secrets minted into custody
like any other client. They are removed after the proof.
- The probe script prints only accept or refuse booleans per case, never tokens or
secrets. It runs attended.
## Tasks
```task
id: KEY-WP-0039-T01
status: todo
priority: medium
```
### T01 — Define the probe registrations
Write a non-secret request packet under `docs/` with one probe client per claim
mismatch, each identical to `secrets-engine` except for:
- `aud`: a different access-token resource audience
- `sub`: a different service subject
- `tenant`: a tenant other than `platform`
- `roles`: a role other than `secrets-engine`
- `scope`: a scope outside the role's bound scope
Two cases need no probe client. A service token always carries
`principal_type: service` (`src/internal/server/oidc/token.go`), so the wrong
`principal_type` case uses a human access token from an attended login. The
coding-agent case uses the existing `codex-railiance-platform` registration (role
`coding-agent`, `docs/openbao-service-auth-contract.md`) if it is registered live;
T01 confirms that.
The expired-JWT case reuses any probe client: request a token, wait past its
60-second lifetime, then present it.
Acceptance: railiance-platform agrees the packet covers every refusal that
CCR-2026-0038 names.
```task
id: KEY-WP-0039-T02
status: wait
depends_on: [KEY-WP-0039-T01]
blocking_reason: "Live registration and custody need owner authorization."
priority: medium
```
### T02 — Register the probe clients, run the attended probe, then remove them
Write the attended script, which prints booleans only. Register the clients
through the normal registration path, hand the run to railiance-platform, record
a value-free receipt in `docs/evidence/`, and deregister the clients afterwards.
Acceptance: every case is refused, the receipt is committed, and the probe clients
are gone.