lamp-the-djinn
A sandbox for running any coding agent with full autonomy, without risking your system.
Docs: https://thekevinscott.github.io/lamp-the-djinn/ — Read the blog post for more context.
What It Solves
You want a coding agent to run with full autonomy (Claude Code's --dangerously-skip-permissions, or any other harness in YOLO mode) so it can work without constant interruptions. But you don't fully trust it — especially when it's driven by an open-weights model — not to delete your hard drive or quietly exfiltrate your secrets.
lamp-the-djinn runs any coding agent inside a Docker container — claude, npx @anthropic/claude, aider, or any command you pass. The container mounts only your project directory, so the agent can read and write your code but can't touch anything else on your system, and a default-deny network firewall blocks exfiltration. The model it talks to — local or hosted — is wired through a proxy that holds the real credentials, so they never enter the cage.
Quick Start
uvx --from git+https://github.com/thekevinbot/lamp-the-djinn lamp-the-djinn
Usage
The command after ltd's own options is what runs inside the cage:
ltd [ltd-opts] <command...>. The command's own flags are passed through
untouched (ltd's option parsing stops at the first command token).
ltd # bare: runs `claude --dangerously-skip-permissions`
ltd claude -p "fix the bug" # the -p goes to claude, not ltd
ltd npx @anthropic/claude # run an agent straight from npm
ltd --model glm-5.2 aider # route a different harness through the proxy
ltd pi -c # harness flags pass through: pi's -c resumes its last session
ltd --safe-mode # bare claude with permission prompts on
When you pass a command, a --model, or a --proxy-url (or set LTD_MODEL /
LTD_PROXY_URL), ltd wires the harness to a LiteLLM proxy on the host and
injects both provider env families (OPENAI_* and ANTHROPIC_*) so the agent
finds whichever it speaks. Bare ltd with none of these stays fully
backward-compatible: no proxy, just claude.
Some harnesses ignore provider env vars and read their own config file instead —
pi (@earendil-works/pi-coding-agent), for example, resolves providers only from
~/.pi/agent/models.json. Mount just that file with -v, and quote the whole
command as one string:
ltd -v ~/.pi/agent/models.json pi
pi and claude are preinstalled in the cage image, so naming them directly
costs nothing at startup. npx -y @earendil-works/pi-coding-agent still works
and is how you pin a specific version, but it re-downloads the package on every
run — the default (untrusted) run mounts no package cache.
Scope the mount to the single file the harness needs — not the whole ~/.pi.
That directory also holds auth.json (your pi credentials), and everything
mounted into the cage is readable by the untrusted agent; mounting one file
keeps the secret out.
A -v path under your home maps to the cage user's home
(~/.pi/agent/models.json → the same path under the cage's home), because the
cage runs as node, not as you — so a plain path-identity mount of a home path
would land where the harness never looks. When that mount is a single file, ltd
also makes the parent directories Docker creates for it (here ~/.pi and
~/.pi/agent) owned by the cage user, so a harness that writes next to the
file can do so — pi, for instance, creates its session dir at
~/.pi/agent/sessions/. A -v path outside your home keeps its own path. Point
pi's config at a cage provider whose baseUrl is
http://host.docker.internal:4000/v1 (the proxy as seen from inside the cage).
Admin TUI
ltd admin opens a small full-screen admin view on the host — no cage, no
Docker:
ltd admin # host-side admin screen; press q (or Esc / Ctrl-C) to quit
Today it is a hello-world screen: a title, the running version, and a quit hint. It exists to pin the TUI plumbing (alternate screen, raw-mode key read, terminal restore) that the real admin surface — proxy status, running cages, allowlist management — will be built on.
admin is reserved only as the entire command. ltd admin is the TUI;
ltd admin --status or any admin <args> form still passes through to the cage
untouched, as does ltd --shell admin if you really need to run a cage program
literally named admin with no arguments.
With stdout not attached to a terminal (ltd admin | cat), the screen is
printed once as plain text and ltd exits, so it stays pipe-safe.
Configuration
For a dedicated GitHub identity (recommended for distinguishing AI commits from your own):
uvx --from git+https://github.com/thekevinbot/lamp-the-djinn lamp-the-djinn \
--ssh-key-file ~/.ssh/id_ed25519_yourbot \
--git-user-name yourbot \
--git-user-email "yourbot@users.noreply.github.com" \
--gpg-key-id YOUR_GPG_KEY_ID \
--gh-token ghp_your_github_token
Allowlisting extra egress domains
The cage ships a baked-in domain allowlist (npm, PyPI, GitHub, …); everything else is blocked by default-deny egress. Two host-side ways to open more domains without rebuilding the image:
- Machine-local, all cages: drop one domain per line in
~/.config/lamp-the-djinn/allowed-domains.txt. ltd mounts it read-only into every cage and the firewall resolves it at startup. Use this for domains you always want reachable on this machine. - Per-run, this cage only: pass
--allow-domains-file PATH(or setLTD_ALLOW_DOMAINS_FILE). ltd mounts that file read-only into the single cage it launches, so only that run gets the extra domains.
ltd --allow-domains-file ./this-task-domains.txt 'claude -p "fetch the docs"'
Both files are mounted read-only: the firewall reads them once at startup,
before the agent runs, and the agent cannot edit them from inside the cage (the
mount is EROFS) — so the agent can never widen its own egress. A domain not in
the baked-in list, the machine-local file, or the per-run file stays blocked.
How It Works
Two swappable seams (see ARCHITECTURE.md for the full design):
- Isolation seam: the agent runs in Docker, accessing only the mounted project directory. Uses gVisor (
runsc) automatically when it's installed, else stockrunc; a remote Fly (Firecracker microVM) backend is scaffolded infly/. - Provider seam: one LiteLLM proxy on the host fronts your local llama.cpp and OpenRouter (e.g. GLM-5.2); the cage only ever sees the proxy, never the real key. Config edits hot-apply: run
docker compose up -d --watchand compose syncslitellm/config.yamlinto the container and restarts the proxy on every change. - Network firewall: default-deny outbound; allowlisted domains only, with an opt-in proxy-only mode.
- Cooldown cache: harnesses are pre-fetched by a trusted nightly job with a minimum-release-age window and mounted read-only — supply-chain defense, so the untrusted agent never reaches a registry.
- Git as undo: lean on
git reset --hardas your escape hatch.
Credential Persistence
To let harness sessions survive across runs (e.g. an OAuth token an agent
writes after login), ltd bind-mounts a writable host directory
(~/.cache/lamp-the-djinn/auth) into the cage at
/home/node/.config/ltd-auth.
Principle: anything in this volume is readable by the untrusted agent. The agent has full read access to everything you persist here, so treat it as exposed. Concretely:
- Persist only scoped, revocable credentials here — e.g. a session token you can invalidate, never a long-lived root credential.
- The model API key never enters the cage. It stays in the LiteLLM proxy on the host; the cage only ever sees the proxy's base URL and a local proxy key.
- Keep your primary git identity out. Push host-side, or supply a fine-grained, single-repo, revocable PAT — not your account-wide SSH key or a broad GitHub token.
The existing ~/.claude mount stays read-only, so the agent can read your
settings/hooks but cannot modify them or exfiltrate keys through them.
Forking and Customizing
This is designed to be forked. Make it your own.
Adding dependencies: Edit the Dockerfile to add tools you need. Use Claude Code to help:
Add playwright and ffmpeg to the Dockerfile
License
MIT
Metadata
Release files for lamp-the-djinn 0.1.5
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| lamp_the_djinn-0.1.5.tar.gz | 37.0 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| lamp_the_djinn-0.1.5-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 84.6 kB
Release files / lamp_the_djinn-0.1.5.tar.gz
| Download URL | lamp_the_djinn-0.1.5.tar.gz |
|---|---|
| Size | 37.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f4ef72904dedc9db8e5379c33431f40d815ee6ed849eaaca1f372a20f6dedee9
|
|
BLAKE2b-256 checksum How to use checksums |
8896a4f19e6795a9a4eb9cbdedec65acc978368f55b49ae582eae7703cc00f98
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 16, 2026.
Transparency logRelease files / lamp_the_djinn-0.1.5-py3-none-any.whl
| Download URL | lamp_the_djinn-0.1.5-py3-none-any.whl |
|---|---|
| Size | 47.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
343b1c015d0e921421bc81ecf46e0617fba169b7e188e9c8cf94c226b4fede20
|
|
BLAKE2b-256 checksum How to use checksums |
fdf680b1cd932bceeb677b0b246628c5f9b508b9d47943d0fba6032191373ad5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 16, 2026.
Transparency logSecurity for lamp-the-djinn 0.1.5
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for lamp_the_djinn-0.1.5.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 16, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.