import type { IncomingMessage, ServerResponse } from "node:http"; import type { ServiceConfig } from "../config-types.js"; /** Coerce a possibly-repeated request header to a single string (or undefined). */ export declare function headerStr(v: string | string[] | undefined): string | undefined; export declare function safeEqual(a: string, b: string): boolean; export declare function authorized(req: IncomingMessage, token: string): boolean; /** Which SYSTEM credential authenticated this request (clay 2026-06-12: source must be credential-DERIVED, * never self-declared). Returns the system name for a SERVICE_AUTH_TOKENS match; null for the legacy single * SERVICE_AUTH_TOKEN (authenticated, no system identity); undefined = no valid service credential. */ export declare function systemFor(req: IncomingMessage, config: ServiceConfig): string | null | undefined; /** "presented credentials must verify":Authorization 带 Bearer/token scheme 且三路全验不过 → true。 * 其他 scheme(Basic 等,非我方凭证空间)不管——由原有门语义处置,避免误伤未知代理注头。 */ export declare function bearerPresentedButUnverified(req: IncomingMessage, config: ServiceConfig): boolean; /** loopback 三形(IPv4 / IPv6 / IPv4-mapped)—— 一处定义,diagnostics 门与任何「本机自用」判据同吃。缺席 ⇒ false(fail-closed)。 */ export declare function isLoopbackAddress(addr: string | undefined): boolean; /** * 这次请求的身份是不是**密码学验过**的(而不是调用方自述的 header):`sso` = registry JWT(入口预认证的签名真源)/ * `direct_door_jwt` = direct-door principal JWT 验过 / `service_token` = 出示了单一或 per-system service token。 * 缺席 = 只有自述头或什么都没有。一处判定(7.63.0 codex [high]+[medium]):off-box 读 operator 面的门用它——名单只在 * 有可验身份时才是身份,零凭据下它只是谁都能写的字符串;而 JWT 身份不依赖静态 token 的在场。 */ export declare function verifiedIdentitySource(req: IncomingMessage, config: ServiceConfig): "sso" | "direct_door_jwt" | "service_token" | undefined; export declare function gatedPrincipal(req: IncomingMessage, config: ServiceConfig): string | undefined; /** * 「**出示了凭证但验不过** ≠ **没出示**」在 **direct-door principal JWT** 面上的形 —— 本仓 * {@link bearerPresentedButUnverified} 早就是同一条律在 Bearer 面的形,这里只是把它推到另一面。 * * 判据三段,缺一不成立:①这台机器是直连门(BFF/gated 上 `principalHeader` 由上游验过,本函数恒 false ⇒ * 那条部署形上零行为变化);②`X-Approval-Principal-Token` **在场**(缺席 = 真的没出示,那是 * `auth.principal_required` 的语义,不许借本码);③它**验不过**({@link verifiedPrincipal} 回 undefined)。 */ export declare function principalTokenPresentedButUnverified(req: IncomingMessage, config: ServiceConfig): boolean; /** * 🔴 **`requirePrincipal` 部署上「这次请求没有可用身份」的唯一 401 铸点**(S-305,7.76.2)。 * * 此前 45 个路由各自手抄同一行 `sendError(res, 401, "auth.principal_required", …)`,于是直连门上 * 「JWT 出示了但过期/签不上」与「什么头都没带」在 wire 上**同形**(黑盒实测:plan_review 带过期 JWT * 拿到 `auth.principal_required`,而契约与路由层的 `verifyDirectDoorProof` 说的是 `principal_unverified`)。 * 两件事的处置完全不同 —— 前者「重签一张再来」,后者「你压根没接上身份面」。 * * 一条规则、一处落点:{@link principalTokenPresentedButUnverified} 为真 ⇒ `401 principal_unverified` * (既有码,文案与 `principal-jwt.ts` 的验签层同句);否则 `401 auth.principal_required`(原样)。 * 路由层 `verifyDirectDoorProof` 自己那条 `principal_unverified` 臂保留 —— 它守的是 decide/plan_review * 调用链上的**证明三件套**,与本门(身份面)是两道门。 */ export declare function sendPrincipalRefusal(res: ServerResponse, req: IncomingMessage, config: ServiceConfig): void; /** * 🔴 **「这个调用方读得到这一行 run 吗」的唯一谓词**(S-297 抽取;此前只活在 `server.ts runOwnerOk` 的 * 函数体里,而 `runOwnerOk` 把**判断**与**404 怎么发**焊在一起 —— 于是任何需要同一条判断但不想 404 的 * 读面(如 `/mcp` 面板的 `lastLegMcp`:非属主就不投这个键、面板照常 200)只能手抄一份,而手抄那份必然 * 漏掉下面这条窄互认臂:`explicitOperatorOk` 顶注已经为同族的病写过「SINGLE mint point,不要再造兄弟」)。 * * 三条放行臂,别的一律不放: * · `owner === null` —— 无属主的行(dev / 存量),本来就是开放的; * · `principal === owner` —— 自己的行。⚠️ `principal` 必须是**验过**的身份(`gatedPrincipal`), * 不是自述头 —— `owner` 是创建时从验过的 principal 落库的,拿自述头来比就等于谁都能读别人的 run; * · [ref] C8 **窄互认**:非 `requirePrincipal` 部署上那批 `anon:shell-live` 哨兵行 + 无身份的调用方 * (cli 停发哨兵头后的存量会话,否则「能续跑但看不见」)。`gatedPrincipal` 的回值是 `string | undefined` * ⇒ 原文那半个 `principal === null` 比较结构上不可达,抽取时一并去掉(语义逐字不变)。 * * **不含 operator 臂**(刻意,[ref]⑥ 台账):跨属主 operator 在 poll/events/cancel 三读面上今天就是 404, * 放宽它是一次独立裁定。需要 operator 臂的读面在**自己的调用点**显式并上 {@link explicitOperatorOk} —— * 那样「谁多给了 operator 一条路」在代码上看得见,而不是被偷偷塞进共享谓词里。 */ export declare function runOwnerReadable(principal: string | undefined, owner: string | null, config: ServiceConfig): boolean; /** The EXPLICIT operator gate (IMAGE-API-DESIGN.md §P2.4a MUST-FIX) — NOT the bare `isOperator` (whose empty-list * "true-for-all" is an unrestricted-write exec door the instant OPERATOR_PRINCIPALS is empty). Requires a non-empty * operator set AND a present principal IN it. The same form the preempt/resume endpoints already use. * 🔴 SINGLE mint point (E1): a byte-identical twin `explicitOperator` used to live below (image-visibility lane) * and two more hand-inlined copies sat in approvals-assistant — a future hardening of this judgment would have * missed them. All call sites now consume THIS function; do not re-introduce a sibling. */ export declare function explicitOperatorOk(principal: string | undefined, operatorPrincipals: readonly string[]): boolean; /** May `principal` act as an OPERATOR on the F4 approval queue? Empty `operatorPrincipals` = legacy * behavior (the shared service token IS the operator boundary → everyone authenticated is an operator). * Otherwise only listed principals are operators. */ export declare function isOperator(principal: string | undefined, operatorPrincipals: readonly string[]): boolean; /** Operator check WITHOUT the empty-list "everyone is an operator" legacy fallback — an EMPTY `operatorPrincipals` * yields `false` (no one is an operator). Use this where empty-operators-means-all would be a SECURITY hole rather * than a back-compat convenience: image VISIBILITY scoping (a tenant-scoped image must not leak to every caller * just because OPERATOR_PRINCIPALS is unset). Adversarial-review HIGH: `isOperator([], p)=true` made * `latestPublished(..., {operator:true})` bypass tenant visibility for any caller on an operator-less deployment. * (The byte-identical `explicitOperator` twin that used to live here was folded into `explicitOperatorOk` above — * one judgment, one mint point.) */ //# sourceMappingURL=principal-gate.d.ts.map