/** * 中央前门 → worker 的「平台只读访客」**展示层**提示头(#933 回归修复)。 * * 背景:平台注入的 Cookie / `X-Botmux-Role` 在两条路上都会被 dashboard 前门剥掉 * (显式 query capability 走 P1-6 的 stripBrowserCredentials;无 capability 时换成 * 内部签名 grant)。剥掉之后 worker 的 `platformReadonly` 恒为 false,只读终端页 * 便不再显示「owner 登录后可操作 →」的 SSO 引导——冷打开飞书卡片链接的平台访客 * 被困在一个没有任何登录入口的只读页里。 * * 该头只影响只读页渲染哪条横幅(登录引导 vs 纯只读提示),**不进入任何读/写授权 * 判定**:直连 worker 伪造它,最多让一个本来就只读的页面多显示一条登录引导链接。 * 前门在 terminalForwardHeaders 丢弃全部客户端 `x-botmux-*` 之后才设置它,浏览器 * 无法夹带。 */ export declare const TERMINAL_PLATFORM_READONLY_HINT_HEADER = "x-botmux-platform-readonly"; export interface TerminalWriteInput { /** Value of the `X-Botmux-Role` request header (normalized to a single string, or undefined). */ role: string | undefined; /** Whether the request's `?token=` matched the worker's write token. */ tokenMatches: boolean; /** Whether this machine is bound to a central platform (a trusted boundary fronts `/s`). */ platformBound: boolean; /** Whether the request carried a valid platform-injected dashboard-token cookie, * i.e. it genuinely traversed the platform's authenticated reverse proxy. */ platformProxied: boolean; } export interface TerminalAccessInput extends TerminalWriteInput { /** Whether the request's `?viewToken=` matched the worker's read capability. */ viewTokenMatches: boolean; } export interface TerminalAccessDecision { hasRead: boolean; hasWrite: boolean; platformReadonly: boolean; } /** * Derive a stable WRITE (operate) capability for one session. Uses a DISTINCT * domain separator from the retired stable view token so the two capabilities * can never collide — knowing a read capability must never yield the write * token (and vice versa) even for the same session+secret. * * Rationale: the write token used to be a per-process `randomBytes(16)`, so an * already-issued 「操作链接」/write link (`?token=`) died the moment its worker * restarted (a silent daemon restart re-forks every worker → new token → old * link 403s). Deriving it from the host-only dashboard secret keeps the operate * link valid across restarts, exactly like the read-only view token. The * host-only secret is masked from sandboxed CLIs, so a sandboxed CLI still * can't mint its own write link. */ export declare function deriveTerminalWriteToken(secret: string, sessionId: string): string; export declare function resolveTerminalWrite({ role, tokenMatches, platformBound, platformProxied }: TerminalWriteInput): { hasWrite: boolean; platformReadonly: boolean; }; /** Resolve both read and write access without ever promoting a view token. */ export declare function resolveTerminalAccess(input: TerminalAccessInput): TerminalAccessDecision; /** Constant-time equality (avoids leaking the dashboard token through compare timing). */ export declare function safeTerminalTokenEqual(a: string | null | undefined, b: string): boolean; /** Extract the `botmux_dashboard_token` value from a request Cookie header. */ export declare function readDashboardCookie(cookieHeader: string | string[] | undefined): string | null; /** * Resolve terminal write for one request: extract the `X-Botmux-Role` header * (a duplicated/array header is treated as absent), verify the request came * through the platform proxy (dashboard-token cookie matches this machine's * active token), and gate the role's trust on the machine's platform binding. * * Both `isPlatformBound` and `getDashboardToken` are thunks evaluated on EVERY * call — never snapshotted. `botmux bind`/unbind and `botmux dashboard rotate` * rewrite state that the dashboard hot-reloads WITHOUT restarting live workers; * a cached value would go stale — keep trusting a request after an unbind / token * rotation, or deny legitimate platform writes after a bind. */ export declare function resolveTerminalWriteForRequest(headers: Record, tokenMatches: boolean, isPlatformBound: () => boolean, getDashboardToken: () => string | null): { hasWrite: boolean; platformReadonly: boolean; }; /** * Request-level terminal access gate. Unlike the legacy write-only resolver, * this explicitly denies observation unless the caller has a view/write * capability or an authenticated dashboard cookie. */ export declare function resolveTerminalAccessForRequest(headers: Record, tokenMatches: boolean, viewTokenMatches: boolean, isPlatformBound: () => boolean, getDashboardToken: () => string | null): TerminalAccessDecision; //# sourceMappingURL=terminal-write-auth.d.ts.map