v0.1 draft. Single-binary JWT validator + AI Procurement Decision Card v0.3 reveal-role enforcement gate. Verifies an incoming JWT against a JWKS endpoint, asks the buyer's signed Decision Card whether the principal's role is permitted to reveal the requested field, and emits a hash-chained governance event into the Kinetic Gain Suite audit-stream.
Part of the Kinetic Gain Protocol Suite. First Go binary in the portfolio.
┌─────────────────────────────┐
│ buyer's host app │
└────────────┬────────────────┘
JWT (Bearer)
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ kg-token-validator │
│ │
│ 1. verify JWT against JWKS (RS256 / ES256, kid rotation) │
│ 2. check iss · aud · exp · nbf (with configurable skew) │
│ 3. load Decision Card v0.3, look up data_vault_targets[] │
│ 4. does ANY target authorize this (role, field) pair? │
│ 5. emit token_validator.access_{allowed,denied} → audit-stream │
│ │
│ ALLOW → HTTP 200 with verdict + audit_event_id │
│ DENY → HTTP 403 with verdict + reason │
└─────────────────────────────────────────────────────────────────┘
Every Decision Card in the Suite declares data_vault_targets[] (v0.2) — which fields a vendor may reveal, and which roles may detokenize. But the spec doesn't run anything. A buyer publishes a signed Decision Card; somebody has to enforce it at request time.
kg-token-validator is that enforcement layer, in the smallest possible form. It plugs in:
- In front of any service that needs to reveal vaulted PII — sits as a sidecar / a small mTLS-fronted Go binary in the buyer's perimeter, validates the call, returns allow/deny.
- As the audit emitter — every decision is a hash-chained event on the same audit-stream the rest of the Suite uses (Decision Card drafted, attestation verified, retention deletion proven). One verifiable record across the whole vault contract lifecycle.
The CyberArk-shaped DNA shows up here: identity propagation + privileged-access enforcement + tamper-evident audit are the boring-and-correct way to do this, not a bespoke RBAC layer per surface.
# Build (single binary, no runtime deps)
go build -o kg-token-validator ./cmd/kg-token-validator
# Run against the example Decision Card
./kg-token-validator \
--addr :8080 \
--jwks-url https://buyer.example/.well-known/jwks.json \
--issuer https://buyer.example/ \
--audience kg-token-validator \
--decision-card ./examples/sample-decision-card.json \
--audit-stream-url http://audit-stream:8080 \
--audit-source kg-token-validator-prod-us-east
# Authorize a reveal
curl -X POST http://localhost:8080/authorize \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{"field": "student.email"}'
# → HTTP 200
# {
# "allow": true,
# "verdict": "allow",
# "reason": "decision SPRINGFIELD-DEC-2026-001 authorizes role \"principal\" to reveal field \"student.email\" via vault skyyflow",
# "principal": "user-9001",
# "role": "principal",
# "field": "student.email",
# "decision_id": "SPRINGFIELD-DEC-2026-001",
# "audit_event_id": "f3a8c2b1c4d5e6f7",
# "audit_degraded": false
# }| Package | Purpose |
|---|---|
pkg/decision |
Parses the AI Procurement Decision Card v0.3, answers Authorize(role, field, now) against data_vault_targets[]. Distinct verdicts for field-not-vaulted, role-not-permitted, decision-withdrawn, decision-expired, no-vault-targets. |
pkg/jwks |
Fetches + caches a JWKS endpoint. RS256 + ES256/384/521. Stale-on-error fallback. kid-miss triggers refresh so rotations propagate without a restart. |
pkg/audit |
Hash-chained event emitter matching audit-stream-py's canonical-JSON SHA-256 + prev_hash convention. Fail-safe: events are still computed locally and the chain advances even if the network write fails (returns ErrEmissionDegraded, sets audit_degraded: true on the response). |
pkg/validator |
Wires the above into one Authorize(ctx, Request) call. Also exposes VerifyClientCert(cert, allowed) for mTLS sidecar use. |
internal/server |
Tiny net/http handler. POST /authorize, GET /healthz. No middleware beyond a request log. |
cmd/kg-token-validator |
The binary entry. Pure flag-driven config. |
| Flag | Required | Default | Purpose |
|---|---|---|---|
--addr |
:8080 |
Listen address | |
--jwks-url |
✓ | Buyer's JWKS endpoint | |
--jwks-ttl |
5m |
JWKS cache TTL | |
--issuer |
✓ | Expected JWT iss claim |
|
--audience |
✓ | Expected JWT aud claim |
|
--clock-skew |
30s |
exp / nbf tolerance |
|
--role-claim |
role |
JWT claim that names the principal's role | |
--decision-card |
✓ | File path OR http(s) URL of the Decision Card v0.3 | |
--audit-stream-url |
(none) | Suite audit-stream endpoint. Empty → emit to local chain only | |
--audit-source |
kg-token-validator |
Name this instance in emitted events |
| Verdict | HTTP | Meaning |
|---|---|---|
allow |
200 | A data_vault_target authorizes this role for this field |
deny:field-not-vaulted |
403 | No target lists this field in fields_authorized |
deny:role-not-permitted |
403 | Field is vaulted, but no target lists this role in reveal_roles |
deny:decision-expired |
403 | decision.effective_until (or per-target expires_at) has passed |
deny:decision-withdrawn |
403 | decision.status == "withdrawn" |
deny:no-vault-targets |
403 | Decision Card declares no data_vault_targets[] at all |
deny:jwt-invalid |
403 | JWT signature, claims, or kid lookup failed |
deny:role-claim-missing |
403 | JWT verified but carries no role claim |
deny:decision-card-load-failed |
403 | Decision Card source unreachable or unparseable |
Every verdict — allow and deny alike — emits a token_validator.access_allowed or token_validator.access_denied event on the audit-stream with the principal, role, field, verdict, reason, and the JWT issuer.
- JWKS unreachable + previously-cached key → use the cached key (stale-on-error). No window where the gate is down because of a transient JWKS hiccup.
- JWKS unreachable + no cached key → deny everything with
deny:jwt-invalid. Fail closed. - Audit-stream unreachable → still return the verdict. Set
audit_degraded: trueon the response. The local hash chain advances so once the stream comes back, no events are lost (Phase 1 will add a buffered re-emit). - Decision Card source unreachable → deny everything with
deny:decision-card-load-failed. Fail closed.
- Not a Decision Card schema validator. Pair with
ai-procurement-decision-spec'sdecision-card.schema.jsonif you need that. - Not a vault. It tells the caller whether they may reveal; the vault still has to honor the verdict. Skyflow / Piiano / VGS / etc. integrations are upstream of this gate.
- Not a retention enforcer. Retention is the storage layer's job. See
phi-vault-contract-profile+ the Decision Card'sretention_envelope[]for the retention side. - Not an OIDC provider. It consumes JWTs the buyer's identity provider mints.
| Repo | Role |
|---|---|
ai-procurement-decision-spec (v0.3) |
The buyer-side artifact this gate consults |
phi-vault-contract-profile |
Healthcare profile of the v0.3 vault contract |
fhir-resource-access-audit |
Emits audit events on the same audit-stream this binary writes to |
audit-stream-py |
The hash-chained log every verdict lands on |
hash-attestation-rs |
Sibling: ed25519 attestation primitives, same canonical-JSON SHA-256 convention |
mcp-permission-broker |
Sibling enforcement layer for MCP tool calls (Python). Different surface, same Decision Card |
pkg/validator.VerifyClientCertis implemented but the binary doesn't yet require it. Phase 1 wirestls.Config{ClientAuth: tls.RequireAndVerifyClientCert}behind an--mtls-cert-allowlistflag.- Multi-region Decision Card cache (Phase 0 reloads on every call).
- RS384/RS512 support (Phase 0 ships RS256 + ES256 only).
- Prometheus metrics endpoint.
- Phase 0 → v1.0-prod hardening pass (per the standing squad-hardening discipline).
Audit-stream readiness scaffolding. The gate's verdicts and the audit-stream record support a covered entity's HIPAA Security Rule §164.312(a)(1) (Access Control), §164.312(b) (Audit Controls), and §164.312(d) (Person or Entity Authentication) program — but do not by themselves establish compliance. Per the standing public-language guardrail: readiness · evidence · posture · controls · scaffolding — never "HIPAA-compliant" without an external attestation.
MIT.