Metadata-Version: 2.5
Name: keepout
Version: 0.2.0
Summary: File rules for AI agents, enforced by the OS kernel (no-read, read-only, append-only). macOS + Linux.
Project-URL: Homepage, https://github.com/sahasrarjn/keepout
Project-URL: Issues, https://github.com/sahasrarjn/keepout/issues
Author: Sahasra Ranjan
License-Expression: MIT
License-File: LICENSE
Keywords: ai-agents,claude-code,codex,sandbox,secrets,security
Classifier: Environment :: Console
Classifier: Operating System :: MacOS
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Security
Requires-Python: >=3.9
Description-Content-Type: text/markdown

# keepout

**Files AI agents can't touch, enforced by the OS kernel.**

```bash
keepout claude
```

Install:

```bash
uv tool install git+https://github.com/sahasrarjn/keepout   # or: pipx install git+https://github.com/sahasrarjn/keepout
```

That's it. On first run, keepout writes a `.keepout` file protecting `.env*`, keys and
your SSH/AWS credentials, then starts the agent inside a kernel sandbox. It works with
any agent or command: `keepout codex`, `keepout npm test`. Works on **macOS and Linux**,
with no dependencies beyond Python 3.9+ (plus `bubblewrap` on Linux).

## Why

Tool hooks and permission rules only see the agent's *own* file tools. `cat .env`,
`python -c "open('.e'+'nv')"`, or a test that loads `.env` all slip past them.
keepout compiles your rules into an OS sandbox around the **whole agent process tree**,
so every one of those paths hits the same wall: `Operation not permitted` (Linux: `Permission denied`).

## `.keepout`

gitignore syntax, three sections:

```gitignore
[no-read]        # can't read, copy, move or edit
.env*
!.env.example
*.pem
~/.ssh/id_*
!~/.ssh/*.pub

[read-only]      # can read, can't modify or delete
migrations/

[append-only]    # can add to the end, can't rewrite, truncate or delete
logs/
DECISIONS.md
```

`name` matches anywhere in the project, `dir/name` is relative to the project root,
`./name` means the root only, `~/` is your home directory, and `/` is an absolute path.

## Built for agents

| | |
|---|---|
| `keepout why <path> --json` | is it allowed, per operation, and which rule decides |
| `keepout status --json` | all rules, the files they match, and whether this shell is sandboxed |
| `keepout propose add\|remove <section> <pattern> --reason ...` | the agent asks; a human runs `keepout apply` |
| `keepout test --json` | a 25-case attack matrix that checks enforcement on this machine |
| `keepout init` | also adds a rules note to `AGENTS.md`/`CLAUDE.md` and installs Claude Code hooks, so the model gets a clear reason instead of a bare `EPERM` and doesn't retry |
| skill | `skills/keepout/SKILL.md`, so agents discover it when a user says "don't let Claude read my .env" |

**Agents can't loosen their own rules.** Inside the sandbox, `.keepout`, keepout itself
and its state are write-protected, and `keepout apply` refuses to run. If `.keepout`
changes outside the sandbox, the next `keepout <agent>` shows the diff and requires a
human to approve it in a terminal.

## How it works

### macOS: Seatbelt

- `no-read`: Seatbelt `deny file-read* file-write*`. Blocking writes too stops
  "rename it, then read it".
- `read-only`: Seatbelt `deny file-write*`.
- `append-only`: the kernel's user append-only flag (`chflags uappnd`), plus a Seatbelt
  rule denying flag changes, unlink and rename, so the agent can't remove it. Flags are
  cleared when the session ends.
- Seatbelt matches the file itself, not the path you typed, so symlinks, `../`, and
  case tricks (`.ENV`) are all caught.

### Linux: bubblewrap

Landlock only supports allowlists, so it can't express "everything except `.env`".
keepout uses a [bubblewrap](https://github.com/containers/bubblewrap) mount namespace
instead:

- `no-read`: each matching file or directory gets an empty, mode-000, read-only mount
  over it. Reads fail with `Permission denied`. `mv`, `rm` and `ln` fail because you
  can't rename, unlink or hardlink a mount point, and `chmod` fails because the mount
  is read-only.
- `read-only`: a read-only bind mount (`--ro-bind`).
- `append-only`: `chattr +a`. Setting it needs `CAP_LINUX_IMMUTABLE`, so it's enforced
  only when keepout runs as root. keepout warns when it can't enforce it. When run as
  root, keepout drops all capabilities inside the sandbox so the agent can't bypass
  the masks or run `chattr -a`.
- Needs unprivileged user namespaces. On Ubuntu 24.04+, AppArmor may block them for
  `bwrap`; `keepout test` tells you if it's broken.

## Limits (v0.2)

- Linux: rules match files that exist when the session starts. New files matching a
  `no-read` pattern created later aren't masked. append-only needs root (see above).
- Seatbelt can't stack different profiles, so Claude Code's own `/sandbox` mode can't
  run inside keepout. Use keepout instead of it.
- Hardlinks to a secret that existed *before* the session aren't caught.
- New files in an `append-only` directory become append-only at the next launch.
- Windows: use WSL2 (Linux).
- `[ask]` rules (pause and ask the human) need FUSE or Endpoint Security. Planned.
