import { type KeyObject } from "node:crypto"; export declare const BROWSER_EFFECT_CLASSES: readonly ["read", "navigate", "message", "publish", "consent", "upload", "download", "purchase", "account_change", "data_export", "destructive"]; export type BrowserEffectClass = (typeof BROWSER_EFFECT_CLASSES)[number]; export declare const BROWSER_EFFECT_RANK: Readonly>; export declare const BROWSER_GRANT_ISSUER = "melaya-control"; export declare const BROWSER_GRANT_ALG: "EdDSA"; /** The algorithm allowlist is exactly one entry by design. */ export declare const ALLOWED_ALGS: readonly string[]; export interface BrowserGrantClaims { sub: string; aud: string[]; tenant: string; project: string; run: string; runnerDevice: string; socketGeneration: number; browserSession: string; target: { ref: string; }; profile?: string; space?: string; originScopes: string[]; actionScopes: BrowserEffectClass[]; effectCeiling: BrowserEffectClass; } export interface BrowserGrant extends BrowserGrantClaims { iss: typeof BROWSER_GRANT_ISSUER; kid: string; jti: string; iat: number; nbf: number; exp: number; } export type BrowserGrantErrorCode = "malformed" | "alg_not_allowed" | "kid_unknown" | "bad_signature" | "iss_mismatch" | "aud_mismatch" | "expired" | "not_yet_valid" | "claims_invalid" | "revoked" | "replayed" | "replay_store_unavailable"; export declare class BrowserGrantError extends Error { readonly code: BrowserGrantErrorCode; constructor(code: BrowserGrantErrorCode, message: string); } export interface ReplayStore { /** Atomically register a jti. Returns true exactly once per jti * (first caller wins); false means the grant was already used. * Implementations MUST throw (not return true) when the store is * unreachable: the verifier maps that to replay_store_unavailable * and refuses the grant (fail closed). */ registerOnce(jti: string, ttlMs: number): Promise; } /** In-process replay store for the single-runner case. The runner is * one OS process, and every grant is bound to THIS runner device via * `aud`, so process-local single-use is sufficient on this side; the * server keeps the authoritative Redis store for its own paths. */ export declare function createInMemoryReplayStore(): ReplayStore; /** Accepts base64 of a 32-byte raw Ed25519 public key (the wire form * the server ships via getBrowserGrantPublicKeys) or of an SPKI DER * key, or an already-built KeyObject. Throws BrowserGrantError * ("kid_unknown") on anything that does not decode to Ed25519. */ export declare function toPublicKeyObject(key: string | KeyObject, kid: string): KeyObject; /** Optional operator pinning: parse MEL_BROWSER_GRANT_PUBLIC_KEYS * ("=,..." in the same format the server env uses) into * a publicKeysByKid map. The normal delivery path is the server * pushing getBrowserGrantPublicKeys() output over the socket; this * env var lets a security-conscious operator pin keys locally so a * compromised socket cannot swap them. */ export declare function loadPinnedPublicKeysFromEnv(): Record; export declare const DEFAULT_CLOCK_TOLERANCE_SECONDS = 30; export interface VerifyBrowserGrantOptions { /** This verifier's own identity (runner device id, or extension * instance id on the extension path). Must appear in `aud`. */ expectedAud: string; /** kid -> public key (base64 raw 32-byte / base64 SPKI DER / * KeyObject). Delivered by the server or pinned via env. */ publicKeysByKid: Record; /** Single-use jti store. Use createInMemoryReplayStore() on the * runner. REQUIRED: single-use is part of the trust contract. */ replayStore: ReplayStore; /** Optional revocation hook (server RPC or local cache). Returning * true, or throwing, rejects the grant. When omitted, revocation * is enforced only by expiry (acceptable for 120s grants when the * transport also delivers explicit stop signals). */ isRevoked?: (jti: string) => Promise; /** Clock tolerance in seconds for exp/nbf/iat. Default 30. */ clockToleranceSeconds?: number; /** Test hook: current time in epoch ms. Default Date.now(). */ nowMs?: number; } /** Verify a browserGrant compact JWS. Resolves to the typed, verified * BrowserGrant on success; throws BrowserGrantError on EVERY reject * path (fail closed). The jti is consumed in the replay store as the * final step, so a grant that fails any earlier check is NOT burned * and a retried delivery of a valid token still works exactly once. */ export declare function verifyBrowserGrant(token: string, opts: VerifyBrowserGrantOptions): Promise;