Inside CORE
FORGE is a CORE component. It has no domain, no DNS record, no OAuth client, no login
screen, no session store and no sign-out. There is no forge.runink.org. It appears as
the FORGE category in CORE’s console rail, and it never needs a route, origin or
record of its own.
The path
- The browser is at
https://core.runink.org/forge/…. - CORE’s console strips
/forgeand reverse-proxies the request to the FORGE service. That service serves the Flutter bundle and gRPC-web at/. - The bundle is built with
--base-href /forge/. The studio therefore resolves its gRPC-web calls relative to the page, as<origin>/forge/<service>/<method>.
Because of the base href, opening the backend’s own / directly cannot load the bundle’s
assets. Locally, use forge dev proxy.
Identity
On every request, CORE’s proxy removes any inbound X-Core-Identity and attaches a
fresh assertion, made with github.com/org-runink/security/coreidentity. The assertion is
an HMAC-SHA256 over {sub: email, aud: "forge", iat} with a maximum age of two minutes,
keyed with CORE_FORGE_IDENTITY_KEY. The key is shared only between CORE’s console and
FORGE.
Every RPC except gRPC reflection passes through FORGE’s auth interceptors, which verify the assertion. gRPC-web headers reach the interceptor as metadata. A stream is checked when it opens, so the two-minute limit does not cut a stream that started with a fresh assertion.
| Case | Answer |
|---|---|
Valid assertion, FORGE_OWNER_EMAIL unset | Admitted. CORE’s sign-in is the only gate. |
Valid assertion, address listed in FORGE_OWNER_EMAIL | Admitted |
Valid assertion, FORGE_OWNER_EMAIL set but the address not in it | PermissionDenied: this account is not on this forge instance's allowlist (FORGE_OWNER_EMAIL) |
| Missing, malformed, expired, future-dated, wrong-audience, badly signed or duplicated assertion | Unauthenticated: FORGE is opened from CORE — sign in to CORE |
CORE_FORGE_IDENTITY_KEY unset, not base64, or shorter than 32 bytes | FailedPrecondition naming the variable, on every RPC (fail closed) |
Two assertions on one request mean a proxy failed to strip the inbound one. FORGE refuses
rather than guess which one CORE wrote. A refusal is logged as the coreidentity error
sentinel only: the header value is a bearer credential and never reaches a log or a reply.
The verified email is the actor. IdentityService.WhoAmI returns it, the chat store is
keyed by it, and the audit records it.
What the person sees
When WhoAmI fails, the studio shows one plain line and an Open CORE button:
FORGE is opened from CORE — sign in to CORE.Your CORE account is not on this FORGE's allowlist.FORGE could not check who you are: …
Any later call that answers Unauthenticated reads as CORE did not vouch for you — sign in to CORE again., because it means CORE’s session ended. Signing out is done from CORE’s
rail footer.
How CORE shows FORGE
- The rail is FORGE’s chat history: New chat (
/forge/?chat=new), then the recent chats, newest first. CORE reads the list through the same-origin/forge/proxy, usingrunink.ui.chat.v1.ChatService/ListChatson the CORE session. - The page header names the chat. The studio reports what it shows with a same-origin
postMessageof{"type":"forge.chat","id","title","project"}, which carries ids and display text only. CORE accepts it only from its own origin and from the studio iframe’s own window. - CORE files no briefs. Filing is FORGE’s alone, through
ApproveChat. CORE’s coding agents consume theforge-labelled issues, and FORGE is their only producer.
Network
FORGE’s on-host manifest (merged in core) creates the forge-system namespace, a Service,
and NetworkPolicies that admit only CORE’s console. The image listens on :: because
the platform’s pod network is IPv6 single-stack.
Sources
grpc/cmd/auth_interceptor.go; grpc/internal/auth/identity.go;
flutter/lib/core/auth/core_gate_screen.dart; flutter/lib/core/grpc/channel_provider.dart
(forgeErrorText); flutter/lib/features/forge/core_frame_web.dart; CLAUDE.md
(“Authentication — CORE vouches”); core’s CLAUDE.md (the FORGE rail and forgeproxy.go).