labgate 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,107 @@
1
+ # LabGate
2
+
3
+ Policy-controlled sandboxes for LLM coding agents on HPC systems.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm i -g labgate
9
+ ```
10
+
11
+ LabGate auto-detects your container runtime (Apptainer, Singularity, Podman, or Docker) and offers to install one if none is found.
12
+
13
+ ## Quick start
14
+
15
+ ```bash
16
+ labgate init # create ~/.labgate/config.json
17
+ labgate claude # launch Claude Code in current dir
18
+ labgate codex /projects/my-analysis # launch Codex in a specific dir
19
+ ```
20
+
21
+ ## What it does
22
+
23
+ LabGate runs your AI coding agent inside a sandboxed container with:
24
+
25
+ - **Scoped filesystem** — only your working directory and configured paths are visible
26
+ - **Credential blocking** — `.ssh`, `.aws`, `.env`, `.gnupg`, and other sensitive paths are hidden by default
27
+ - **Network isolation** — no outbound network by default (configurable)
28
+ - **Command blocking** — `ssh`, `curl`, `wget`, and other commands are blocked by default
29
+ - **Audit logging** — session start/stop and mount configuration logged to `~/.labgate/logs/`
30
+ - **HPC ready** — first-class Apptainer/Singularity support for shared clusters
31
+
32
+ ## Configuration
33
+
34
+ Edit `~/.labgate/config.json` to customize:
35
+
36
+ ```bash
37
+ $EDITOR ~/.labgate/config.json
38
+ ```
39
+
40
+ Or start fresh:
41
+
42
+ ```bash
43
+ labgate init --force
44
+ ```
45
+
46
+ ### Key settings
47
+
48
+ | Setting | Default | What it does |
49
+ |---------|---------|-------------|
50
+ | `runtime` | `auto` | `auto`, `apptainer`, `singularity`, `podman`, or `docker` |
51
+ | `image` | `node:20-slim` | Container image |
52
+ | `session_timeout_hours` | `8` | Max session length |
53
+ | `filesystem.blocked_patterns` | `.ssh, .aws, .env, ...` | Hidden from sandbox |
54
+ | `filesystem.extra_paths` | `[]` | Additional mounts |
55
+ | `network.mode` | `none` | `none`, `filtered`, or `host` |
56
+ | `commands.blacklist` | `ssh, curl, wget, ...` | Blocked commands |
57
+
58
+ ## Commands
59
+
60
+ ```bash
61
+ labgate claude [workdir] # launch Claude Code
62
+ labgate codex [workdir] # launch Codex
63
+ labgate status # list running sessions
64
+ labgate stop <id> # stop a session
65
+ labgate init [--force] # create/reset config
66
+ ```
67
+
68
+ ### Options
69
+
70
+ ```bash
71
+ labgate claude --dry-run # print the sandbox command without running
72
+ labgate claude --image my-image:tag # use a different container image
73
+ labgate claude --no-footer # disable the status footer line
74
+ ```
75
+
76
+ ## How it works
77
+
78
+ LabGate builds a sandboxed container from your config:
79
+
80
+ 1. Detects the best available runtime (apptainer > singularity > podman > docker)
81
+ 2. Mounts your working directory at `/work`
82
+ 3. Mounts persistent sandbox HOME at `/home/sandbox` (for npm cache, agent config)
83
+ 4. Overlays blocked paths (`.ssh`, `.aws`, etc.) with empty mounts
84
+ 5. Applies network isolation and capability restrictions
85
+ 6. Installs the agent (if not cached) and runs it interactively
86
+
87
+ On macOS, LabGate syncs your Claude credentials from the system keychain so the agent can authenticate automatically.
88
+
89
+ ## Audit logs
90
+
91
+ Session events are logged to `~/.labgate/logs/YYYY-MM-DD.jsonl`:
92
+
93
+ ```bash
94
+ cat ~/.labgate/logs/2025-02-05.jsonl | jq .
95
+ ```
96
+
97
+ ## Roadmap
98
+
99
+ - **M0** CLI + sandbox engine + config + audit (this release)
100
+ - **M1** Mount allowlists, network filtering, project-level config
101
+ - **M2** SLURM proxy (submit/status/cancel from inside sandbox)
102
+ - **M3** Web UI for config + audit viewer
103
+ - **M4** Institutional mode (/etc/labgate/ policies, admin locks)
104
+
105
+ ## License
106
+
107
+ MIT
package/bin/labgate.js ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+
3
+ require('../dist/cli.js');
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const commander_1 = require("commander");
4
+ const path_1 = require("path");
5
+ const fs_1 = require("fs");
6
+ const config_js_1 = require("./lib/config.js");
7
+ const init_js_1 = require("./lib/init.js");
8
+ const container_js_1 = require("./lib/container.js");
9
+ const runtime_js_1 = require("./lib/runtime.js");
10
+ const AGENTS = ['claude', 'codex'];
11
+ const program = new commander_1.Command();
12
+ program
13
+ .name('labgate')
14
+ .description('Policy-controlled sandboxes for LLM coding agents')
15
+ .version('0.1.0');
16
+ // ── labgate init ──────────────────────────────────────────
17
+ program
18
+ .command('init')
19
+ .description('Create default config at ~/.labgate/config.json')
20
+ .option('--force', 'Overwrite existing config')
21
+ .action(async (opts) => {
22
+ try {
23
+ await (0, init_js_1.initConfig)({ force: opts.force });
24
+ }
25
+ catch (err) {
26
+ console.error(`Error: ${err.message}`);
27
+ process.exit(1);
28
+ }
29
+ });
30
+ // ── labgate <agent> [workdir] ─────────────────────────────
31
+ // Register each agent as a subcommand so commander doesn't
32
+ // choke on unknown positional args. The handler is the same.
33
+ for (const agent of AGENTS) {
34
+ program
35
+ .command(agent)
36
+ .description(`Launch ${agent} in a sandboxed container`)
37
+ .argument('[workdir]', 'Working directory to mount', process.cwd())
38
+ .option('--dry-run', 'Print the container command without running it')
39
+ .option('--image <uri>', 'Override container image')
40
+ .option('--no-footer', 'Disable status footer line')
41
+ .option('--no-statusline', 'Disable Claude Code status line')
42
+ .action(async (workdir, opts) => {
43
+ await runAgent(agent, workdir, opts);
44
+ });
45
+ }
46
+ // ── labgate status ────────────────────────────────────────
47
+ program
48
+ .command('status')
49
+ .description('List running labgate sessions')
50
+ .action(async () => {
51
+ const { listSessions } = await import('./lib/container.js');
52
+ await listSessions();
53
+ });
54
+ // ── labgate stop <id> ─────────────────────────────────────
55
+ program
56
+ .command('stop')
57
+ .description('Stop a running labgate session')
58
+ .argument('<id>', 'Session ID or container name')
59
+ .action(async (id) => {
60
+ const { stopSession } = await import('./lib/container.js');
61
+ await stopSession(id);
62
+ });
63
+ async function runAgent(agent, workdir, opts) {
64
+ // Resolve workdir
65
+ const resolved = (0, path_1.resolve)(workdir);
66
+ if (!(0, fs_1.existsSync)(resolved)) {
67
+ console.error(`Error: directory does not exist: ${resolved}`);
68
+ process.exit(1);
69
+ }
70
+ // Load config (creates default if missing) — need config.runtime for runtime check
71
+ const configPath = (0, config_js_1.getConfigPath)();
72
+ if (!(0, fs_1.existsSync)(configPath)) {
73
+ console.log('[labgate] No config found. Running "labgate init" first...\n');
74
+ await (0, init_js_1.initConfig)({ force: false });
75
+ }
76
+ const config = (0, config_js_1.loadConfig)();
77
+ // Check container runtime is available — offer to install if missing
78
+ // (skip for dry-run so users can preview without runtime)
79
+ if (!opts.dryRun) {
80
+ const runtime = await (0, runtime_js_1.ensureRuntime)(config.runtime);
81
+ if (!runtime.ok) {
82
+ process.exit(1);
83
+ }
84
+ }
85
+ // Start the session
86
+ await (0, container_js_1.startSession)({
87
+ agent,
88
+ workdir: resolved,
89
+ config,
90
+ dryRun: opts.dryRun ?? false,
91
+ imageOverride: opts.image,
92
+ footerMode: opts.footer === false ? 'off' : 'once',
93
+ statusline: opts.statusline ?? true,
94
+ });
95
+ }
96
+ program.parse();
97
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import { LabgateConfig } from './config.js';
2
+ export interface AuditEvent {
3
+ timestamp: string;
4
+ session: string;
5
+ event: string;
6
+ [key: string]: unknown;
7
+ }
8
+ export declare function writeAuditEvent(config: LabgateConfig, event: AuditEvent): void;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.writeAuditEvent = writeAuditEvent;
4
+ const fs_1 = require("fs");
5
+ const path_1 = require("path");
6
+ const config_js_1 = require("./config.js");
7
+ function getLogFile(config) {
8
+ const dir = (0, config_js_1.getLogDir)(config);
9
+ (0, fs_1.mkdirSync)(dir, { recursive: true });
10
+ const date = new Date().toISOString().slice(0, 10);
11
+ return (0, path_1.join)(dir, `${date}.jsonl`);
12
+ }
13
+ function writeAuditEvent(config, event) {
14
+ if (!config.audit.enabled)
15
+ return;
16
+ try {
17
+ const file = getLogFile(config);
18
+ (0, fs_1.appendFileSync)(file, JSON.stringify(event) + '\n', 'utf-8');
19
+ }
20
+ catch (err) {
21
+ // Don't crash if audit logging fails — just warn
22
+ console.error(`[labgate] Warning: audit log write failed: ${err.message}`);
23
+ }
24
+ }
25
+ //# sourceMappingURL=audit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.js","sourceRoot":"","sources":["../../src/lib/audit.ts"],"names":[],"mappings":";;AAmBA,0CAUC;AA7BD,2BAA+C;AAC/C,+BAA4B;AAE5B,2CAAuD;AASvD,SAAS,UAAU,CAAC,MAAqB;IACvC,MAAM,GAAG,GAAG,IAAA,qBAAS,EAAC,MAAM,CAAC,CAAC;IAC9B,IAAA,cAAS,EAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACpC,MAAM,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACnD,OAAO,IAAA,WAAI,EAAC,GAAG,EAAE,GAAG,IAAI,QAAQ,CAAC,CAAC;AACpC,CAAC;AAED,SAAgB,eAAe,CAAC,MAAqB,EAAE,KAAiB;IACtE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO;QAAE,OAAO;IAElC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;QAChC,IAAA,mBAAc,EAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC;IAC9D,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAClB,iDAAiD;QACjD,OAAO,CAAC,KAAK,CAAC,8CAA8C,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;IAC7E,CAAC;AACH,CAAC"}
@@ -0,0 +1,47 @@
1
+ export interface MountPath {
2
+ path: string;
3
+ mode: 'rw' | 'ro';
4
+ }
5
+ export type RuntimePreference = 'auto' | 'apptainer' | 'singularity' | 'podman' | 'docker';
6
+ export interface LabgateConfig {
7
+ /** Container runtime preference: auto detects best available */
8
+ runtime: RuntimePreference;
9
+ /** Container image URI */
10
+ image: string;
11
+ /** Session timeout in hours */
12
+ session_timeout_hours: number;
13
+ /** Paths to mount into the sandbox */
14
+ filesystem: {
15
+ /** Additional paths to mount (workdir is always mounted) */
16
+ extra_paths: MountPath[];
17
+ /** Glob patterns to block via empty overlays */
18
+ blocked_patterns: string[];
19
+ };
20
+ /** Commands blocked inside the sandbox */
21
+ commands: {
22
+ blacklist: string[];
23
+ };
24
+ /** Network mode */
25
+ network: {
26
+ mode: 'none' | 'filtered' | 'host';
27
+ allowed_domains: string[];
28
+ };
29
+ /** SLURM proxy settings (M2, ignored for now) */
30
+ slurm: {
31
+ enabled: boolean;
32
+ };
33
+ /** Audit log settings */
34
+ audit: {
35
+ enabled: boolean;
36
+ log_dir: string;
37
+ };
38
+ }
39
+ export declare const DEFAULT_CONFIG: LabgateConfig;
40
+ export declare const LABGATE_DIR: string;
41
+ export declare const CONFIG_FILE = "config.json";
42
+ export declare function getConfigPath(): string;
43
+ export declare function getSandboxHome(): string;
44
+ export declare function getImagesDir(): string;
45
+ export declare function getEmptyDir(): string;
46
+ export declare function getLogDir(config: LabgateConfig): string;
47
+ export declare function loadConfig(): LabgateConfig;
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CONFIG_FILE = exports.LABGATE_DIR = exports.DEFAULT_CONFIG = void 0;
4
+ exports.getConfigPath = getConfigPath;
5
+ exports.getSandboxHome = getSandboxHome;
6
+ exports.getImagesDir = getImagesDir;
7
+ exports.getEmptyDir = getEmptyDir;
8
+ exports.getLogDir = getLogDir;
9
+ exports.loadConfig = loadConfig;
10
+ const fs_1 = require("fs");
11
+ const path_1 = require("path");
12
+ const os_1 = require("os");
13
+ // ── Defaults ──────────────────────────────────────────────
14
+ exports.DEFAULT_CONFIG = {
15
+ runtime: 'auto',
16
+ image: 'docker.io/library/node:20-slim',
17
+ session_timeout_hours: 8,
18
+ filesystem: {
19
+ extra_paths: [],
20
+ blocked_patterns: [
21
+ '**/.ssh',
22
+ '**/.gnupg',
23
+ '**/.aws',
24
+ '**/.config/gcloud',
25
+ '**/.azure',
26
+ '**/.env',
27
+ '**/.netrc',
28
+ '**/.git-credentials',
29
+ '**/*.pem',
30
+ '**/*.key',
31
+ '**/id_rsa*',
32
+ '**/id_ed25519*',
33
+ '**/credentials*',
34
+ '**/secrets*',
35
+ ],
36
+ },
37
+ commands: {
38
+ blacklist: [
39
+ 'ssh', 'scp', 'rsync',
40
+ 'curl', 'wget',
41
+ 'mount', 'umount',
42
+ 'dd', 'mkfs',
43
+ 'passwd', 'chown',
44
+ 'reboot', 'shutdown',
45
+ ],
46
+ },
47
+ network: {
48
+ mode: 'none',
49
+ allowed_domains: [
50
+ 'api.anthropic.com',
51
+ 'api.openai.com',
52
+ 'pypi.org',
53
+ 'files.pythonhosted.org',
54
+ 'conda.anaconda.org',
55
+ 'registry.npmjs.org',
56
+ 'github.com',
57
+ ],
58
+ },
59
+ slurm: {
60
+ enabled: false,
61
+ },
62
+ audit: {
63
+ enabled: true,
64
+ log_dir: '~/.labgate/logs',
65
+ },
66
+ };
67
+ // ── Paths ─────────────────────────────────────────────────
68
+ exports.LABGATE_DIR = (0, path_1.join)((0, os_1.homedir)(), '.labgate');
69
+ exports.CONFIG_FILE = 'config.json';
70
+ function getConfigPath() {
71
+ return (0, path_1.join)(exports.LABGATE_DIR, exports.CONFIG_FILE);
72
+ }
73
+ function getSandboxHome() {
74
+ return (0, path_1.join)(exports.LABGATE_DIR, 'ai-home');
75
+ }
76
+ function getImagesDir() {
77
+ return (0, path_1.join)(exports.LABGATE_DIR, 'images');
78
+ }
79
+ function getEmptyDir() {
80
+ return (0, path_1.join)(exports.LABGATE_DIR, 'empty');
81
+ }
82
+ function getLogDir(config) {
83
+ return config.audit.log_dir.replace(/^~/, (0, os_1.homedir)());
84
+ }
85
+ // ── Loader ────────────────────────────────────────────────
86
+ function loadConfig() {
87
+ const configPath = getConfigPath();
88
+ if (!(0, fs_1.existsSync)(configPath)) {
89
+ return { ...exports.DEFAULT_CONFIG };
90
+ }
91
+ try {
92
+ const rawText = (0, fs_1.readFileSync)(configPath, 'utf-8');
93
+ // Strip // comments (our config uses them for documentation)
94
+ const stripped = rawText.replace(/^\s*\/\/.*$/gm, '');
95
+ const raw = JSON.parse(stripped);
96
+ // Merge with defaults so missing fields get filled in.
97
+ // Shallow merge per section — good enough for M0.
98
+ return {
99
+ runtime: raw.runtime ?? exports.DEFAULT_CONFIG.runtime,
100
+ image: raw.image ?? exports.DEFAULT_CONFIG.image,
101
+ session_timeout_hours: raw.session_timeout_hours ?? exports.DEFAULT_CONFIG.session_timeout_hours,
102
+ filesystem: {
103
+ extra_paths: raw.filesystem?.extra_paths ?? exports.DEFAULT_CONFIG.filesystem.extra_paths,
104
+ blocked_patterns: raw.filesystem?.blocked_patterns ?? exports.DEFAULT_CONFIG.filesystem.blocked_patterns,
105
+ },
106
+ commands: {
107
+ blacklist: raw.commands?.blacklist ?? exports.DEFAULT_CONFIG.commands.blacklist,
108
+ },
109
+ network: {
110
+ mode: raw.network?.mode ?? exports.DEFAULT_CONFIG.network.mode,
111
+ allowed_domains: raw.network?.allowed_domains ?? exports.DEFAULT_CONFIG.network.allowed_domains,
112
+ },
113
+ slurm: {
114
+ enabled: raw.slurm?.enabled ?? exports.DEFAULT_CONFIG.slurm.enabled,
115
+ },
116
+ audit: {
117
+ enabled: raw.audit?.enabled ?? exports.DEFAULT_CONFIG.audit.enabled,
118
+ log_dir: raw.audit?.log_dir ?? exports.DEFAULT_CONFIG.audit.log_dir,
119
+ },
120
+ };
121
+ }
122
+ catch (err) {
123
+ console.error(`Warning: could not parse ${configPath}: ${err.message}`);
124
+ console.error('Using default config.');
125
+ return { ...exports.DEFAULT_CONFIG };
126
+ }
127
+ }
128
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import { LabgateConfig } from './config.js';
2
+ export interface SessionOpts {
3
+ agent: string;
4
+ workdir: string;
5
+ config: LabgateConfig;
6
+ dryRun: boolean;
7
+ imageOverride?: string;
8
+ footerMode?: 'off' | 'once' | 'sticky';
9
+ statusline?: boolean;
10
+ }
11
+ /**
12
+ * Convert a container image URI to a local SIF filename.
13
+ * e.g. "docker.io/library/ubuntu:22.04" → "docker.io_library_ubuntu_22.04.sif"
14
+ */
15
+ export declare function imageToSifName(image: string): string;
16
+ export declare function buildEntrypoint(agent: string, statuslineEnabled?: boolean): string;
17
+ export declare function startSession(session: SessionOpts): Promise<void>;
18
+ export declare function listSessions(): Promise<void>;
19
+ export declare function stopSession(id: string): Promise<void>;