Skip to main content

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 set LTD_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 stock runc; a remote Fly (Firecracker microVM) backend is scaffolded in fly/.
  • 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 --watch and compose syncs litellm/config.yaml into 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 --hard as 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.6

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for lamp-the-djinn 0.1.6
File Size Uploaded Provenance
lamp_the_djinn-0.1.6.tar.gz 37.2 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for lamp-the-djinn 0.1.6
File Interpreter ABI Platform
lamp_the_djinn-0.1.6-py3-none-any.whl Python 3 none any Details

Total release size: 84.8 kB

Release files / lamp_the_djinn-0.1.6.tar.gz

Download URL lamp_the_djinn-0.1.6.tar.gz
Size 37.2 kB
Tags Source
SHA-256 checksum
How to use checksums
d5b5783e8147a7f4a04e14f3e8f5098326fbffbbb8598609e0535790b58556a3
BLAKE2b-256 checksum
How to use checksums
b693fad0ced50f3f9e49dcf00c31cab97eb34cee7ea8fa890a8f3233b41fccef
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 log

Release files / lamp_the_djinn-0.1.6-py3-none-any.whl

Download URL lamp_the_djinn-0.1.6-py3-none-any.whl
Size 47.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5c126cb7bc7b1e8e092753b60c9ac4cfdd9e2e4191d3991025d4660a36c209fd
BLAKE2b-256 checksum
How to use checksums
1f4e21e26862524de209329d99ca6898b14060aafe08f00827b3d7af1244f3a0
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 log

Security for lamp-the-djinn 0.1.6

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.

Select from 2 files

Attestation for lamp_the_djinn-0.1.6.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 log

A 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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page