import * as plugins from './plugins.js'; import { type IControllerMcpCaller, type IControllerRuntimeId, type TControllerMcpSubject, } from '../ts_interfaces/index.js'; /** * Caller credentials are minted per spawn and held only in memory. Every credential-bearing * process is a controller child — terminals, the OpenCode server — so no credential can outlive * the controller generation that minted it, and nothing here belongs in persistence. * * A credential is secret material. It never reaches audit records, error text, responses or logs: * only the derived {@link IControllerMcpCaller.auditPeerId} does, and that is a one-way digest. */ const credentialByteLength = 32; const auditDigestBytes = 16; export const anonymousControllerMcpCaller: IControllerMcpCaller = Object.freeze({ subject: Object.freeze({ kind: 'anonymous' }) as TControllerMcpSubject, auditPeerId: 'mcp:anonymous', }); const digest = (valueArg: string): string => plugins.crypto .createHash('sha256') .update(valueArg, 'utf8') .digest() .subarray(0, auditDigestBytes) .toString('base64url'); const auditPeerIdFor = (subjectArg: TControllerMcpSubject): string => { if (subjectArg.kind === 'session') return `mcp:session:${digest(subjectArg.sessionIdentityId)}`; if (subjectArg.kind === 'terminal') return `mcp:terminal:${digest(subjectArg.resourceId)}`; if (subjectArg.kind === 'runtime') return `mcp:runtime:${digest(subjectArg.runtimeGeneration)}`; return anonymousControllerMcpCaller.auditPeerId; }; const credentialKey = (credentialArg: string): string => plugins.crypto .createHash('sha256') .update(credentialArg, 'utf8') .digest('base64url'); /** Minted credentials, keyed by digest so the raw value is never retained. */ export class ControllerMcpCallerRegistry { private readonly callersByCredentialKey = new Map(); public mint(subjectArg: Exclude): string { const credential = plugins.crypto.randomBytes(credentialByteLength).toString('base64url'); this.callersByCredentialKey.set(credentialKey(credential), { subject: subjectArg, auditPeerId: auditPeerIdFor(subjectArg), }); return credential; } /** An unknown, malformed or revoked credential degrades to anonymous, never to an error. */ public resolve(credentialArg: string | undefined): IControllerMcpCaller { if (credentialArg === undefined) return anonymousControllerMcpCaller; return this.callersByCredentialKey.get(credentialKey(credentialArg)) ?? anonymousControllerMcpCaller; } public revokeCredential(credentialArg: string): void { this.callersByCredentialKey.delete(credentialKey(credentialArg)); } public revokeTerminal(projectIdArg: string, resourceIdArg: string): void { this.revokeWhere((subject) => ( subject.kind === 'terminal' && subject.projectId === projectIdArg && subject.resourceId === resourceIdArg )); } public revokeSession(projectIdArg: string, sessionIdArg: IControllerRuntimeId): void { this.revokeWhere((subject) => ( subject.kind === 'session' && subject.projectId === projectIdArg && subject.sessionId.harnessId === sessionIdArg.harnessId && subject.sessionId.nativeId === sessionIdArg.nativeId )); } public revokeRuntime(runtimeGenerationArg: string): void { this.revokeWhere((subject) => ( subject.kind === 'runtime' && subject.runtimeGeneration === runtimeGenerationArg )); } public clear(): void { this.callersByCredentialKey.clear(); } public get size(): number { return this.callersByCredentialKey.size; } private revokeWhere(predicateArg: (subjectArg: TControllerMcpSubject) => boolean): void { for (const [key, caller] of [...this.callersByCredentialKey]) { if (predicateArg(caller.subject)) this.callersByCredentialKey.delete(key); } } } /** * The private endpoint carries the caller in the request envelope, which typed handlers never see. * The bounded router resolves it once and runs the handler inside this context, so no handler * signature has to carry an authorization argument and no per-request map needs cleanup. */ const callerContext = new plugins.asyncHooks.AsyncLocalStorage(); export const runWithControllerMcpCaller = ( callerArg: IControllerMcpCaller, operationArg: () => T, ): T => callerContext.run(callerArg, operationArg); export const currentControllerMcpCaller = (): IControllerMcpCaller => ( callerContext.getStore() ?? anonymousControllerMcpCaller );