key-cape/docs/current-token-client.md
tegwick e2c5a94782
Some checks failed
Authentication acceptance / acceptance (push) Failing after 1m4s
Authentication acceptance / provider-contract (push) Successful in 12s
Build and Publish Container Image / build-and-push (push) Successful in 31s
Bind online human ID tokens to the exact current access grant
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a14bfc-9643-7872-aa12-98326dcf25e7
2026-10-18 12:13:56 +02:00

8.5 KiB

Shared current-token client and private CLI observations

Source preparation under proposed KEY-WP-0042 and the existing CUST-WP-0080 / NK48 / Apps operation and application obligations. This is a client of the issuer's optional online profile, not another issuer, PDP, approval service or application-local token registry. Native issuer/profile/consumer admission is still required. Actual operation/effect permission remains with existing owners.

One producer, two consumption surfaces

src/internal/authclient/online.go is the single implementation. The existing KeyCape executable exposes it as check-current-token, revoke-token and check-inactive-token for owner processes. Go consumers use the generated adapters/go/tokenstatus module and import keycape/pkg/tokenstatus through an exact pinned source/dependency lock. make tokenstatus-export generates that five-file module; make tokenstatus-check, ordinary make test and the root Go suite refuse drift. The sole client transformation is its first package declaration; all remaining bytes and the shared JOSE primitive are exact. No policy evaluator, signing key, credential, daemon or deployment is exported.

The client uses the existing producer internal/jose verifier. Its additive VerifyTyped path requires the actual RS256 / JWT producer header; legacy Verify callers retain their existing type policy. Keys are independently admitted public JWKS, never fetched from the status response or discovered by the token. The minimal generated module uses Go 1.23 and standard libraries. Its file boundary is the current Linux owner profile: POSIX mode checks do not establish Windows ACL behavior or a broader platform admission.

Current status is an authenticated fact

Online human PKCE ID tokens additionally carry the exact access-token at_hash binding described in online-token-status.md. This client verifies access-token facts; a consumer that also uses an ID token separately authenticates that token, checks its purpose, and joins its signed hash, identity, issuance, assurance and tenant provenance to the exact access grant. Neither token substitutes for the other. The online profile's hash is additive; disabled legacy issuance is unchanged.

OnlineClient.Check(ctx, token, ExpectedIdentity{...}) validates the signature, issuer, exact single resource audience, subject, tenant, human/service type, optional authorized-party binding, access-token purpose, unique jti and integer issued/expiry/not-before bounds before reading the private caller credential. It bounds identity by expiry and an independently admitted token age (at most one hour), then performs a new authenticated introspection. It joins every returned identity/client/audience/jti/scope/time field to those signed facts. After network I/O it checks time again and reloads JWT trust so a delayed answer cannot retain expired facts or a withdrawn signing key. No positive is cached.

The explicit HTTPS issuer origin supplies the exact /introspect and /revoke paths; redirects, query/fragment/ambiguous issuer paths, environment proxies and ambient transports are not used. CA, JWKS and private caller credential files are reread for each operation with regular-file/mode/size guards. File opens do not block on FIFO replacement. Private credentials can be atomically rotated without retaining a cached value. Requests have a five-second bound and responses a 64 KiB bound. Duplicate/null/missing/extra fields, mismatched facts, non-JSON or cacheable status responses and unavailable/unauthenticated transport refuse with one fixed error. Secret, token and response values are never part of errors.

CurrentToken.Claims() returns a separate copy of verified signed facts. Those facts remain snapshots: original authentication time is not the current lookup time. Minimum assurance/authentication age, tenant provenance, current groups, resource/action policy, exact approval and effect authority remain the existing consumer profile's obligations. A fresh status response is not a new MFA act or permission. A Forgejo sign-in token does not grant release-write or inverse authority, and no operator client/route is selected by this source package.

Negative scope and cleanup

ObserveInactive performs an independent read-only status lookup. It never infers inactivity from transport failure, malformed output or an unsuccessful positive check. Revoke sends the exact token, requires an empty successful response and then obtains that separate negative readback. Both return only InactiveForClient: the exact digest is currently inactive for the configured client. Unknown and other-client tokens have the same negative response.

That result alone cannot prove original grant ownership, global revocation or a completed operation's cleanup. The owner retains its independently authenticated original issuance/current-positive evidence, exact token digest/jti and operation correspondence before mapping a negative observation to a parent-session receipt. Cleanup can continue after business-token freshness/key withdrawal; it creates no business authority. Unavailable/ambiguous cleanup remains unverified and requires the existing durable owner retry/recovery path. It is not success.

Existing CLI

The public admission file contains only references and independently admitted bindings; for example (paths/client are placeholders, not operational defaults):

{
  "issuer": "https://owner-admitted-issuer.example",
  "audience": "owner-admitted-resource",
  "client_id": "owner-admitted-confidential-client",
  "ca_file": "/run/owner-identity/issuer-ca.pem",
  "jwks_file": "/run/owner-identity/admitted-jwks.json",
  "secret_file": "/run/owner-identity/current-client-credential",
  "max_token_age": "5m"
}

Use an existing owner-acquired private KeyCape token JSON file, and the access token's exact sha256: digest from the owner-bound session/transaction record. No token or client credential goes in argv. Follow the existing Warden routing / owner-native acquisition and custody path; this source adds no credential lane.

keycape check-current-token --admission-file /ABS/profile.json --token-file /ABS/private-token.json --token-sha256 sha256:OWNER_BOUND_DIGEST --subject OWNER_EXPECTED_SUBJECT --tenant OWNER_EXPECTED_TENANT --principal-type human --out /ABS/PRIVATE-DIR/new-observation.json
keycape revoke-token --admission-file /ABS/profile.json --token-file /ABS/private-token.json --token-sha256 sha256:OWNER_BOUND_DIGEST --out /ABS/PRIVATE-DIR/new-cleanup-observation.json
keycape check-inactive-token --admission-file /ABS/profile.json --token-file /ABS/private-token.json --token-sha256 sha256:OWNER_BOUND_DIGEST --out /ABS/PRIVATE-DIR/new-independent-negative.json

The placeholders deliberately do not form an operational command. Current checks require explicit expected subject/tenant/type. Negative/cleanup commands refuse those business-identity flags because they report only the bounded client status. Duplicate flags and duplicate/unknown/null input fields refuse. The token file must be private, the digest must match the exact parsed access token, and the new observation must have a 0700 parent outside Git. Outputs are exclusive 0600 files; failures remove only their own inode and cannot delete another attempt's replaced file. Standard output/errors expose fixed status, not claims or credential values.

keycape.current-token-observation.v1 is a private, unsigned producer command result, not a portable execution receipt. Current output includes bounded identity/token references and signed assurance/tenant-provenance facts; it omits bearers, credentials and group/role snapshots. Negative output states its client scope. The invoking owner authenticates exact binary/config/custody and binds its own case, acceptance, descriptor, source/target, principal, transaction and effect. Local file presence, digests or a successful command do not supply those grants.

Scope of verification

Source checks use actual token/status handlers over isolated HTTPS, actual human PKCE claim construction and the same CLI implementation. The separate cross-owner fixture runs a built executable, generated public Go consumer and actual Warden Executor/SQLite in distinct Python processes. Synthetic case/catalog/owner attestations and a disposable file target do not establish Forgejo/browser acceptance, real-factor possession, production custody or approved native effects. The online issuer profile, owner mappings and source/package/type admission remain under review; all corresponding native gates stay open.