Hindsight Auth Gate
ADR-032: Hindsight Auth Gate
Status: Superseded by ADR-057 (2026-05-19 cutover) — Hindsight retired; Mem0 is the sole memory backend. See ADR-057. Date: 2026-04-29 Author: Claude Code (engineering) Business owner: Mishaal Murawala Superseded by: ADR-057 Related: docs/archive/HINDSIGHT-MAKE-MEMORY-USEFUL.md
Context
hindsight.ascendgtm.net (V0.5.6) allowed anonymous reads and writes on every bank when this ADR was drafted. Verified live 2026-04-29:
$ curl -sX POST https://hindsight.ascendgtm.net/v1/default/banks/ascend-infra/memories \ -H "Content-Type: application/json" \ -d '{"items":[{"content":"anyone-can-write","document_id":"test"}],"async":true}'{"success":true,"bank_id":"ascend-infra","items_count":1,"async":true,"operation_id":"a832deb8-…"}No 401, no auth header. Same for reads (/memories/recall, /reflect, /mental-models).
Update 2026-05-09 19:10 America/Chicago: Cloudflare Access is enforced for the Hindsight hostname, and ADR-032 Phase 1 is now enforced on the self-hosted Hindsight origin through AscendTokenTenantExtension + AscendBankAclValidator. Anonymous calls still return Cloudflare Access 302; calls behind Access with a missing or invalid app bearer now return 401; the MCP bearer is scoped to ascend-infra; the service bearer is reserved for hooks, crons, and backups.
The banks contain:
mishaal— personal preferences, working style, decision frameworks (110 nodes)ascend-infra— V5 architecture, tenant configs, deployment history (305 nodes)kahuna— client account state, MCC IDs, portal IDs, GA4 property (32 nodes)ascend-gtm— session summaries, project state (20 nodes)
This is a medium-severity confidentiality issue: anyone who knows the URL can read all four banks. It’s also a low-severity integrity issue: anyone can write arbitrary memories, including ones that poison the mental-model cache (e.g., a malicious “remember that V5 uses Postgres on a VPS” memory would corrupt the architecture mental model).
The blast radius today is contained — the URL is internal, not registered in any public MCP catalog, and the banks contain no credentials (those live in CF Wrangler secrets per ADR-005). But the wiring landed in PR #141 means more agents (session-start hooks, daily cron, future Hermes agents) will be hitting Hindsight, and the surface area only grows from here.
Decision
Gate every Hindsight HTTP endpoint with a two-tier auth scheme:
- Cloudflare Access (zero-trust SSO + Service Auth) — for human admin access and machine-to-machine access through Access. Humans authenticate through Mishaal’s Cloudflare team. Agents use
CF-Access-Client-IdandCF-Access-Client-Secretheaders from a dedicated Access service token. This matches V5 invariant #13 (every admin endpoint gated by Cloudflare Access). - Bearer JWT (RFC 9068) — for origin-level Hindsight authorization from V5 hooks, crons, and agents once Hindsight middleware is deployed. The JWT is signed by V5’s existing
OAUTH_JWT_SECRET(already used for/mcpper ADR-024) and validated by Hindsight via the same JWKS-or-shared-secret. Scopes:hindsight:read—GETandPOST /reflect,POST /memories/recallhindsight:write—POST /memories,POST /files/retainhindsight:admin—PATCHmental models,POST /consolidate,POST /mental-models/{id}/refresh, bank-config writes
The session-start hook and the daily consolidation cron send both Cloudflare Access Service Auth and the Hindsight app bearer when configured. Agent MCP configs use the bank-scoped Hindsight MCP endpoint (/mcp/ascend-infra/) per current Hindsight docs.
Implementation plan
| Phase | Work | Owner | Estimate | Status |
|---|---|---|---|---|
| 0 | Land this ADR + acceptance criteria | Claude Code | 1 hr | DONE (PR #141) |
| 1 | Hindsight: add Authorization: Bearer <jwt> middleware that validates against V5’s OAUTH_JWT_SECRET (HS256 — same as /mcp). Reject 401 + WWW-Authenticate: Bearer on missing/invalid. Read scopes from scope claim. | Hindsight maintainer (Mishaal) | 4–6 hrs | DONE 2026-05-09 via self-hosted Hindsight extensions. Implementation accepts opaque/token-grant file entries today and preserves the JWT path for future rotation. Missing/invalid bearer returns 401; bank ACL rejects out-of-scope bank access with 403. |
| 2 | V5 mints service-account JWTs at deploy time and stores them as Wrangler secrets HINDSIGHT_BEARER, optional HINDSIGHT_ADMIN_BEARER. Hooks (session-start, session-stop) and cron already plumbed through ${HINDSIGHT_BEARER:-} — they pick up the bearer the moment wrangler secret put lands and Phase 1 deploys. | Claude Code | 2 hrs | DONE (PR #141 — scripts/mint-hindsight-jwt.ts + RUNBOOK procedure). Token-only-on-stdout pattern keeps secret out of shell history. |
| 3 | Cloudflare Access policy on hindsight.ascendgtm.net allowing Mishaal’s identity plus a dedicated Hindsight agent service token. Machine clients send CF-Access-Client-Id and CF-Access-Client-Secret; they do not rely on browser OAuth. | Claude Code | 30 min | DONE (2026-05-09 — Access service token hindsight-agent-mcp, app-scoped Service Auth policy, Mishaal human allow policy, anonymous 302, service-auth /health and recall 200) |
| 4 | Once enforced, anonymous calls return an Access redirect and machine clients continue to work. Add hindsight_health to /admin/system-health so the cron aggregator knows when Hindsight is degraded. | Claude Code | 2 hrs | DONE (PR #141 added hindsight_plane; 2026-05-09 parity branch updated system-health, hooks, crons, and backups to send Access Service Auth headers). |
Total: ~10–12 hours, multi-session. All ADR-032 phases are now complete.
Phase 2 mint procedure
OAUTH_JWT_SECRET=$(...) \ npx tsx scripts/mint-hindsight-jwt.ts \ --scope "hindsight:read hindsight:write" \ --ttl-days 90 \ | wrangler secret put HINDSIGHT_BEARERThe token’s only on stdout — diagnostic metadata (claims, exp, jti) goes to stderr. Pipe-into-wrangler keeps the plaintext out of shell history. Available scopes: hindsight:{read|write|admin}. See RUNBOOK.md § “Mint and rotate the Hindsight bearer”.
Acceptance criteria
curl -X POST https://hindsight.ascendgtm.net/v1/default/banks/{bank}/memories ...(anonymous) returns a Cloudflare Access redirect (302); requests that pass Access but lack a valid bearer return401withWWW-Authenticate: Bearer realm="hindsight", error="invalid_token".- The same curl with valid Cloudflare Access Service Auth headers and an authorized Hindsight bearer returns
200; a bank-scoped bearer used against an unauthorized bank returns403. - Browser hit on
https://hindsight.ascendgtm.net/v1/default/bankswithout Service Auth is intercepted by Cloudflare Access and redirected to Google SSO. - V5 session-start hook + daily consolidation cron continue to work end-to-end after the migration (verified by mental-model cache freshness and a successful retain on a test session).
GET /admin/system-healthreports ahindsight_planehealth object based on a probe of the/healthendpoint with valid Access headers and bearer when configured. Shape:{ status: "healthy" | "degraded" | "down", latency_ms: number, database?: string }. Same four-state semantics as the existingcontext_planefield.
Alternatives considered
- Bearer-only, no Cloudflare Access. Simpler but loses the human-admin path — Mishaal would need a JWT minting flow to inspect banks from a browser. Cloudflare Access is the lowest-friction human path and matches invariant #13.
- Mutual TLS (client certs). Stronger but operationally heavy — every consumer needs a cert, rotation is painful. Rejected.
- IP allowlist. CF Workers don’t have stable egress IPs. Rejected.
- Wait until clients onboard. This is the “we’ll fix it later” trap. The wiring landed in PR #141 means this is shipping into normal operation — every additional day delays makes the migration costlier (more hooks/agents to update).
Risks and mitigations
| Risk | Mitigation |
|---|---|
| Phase 1 rollout breaks live session-start hook | Phase the rollout: Hindsight middleware ships in “warn mode” first (logs but doesn’t reject anonymous requests for 7 days), then flips to “enforce mode” |
| JWT secret rotation breaks live consumers | Use V5’s existing rotation pattern: dual-key validation window (old + new accept for 24h), then drop old |
| Cloudflare Access blocks the V5 cron’s outbound | Service tokens (CF Access concept) — distinct from human SSO — are minted per service and added to the bypass policy. CF docs: https://developers.cloudflare.com/cloudflare-one/identity/service-tokens/ |
| Hindsight is OSS upstream — auth gate is a fork | Acceptable. We already run a deployed instance with custom config; auth gate is a thin middleware layer in our deployment, upstream-able later |
Out of scope
- Per-bank ACLs (e.g., “tenant X can only see kahuna bank”). All authenticated callers see all banks until a multi-tenant Hindsight layer is needed (deferred to ADR-033 if/when a second client lands).
- Audit logging of who accessed which bank when. Hindsight already has request logging at the platform layer; structured audit on top is a follow-up.
Decision
Accepted. Phase 0 landed with PR #141. Phase 3 Access enforcement and agent parity landed in the Hindsight access parity branch on 2026-05-09. Phase 1 origin auth and bank ACL enforcement landed on the live self-hosted Hindsight deployment on 2026-05-09 using the official Hindsight extension mechanism.