{"id":50050914,"url":"https://github.com/nmicic/compartment","last_synced_at":"2026-05-21T09:12:19.226Z","repository":{"id":348348182,"uuid":"1196690954","full_name":"nmicic/compartment","owner":"nmicic","description":"Kernel-enforced sandboxing for untrusted processes. Two zero-dependency core tools, one shared profile format, plus an optional BPF-LSM module.","archived":false,"fork":false,"pushed_at":"2026-05-19T17:28:21.000Z","size":843,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-19T20:47:09.233Z","etag":null,"topics":["bpf-lsm","defense-in-depth","ebpf","hardening","landlock","linux","linux-security-module","namespace","privilege-separation","process-isolation","sandboxing","seccomp","security","syscall-filtering"],"latest_commit_sha":null,"homepage":"","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nmicic.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","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-31T00:19:21.000Z","updated_at":"2026-05-19T17:28:20.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/nmicic/compartment","commit_stats":null,"previous_names":["nmicic/compartment"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/nmicic/compartment","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nmicic%2Fcompartment","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nmicic%2Fcompartment/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nmicic%2Fcompartment/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nmicic%2Fcompartment/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nmicic","download_url":"https://codeload.github.com/nmicic/compartment/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nmicic%2Fcompartment/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33295415,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-21T02:57:32.698Z","status":"ssl_error","status_checked_at":"2026-05-21T02:57:31.990Z","response_time":62,"last_error":"SSL_read: 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":["bpf-lsm","defense-in-depth","ebpf","hardening","landlock","linux","linux-security-module","namespace","privilege-separation","process-isolation","sandboxing","seccomp","security","syscall-filtering"],"created_at":"2026-05-21T09:12:18.526Z","updated_at":"2026-05-21T09:12:19.215Z","avatar_url":"https://github.com/nmicic.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003c!-- Copyright (c) 2026 Nenad Mićić \u003cnenad@micic.be\u003e --\u003e\n\u003c!-- SPDX-License-Identifier: Apache-2.0 --\u003e\n\n# Compartment — Linux Process Isolation Toolkit\n\nKernel-enforced sandboxing for untrusted processes. Two zero-dependency\ncore tools, one shared profile format, plus an optional BPF-LSM module.\n\n\u003e **v1.3.0 note:** `compartment-user` and `compartment-root` are\n\u003e unchanged and remain the zero-dependency core. `compartment-bpf`\n\u003e is a new optional advanced module for kernel-level inode sealing,\n\u003e with its own kernel and toolchain requirements.\n\n\u003e **Note:** This is an open-source Linux isolation toolkit, not a\n\u003e formally validated security product. The code has been through\n\u003e multiple review rounds and 51 automated tests, but it has not\n\u003e undergone professional penetration testing or formal verification.\n\u003e The automated tests do not yet cover all bypass vectors (e.g.,\n\u003e direct network egress in sandbox mode, compartment-root under\n\u003e root). Use it as a defense-in-depth layer, not as your sole\n\u003e security boundary. See [DESIGN.md](DESIGN.md) for documented\n\u003e limits and the full security review log.\n\n## What\n\n| Tool | Purpose | Root? | Deps |\n|------|---------|-------|------|\n| **compartment-user** | Landlock + seccomp + env sanitize + audit | no | none |\n| **compartment-root** | Full namespace container + seccomp + audit | yes | none |\n| **sandbox.sh** | Network namespace + proxy bridge | no | unshare, socat, newuidmap |\n| **compartment-bpf** | Optional BPF LSM inode sealing (kernel-side deny, even root) | yes (CAP_BPF + CAP_SYS_ADMIN) | clang ≥ 12, libbpf, bpftool, libsodium, BTF, kernel ≥ 6.6 with `lsm=...,bpf` |\n\n`compartment-user` / `compartment-root` use Landlock + seccomp\n(userspace policy) and are the default zero-dependency path.\n`compartment-bpf` seals individual inodes at the BPF LSM level —\nenforcement lives in the kernel and survives even root. The two\napproaches are complementary. See `compartment-bpf/HOWTO.md` for\nthe BPF tool and its requirements.\n\n## Quick Start\n\n```bash\nmake\n./compartment-user -- /bin/sh          # sandboxed shell in 2 commands\n./compartment-user --dry-run -- /bin/sh # see what would be applied\n```\n\n## Build\n\n```bash\nmake                    # builds the zero-dependency core tools\nmake test               # run core tests (Landlock + seccomp + env + inheritance)\nmake test-integration   # run all tests (includes Claude CLI smoke test)\nmake hardened           # build with randomized shell stash path\n```\n\nOptional BPF module:\n\n```bash\ncd compartment-bpf\nmake vmlinux.h \u0026\u0026 make\n```\n\nRequires Linux kernel 6.6+ with BPF LSM enabled, plus clang,\nlibbpf-dev, bpftool, libsodium-dev, and BTF for the running kernel.\n\n## Usage\n\n```bash\n# Sandbox an AI agent (Landlock + seccomp, rootless)\n./compartment-user -- claude --model claude-opus-4-6\n\n# Use a profile file\n./compartment-user --profile strict -- codex --full-auto\n\n# See what would be applied without running\n./compartment-user --dry-run -- claude\n\n# Full namespace container (requires root)\n./compartment-root --profile examples/container.conf -- /bin/sh\n./compartment-root --rootdir /srv/jail -U svc --audit -- /usr/bin/myapp\n\n# Full isolation (network namespace + proxy + Landlock + seccomp)\n./sandbox.sh claude --model claude-opus-4-6\n```\n\n## Example: Hardened SSH (Privilege Separation for Network Clients)\n\nCompartment can lock down any network client — not just AI agents.\nHere is a worked example using SSH, showing how to split a process\ninto privilege-separated components so that no single compromise can\nboth access secrets AND exfiltrate them.\n\n### Problem\n\nIf a remote SSH server is compromised, it can reverse-exploit the SSH\nclient. A trojanized client could:\n- Write stolen credentials to `~/.ssh/exfil.txt`\n- Log keystrokes to a hidden file\n- Exfiltrate data over the network to a third-party host\n\n### Solution 1: Read-Only SSH (`ssh.conf`)\n\nLock the SSH client to read-only filesystem access. It can read keys\nto authenticate but cannot write anything to disk:\n\n```bash\n# One-liner: SSH with no filesystem writes\n./compartment-user --profile examples/ssh.conf -- ssh user@host\n\n# What happens if the SSH binary tries to write:\n#   touch /tmp/exfil.txt     → EACCES (blocked by Landlock)\n#   echo x \u003e ~/.ssh/log.txt  → EACCES (blocked by Landlock)\n#   cat ~/.ssh/id_ed25519    → OK (read allowed)\n```\n\n### Solution 2: Paranoid SSH (`paranoid-ssh.sh`)\n\nSplit SSH into two sandboxed processes with complementary restrictions:\n\n```\n┌──────────────────────────┐     ┌──────────────────────────┐\n│  SSH (read-only fs)      │────▶│  socat (no user files)   │────▶ remote:PORT\n│  • can read keys         │     │  • no $HOME access       │\n│  • cannot write anywhere │     │  • cannot read SSH keys  │\n│  • Landlock + seccomp    │     │  • Landlock + seccomp    │\n└──────────────────────────┘     └──────────────────────────┘\n         localhost:RANDOM_PORT\n```\n\n```bash\n# Paranoid SSH to a remote server\n./examples/paranoid-ssh.sh user@remote-host\n\n# With a custom port\n./examples/paranoid-ssh.sh user@remote-host -p 2222\n\n# Run a command\n./examples/paranoid-ssh.sh user@remote-host \"uptime\"\n```\n\n**Security properties:**\n- **SSH process** can read `~/.ssh/` keys but cannot write to disk\n  → a reverse-exploited SSH cannot save stolen data locally\n- **socat process** has network access but cannot read any user files\n  → even if socat is exploited, attacker cannot access credentials\n- **Neither process alone** can both access secrets AND exfiltrate them\n\nThis is the same principle as OpenSSH's own privilege separation, but\napplied at the OS level with Landlock + seccomp instead of trusting the\napplication to separate itself.\n\n### Why This Matters (2026 Paradigm)\n\nTraditional sysadmin thinking: \"SSH is trusted, the network is untrusted.\"\n\nCompartment thinking: \"Nothing is fully trusted. Split every process so\nthat compromise of any single component cannot achieve both data access\nand data exfiltration.\"\n\nThis pattern applies to any network client:\n- **curl/wget** — read-only profile prevents saving downloaded malware\n- **git** — read-only profile for fetch, write-only for the workdir\n- **database clients** — prevent credential logging to disk\n- **AI agents** — the primary use case (see `sandbox.sh`)\n\n## How It Works\n\n**compartment-user** applies kernel-enforced restrictions before exec:\n\n1. `PR_SET_NO_NEW_PRIVS` — prevent privilege escalation\n2. **Landlock** — filesystem path restrictions (read-only system paths, writable workdir)\n3. **seccomp BPF** — block dangerous syscalls (ptrace, mount, kexec, bpf, io_uring, ...)\n4. **Environment sanitize** — strip LD_PRELOAD, LD_LIBRARY_PATH, etc.\n5. **Audit logging** — file-per-day log with PPID chain\n\nAll restrictions are inherited by child processes and cannot be removed.\n\n**compartment-root** creates a fully isolated container:\n\n1. `clone()` with new UTS, mount, PID, IPC, net, user namespaces\n2. **pivot_root** — old root fully unmounted (stronger than chroot)\n3. Minimal `/dev`, masked `/proc`, isolated hostname\n4. **Capability drop** — raw prctl + capset, no libcap. `cap-allow` preserves\n   named capabilities for the service user via `PR_SET_KEEPCAPS` + `capset()`\n5. **seccomp BPF** — raw BPF, no libseccomp\n6. **Environment sanitize** + **audit logging** (same as compartment-user)\n\n**sandbox.sh** wraps the command in a network-isolated user+mount namespace:\n\n1. `unshare --user --mount --net` — HARD mode: loopback-only (no external interfaces);\n   SOFT fallback: slirp4netns with `--disable-host-loopback`\n2. Unix socket proxy bridge — API traffic routed through corporate proxy\n3. Bind-mount shell replacement — every `/bin/bash` subprocess gets sandboxed\n   (requires mount namespace, which sandbox.sh creates)\n\nIn HARD mode with a proxy configured, the namespace has no external\ninterfaces — network traffic is intended to flow only through the unix\nsocket proxy bridge. (The automated tests verify proxy reachability\nbut do not yet include direct-bypass resistance tests.)\n\n## Profile Files\n\nBoth tools share the same `.conf` format:\n\n```conf\n# Filesystem (compartment-user: Landlock)\nro /usr\nrw $HOME\n\n# Filesystem (compartment-root: namespaces)\nrootdir /srv/containers/default\nuid 1000\ngid 1000\nusername svc\nloopback on\n\n# Syscalls\nblock ptrace\nblock mount\n# Or allow-list mode:\n# allow read\n# allow write\n\n# Environment\nenv-deny LD_PRELOAD\n\n# Features\nseccomp on\nno-new-privs on\nenv-sanitize on\naudit on\n```\n\nSearch order: `--profile /path/file.conf` → `~/.config/compartment/\u003cname\u003e.conf` → `/etc/compartment/\u003cname\u003e.conf` → built-in.\n\nSee [HOWTO.md](HOWTO.md) for full format reference.\n\n## Examples\n\nEach profile addresses a different threat model. Pick the one that\nmatches what you're protecting against.\n\n| File | Use when | Protects against |\n|------|----------|------------------|\n| `ai-agent.conf` | Running Claude, Codex, Gemini CLIs | Agent reads/writes outside working directory, spawns unexpected processes |\n| `strict.conf` | Untrusted code, tighter than ai-agent | Same as above, smaller syscall surface |\n| `ssh.conf` | Running SSH client on a box you don't fully trust | Compromised SSH binary writing credentials to disk |\n| `socat-proxy.conf` | Used internally by `paranoid-ssh.sh` | socat having access to your SSH keys |\n| `container.conf` | Full namespace isolation via compartment-root | Process escaping its root directory |\n| `dev.conf` | Development and debugging | Nothing — this is intentionally relaxed |\n\n**Which one should I use?**\n\n- **Sandboxing an AI agent** → `ai-agent.conf` (default) or `strict.conf` (tighter)\n- **Connecting to a remote server** → `paranoid-ssh.sh`, which combines\n  `ssh.conf` + `socat-proxy.conf`. The SSH process can read your keys but\n  cannot write anywhere. The socat process handles the network connection\n  but cannot read your keys. Neither alone can both steal credentials and\n  exfiltrate them.\n- **Running an untrusted service** → `container.conf` with `compartment-root`\n- **Figuring out why something is being blocked** → `dev.conf`, then tighten\n  from there\n\n## Profiling Any Program\n\nDon't write profiles by hand. Use `tools/syscall.py` to generate one\nfor any program automatically:\n\n```bash\n# Step 1: Check — will the default profile break your program?\npython3 tools/syscall.py check --profile ai-agent -- wget -q -O /dev/null https://example.com\n\n# Step 2: If it breaks, generate a custom profile\npython3 tools/syscall.py profile -o examples/wget.conf -- wget -q -O /dev/null https://example.com\n\n# Step 3: Use it\n./compartment-user --profile examples/wget.conf -- wget https://example.com\n```\n\nWorks with anything: `curl`, `git`, `ssh`, `rsync`, `python3`, database\nclients — any program you can run, you can profile and sandbox.\n\n```bash\n# More examples\npython3 tools/syscall.py profile -o curl.conf -- curl -s https://example.com\npython3 tools/syscall.py profile -o git.conf  -- git clone https://github.com/user/repo\npython3 tools/syscall.py profile -o psql.conf -- psql -c \"SELECT 1\"\n\n# Strict allow-list (only permit observed syscalls, deny everything else)\npython3 tools/syscall.py profile --seccomp-mode allow -o strict-curl.conf -- curl https://example.com\n\n# See what syscalls a program actually uses\npython3 tools/syscall.py trace -- ssh user@host \"echo hello\"\n```\n\nRequires `strace` (`apt install strace`). See\n[tools/HOWTO-syscall-profiling.md](tools/HOWTO-syscall-profiling.md)\nfor the full guide.\n\n## Shell Replacement\n\ncompartment-user can transparently intercept `/bin/bash` so every\nsubprocess an AI agent spawns gets sandboxed:\n\n```\n/bin/bash (bind mount) → compartment-user\n  → Landlock + seccomp applied\n  → exec /bin/shells/bash (the real shell)\n```\n\nThis happens automatically inside `sandbox.sh` when compartment-user\nis built and available. See [HOWTO.md](HOWTO.md) for manual setup options.\n\n## Advanced Deployment: Compartmented Login Shell\n\nCompartment can be deployed as the login shell for non-admin users,\nso that every interactive session and every `execve(\"/bin/sh\", ...)`\n— including remote exploit payloads — enters a sandboxed shell\nautomatically.\n\nThis is an opinionated setup for controlled environments (hardened\nservers, jump boxes, CI runners), not a universal recommendation.\n\n### Setup\n\n```bash\n# Build with a randomized shell stash path\nmake hardened\n# Output: REAL_SHELL_DIR=/bin/.shells_a1b2c3d4e5f6\n\n# Preserve real shells in the stash directory\nsudo mkdir -p /bin/.shells_a1b2c3d4e5f6\nsudo mv /bin/bash /bin/.shells_a1b2c3d4e5f6/bash\nsudo mv /bin/sh   /bin/.shells_a1b2c3d4e5f6/sh\n\n# Install compartment-user as the system shell\nsudo cp compartment-user /bin/bash\nsudo ln -sf /bin/bash /bin/sh\n\n# Preserve a normal shell for the designated admin account\nsudo chsh -s /bin/.shells_a1b2c3d4e5f6/bash root\nsudo chsh -s /bin/.shells_a1b2c3d4e5f6/bash your-admin-user\n```\n\nWhen invoked as `bash` or `sh` (detected via `argv[0]`),\ncompartment-user applies the `ai-agent` profile and execs the real\nshell from the stash directory.\n\n### Privilege model\n\n```\nroot / admin  →  /bin/.shells_.../bash  (real shell, no sandbox)\nall others    →  /bin/bash              (compartment → sandboxed shell)\n                 Landlock + seccomp + env sanitize + audit\n```\n\n### What this stops\n\nA remote exploit that calls `execve(\"/bin/sh\", ...)` gets compartment,\nnot bash. The payload hits Landlock filesystem restrictions and seccomp\nsyscall filtering before executing a single attacker-controlled\ninstruction. The sandboxed shell cannot `ptrace`, cannot load kernel\nmodules, cannot mount filesystems, and writes only to allowed paths.\n\n### Caveats\n\n- **Compatibility**: some workflows expect an unrestricted interactive\n  shell and may break. Test thoroughly before deploying to production.\n- **Not a substitute** for correct host hardening, patching, and\n  privilege separation. This is a defense-in-depth layer.\n- **Bypass paths exist**: an attacker who can write an ELF binary to\n  an executable path and invoke it directly (not through `/bin/sh`)\n  will bypass the shell-replacement layer. Landlock on the parent\n  process limits where they can write, but this is not airtight.\n- **Recovery**: always keep at least one admin account with a real\n  shell. If compartment-user has a bug, you need a way back in.\n\n## Requirements\n\n- Linux \u003e= 5.13 (Landlock) — compartment-user\n- Linux \u003e= 4.6 (cgroup namespace) — compartment-root\n- Linux \u003e= 3.8 (user namespaces) — sandbox.sh\n- No external libraries. No root for compartment-user.\n\n## Files\n\n```\ncompartment.h          — shared code: profiles, audit, seccomp BPF, env sanitize\ncompartment-user.c     — Landlock + seccomp + audit (zero deps, rootless)\ncompartment-root.c     — Full namespace container (zero deps, requires root)\nsandbox.sh             — Network namespace + proxy bridge\nMakefile               — Build targets\nHOWTO.md               — Detailed setup guide\nDESIGN.md              — Architecture, security review, lineage from shell-guard\nSECURITY.md            — Vulnerability reporting policy\nexamples/\n  ai-agent.conf        — Profile for Claude/Codex/Gemini\n  strict.conf          — Locked-down profile (inherits ai-agent)\n  container.conf       — Full namespace isolation profile\n  dev.conf             — Relaxed profile for development\n  ssh.conf             — Read-only SSH client (no filesystem writes)\n  socat-proxy.conf     — Network-only socat bridge (no user file access)\n  paranoid-ssh.sh      — Privilege-separated SSH (SSH+socat split)\ntools/\n  syscall.py           — Profile generator: trace any program, emit .conf\n  HOWTO-syscall-profiling.md — Full guide to syscall profiling\nman/\n  compartment-user.1   — Man page (section 1: user commands)\n  compartment-root.8   — Man page (section 8: system administration)\ntests/\n  probes/deny_probe.c  — Sandbox validation probe (machine-parseable output)\n  profiles/            — Test-specific .conf profiles\n  scripts/run_all.sh   — Top-level test runner (52 tests across 4 suites)\n  README.md            — Test documentation\narchive/\n  shell-guard/         — Archived shell-replacement tool (~2003, self-contained)\n```\n\n## vs Alternatives\n\n```\n                          root required?\n                          no              yes\n                        ┌───────────────┬───────────────┐\n  filesystem            │ compartment-  │ compartment-  │\n  restriction           │ user          │ root          │\n  mechanism             │ (Landlock)    │ (pivot_root)  │\n                        │               │               │\n                        │ Firejail      │ bwrap (setuid)│\n                        │ bwrap (userns)│ Docker/Podman │\n                        ├───────────────┼───────────────┤\n  no filesystem         │ seccomp-only  │ AppArmor      │\n  restriction           │ wrappers      │ SELinux       │\n                        └───────────────┴───────────────┘\n```\n\n- **Firejail** (~100K lines) — closest comparison; mature profile ecosystem\n  for desktop apps, but large attack surface with CVE history.\n  compartment-user is 100x smaller and auditable in one sitting.\n- **bwrap** (~3K lines) — mount/PID/network namespaces. Architecturally\n  different (namespaces vs Landlock). Use bwrap when you need full mount\n  isolation or kernel \u003c 5.13; use compartment-user when you need profiles,\n  shell-replacement, or work in containers where user namespaces are disabled.\n- **Minijail** (Google) — expressive seccomp arg filtering, but requires\n  libminijail. compartment-user trades arg filtering for zero-dep deployment.\n- **AppArmor/SELinux** — system-wide MAC, finer granularity, but requires\n  admin access and system policy installation. compartment-user is\n  user-deployable with no system configuration changes.\n\nNo existing tool combines: zero deps, profile files with inheritance,\nshell-replacement mode, and PPID chain audit logging in ~1600 lines.\n\n## Related\n\n- [bubblewrap](https://github.com/containers/bubblewrap) — Namespace-based sandboxing (complementary)\n- [firejail](https://github.com/netblue30/firejail) — Namespace + seccomp (setuid, profile files)\n\n## Development\n\nThis project was developed with AI assistance:\n\n- **[Claude Code](https://claude.ai/code)** (Anthropic) — primary coding,\n  testing, debugging, and implementation across all C source, shell scripts,\n  profiles, and test infrastructure\n- **ChatGPT** (OpenAI), **Gemini** (Google), **Codex** (OpenAI) — independent\n  code review rounds that identified 18 security bugs, all fixed before release\n- **Human** — architecture, design decisions, review coordination, and final\n  approval\n\n## License\n\nApache-2.0. See [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnmicic%2Fcompartment","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnmicic%2Fcompartment","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnmicic%2Fcompartment/lists"}