export type TerminalControlScope = 'read' | 'write'; /** * Audience of a grant that is allowed to travel in a browser-visible URL. * `central` means: only the central dashboard front proxy may consume it, and * only by presenting the matching forward proof below (P1-5). A grant without * an audience is an internal loopback-hop credential and may never appear in a * `?viewToken=`. */ export type TerminalGrantAudience = 'central'; export interface TerminalControlGrantClaims { version: 1; scope: TerminalControlScope; sessionId: string; userId: string; authSessionId: string; grantId: string; issuedAt: number; expiresAt: number; /** Set only on view capabilities minted for the browser (P1-5). */ audience?: TerminalGrantAudience; /** * Worker boot generation the capability was minted against (P1-5). The * worker re-derives it from its own per-boot view token and refuses any * mismatch, so a restart kills every grant issued to the previous boot even * though verification itself stays stateless. */ workerGeneration?: string; } export type TerminalControlGrantVerification = { ok: true; claims: TerminalControlGrantClaims; } | { ok: false; reason: 'missing' | 'malformed' | 'invalid' | 'expired' | 'session_mismatch'; }; /** * Mint a dashboard-to-worker grant. The returned capability is for the * loopback proxy hop only and must never be returned in an API body or URL. */ export declare function issueTerminalControlGrant(secret: string, input: Omit & { grantId?: string; }): string; /** * Cheap shape probe: does this query/header value even claim to be a signed * terminal grant? Lets hot paths skip the synchronous secret-file read for * ordinary random capability tokens (worker per-boot view token, write token) * that can never verify anyway. */ export declare function looksLikeTerminalControlGrant(value: string | null | undefined): value is string; export declare function deriveWorkerViewGeneration(secret: string, workerViewToken: string | null | undefined): string | null; /** * P1-5 — proof that a `?viewToken=` view capability reached the worker THROUGH * the central dashboard front proxy. * * The audience claim above states the intent; this header carries the evidence. * The worker has no channel to the dashboard's auth-session table, so it cannot * re-check liveness itself — but it can demand that the only component which * does (the front proxy) countersign the exact capability being presented. The * value is an HMAC of the capability under the host-only dashboard secret, so a * browser holding the raw URL cannot compute it and a direct dial to the worker * port (or to the daemon's own `/s/` reverse proxy, which is network-bound) * fails closed no matter what headers it forges. * * The proof is deterministic per capability, which is deliberate: it only ever * exists on the 127.0.0.1 hops (dashboard → daemon proxy → worker) and is never * written into a URL, a page or a log, so the only way to hold a (capability, * proof) pair is to already own loopback on this host. Binding it to a time * window would buy nothing against an attacker who is already there. */ export declare const TERMINAL_VIEW_FORWARD_HEADER = "x-botmux-terminal-view"; export declare function signTerminalViewForward(secret: string, viewGrant: string | null | undefined): string | null; export declare function verifyTerminalViewForward(secret: string, viewGrant: string | null | undefined, proof: string | string[] | undefined): boolean; export declare function verifyTerminalControlGrant(secret: string, token: string | string[] | undefined, expectedSessionId: string, now?: number): TerminalControlGrantVerification; //# sourceMappingURL=terminal-control-grant.d.ts.map