universal-opencode-sandbox 0.0.0-stage → 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Benjamin Jacobs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,113 @@
1
- # Temporary Holding Version
1
+ # universal-opencode-sandbox
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A [OpenCode](https://opencode.ai) 2.x plugin that runs every agent shell command inside an
4
+ OS-level sandbox enforced by [Microsoft MXC](https://github.com/microsoft/mxc). One
5
+ plugin, all three desktop platforms:
6
+
7
+ | Platform | Mechanism | Requirement |
8
+ |----------|-----------|-------------|
9
+ | macOS 15+ (Apple Silicon) | Seatbelt | none beyond this plugin |
10
+ | Linux | bubblewrap | `bwrap` >= 0.5.0 installed, unprivileged user namespaces |
11
+ | Windows 11 | ProcessContainer | native process-container support (probed automatically) |
12
+
13
+ Commands keep OpenCode's own streaming output, timeout and background behavior: the
14
+ plugin swaps the command string for an invocation of the bundled MXC executor with a
15
+ per-call policy file, so the sandboxed process is spawned by the executor while the
16
+ shell tool watches it like any other command.
17
+
18
+ ## Install
19
+
20
+ Requires OpenCode 2.x (the `@opencode/plugin` 2.x contract).
21
+
22
+ ```jsonc title="opencode.jsonc"
23
+ {
24
+ "plugins": ["universal-opencode-sandbox"],
25
+ }
26
+ ```
27
+
28
+ The sandbox is on by default once the plugin is installed, in its strictest useful
29
+ mode: the workspace is writable (and the system temp directory), everything else is
30
+ read-only, the network is denied, and a command the sandbox cannot run is blocked
31
+ rather than run unconfined.
32
+
33
+ ## Configure
34
+
35
+ Policy is read only from places a sandboxed command cannot reach, so a repository
36
+ can never enable, loosen or tighten your sandbox. OpenCode plugin options are ignored
37
+ on purpose: they can be set from a repository's own config.
38
+
39
+ `~/.config/universal-opencode-sandbox/config.json` (respecting `XDG_CONFIG_HOME`):
40
+
41
+ ```jsonc
42
+ {
43
+ "enabled": true,
44
+ // "enforce" (default) blocks a command when the sandbox cannot run it;
45
+ // "permissive" runs it unsandboxed with a warning.
46
+ "mode": "enforce",
47
+ "filesystem": {
48
+ // "read-only" (default) denies every write; "workspace-write" allows writes to
49
+ // the workspace, the temp directory, and writable_roots.
50
+ "mode": "workspace-write",
51
+ "writable_roots": [],
52
+ // Paths the sandboxed command can neither read nor write, e.g. credentials.
53
+ "denied_paths": ["~/.ssh", "~/.aws"]
54
+ },
55
+ // "deny" (default) blocks all network access for sandboxed commands.
56
+ "network": "deny",
57
+ // What happens when a tool call asks to skip the sandbox (shell input
58
+ // "sandbox: false"): "deny" (default) blocks it, "allow" runs it unsandboxed.
59
+ "unsandboxed": "deny",
60
+ // Per-project overrides, keyed by the workspace's absolute path. Every field is
61
+ // optional and can only make the policy stricter, never looser.
62
+ "projects": {
63
+ "/home/me/src/scratch": { "network": "allow" }
64
+ }
65
+ }
66
+ ```
67
+
68
+ `UNIVERSAL_SANDBOX_CONFIG` (a JSON string) tightens the same fields per invocation.
69
+
70
+ ## What is enforced
71
+
72
+ - **Filesystem**: in `workspace-write` mode, writes are allowed in the workspace and
73
+ the temp directory only. In `read-only` mode no write is allowed at all.
74
+ - **Protected paths**: `opencode.json`, `opencode.jsonc`, `.opencode`, `.claude` and
75
+ `.agents` in the workspace can never be written or planted by a sandboxed command,
76
+ so a command cannot install a config or plugin that would run unsandboxed after a
77
+ restart. Per platform: macOS enforces this for existing and not-yet-existing paths;
78
+ Linux enforces it for paths that exist (bwrap cannot bind a missing read-only
79
+ source, and the deny fallback has an upstream bug, microsoft/mxc#1780); Windows
80
+ denies the paths outright.
81
+ - **Network**: denied by default. On Windows the backend currently cannot enforce a
82
+ network-deny policy (verified against Windows 11 and MXC 1.1.0), so with
83
+ `network: "deny"` (the default) sandboxed commands fail closed there with an
84
+ explanation; set `network: "allow"` for filesystem-only containment on Windows.
85
+ - **Fail-closed**: an unsupported platform, a missing backend (install `bubblewrap` on
86
+ Linux), or a missing executor blocks the command in `enforce` mode instead of
87
+ running it unconfined. `mode: "permissive"` trades that guarantee for convenience.
88
+
89
+ ## Known limitations
90
+
91
+ - With the sandbox enabled, OpenCode's per-command shell permission rules see the
92
+ sandbox wrapper invocation, not the inner command. The OS sandbox is the real
93
+ boundary here; command-pattern rules are weaker than it anyway (and trivially
94
+ bypassed by an adversarial agent). If you rely on `deny` rules for specific
95
+ commands, prefer this plugin's `denied_paths` and filesystem policy.
96
+ - Intel macOS is unsupported: the MXC npm package ships no darwin-x64 runtime yet.
97
+ - MXC release builds on Windows emit required Microsoft diagnostic telemetry; see the
98
+ [MXC documentation](https://github.com/microsoft/mxc).
99
+ - MXC's process streams do not deliver under Bun (microsoft/mxc#1782). This plugin is
100
+ unaffected: the executor is a native binary whose output the shell tool captures.
101
+
102
+ ## Verification
103
+
104
+ Tested against the real backends on macOS (Seatbelt), Debian 13 with bubblewrap 0.12
105
+ and Windows 11 (ProcessContainer): output and exit codes stream through, writes inside
106
+ the workspace succeed, writes outside are denied, protected config files cannot be
107
+ planted, and network egress is blocked (except the documented Windows gap). Also
108
+ verified end to end inside a real OpenCode 2.2.7 session: an agent's shell command
109
+ writes inside the workspace and is denied outside it.
110
+
111
+ ## License
112
+
113
+ MIT
@@ -0,0 +1,3 @@
1
+ import { Plugin } from "@opencode/plugin";
2
+ declare const _default: Plugin.Plugin;
3
+ export default _default;
package/dist/index.js ADDED
@@ -0,0 +1,274 @@
1
+ // src/index.ts
2
+ import fs3 from "node:fs";
3
+ import os3 from "node:os";
4
+ import path3 from "node:path";
5
+ import { Plugin } from "@opencode/plugin";
6
+
7
+ // src/policy.ts
8
+ import fs from "node:fs";
9
+ import os from "node:os";
10
+ import path from "node:path";
11
+ var configDirectory = () => path.join(process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), ".config"), "universal-opencode-sandbox");
12
+ var configPath = () => path.join(configDirectory(), "config.json");
13
+ function resolvePolicy(workspace, environment = process.env, globalFile = configPath()) {
14
+ let merged = { writableRoots: [], deniedPaths: [] };
15
+ const global = readConfig(globalFile);
16
+ if (global)
17
+ merged = tighten(merged, global);
18
+ const project = global?.projects?.[workspace];
19
+ if (project)
20
+ merged = tighten(merged, project);
21
+ const injected = environment.UNIVERSAL_SANDBOX_CONFIG;
22
+ if (injected) {
23
+ try {
24
+ merged = tighten(merged, JSON.parse(injected));
25
+ } catch {}
26
+ }
27
+ return {
28
+ enabled: merged.enabled ?? true,
29
+ mode: merged.mode ?? "enforce",
30
+ filesystem: {
31
+ mode: merged.filesystemMode ?? "read-only",
32
+ writableRoots: [...new Set(merged.writableRoots)],
33
+ deniedPaths: [...new Set(merged.deniedPaths)]
34
+ },
35
+ network: merged.network ?? "deny",
36
+ unsandboxed: merged.unsandboxed ?? "deny"
37
+ };
38
+ }
39
+ function readConfig(file) {
40
+ try {
41
+ return JSON.parse(fs.readFileSync(file, "utf8"));
42
+ } catch {
43
+ return;
44
+ }
45
+ }
46
+ function tighten(merged, raw) {
47
+ if (Array.isArray(raw.filesystem?.writable_roots)) {
48
+ for (const root of raw.filesystem.writable_roots)
49
+ if (typeof root === "string")
50
+ merged.writableRoots.push(root);
51
+ }
52
+ if (Array.isArray(raw.filesystem?.denied_paths)) {
53
+ for (const item of raw.filesystem.denied_paths)
54
+ if (typeof item === "string")
55
+ merged.deniedPaths.push(item);
56
+ }
57
+ return {
58
+ enabled: raw.enabled === undefined ? merged.enabled : raw.enabled === false ? false : merged.enabled === false ? false : true,
59
+ mode: strictest(merged.mode, raw.mode, "enforce", "permissive"),
60
+ filesystemMode: strictest(merged.filesystemMode, raw.filesystem?.mode, "read-only", "workspace-write"),
61
+ network: strictest(merged.network, raw.network, "deny", "allow"),
62
+ unsandboxed: strictest(merged.unsandboxed, raw.unsandboxed, "deny", "allow"),
63
+ writableRoots: merged.writableRoots,
64
+ deniedPaths: merged.deniedPaths
65
+ };
66
+ }
67
+ function strictest(current, incoming, strict, loose) {
68
+ if (current === strict || incoming === strict)
69
+ return strict;
70
+ if (incoming === loose)
71
+ return loose;
72
+ return current;
73
+ }
74
+ function expandHome(item, home = os.homedir()) {
75
+ return item === "~" ? home : item.startsWith("~/") ? path.join(home, item.slice(2)) : item;
76
+ }
77
+
78
+ // src/wire.ts
79
+ import { randomBytes } from "node:crypto";
80
+ import fs2 from "node:fs";
81
+ import os2 from "node:os";
82
+ import path2 from "node:path";
83
+ import { fileURLToPath } from "node:url";
84
+ var PROTECTED = ["opencode.json", "opencode.jsonc", ".opencode", ".claude", ".agents"];
85
+ function wireRequest(input, platform = process.platform) {
86
+ const workspace = real(input.workspace);
87
+ const cwd = real(input.cwd);
88
+ const writable = [
89
+ workspace,
90
+ ...(input.tmpRoots ?? [os2.tmpdir()]).map(real),
91
+ ...input.policy.filesystem.writableRoots.map((root) => real(expandHome(root)))
92
+ ];
93
+ const protectedPaths = PROTECTED.map((item) => path2.join(workspace, item));
94
+ const deniedPaths = input.policy.filesystem.deniedPaths.map((item) => real(expandHome(item)));
95
+ let filesystem;
96
+ if (input.policy.filesystem.mode !== "workspace-write") {
97
+ filesystem = { readwritePaths: [], readonlyPaths: [input.workspace], deniedPaths: existing(deniedPaths, platform) };
98
+ } else if (platform === "win32") {
99
+ filesystem = { readwritePaths: writable, readonlyPaths: [], deniedPaths: [...protectedPaths, ...deniedPaths] };
100
+ } else if (platform === "linux") {
101
+ filesystem = {
102
+ readwritePaths: writable,
103
+ readonlyPaths: protectedPaths.filter((item) => exists(item)),
104
+ deniedPaths: existing(deniedPaths, platform)
105
+ };
106
+ } else {
107
+ filesystem = { readwritePaths: writable, readonlyPaths: protectedPaths, deniedPaths };
108
+ }
109
+ const request = {
110
+ version: "1.0.0",
111
+ containerId: randomBytes(16).toString("hex"),
112
+ containment: platform === "darwin" ? "seatbelt" : platform === "win32" ? "processcontainer" : "bubblewrap",
113
+ ...platform === "darwin" ? { seatbelt: {} } : {},
114
+ ...platform === "win32" ? {
115
+ processContainer: {
116
+ capabilities: input.policy.network === "allow" ? ["internetClient"] : [],
117
+ ui: { isolation: "container", desktopSystemControl: false, systemSettings: "none", ime: false },
118
+ filesystem: undefined,
119
+ network: undefined
120
+ }
121
+ } : {},
122
+ lifecycle: { destroyOnExit: true, preservePolicy: false },
123
+ process: {
124
+ commandLine: input.command,
125
+ timeout: input.timeout,
126
+ cwd,
127
+ inheritDefaultEnv: true
128
+ },
129
+ filesystem,
130
+ ...input.policy.network === "deny" ? { network: { egress: { default: "deny" }, ingress: { default: "deny", hostLoopback: "deny" } } } : { network: { egress: { default: "allow" } } }
131
+ };
132
+ return request;
133
+ }
134
+ function exists(item) {
135
+ try {
136
+ fs2.statSync(item);
137
+ return true;
138
+ } catch {
139
+ return false;
140
+ }
141
+ }
142
+ function real(item) {
143
+ try {
144
+ return fs2.realpathSync(item);
145
+ } catch {
146
+ return item;
147
+ }
148
+ }
149
+ function existing(paths, platform) {
150
+ return platform === "linux" ? paths.filter(exists) : paths;
151
+ }
152
+ function unsupportedNetwork(platform, network) {
153
+ return platform === "win32" && network === "deny";
154
+ }
155
+ function resolveExecutor(platform = process.platform, arch = process.arch) {
156
+ const name = platform === "darwin" ? "mxc-exec-mac" : platform === "win32" ? "wxc-exec.exe" : platform === "linux" ? "lxc-exec" : undefined;
157
+ if (!name)
158
+ return `no MXC executor exists for ${platform}`;
159
+ const sdk = path2.dirname(fileURLToPath(import.meta.resolve("@microsoft/mxc-sdk/package.json")));
160
+ const executor = path2.join(sdk, "bin", arch === "arm64" ? "arm64" : "x64", name);
161
+ if (!fs2.existsSync(executor)) {
162
+ return platform === "darwin" && arch === "x64" ? "the installed @microsoft/mxc-sdk ships no Intel macOS executor yet" : `the MXC executor is missing from the @microsoft/mxc-sdk package: ${executor}`;
163
+ }
164
+ return executor;
165
+ }
166
+ function quote(value, platform = process.platform) {
167
+ if (platform === "win32")
168
+ return windowsArgument(value);
169
+ return `'${value.replaceAll("'", `'\\''`)}'`;
170
+ }
171
+ function windowsArgument(value) {
172
+ if (!/[\s"]/.test(value))
173
+ return value;
174
+ let quoted = '"';
175
+ let backslashes = 0;
176
+ for (const character of value) {
177
+ if (character === "\\") {
178
+ backslashes++;
179
+ continue;
180
+ }
181
+ if (character === '"') {
182
+ quoted += "\\".repeat(backslashes * 2 + 1) + '"';
183
+ backslashes = 0;
184
+ continue;
185
+ }
186
+ quoted += "\\".repeat(backslashes) + character;
187
+ backslashes = 0;
188
+ }
189
+ return quoted + "\\".repeat(backslashes * 2) + '"';
190
+ }
191
+
192
+ // src/index.ts
193
+ var BLOCKED = "universal-opencode-sandbox unavailable; command blocked";
194
+ var src_default = Plugin.define({
195
+ id: "universal-opencode-sandbox",
196
+ setup: async (context) => {
197
+ const policy = resolvePolicy(context.location.directory);
198
+ if (!policy.enabled)
199
+ return;
200
+ const log = (level, message) => console[level](`[universal-opencode-sandbox] ${message}`);
201
+ const enforce = policy.mode === "enforce";
202
+ const executor = resolveExecutor();
203
+ const gap = unsupportedNetwork(process.platform, policy.network);
204
+ const unavailable = !fs3.existsSync(executor);
205
+ const reason = unavailable ? executor : gap ? 'the sandbox backend on Windows cannot enforce network denial yet; set network "allow" in the sandbox config to run sandboxed with filesystem containment only' : undefined;
206
+ if (reason !== undefined) {
207
+ log(enforce ? "error" : "warn", `${reason}; ${enforce ? 'shell commands will be blocked (set mode "permissive" to run unsandboxed)' : "commands will run without a sandbox"}`);
208
+ }
209
+ const inFlight = new Map;
210
+ const requestDirectory = path3.join(os3.tmpdir(), "universal-opencode-sandbox");
211
+ fs3.mkdirSync(requestDirectory, { recursive: true });
212
+ const cleanup = (callID) => {
213
+ const pending = inFlight.get(callID);
214
+ if (pending === undefined)
215
+ return;
216
+ inFlight.delete(callID);
217
+ fs3.rmSync(pending.file, { force: true });
218
+ };
219
+ const before = await context.tool.hook("execute.before", async (event) => {
220
+ if (event.tool !== "shell")
221
+ return;
222
+ const input = event.input ?? {};
223
+ const command = input.command;
224
+ if (typeof command !== "string" || command === "" || inFlight.has(event.id))
225
+ return;
226
+ if (input.sandbox === false) {
227
+ if (policy.unsandboxed === "deny") {
228
+ log("warn", `tool call asked to skip the sandbox; blocked (set unsandboxed "allow" to permit this)`);
229
+ throw new Error("universal-opencode-sandbox: running this command unsandboxed is not allowed");
230
+ }
231
+ return;
232
+ }
233
+ if (unavailable || gap) {
234
+ if (enforce)
235
+ throw new Error(`${BLOCKED}: ${reason}`);
236
+ return;
237
+ }
238
+ try {
239
+ const request = wireRequest({
240
+ command,
241
+ workspace: context.location.directory,
242
+ cwd: typeof input.workdir === "string" ? input.workdir : context.location.directory,
243
+ timeout: input.background === true ? 0 : input.timeout || 120000,
244
+ policy
245
+ });
246
+ const file = path3.join(requestDirectory, `${event.id}.json`);
247
+ fs3.writeFileSync(file, JSON.stringify(request), { mode: 384 });
248
+ inFlight.set(event.id, { file, command, sessionID: event.sessionID });
249
+ input.command = `${quote(executor)} ${quote(file)}`;
250
+ } catch (error) {
251
+ if (enforce)
252
+ throw new Error(`${BLOCKED}: ${error instanceof Error ? error.message : String(error)}`);
253
+ log("warn", `failed to wrap the command; running unsandboxed: ${error instanceof Error ? error.message : String(error)}`);
254
+ }
255
+ });
256
+ const after = await context.tool.hook("execute.after", async (event) => {
257
+ const pending = inFlight.get(event.id);
258
+ if (pending === undefined)
259
+ return;
260
+ if (event.input && typeof event.input === "object")
261
+ event.input.command = pending.command;
262
+ cleanup(event.id);
263
+ });
264
+ return async () => {
265
+ await before.dispose();
266
+ await after.dispose();
267
+ for (const callID of [...inFlight.keys()])
268
+ cleanup(callID);
269
+ };
270
+ }
271
+ });
272
+ export {
273
+ src_default as default
274
+ };
@@ -0,0 +1,22 @@
1
+ export interface Policy {
2
+ readonly enabled: boolean;
3
+ /** "enforce" blocks a command when the sandbox cannot run it; "permissive" runs it unsandboxed with a warning. */
4
+ readonly mode: "enforce" | "permissive";
5
+ readonly filesystem: {
6
+ readonly mode: "read-only" | "workspace-write";
7
+ readonly writableRoots: string[];
8
+ /** Paths the sandboxed command can neither read nor write, resolved against the home directory. */
9
+ readonly deniedPaths: string[];
10
+ };
11
+ readonly network: "deny" | "allow";
12
+ /** What happens when a tool call asks to skip the sandbox (shell input "sandbox: false"). */
13
+ readonly unsandboxed: "deny" | "allow";
14
+ }
15
+ export declare const defaultPolicy: Policy;
16
+ /**
17
+ * Resolves the sandbox policy for a workspace: the global config file, tightened by the
18
+ * matching per-project override, tightened by UNIVERSAL_SANDBOX_CONFIG. Security fields
19
+ * only ever get stricter across explicit values; roots and denied paths accumulate.
20
+ */
21
+ export declare function resolvePolicy(workspace: string, environment?: NodeJS.ProcessEnv, globalFile?: string): Policy;
22
+ export declare function expandHome(item: string, home?: string): string;
package/dist/wire.d.ts ADDED
@@ -0,0 +1,54 @@
1
+ import { type Policy } from "./policy.js";
2
+ /** Workspace paths a sandboxed command must never plant or overwrite. */
3
+ export declare const PROTECTED: readonly ["opencode.json", "opencode.jsonc", ".opencode", ".claude", ".agents"];
4
+ export interface WireRequest {
5
+ version: "1.0.0";
6
+ containerId: string;
7
+ containment: "seatbelt" | "bubblewrap" | "processcontainer";
8
+ seatbelt?: Record<string, never>;
9
+ processContainer?: {
10
+ capabilities: string[];
11
+ ui: Record<string, unknown>;
12
+ filesystem: undefined;
13
+ network: undefined;
14
+ };
15
+ lifecycle: {
16
+ destroyOnExit: boolean;
17
+ preservePolicy: boolean;
18
+ };
19
+ process: {
20
+ commandLine: string;
21
+ timeout: number;
22
+ cwd?: string;
23
+ inheritDefaultEnv?: boolean;
24
+ };
25
+ filesystem: {
26
+ readwritePaths: string[];
27
+ readonlyPaths: string[];
28
+ deniedPaths: string[];
29
+ };
30
+ network?: {
31
+ egress?: unknown;
32
+ ingress?: unknown;
33
+ };
34
+ }
35
+ export interface WireInput {
36
+ readonly command: string;
37
+ readonly workspace: string;
38
+ readonly cwd: string;
39
+ readonly timeout: number;
40
+ readonly policy: Policy;
41
+ readonly tmpRoots?: string[];
42
+ }
43
+ /** Maps a policy and one command onto the wire request, platform-aware. */
44
+ export declare function wireRequest(input: WireInput, platform?: NodeJS.Platform): WireRequest;
45
+ /**
46
+ * A network-deny policy the Windows backend accepts but does not enforce (verified
47
+ * against Windows 11 and MXC 1.1.0: direct egress still works). Callers fail closed
48
+ * instead of running a command with network access the user denied.
49
+ */
50
+ export declare function unsupportedNetwork(platform: NodeJS.Platform, network: "deny" | "allow"): boolean;
51
+ /** Resolves the bundled MXC executor binary for this platform and architecture. */
52
+ export declare function resolveExecutor(platform?: NodeJS.Platform, arch?: string): string;
53
+ /** Quotes one argument for the platform's command-line parser. */
54
+ export declare function quote(value: string, platform?: NodeJS.Platform): string;
package/package.json CHANGED
@@ -1,6 +1,57 @@
1
1
  {
2
2
  "name": "universal-opencode-sandbox",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Universal OS-level sandbox for OpenCode agent shell commands on macOS, Linux and Windows, enforced by Microsoft MXC",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md",
17
+ "LICENSE"
18
+ ],
19
+ "scripts": {
20
+ "build": "bun build ./src/index.ts --outdir dist --target node --format esm --external @microsoft/mxc-sdk --external @opencode/plugin && bun x tsc -p tsconfig.declarations.json",
21
+ "dev": "bun run --watch src/index.ts",
22
+ "test": "bun test",
23
+ "typecheck": "tsc --noEmit",
24
+ "prepublishOnly": "bun run build"
25
+ },
26
+ "keywords": [
27
+ "opencode",
28
+ "opencode-plugin",
29
+ "sandbox",
30
+ "security",
31
+ "mxc",
32
+ "seatbelt",
33
+ "bubblewrap",
34
+ "processcontainer",
35
+ "agent-safety"
36
+ ],
37
+ "license": "MIT",
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "git+https://github.com/benjaminfrombe/universal-opencode-sandbox.git"
41
+ },
42
+ "homepage": "https://github.com/benjaminfrombe/universal-opencode-sandbox#readme",
43
+ "bugs": {
44
+ "url": "https://github.com/benjaminfrombe/universal-opencode-sandbox/issues"
45
+ },
46
+ "dependencies": {
47
+ "@microsoft/mxc-sdk": "^1.1.0"
48
+ },
49
+ "peerDependencies": {
50
+ "@opencode/plugin": ">=2.0.0"
51
+ },
52
+ "devDependencies": {
53
+ "@opencode/plugin": "^2.3.0",
54
+ "@types/bun": "^1.3.9",
55
+ "typescript": "^5.9.0"
56
+ }
57
+ }