labgate
v0.5.30
Published
Secure HPC wrapper for AI coding agents (Claude-first): policy controls, Apptainer sandboxes, and SLURM workflows — https://labgate.dev
Maintainers
Readme
LabGate
Policy-controlled sandboxes for AI coding agents. Built for HPC clusters.
Current Product Focus
- Primary workflow: Claude (
labgate claude) - Primary runtime: Apptainer on HPC
- macOS runtime: Podman (best-effort fallback path)
- Secondary targets (best-effort): other agents
Install
npm i -g labgateNote: LabGate uses node-pty only for the optional sticky footer. On minimal Linux installs, that dependency may fail to build without a compiler toolchain. If it fails, the install still works and LabGate falls back to non-sticky output.
Note: tmux is a host-level dependency for labgate ui / web terminals. npm i -g labgate does not install tmux; install it through your OS or cluster module system.
LabGate prefers Apptainer for sandbox runtime and supports Podman as a fallback (especially on macOS).
On UI startup, LabGate ensures a bundled sample dataset (flowers-iris) is available at ~/.labgate/datasets/flowers-iris for first-run testing.
Quick start
labgate init # optional: pre-create ~/.labgate/config.json
labgate claude # launch Claude Code in current dir
labgate codex /projects/my-analysis # launch Codex in a specific dirWhat it does
LabGate runs your AI coding agent inside a sandboxed container with:
- Scoped filesystem — only your working directory and configured paths are visible
- Credential blocking —
.ssh,.aws,.env,.gnupg, and other sensitive paths are hidden by default - Network policy — configurable network modes (
host,filtered,none) - Command blocking —
ssh,curl,wget, and other commands are blocked by default - Audit logging — session start/stop and mount configuration logged to
~/.labgate/logs/ - Dashboard instructions editor — view and update per-session
AGENTS.md/CLAUDE.mdfrom the UI - Session context injection — LabGate prepends a temporary sandbox-mapping instruction block during active sessions
- HPC ready — first-class Apptainer support for shared clusters
Configuration
Edit ~/.labgate/config.json to customize:
$EDITOR ~/.labgate/config.jsonOr start fresh:
labgate init --forceOr reset a single setting back to defaults:
labgate config reset imageKey settings
| Setting | Default | What it does |
|---------|---------|-------------|
| runtime | auto | auto, apptainer, or podman |
| image | docker.io/library/node:20-bookworm | Container image |
| session_timeout_hours | 8 | Max session length |
| filesystem.blocked_patterns | .ssh, .aws, .env, ... | Hidden from sandbox |
| filesystem.extra_paths | [] | Additional mounts |
| network.mode | host | none, filtered, or host |
| commands.blacklist | ssh, curl, wget, ... | Blocked commands |
| slurm.enabled | true | Enable SLURM CLI passthrough (sbatch, squeue, etc.) and job tracking |
Commands
labgate claude [workdir] # launch Claude Code
labgate codex [workdir] # launch Codex
labgate feedback # submit feedback (interactive or piped)
labgate status # list running sessions
labgate stop <id> # stop a session
labgate ui # start dashboard server on localhost:7700 (auth token required, tmux required)
labgate register <activation-key> [--server <url>] # activate + install enterprise license
labgate license # show enterprise license status
labgate license install <key-or-file> [--system|--user|--path] # install enterprise license key
labgate policy init [--institution ... --admin ...] # create policy template
labgate policy validate [file] # validate policy JSON
labgate logs [-n 20] # view recent audit events
labgate logs --follow # stream new audit events
labgate init [--force] # create/reset configOptions
labgate claude --dry-run # print the sandbox command without running
labgate claude --image my-image:tag # use a different container image
labgate claude --no-footer # disable the status footer line
labgate ui # localhost UI on 7700, logs full token URL + short /s/<code> quick link
labgate ui --socket ~/.labgate/ui.sock # custom Unix socket path
labgate logs --lines 50 --follow # tail last 50 lines and keep followinglabgate claude auto-starts labgate ui when missing in local (non-SSH/non-SLURM) shells.
SLURM inside sandboxes (sbatch / squeue)
For Apptainer sessions, LabGate now attempts SLURM CLI passthrough automatically.
If host sbatch/squeue are available, they are staged into the sandbox, so
labgate claude should work without extra config in the common HPC path.
SLURM tracking and MCP tools are enabled by default (slurm.enabled=true).
If native SQLite (better-sqlite3) is unavailable on a host, LabGate falls back
to a JSON tracking store automatically.
Requirements for automatic sbatch in sandbox:
- Runtime is Apptainer
- The host can resolve SLURM CLI tools when launching LabGate
If sbatch is missing inside the sandbox, run:
which sbatch # on host, before launching labgate
labgate claudeIf your cluster uses environment modules, load SLURM first (host shell), then launch LabGate:
module load slurm # or your site-specific module name
labgate claudeOptional (disable SLURM tracking DB + MCP server):
labgate config set slurm.enabled falseFeedback
Submit feedback from the CLI:
labgate feedback
echo "This was great" | labgate feedback
labgate feedback "Short feedback message"If LABGATE_FEEDBACK_URL is set, LabGate will POST feedback JSON to that URL.
If LABGATE_FEEDBACK_TOKEN is set, it will be sent as a Bearer token.
If no URL is configured, LabGate defaults to https://labgate.dev/api/feedback.
If the request fails (or LABGATE_FEEDBACK_DISABLE=1), feedback is saved locally at ~/.labgate/feedback.jsonl.
Testing
Run tests:
npm run setup # install dependencies
npm run verify:quick # build + unit tests
npm run verify # build + unit + integration tests
npm run dev:claude # start UI in background, then launch local labgate claude
npm run test:unit # fast unit tests
npm run test:integration # dashboard integration tests
npm run test:e2e:real # opt-in real runtime OAuth/browser smoke test
npm test # unit + integration
npm run release:check # verify + npm pack --dry-runCorporate mode smoke test (local or HPC)
# 1) Create a signed enterprise key (issuer side)
export LABGATE_LICENSE_SECRET='replace-with-your-signing-secret'
LICENSE_KEY="$(npx tsx scripts/generate-license.ts \
--institution 'Example University' \
--tier pro \
--expires 2099-12-31 2>/dev/null)"
# 2) Install key on target host (admin side)
labgate license install "$LICENSE_KEY" --path /tmp/labgate/license.key --overwrite
# HPC system-wide install (root):
# sudo labgate license install "$LICENSE_KEY" --system --overwrite
# Optional online activation (instead of direct install):
# Uses default endpoint: https://labgate.dev/api/license/activate
# labgate register '<activation-key-from-vendor>' --path /tmp/labgate/license.key --overwrite
# Optional custom endpoint:
# export LABGATE_ACTIVATION_URL='https://your-control-plane.example.com/api/license/activate'
# 3) Bootstrap and validate policy
labgate policy init --path /tmp/labgate/policy.json --admin "$(whoami)" --force
labgate policy validate /tmp/labgate/policy.json
# 4) Verify forced settings are locked for users
LABGATE_LICENSE_PATH=/tmp/labgate/license.key \
LABGATE_POLICY_PATH=/tmp/labgate/policy.json \
labgate config set runtime auto
# expected: error about admin-locked field
# 5) Open dashboard and verify UI lock labels ("set by admin")
LABGATE_LICENSE_PATH=/tmp/labgate/license.key \
LABGATE_POLICY_PATH=/tmp/labgate/policy.json \
labgate uiAutomated enterprise coverage:
npx vitest run -c vitest.integration.config.ts src/lib/cli.enterprise.integration.test.ts src/lib/ui.integration.test.tsCI automation runs npm run verify on every push to main and on pull requests (.github/workflows/ci.yml).
npm run test:integration automatically rebuilds better-sqlite3 first to avoid Node ABI mismatch errors after Node upgrades.
Dev launcher options:
LABGATE_UI_PORT=7711 npm run dev:claude -- /path/to/workdir # custom UI port + workdir
LABGATE_KEEP_UI=1 npm run dev:claude -- . # keep UI running after claude exitsCoverage:
npm run test:unitcovers config/runtime/container helpersnpm run test:integrationcovers dashboard flow that:- Builds the CLI
- Starts
labgate uion a temporary localhost port - Launches
labgate claudeandlabgate codexwith a mocked runtime - Verifies sessions appear in
/api/sessions(dashboard data source) - Stops a session through
POST /api/sessions/stopand verifies it disappears - Verifies UI port fallback when requested ports are occupied
- Verifies
/api/configaccepts valid payloads and rejects invalid payloads without mutating config - Verifies
labgate logs --followprints tail output and streams newly appended events - Verifies dashboard activity inference for accessed files and websites from agent transcripts
- Verifies
GET/PUT /api/sessions/:id/instructionswith conflict detection forAGENTS.mdandCLAUDE.md
npm run test:e2e:realruns a real runtime smoke test for OAuth/browser opening:- Launches real
labgate claude(no mocked container runtime) - Waits for OAuth URL flow
- Verifies host browser-open hook is triggered
- Optional override:
LABGATE_REAL_E2E_IMAGE
- Launches real
How it works
LabGate builds a sandboxed container from your config:
- Detects Apptainer first, then Podman (or uses explicit runtime)
- Mounts your working directory at
/work - Mounts persistent sandbox HOME at
/home/sandbox(for npm cache, agent config) - Overlays blocked paths (
.ssh,.aws, etc.) with empty mounts - Applies network isolation and capability restrictions
- Installs the agent (if not cached) and runs it interactively
On macOS, LabGate syncs your Claude credentials from the system keychain so the agent can authenticate automatically.
Audit logs
Session events are logged to ~/.labgate/logs/YYYY-MM-DD.jsonl:
cat ~/.labgate/logs/2025-02-05.jsonl | jq .Roadmap
- M0 CLI + sandbox engine + config + audit (this release)
- M1 Mount allowlists, network filtering, project-level config
- M2 SLURM proxy (submit/status/cancel from inside sandbox)
- M3 Web UI for config + audit viewer
- M4 Institutional mode (/etc/labgate/ policies, admin locks)
License
MIT
