/** * serve-descriptor — the ONE validator for the untrusted daemon-discovery * artifact `.openlore/serve.json`. * * The descriptor is a repo-local, attacker-writable file that OpenLore reads at * three sites — the `serve` CLI, the serve-client the stdio MCP server delegates * through, and the Pi extension. Each reader then FETCHES the host/port the file * names for a liveness probe and, on a healthy answer, POSTs the project * directory and full tool arguments to it. A poisoned descriptor is therefore an * SSRF / egress vector, a leak of the directory + tool args, and a result- * poisoning channel into the agent's context (mcp-security: Untrusted Artifact * Deserialization). One threat model must not have three postures. * * This module is the one lock, extracted verbatim from `serve.ts`'s existing * reader: loopback-only host, integer port 1–65535, integer pid > 0, token * absent-or-string. No check is invented here beyond the ones `serve` already * applied. A descriptor that fails any check is treated exactly as ABSENT — the * reader returns null and the caller takes its existing no-daemon path (spawn a * fresh daemon or fall back to in-process dispatch). No field of an invalid * descriptor ever becomes a fetch target or request header. * * Dependency-light by contract (mcp-security ServeDescriptorValidatedAtEveryReader * + the MCP↔Pi parity doctrine): it imports only node builtins and the loopback * predicate it shares with the HTTP guard, so the Pi host can import it without * pulling in the analyzer. */ /** Increment only when the daemon HTTP tool/health contract becomes incompatible. */ export declare const SERVE_PROTOCOL_VERSION = 1; /** * The validated daemon-discovery descriptor. `startedAt` / `version` are * advisory metadata (normalized to '' when absent or ill-typed); the other four * fields are security-critical and are only present on a descriptor that passed * {@link validateServeDescriptor}. */ export interface ServeDescriptor { port: number; pid: number; host: string; token?: string; protocolVersion: typeof SERVE_PROTOCOL_VERSION; startedAt: string; version: string; state?: 'ready' | 'draining'; } export interface ServeHealth { ok: true; protocolVersion: typeof SERVE_PROTOCOL_VERSION; presetDispatchEnforced: true; root: string; pid: number; preset: string; tools: string[]; tokenProtected: boolean; tokenAuthenticated: boolean; draining: boolean; /** * Freshness-watcher state, when the daemon reports it (change: * extend-api-for-supervising-hosts). OPTIONAL on purpose: a daemon from an older release omits * it, and a reader must then say 'unknown' rather than assume either value — which is why this * addition needs no `SERVE_PROTOCOL_VERSION` bump. */ watcher?: 'healthy' | 'stopped'; } export type ServeDescriptorRead = { kind: 'compatible'; descriptor: ServeDescriptor; } | { kind: 'incompatible'; descriptor: { port: number; pid: number; host: string; token?: string; }; } | { kind: 'absent'; }; /** Build the loopback HTTP origin, including the brackets required by IPv6 URLs. */ export declare function serveHttpBaseUrl(host: string, port: number): string; /** Resolve aliases to the stable filesystem identity used in health proofs. */ export declare function canonicalServeRoot(root: string): string; /** Validate the semantic compatibility and identity fields returned by `/health`. */ export declare function validateServeHealth(parsed: unknown, expectedRoot: string, descriptor?: Pick): ServeHealth | null; /** * Validate an already-parsed value as a {@link ServeDescriptor}. Returns the * normalized descriptor, or null if ANY security-critical field is * missing / ill-typed / out of range, or the host is not a loopback form. */ export declare function validateServeDescriptor(parsed: unknown): ServeDescriptor | null; /** * Read + validate a serve.json at `descriptorPath`. Any failure — missing file, * malformed JSON, or a descriptor that fails {@link validateServeDescriptor} — * resolves to null, so a poisoned descriptor is indistinguishable from an absent * one and no field of it ever reaches a fetch target or request header. */ export declare function readServeDescriptor(descriptorPath: string, options?: { includeDraining?: boolean; }): Promise; /** Distinguish a narrowly safe legacy/incompatible announcement from absence. */ export declare function readServeDescriptorState(descriptorPath: string, options?: { includeDraining?: boolean; }): Promise; /** A stale legacy file must not become a permanent repository-local denial of service. */ export declare function incompatibleServeDescriptorIsLive(descriptor: { port: number; pid: number; host: string; token?: string; }, expectedRoot: string): Promise; //# sourceMappingURL=serve-descriptor.d.ts.map