2026-10-18 11:36:26 +02:00
|
|
|
# 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
|
|
|
|
|
|
2026-10-18 12:13:56 +02:00
|
|
|
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.
|
|
|
|
|
|
2026-10-18 11:36:26 +02:00
|
|
|
`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):
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
{
|
|
|
|
|
"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.
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
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.
|