{"id":47718045,"url":"https://github.com/calebfaruki/airlock","last_synced_at":"2026-04-02T19:08:01.535Z","repository":{"id":345153397,"uuid":"1184730403","full_name":"calebfaruki/airlock","owner":"calebfaruki","description":"Credential isolation for agent containers","archived":false,"fork":false,"pushed_at":"2026-03-25T13:24:30.000Z","size":183,"stargazers_count":15,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-03-26T13:39:28.344Z","etag":null,"topics":["agents","docker"],"latest_commit_sha":null,"homepage":"https://calebfaruki.github.io/airlock","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/calebfaruki.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-17T22:00:39.000Z","updated_at":"2026-03-25T13:24:36.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/calebfaruki/airlock","commit_stats":null,"previous_names":["calebfaruki/airlock"],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/calebfaruki/airlock","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/calebfaruki%2Fairlock","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/calebfaruki%2Fairlock/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/calebfaruki%2Fairlock/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/calebfaruki%2Fairlock/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/calebfaruki","download_url":"https://codeload.github.com/calebfaruki/airlock/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/calebfaruki%2Fairlock/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31313956,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T12:59:32.332Z","status":"ssl_error","status_checked_at":"2026-04-02T12:54:48.875Z","response_time":89,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agents","docker"],"created_at":"2026-04-02T19:08:00.726Z","updated_at":"2026-04-02T19:08:01.522Z","avatar_url":"https://github.com/calebfaruki.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Airlock\n\n[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/calebfaruki/airlock/badge)](https://securityscorecards.dev/viewer/?uri=github.com/calebfaruki/airlock)\n[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/12214/badge)](https://www.bestpractices.dev/projects/12214)\n[![made-with-rust](https://img.shields.io/badge/Made%20with-Rust-1f425f.svg)](https://www.rust-lang.org/)\n\nCredential isolation for CLI tools in Docker containers. Proxies git, aws, terraform, and other commands that need SSH keys, cloud configs, or local credentials  without mounting secrets into the container.\n\n## How It Works\n\nThree components:\n\n1. **Container-side shim** — a single static binary inside the container. Reads `argv[0]` to determine which command is being proxied (busybox pattern). Installed via symlinks in the user's Dockerfile.\n\n2. **Daemon** — a long-running process on the host (local) or a shared container (remote compose/k8s). Listens on a unix socket, receives JSON-RPC requests from the shim, executes commands with real credentials, and streams output back.\n\n3. **Command directory** — TOML-based modules defining how each CLI tool is proxied. Built-in modules ship with conservative deny rules. Unknown commands are rejected.\n\nThe shim sends a request, the daemon looks it up in the command directory, applies deny rules and environment isolation, executes the command, and streams output back. The container never sees credentials — they live on the host.\n\n## Why Airlock?\n\nNetwork-level credential proxies (Docker Sandboxes, NemoClaw) protect HTTP API keys by intercepting outbound HTTPS requests and injecting auth headers. But many developer tools don't authenticate over HTTP — they use local files:\n\n- **git** — SSH keys, credential helpers\n- **aws / gcloud / az** — IAM credentials, service account keys\n- **terraform** — inherits cloud CLI credentials\n- **docker push/pull** — registry auth in `~/.docker/config.json`\n- **kubectl / helm** — kubeconfig with cluster certificates\n- **npm / pip / cargo** — registry tokens for private packages\n- **ssh / scp** — SSH keys\n\nThese tools authenticate via files on the host filesystem. No network proxy can intercept that. Airlock solves this by proxying the CLI commands themselves — the container asks the host to run the command, and the host executes it with real credentials. The container never sees the keys.\n\nUse network proxies for HTTP API keys. Use Airlock for everything else.\n\n## Installation\n\n### Local Install (Linux/macOS)\n\n```sh\ncurl -fsSL https://raw.githubusercontent.com/calebfaruki/airlock/main/install.sh | sh\n```\n\nThis downloads the daemon, installs it to `~/.local/bin/`, and runs `airlock init` to set up the system service.\n\n### Remote Deployment\n\nFor Docker Compose or Kubernetes deployments, use the daemon container image:\n\n```sh\ndocker pull ghcr.io/calebfaruki/airlock-daemon:latest\n```\n\n### Container Setup\n\nCopy the shim from the container image or download from [releases](https://github.com/calebfaruki/airlock/releases):\n\n```dockerfile\nCOPY --from=ghcr.io/calebfaruki/airlock-shim:latest /airlock-shim /usr/local/airlock/bin/airlock-shim\nRUN chmod +x /usr/local/airlock/bin/airlock-shim\nENV PATH=\"/usr/local/airlock/bin:$PATH\"\nRUN ln -s airlock-shim /usr/local/airlock/bin/git \\\n    \u0026\u0026 ln -s airlock-shim /usr/local/airlock/bin/terraform \\\n    \u0026\u0026 ln -s airlock-shim /usr/local/airlock/bin/aws\n```\n\n### Agent Configuration\n\nEach agent has its own working directory containing an `airlock.toml` profile:\n\n```\nmy-agent/\n├── airlock.toml          # required — profile\n├── commands/             # optional — per-agent command overrides\n└── hooks/                # optional — per-agent hooks\n```\n\n```sh\nmkdir -p my-agent\ncat \u003e my-agent/airlock.toml \u003c\u003c 'EOF'\ncommands = [\"git\", \"gh\"]\n\n[env]\nset = { GIT_SSH_COMMAND = \"ssh -i ~/.ssh/project_a_key\" }\nEOF\n```\n\nRegister agents in a TOML file:\n\n```toml\n# agents.toml\n[my-agent]\npath = \"/home/admin/agents/my-agent\"\n```\n\nStart the daemon with: `airlock-daemon start --agents agents.toml`\n\n### Docker Run (Local)\n\nMount the agent's socket into the container. The shim connects to `/run/docker-airlock.sock` inside the container.\n\n```sh\ndocker run \\\n    -v ~/.config/airlock/sockets/my-agent.sock:/run/docker-airlock.sock \\\n    your-image\n```\n\n### Docker Compose (Remote)\n\nThe daemon and agent run as containers sharing a socket volume:\n\n```yaml\nservices:\n  airlock:\n    image: ghcr.io/calebfaruki/airlock-daemon:latest\n    command: [\"start\", \"--config\", \"/etc/airlock\", \"--sockets\", \"/run/airlock/sockets\", \"--agents\", \"/etc/airlock/agents.toml\"]\n    volumes:\n      - sockets:/run/airlock/sockets\n      - ./agents.toml:/etc/airlock/agents.toml:ro\n      - ./agents:/agents:ro\n\n  my-agent:\n    image: your-agent-image\n    volumes:\n      - sockets:/run/airlock/sockets\n\nvolumes:\n  sockets:\n```\n\n## Usage\n\n### Daemon\n\n```sh\nairlock-daemon start --agents agents.toml  # Local: start with registered agents\nairlock-daemon start --config /etc/airlock --sockets /run/sockets --agents agents.toml  # Container\nairlock-daemon start                       # Start with zero agents\nairlock-daemon init                        # Install as system service (systemd/launchd)\nairlock-daemon init --uninstall            # Remove system service\nairlock-daemon check --agents agents.toml  # Validate builtins + agent configs\nairlock-daemon doctor --agents agents.toml # Check host binaries and Docker\nairlock-daemon test --agents agents.toml my-agent git push  # Dry-run evaluation\nairlock-daemon version                     # Print version\nairlock-daemon show git                    # Print built-in module\nairlock-daemon profile list --agents agents.toml   # List agents and sockets\nairlock-daemon profile show --agents agents.toml my-agent  # Print agent profile\n```\n\n### Hooks\n\nPlace executable scripts in the agent's `hooks/` directory:\n\n- `pre-exec` — receives the JSON-RPC request on stdin. Exit 0 to allow, non-zero to deny. Write modified JSON to stdout to rewrite the request.\n- `post-exec` — receives the JSON-RPC response on stdin. Exit 0 with modified JSON on stdout to alter output. Non-zero exit passes through the original response.\n\n### Logging\n\nEvery request is logged to `~/.local/share/airlock/airlock.log` as NDJSON. Configure rotation in `~/.config/airlock/config.toml`:\n\n```toml\n[log]\npath = \"~/.local/share/airlock/airlock.log\"\nmax_size_mb = 50\nmax_files = 5\n```\n\n## Enabling Commands\n\nCommand modules are opt-in. List the commands your agents need in `~/.config/airlock/config.toml`:\n\n```toml\n[commands]\nenable = [\"git\", \"terraform\"]\n```\n\nThe daemon exits on startup if `commands.enable` is missing or empty. Only enabled commands are loaded — requests for anything else return \"unknown command\".\n\nAirlock ships with built-in modules for: `git`, `terraform`, `aws`, `ssh`, `docker`. Each has conservative deny rules. Run `airlock-daemon show \u003ccommand\u003e` to see the active configuration.\n\nTo add a custom command or override a built-in, create a TOML file in the agent's `commands/` directory and add the name to `commands.enable`:\n\n```toml\n# my-agent/commands/deploy-cli.toml\n[command]\nbin = \"deploy-cli\"\n\n[deny]\nargs = []\n```\n\n### Three-Layer Security Model\n\nThree independent layers restrict what an agent can do:\n\n1. **Daemon module list** (`commands.enable`) — ceiling that no profile can exceed\n2. **Profile `commands` list** — per-container restriction within the ceiling\n3. **Deny rules** — per-module restriction on flags and arguments\n\nEach layer is independent. A mistake in one layer doesn't compromise the others.\n\n\u003e **SSH requires extra caution.** SSH is remote code execution on external servers. See [`ssh/SECURITY.md`](crates/airlock-daemon/src/commands/builtins/ssh/SECURITY.md) before enabling it.\n\n## Built-in Protections\n\nBuilt-in command modules ship with security hardening based on known attack vectors. Each module has a [`SECURITY.md`](crates/airlock-daemon/src/commands/builtins/) documenting the threat model behind every deny rule and environment variable.\n\nRun `airlock-daemon show \u003ccommand\u003e` to see the active configuration. To customize, add a `commands/\u003cname\u003e.toml` to the agent's directory.\n\n## Observability\n\nOn startup, the daemon prints loaded commands to stderr:\n\n```\nairlock: enabled commands: git, terraform\n```\n\nRun `airlock-daemon check --agents agents.toml` before restarting to catch TOML syntax errors and missing binaries early.\n\n## Agents\n\nEach agent has its own working directory registered via a TOML file passed with `--agents`. The daemon creates one socket per agent at `~/.config/airlock/sockets/\u003cagent\u003e.sock`.\n\n### Profile Schema (`airlock.toml`)\n\n```toml\n# Required: allowlist of commands. Use [] to allow all.\ncommands = [\"git\", \"gh\"]\n\n# Optional: environment variables injected before command execution.\n[env]\nset = { GIT_SSH_COMMAND = \"ssh -i ~/.ssh/project_a_key\", AWS_PROFILE = \"readonly\" }\n```\n\nThe `commands` field is required. Use `commands = []` to allow all commands. An empty file without `commands` is rejected at startup.\n\n### Env Merge Order\n\nThree sources, applied in order:\n\n1. **Command module `[env] strip`** — removes dangerous vars. Always wins.\n2. **Profile `[env] set`** — injects credential vars.\n3. **Command module `[env] set`** — injects hardening vars. Overrides profile on conflict.\n\nA profile cannot override security hardening set by a command module.\n\n### Socket Paths\n\n| Path |\n|------|\n| `~/.config/airlock/sockets/\u003cagent\u003e.sock` |\n\n### Zero Agents\n\nThe daemon starts with zero agents and zero sockets if no `--agents` flag is provided.\n\n## Upgrading\n\n### From v0.3.x\n\nv0.4.0 replaces `~/.config/airlock/profiles/` with the `--agents` registration file. Create agent directories with `airlock.toml`, write a registration file, and pass it via `--agents`. The `diff` and `eject` subcommands have been removed — agent overrides go in the agent's `commands/` directory.\n\n### From v0.1.x\n\nv0.2.0 requires explicit command enablement. Add `[commands] enable` to `~/.config/airlock/config.toml`.\n\n## Security Model\n\n- The daemon never passes arguments through a shell — always `execve` with an explicit arg array.\n- Unknown commands are rejected. The command directory is an allowlist.\n- The container never holds credentials.\n- Agent overrides are full replace — no merging with built-ins.\n- Built-in modules are compiled into the binary and upgrade with the daemon.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcalebfaruki%2Fairlock","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcalebfaruki%2Fairlock","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcalebfaruki%2Fairlock/lists"}