/** * Per-session agent state — Phase 3 of the Cursell Trust Layer. * * The previous implementation wrote to `/tmp/cursell/agent-${ppid}.json`, * a path under a shared `/tmp` directory. On Linux that is the only HIGH * finding from the security audit: any local user could pre-create * `/tmp/cursell/`, plant a symlink at `agent-${some-pid}.json` pointing * at e.g. `~/.ssh/authorized_keys`, and our `writeFile` would dutifully * follow the symlink and trash the target. * * The fix moves state to `~/.cursell/state/` and adds: * - non-recursive `mkdir 0700` on each path component (so a hostile * symlinked parent cannot redirect the descent) * - `lstat` on each component validating: `isDirectory()`, * `!isSymbolicLink()`, `uid === process.getuid()` (where defined), * `(mode & 0o077) === 0` (no group/world bits) * - `writeFile` opened with `O_NOFOLLOW` where supported (Linux/macOS), * so even a same-tick race that plants a symlink at the leaf is * refused * - filename `agent-${ppid}-${SESSION_UUID}.json` — the per-process * session UUID disambiguates state from a previous, crashed process * that reused the same PID * - the statusline reader ignores files older than 1h to handle PID * reuse after a crash that left the cleanup unrun * * On Windows, `process.getuid` is `undefined` and `O_NOFOLLOW` is absent * from `constants`. Both checks short-circuit harmlessly; the symlink * vector is Linux/macOS-specific anyway (Windows tmpdir is per-user). */ export declare const SESSION_UUID: `${string}-${string}-${string}-${string}-${string}`; declare function resolvePaths(): { PARENT: string; STATE_DIR: string; }; /** * Create a directory if it doesn't exist, then assert that what's * actually on disk is a regular directory owned by us with no * group/world permissions and no symlink. * * `mkdir(..., {recursive:true})` follows existing symlinks at any * path component, including the parent, so we cannot use it. Instead * we create each level non-recursively and lstat after — a hostile * pre-existing symlink at any component fails the lstat check rather * than silently redirecting our writes. */ declare function ensureSafeDir(dir: string): Promise; export declare function stateFilePath(): string; export interface ActiveAgentState { /** * Phase 3 of agent-namespace: full canonical `/` * ref. Renamed from v0 `slug` so the statusline / active-agent * indicator reflects the unique identity, not just the bare slug. * The CLI ships its own statusline binary alongside the MCP * server so this rename is atomic — no compat shim needed. */ ref: string; name: string; version: string; category: string; sizeTag: string; loadedAt: string; sessionUuid?: string; } export declare function writeActiveAgent(state: ActiveAgentState): Promise; export declare function installStateCleanup(): void; export declare const __testing: { resolvePaths: typeof resolvePaths; ensureSafeDir: typeof ensureSafeDir; }; export {};