/** * Read confinement for the extraction ladder. Every rung drives a coding agent * with the read-only three (Read/Glob/Grep) over the host root; a blanket * auto-allow grants those tools on ANY path, so a hostile repo's content can * prompt-inject the agent into `Read ~/.aws/credentials`. The ladder's contract * is "read-only tools rooted at the host directory", which means such a read * must FAIL, not ship the dev's unrelated local files to the model. * * It lives at the ladder level, not inside one rung, so the guarantee outlives * whichever rung happens to enforce it. Two forms, because the rungs enforce * it in two different places: `confineToolToRoot` is the in-process decision * the Agent SDK rung wires as its `canUseTool` callback, and * `rootScopedToolRules` is the same confinement expressed as the permission * rules the two CLI rungs pass on argv — a subprocess has no callback to hand * it. */ /** The subset of the SDK's `canUseTool` return union this file produces. The * real type lives behind claude-harness.ts's dynamic import of the SDK. */ export type ConfinementVerdict = { behavior: "allow"; updatedInput: Record; } | { behavior: "deny"; message: string; }; /** * Realpath of the deepest existing ancestor, with the not-yet-existing tail * re-appended — so a symlink anywhere on the path is resolved to its real * target before containment is judged, and a path that does not exist yet * still gets an honest verdict from its existing parent. * * Exported because the ROOT needs the same normalization before it can be * compared against: on macOS `/tmp` is a symlink to `/private/tmp`, so tool * paths resolve to the real side and a string-prefix check against the raw * root would misjudge every one of them. */ export declare function resolveThroughSymlinks(target: string): string; /** * The pure(-ish: it reads the filesystem for realpath, never writes) heart of * the read confinement. Denies any Read/Glob/Grep whose path input — absolute, * relative, `..`-climbing, or reached through a symlink — resolves outside * `rootRealpath` (which must already be a realpath, see resolveThroughSymlinks). * Everything else is allowed: the session's `tools` option already bounds the * tool set to the read-only three, so this callback is a path check, not a * second allowlist. */ export declare function confineToolToRoot(toolName: string, input: Record, rootRealpath: string): ConfinementVerdict; /** * The CLI rungs' form of the same confinement: `--allowedTools` rules scoped * to the root, never the bare tool names. A bare `Read` auto-allows Read on * ANY path; `Read(//abs/root/**)` (the `//` anchor is the CLI's own syntax for * a filesystem-absolute path) allows it only inside the root, and anything * else falls back to a permission prompt that headless mode cannot answer — * so it is denied. * * The CLI matches an allow rule against BOTH the path the model supplied and * the path it resolves to, which is what makes this a real confinement rather * than a string-prefix check: a `../` climb and an in-root symlink pointing * outside are each denied (verified against @anthropic-ai/claude-code 2.1.224 * — see confine-to-root.live.test.ts). That same both-sides matching is why * the realpath of the root gets its own rule when it differs from the root as * given: on macOS `/tmp` is a symlink to `/private/tmp`, so the supplied side * is the symlinked form and the resolved side is the realpath, and naming only * one of them would deny every in-root read. Naming the same directory twice * does not widen the confinement. */ export declare function rootScopedToolRules(root: string): string[];