key-cape/workplans/KEY-WP-0039-openbao-negative-probe-tokens.md
tegwick d803e9282a docs(key39): scope pending task authorization correctly
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a1468b-63fa-7710-85dc-8f2e16b3ebfa
2026-10-17 13:39:02 +02:00

22 KiB

id type title domain repo status flavor owner topic_slug created updated depends_on related_repos state_hub_workstream_id
KEY-WP-0039 workplan Negative-check tokens for the secrets-engine OpenBao JWT role infotech key-cape active implementation claude-code netkingdom 2026-10-10 2026-10-17
railiance-platform
secrets-engine
09c3bddf-6058-5603-8488-cccdb7be4f86

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.

T01 — Define the probe registrations

id: KEY-WP-0039-T01
status: done
priority: medium
state_hub_task_id: "4d06b059-3768-53bb-83a4-a9a71c0850b8"

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.

2026-10-10: railiance-platform agreed that the case list covers every CCR-2026-0038 claim negative. The remaining CCR checks (capabilities-self deny, no provider fallback) are theirs and secrets-engine's. Packet written at docs/openbao-negative-probe-clients.yaml. It departs from the agreed list in two ways, both sent back for re-agreement:

  • Expired case. A token from another probe already fails a claim, so its refusal does not show that expiry was checked. It needs a sixth client, openbao-probe-expiry, identical to secrets-engine-openbao in every claim. That makes it a second credential able to log in as secrets-engine for the session. OpenBao's expiration_leeway (150s by default) also extends the wait.
  • Audience pin. A service token's aud is its clientId unless audience is set. Every probe except the aud probe therefore pins audience: secrets-engine-openbao, so it fails only on its own claim.

Condition (a), no OpenBao custody, holds. The values still have to reach KeyCape's environment, because KeyCape resolves secretRef at startup. The packet uses a transient Kubernetes Secret that is deleted in the same session.

T02 — Register the probe clients, run the attended probe, then remove them

id: KEY-WP-0039-T02
status: wait
depends_on: [KEY-WP-0039-T01, KEY-WP-0038-T02, KEY-WP-0038-T03]
blocking_reason: "Ordered identity gate: Forgejo first SecurityCase registration/claims proof, then NetKingdom registry session and attended human/coding-agent custody; original secrets-engine-openbao registration go remains resolved; probe/coding-client custody and attended readiness are not established."
priority: medium
state_hub_task_id: "03da4c38-1dac-53b1-8b0b-e697df48e9d3"

2026-10-11: requalified — live registration go caafe4be was already resolved; owner authorization present. See the-custodian docs/decisions/2026-10-11-security-unblock-decisions.md.

Write the attended script, which prints booleans only.

2026-10-10: script written at scripts/openbao_negative_probe.py, with tests in scripts/test_openbao_negative_probe.py. It meets condition (b): one refused boolean per case plus the refusal class (claim_mismatch, expired, not_service_principal), and no token, secret, claim value or OpenBao error text. A transport failure or an unexpected response is reported as inconclusive, never as a refusal. A token that OpenBao accepts is revoked at once and fails the run. Still waiting: railiance-platform's re-agreement on the two departures above, and an attended registry session with net-kingdom. 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.

2026-10-11 source preparation: T01 remains progress because the exact expiry client and audience-pin amendments require platform-owner re-agreement; the prior agreement covered the earlier case list, not this revised packet. The prepared runner revision adds a valid expiry positive control and immediate revoke, exact decoded claim-isolation preflight (not signature evidence), semantic refusal classes, bounded expiry TTL and redirect refusal. Seventeen local tests pass; original-source semantic regressions demonstrate two false successes. See docs/openbao-negative-probe-session-proposal.md for exact owner returns, live authority, effective leeway, custody boundaries and cleanup. No live token, registry or OpenBao action occurred. T02 continues to wait for owner re-agreement plus separate attended live authorization. MASON-WP-0009-T05 operator/ESO capability and nonmember checks remain independent acceptance.

2026-10-11: Platform independently re-agreed the final amended design (session proposal SHA25623f13d2a5a4204f749f35c5d44ef39d213838cd2fce1c36fb4775254ef1c7f09). Original conditional review and narrow confirmation are retained in railiance-platform/docs/reviews/2026-10-11-keycape-openbao-negative-session-owner-review*. Source acceptance T01 is complete; actual registry/leeway/readiness, founder live go, negative/cleanup and separate capability/no-fallback obligations remain T02 and owner plans.

2026-10-17 Custodian coordinator finalization review (not a KeyCape formal owner acknowledgment): the infrastructure assessment confirms all 14 executable/test/producer-decision pins and three Platform source pins still match. Source reviews remain complete: T01/T03 done, T02 wait. Existing source acceptance remains valid. Resolved registration go caafe4be covers only the original secrets-engine-openbao client (KEY-WP-0037-T03, deployed 6f79a2af, NK-WP-0046 exact-match helper); it does not authorize new probe clients or coding-client custody, and no renewed original-client approval is requested. The standing attended rule remains separate, for reviewed commands within existing scope; no new protocol or blanket approval request is introduced. The remaining sequence is KEY-WP-0038-T02/T03 first-case registration/claims proof, admission of the existing coding-client proposal with sanitized owner readiness returns, then one attended negative/revoke/cleanup receipt committed with probe clients removed. No native acceptance or credential/registration action is inferred from this coordinator review.

KeyCape issuer-source compatibility inspection for lifecycle v2

id: KEY-WP-0039-T03
status: done
priority: medium
state_hub_task_id: "54d37ad8-2352-51c2-bb2e-af6070dbbd73"

2026-10-11: Completed KeyCape's own issuer-source inspection for KEY-WP-0039-T02/attended-lifecycle-v2, exact manifest SHA256 6fc0fe47f99a97c11c6e7538f5ea02054335df7d46055d3f84bed4e194aeaaed. Read AGENTS.md, governing INTENT.md, current client-credentials claim source, registration fragments, native caller-authentication commands and frozen v2 issuer transport/probe wiring. Seventeen unchanged runner tests pass locally. This task completes only issuer-source inspection; the resulting narrow source acknowledgment is in docs/reviews/2026-10-11-key39-lifecycle-v2-keycape-source-ack.json (retained frozen prior acknowledgment). T01 stays done and T02 stays open for actual native registration, token input, refusal and cleanup proof. No Secret, token, credential or native environment was accessed; no owner checkout was changed during this source inspection. Owner source files were read for the bounded issuer compatibility check.

Current token.go:306-309 returns HTTP 401 invalid_profile_usage with feature=client_id for a removed confidential client; wrong-secret feature=Authorization cannot count as removed-registration evidence. The proposal and packet comments are narrowly corrected against that current issuer contract. Prior T01/source re-agreement history is preserved; this source acknowledgment supplies no NetKingdom runtime-owner review or founder live go.

The private token-producer handoff remains a real execution gap under T02. Existing keycape login/service-token commands require a new private output file; login also needs a concrete admitted public PKCE client/callback and attended browser/MFA. The lifecycle expects an in-memory private stdin object containing human_token and coding_agent_token, but no reviewed file-to-process or process-to-process producer, admitted human client/callback, custody-injected coding-agent source, native placement or producer cleanup receipt has been provided. No unattended token producer or admitted secret route is invented. Resolve those exact owner contracts before founder native approval; existing static registration is not live token custody or producer admission.

2026-10-11 same T03 source-inspection refinement for lifecycle V4: completed KeyCape issuer-facing inspection before the new formal acknowledgment in docs/reviews/2026-10-11-key39-lifecycle-v4-keycape-source-ack.json. Exact V4 manifest SHA256 d079f85c47d5c2635e1ee633ea4d1346b1737ce2be9ca273f22a38e620f418d8, executor b4687c2d07819c43b45d0cddcad0488b62eb49175b2a06a43f7806a0838d3a4d. NetKingdom's independent V4 source review is approved, receipt SHA256 4f4498203a31bd46dbc3715c0df1cbb9eee63bed5b2c1baf430ff3c4a37c13cb. The V2/V3 unsuccessful reviews and earlier KeyCape acknowledgment remain retained as one correction trial; they are not extra scored successes.

V4 retains the unchanged issuer runner and six-client claim design, and fixes finite/nonboolean windows, true total request deadlines, late-known OpenBao session private revoke/refusal and guarded bounded final Secret deletion. The issuer-facing acknowledgement covers only stopping late token exchanges, claim source/shape and preserving token cleanup failure/unknown outcomes; it does not replace NetKingdom runtime or Platform backend acceptance. Tests: 41 supplied lifecycle, 17 unchanged runner and 17 independent owner checks pass locally. No native tokens, credentials, issuer mutations or live cleanup were performed. T01 stays done; T02 stays open and the genuine private token-producer handoff remains its next source prerequisite before actual founder native go.

2026-10-11 same T03 source-inspection refinement for lifecycle V5: completed KeyCape's own current issuer-source/claim/cleanup-contract inspection before any new formal acknowledgment. Current token.go:306-309 still distinguishes removed-client invalid_profile_usage feature=client_id from wrong-secret feature=Authorization. The six-client design, audience source and expiry positive control remain unchanged, and the original runner's 17 tests pass. Exact V5 manifest SHA256 d1f997624db46693a7126a6eb6435f6b3a353d5bd67dcc8a539683e4b7147285; executor 3bcceebc845b17492040c76223d77088257c547a2b890198e7b5c2b04a0cd360. V5 has 46 passing supplied lifecycle checks. It adds a single exceptional response-close abort under a fixed20ms cap clipped to nonresettable aggregate cleanup, and to positive original request time remaining, with expired aggregate zero calls, fixed private errors and truthful known-session revoke/unknown outcomes. This inspection covers issuer-facing token lifetime and claim interfaces only; NetKingdom and Platform independently own their runtime and backend review decisions. Previous V2/V3 Net source refusals and Platform V4 exceptional-close refusal remain retained; KeyCape's V2/V4 acknowledgments are historical narrow source inspections, never aggregate native approval. T01 remains done, T02 open. No native actions or credentials occurred. The real private token-producer/client/callback/coding-agent custody/cleanup handoff gap stays the next prerequisite; no unattended admission is inferred.

Exact independent V5 source decisions were then verified: NetKingdom approved receipt SHA256 32a62bd1ca1bd7311965d016d4d6a6e4d9177f3423c63add436d9d309865eacc (46 supplied +17 runner +21 independent checks); Platform approved receipt SHA256 ba872dc53061b1dcca9dea1e8897597c394c1f9365e0fc49ee6f2095f57f11e0 (46 supplied +5 independent +17 previously verified runner checks). Those are foreign owners' own source decisions, not KeyCape runtime/backend reviews or founder native authority. Formal KeyCape bounded issuer acknowledgment follows in docs/reviews/2026-10-11-key39-lifecycle-v5-keycape-source-ack.json.

2026-10-11 T02 continuing source preparation: private producer decision packet KEY-WP-0039-T02/private-producer-handoff-v1 stages supported existing CLI flows, selected existing netkingdom-bootstrap-console literal-loopback human client subject to owner purpose/MFA/listener admission, and the existing codex-railiance-platform source identity subject to actual live/custody/consumer admission. No existing coding-agent delivery receipt was found; AppRole tokens do not substitute for issuer JWTs. A bounded pipe-only adapter for the current CLI's two caller-owned private files has 18 passing synthetic checks, with identity-guarded explicit file consumption and fixed value-free failure output. Decoded claim shape does not prove signatures or authenticated admission. See docs/openbao-negative-token-producer-decision.md and .json for exact owner choices and missing receipts. The exact trusted workstation-to-node launcher, private-file allowance and railiance01 Bao route/public-health guard still need source review and founder disposition before any token/temporary credential/ mutation/restart. No registration, custody, login, native action or credential request occurred. V5 and failed review histories retained; T01 stays done and T02 open. No new work record or native acceptance is inferred.

2026-10-11 same T02 source-preparation correction for KEY-WP-0039-T02/private-producer-handoff-v2: NetKingdom's exact V1 consumer refusal remains recorded (no child-ready gate, stdio alias, non-atomic unlink). V2 has source-pinned private launch/ready handshake compatible with exact Net V9 manifest84542ddc: fresh nonce/source/envelope/health/window marker before ANY caller-token file load/consume/write; separate local data/control pipes reject kernel identity aliases of stdin/stdout/stderr. Adapter read/write budget only clips downward after readiness. 31 distinct producer/launcher synthetic checks and 74 compatible consumer supplied checks pass. No SSH/native/login or credential access was performed. Stat/unlink remains non-atomic and requires continuously exclusive sameUID custody; the retained race test is a custody violation counterexample, not a claim of repaired filesystem atomicity. Receipt keeps atomic delete/copy cleanup unverified and possible copies true. Exact trusted launcher/directSSH profile/private tmpfs allowance, selected human client/MFA purpose reuse and coding-agent registration/custody/delivery remain unadmitted owner choices. Native node route, role/leeway/window/founder decision and actual negative/cleanup acceptance remain pending. T01done and T02open; no new task or plan, and V1/V5-V8/current failure histories are not overwritten.

2026-10-11 same T02 producer-preparation correction V3: Platform V2 pair source refusal RPF-KEY39-PAIR-V2-F01 is retained. Private write uncertainty now propagates into sticky remote consumer uncertainty before launcher early return; local SSH termination never proves remote no-effects/recovery. Actual full-payload then interrupted confirmation and4096-byte partialpipe failures are retained regressions. 33 distinct source checks pass. Exact Net V9 source bytes unchanged; new source reviews required, not implicit repin. Native client/MFA/coding-agent custody/transport/tmpfsfile and actualroute/session authority remain unadmitted. T01done/T02open; no new task/plan or native action.

2026-10-11 same T02 producer-preparation V4: exact V3 Net consumer refusal NET-PRODUCER-V3-F01 retained. Actual CLI contract acquisition now bounded nofollow/nonblocking regular stable reads and one ORIGINAL5-second preparation budget across contract/source/envelope/SSHspawn/ready/file/delivery phases, not a renewed launcher allowance. 40 own source tests plus9 retained paired wrapper/child handshake regressions pass with synthetic/no native transport. V3stickyremoteunknown/full+partialdelivery regressions retained; Net V9 exact bytes unchanged. Prior issuer/custody/transport/file/route/readiness-Argo/role/ maintenanceexception/founder choices remain unadmitted. No SSH/network/Secrets/ credentials/native/owner checkout changes; no task/plan duplication. T01done and T02open; source correction is not native acceptance.

2026-10-11 T02 source-design disposition checkpoint: founder accepted exact source memo infd-20261011-key39-source-design-r1, review review-62626f54-0212-4684-9b45-0a7222d61957 version1 at 2026-10-11T17:19:01Z. Disposition disp-870f9c1b-0444-4216-ae7f-f5463d590e36, approval ca118afc-d746-4454-a5b9-9b387b120cd9; authenticated person subject uid=platform-root,ou=people,dc=netkingdom,dc=local. Exact canonical public review URL: https://decisions.coulomb.social/review?memo_id=review-62626f54-0212-4684-9b45-0a7222d61957. Root's authenticated confirmed readback is the-custodian/docs/evidence/2026-10-10-security-consolidation/key39-source-design-publication/founder-source-disposition-readback-r1.json, SHA256 453213194d605336e9c424976f1ac27691def39e225026f0a99628d1cfc97d1d; observed 2026-10-11T17:30:47.080081+00:00. Source commands SHA256 36e20813596f3ddbb86e01fd0b838867ece2f4916c5880305cf12cd61b567f89 and source inventory SHA256 aea196db9e513900d5e7165455d6e9c3dc68749a606dc219ec340ac595642cc0 bind the accepted source direction.

Acceptance covers recommended source preparation for existing bootstrap-console human-client purpose reuse, returning existing coding-agent custody admission or preparing a narrow owner proposal, proposed founder directSSH source profile, and conditional exclusive-owner tmpfs files. It does not establish actual registration/MFA/listener, coding-agent custody/delivery, transport/file admission, node-local Bao route, issuer/OpenBao role/leeway/maintenance readiness or fresh attended native go. The published choice documents remain historical exact pending proposals; this appended receipt records their later source disposition. T02 remains wait on external owner custody/admission and separate attended execution authorization. Source acceptance permits further source preparation; actual registry, token exchange, negative probe, revoke and cleanup remain pending. T01 and T03 remain done. No native or credential action is authorized by this source receipt. See docs/proposals/2026-10-11-key39-next-execution-prerequisites.json for exact owner returns and safe metadata evidence needed before preparing a runnable attended execution proposal.

2026-10-11 ordered-goal qualification (T02 only): the final source runner already exists and its 17 local semantic tests pass. No new helper or source protocol is needed to repeat accepted preparation. Native execution follows the first Forgejo SecurityCase registration and claims proof (KEY-WP-0038-T02/T03); only those task metadata were checked, not wait-task bodies. NetKingdom owns the registry/env/restart session, KeyCape owns issuance/isolation, and Platform owns leeway/refusal/capability/no-fallback/cleanup proof. The live registration go is already recorded resolved; do not ask again. Attended human authentication and owner-held existing coding-agent custody go on the single attended checklist. No credentials, live registry/API changes or KEY-WP-0040 recovery work occurred.

2026-10-17 Platform owner returns (source-prepared, not admitted): railiance-platform docs/proposals/2026-10-17-key39-t02-owner-returns.md (commit 0a9617a). The coding client codex-railiance-platform has no admission, custody or delivery record. The existing narrow proposal docs/proposals/2026-10-11-key39-coding-client-custody-proposal.json (9460f6f) remains the one to admit, so the coding-agent case stays unrunnable until owners admit it. The role source (openbao/auth/secrets-engine-jwt-role.json) matches the packet's claim design. It declares no expiration_leeway, so the expiry probe must assume OpenBao's 150s default until an attended sanitized read confirms the live value. The node-local Bao origin is not declared in source and also needs that attended read. T02 stays wait.