@slopus/happy-agent-supervisor 0.0.0 → 0.0.1-darwin-arm64

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 Happy Contributors
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,7 +1,238 @@
1
- # @slopus/happy-agent-supervisor
1
+ # Happy Agent Supervisor
2
2
 
3
- Version 0.0.0 holds the name and contains no code.
3
+ `@slopus/happy-agent-supervisor` is the small trusted native boundary used to
4
+ launch agent workloads without an intermediary shell. You hand it a policy and
5
+ an argument vector; it applies the operating system's own isolation and then
6
+ `execve`s your command inside it.
4
7
 
5
- It exists so that npm trusted publishing can be configured for this package.
6
- Real releases are published from the `publish-sandbox` GitHub Actions workflow in
7
- [slopus/rig](https://github.com/slopus/rig).
8
+ ```text
9
+ caller
10
+ │ policy JSON on fd or file
11
+ │ argv after --
12
+
13
+ supervisor
14
+ ├─ Linux: user + mount + PID namespaces, private procfs, mount policy,
15
+ │ seccomp, zero capabilities, no_new_privs
16
+ └─ macOS: in-process Seatbelt profile
17
+
18
+ execve(target, argv, inherited environment)
19
+ ```
20
+
21
+ The package is two things in one npm name: a native executable per platform,
22
+ and a small TypeScript API that validates a policy and tells you where the
23
+ matching executable lives.
24
+
25
+ ## Where it runs
26
+
27
+ | Platform | Architecture | Rust target | Enforcement |
28
+ | -------- | ------------ | ---------------------------- | ------------------------------------------------------- |
29
+ | macOS | arm64 | `aarch64-apple-darwin` | Seatbelt profile installed in-process |
30
+ | macOS | x64 | `x86_64-apple-darwin` | Seatbelt profile installed in-process |
31
+ | Linux | arm64 | `aarch64-unknown-linux-musl` | namespaces, mount policy, seccomp, capability removal |
32
+ | Linux | x64 | `x86_64-unknown-linux-musl` | namespaces, mount policy, seccomp, capability removal |
33
+
34
+ The Linux binaries are static musl builds, so they run on any distribution and
35
+ can be mounted read-only into a container that has no toolchain of its own.
36
+ Windows is not supported; `resolveSupervisorBinary()` throws there rather than
37
+ returning something that cannot enforce anything.
38
+
39
+ ## Install
40
+
41
+ ```sh
42
+ npm install @slopus/happy-agent-supervisor
43
+ pnpm add @slopus/happy-agent-supervisor
44
+ ```
45
+
46
+ Nothing is downloaded by a post-install script. The native binaries ship as
47
+ ordinary npm packages, and the root package names them as optional dependencies
48
+ guarded by `os` and `cpu`, so your package manager installs exactly the one that
49
+ matches the machine and silently skips the other three.
50
+
51
+ The binaries are published as versions of the same npm name, one per target:
52
+
53
+ | Optional dependency | Resolves to |
54
+ | ----------------------------------------------- | --------------------------------------------- |
55
+ | `@slopus/happy-agent-supervisor-darwin-arm64` | `@slopus/happy-agent-supervisor@<version>-darwin-arm64` |
56
+ | `@slopus/happy-agent-supervisor-darwin-x64` | `@slopus/happy-agent-supervisor@<version>-darwin-x64` |
57
+ | `@slopus/happy-agent-supervisor-linux-arm64` | `@slopus/happy-agent-supervisor@<version>-linux-arm64` |
58
+ | `@slopus/happy-agent-supervisor-linux-x64` | `@slopus/happy-agent-supervisor@<version>-linux-x64` |
59
+
60
+ Each of those packages contains one executable at
61
+ `vendor/<rust-target>/bin/happy-agent-supervisor` plus a `SHA256SUMS` file for it.
62
+
63
+ ### Getting a binary for another platform
64
+
65
+ Two cases need a binary that does not match the host: building a Linux image on
66
+ a Mac, and shipping a container that carries the supervisor. Install the target
67
+ package explicitly and read its path from your build script:
68
+
69
+ ```sh
70
+ # The binary you want to copy into a linux/amd64 image.
71
+ npm install --no-save @slopus/happy-agent-supervisor@0.0.1-linux-x64
72
+ ```
73
+
74
+ Or ask your package manager for every variant at once, which is what a release
75
+ pipeline usually wants:
76
+
77
+ ```sh
78
+ npm install --force \
79
+ @slopus/happy-agent-supervisor@0.0.1-linux-x64 \
80
+ @slopus/happy-agent-supervisor@0.0.1-linux-arm64
81
+ ```
82
+
83
+ Then point the container at it:
84
+
85
+ ```ts
86
+ import { resolveLinuxSupervisorBinary } from "@slopus/happy-agent-supervisor";
87
+
88
+ // Accepts OCI, Node.js, and Rust spellings: amd64 | x64 | x86_64 | arm64 | aarch64.
89
+ const hostPath = resolveLinuxSupervisorBinary("amd64");
90
+ // docker run -v ${hostPath}:/usr/local/bin/happy-agent-supervisor:ro ...
91
+ ```
92
+
93
+ ## Using the TypeScript API
94
+
95
+ ```ts
96
+ import { spawn } from "node:child_process";
97
+ import {
98
+ parseSupervisorPolicy,
99
+ resolveSupervisorBinary,
100
+ } from "@slopus/happy-agent-supervisor";
101
+
102
+ const policy = parseSupervisorPolicy({
103
+ mode: "workspace_write",
104
+ allowedWritePaths: ["/work/project"],
105
+ network: { egress: false, localBinding: false },
106
+ });
107
+
108
+ const child = spawn(
109
+ resolveSupervisorBinary(),
110
+ ["--policy-fd", "3", "--", "/usr/bin/env", "node", "build.mjs"],
111
+ {
112
+ cwd: "/work/project",
113
+ stdio: ["inherit", "inherit", "inherit", "pipe"],
114
+ },
115
+ );
116
+ child.stdio[3].end(JSON.stringify(policy));
117
+ ```
118
+
119
+ `parseSupervisorPolicy(value)` validates against the TypeBox schema and throws a
120
+ readable error listing every offending field; the schemas and their `Static`
121
+ types are exported if you want to compose them yourself.
122
+
123
+ `resolveSupervisorBinary(binaryPath?)` returns the executable for the current
124
+ host, and `resolveLinuxSupervisorBinary(architecture, binaryPath?)` returns a
125
+ Linux one regardless of host. Both accept an explicit path that wins over
126
+ lookup — useful in tests and for a locally built binary — and both fall back to
127
+ this repository's `native/target` build directories when the optional package is
128
+ not installed. If neither is present they throw, naming the package to reinstall.
129
+
130
+ ## Command line
131
+
132
+ Policy stays out of argv, and the command is always an argument vector:
133
+
134
+ ```sh
135
+ happy-agent-supervisor --policy-fd 3 -- /bin/sh -c 'printf "%s\n" "$VALUE"'
136
+ happy-agent-supervisor --policy-file /trusted/policy.json -- /usr/bin/env
137
+ ```
138
+
139
+ Exactly one of `--policy-fd` and `--policy-file` is required, and everything
140
+ after `--` is the target command. The supervisor consumes the policy descriptor;
141
+ descriptors `0`, `1`, and `2` are rejected. Policy files are read and closed
142
+ before sandbox setup. Policy JSON is limited to 1 MiB and rejects unknown fields.
143
+ A supervisor-level failure — a bad argument, an invalid policy, an enforcement
144
+ step that would not apply — exits `125` with a message on stderr, which keeps it
145
+ distinguishable from the workload's own status. Otherwise the workload's exit
146
+ status is reproduced as its own.
147
+
148
+ ## Policy
149
+
150
+ The policy names match `ComputePermissions`:
151
+
152
+ ```json
153
+ {
154
+ "mode": "workspace_write",
155
+ "allowedReadPaths": [],
156
+ "deniedReadPaths": [],
157
+ "allowedWritePaths": [],
158
+ "deniedWritePaths": [],
159
+ "network": {
160
+ "egress": true,
161
+ "allowedHosts": [],
162
+ "localBinding": false
163
+ }
164
+ }
165
+ ```
166
+
167
+ `mode` is one of `read_only`, `workspace_write`, `auto`, or `full_access`. For
168
+ `workspace_write` and `auto`, the process working directory is the workspace
169
+ write root — the cwd is the single source of truth, so it is not repeated in the
170
+ document. Denials win over grants. Linux write-denied paths and writable roots
171
+ must already exist so the supervisor never creates a user-visible mount point
172
+ while privileged.
173
+
174
+ ## Outgoing proxy
175
+
176
+ Filtered egress is asked for by adding `network.outgoingProxy`, which names only
177
+ the front-ends to offer inside the sandbox:
178
+
179
+ ```json
180
+ {
181
+ "network": {
182
+ "egress": true,
183
+ "allowedHosts": ["example.com", "*.internal.example.com"],
184
+ "localBinding": false,
185
+ "outgoingProxy": { "frontEnds": ["http", "socks5"] }
186
+ }
187
+ }
188
+ ```
189
+
190
+ The supervisor provides the whole proxy. It forks an egress process before the
191
+ sandbox exists and joins the two with a socketpair, so the caller supplies no
192
+ descriptor and no token, and nothing inside the sandbox reaches the proxy — or
193
+ anything else — by address. The workload is given ordinary `HTTP_PROXY` and
194
+ `ALL_PROXY` addresses on loopback, carrying a secret generated for that one
195
+ invocation; both front-ends refuse a client that does not present it, as HTTP
196
+ Basic and as RFC 1929 respectively.
197
+
198
+ The egress process decides every destination. The requested name must match one
199
+ `allowedHosts` entry exactly or under one `*.suffix`, and the address that name
200
+ actually resolved to must not be loopback, private, link-local, or multicast
201
+ unless the policy named that IP literal directly. A bare `*` is refused: open
202
+ egress is expressed by configuring no proxy at all, and an empty list with a
203
+ proxy configured reaches nothing.
204
+
205
+ Egress with a non-empty `allowedHosts` and no proxy fails closed, because nothing
206
+ would be enforcing the list. A host list with egress disabled is already enforced
207
+ by the isolated network namespace. No TLS is terminated anywhere, so the boundary
208
+ is which host may be reached rather than what is sent to it.
209
+
210
+ ## Process hardening
211
+
212
+ The supervisor runs as the same user as the workload and holds the workload's
213
+ only route out of the jail, so before it forks anything it makes itself harder
214
+ to read: non-dumpable on Linux, debugger attachment denied on macOS, and core
215
+ dumps disabled. The egress process asks for the same again after the fork,
216
+ because macOS gives a child fresh process flags. Any of these failing stops the
217
+ run rather than continuing unprotected.
218
+
219
+ It also drops `LD_*` and `DYLD_*` from its own environment, so nothing chosen by
220
+ the caller is loaded into the process that is about to become the boundary. The
221
+ workload's environment is taken before that happens and passed on unchanged: a
222
+ sandboxed build may legitimately need `LD_LIBRARY_PATH`, and the workload can set
223
+ these variables for its own children in any case.
224
+
225
+ ## Building from source
226
+
227
+ The native workspace lives in `native/` and pins its own Rust toolchain.
228
+
229
+ ```sh
230
+ pnpm build # TypeScript API into dist/
231
+ pnpm build:native # host supervisor binary into native/target/release/
232
+ pnpm test # TypeScript tests
233
+ pnpm test:native # Rust behavior tests, which need the host's real kernel
234
+ ```
235
+
236
+ A binary built this way is picked up automatically by the resolvers, so a
237
+ checkout works without any published package installed. Release packaging and
238
+ publishing are described in [`release/README.md`](release/README.md).
package/SHA256SUMS ADDED
@@ -0,0 +1 @@
1
+ 4abd002129c8765ce2e8ba6e8d3160a46bb29f0fe31f5aa1c117a9f89e96eafe vendor/aarch64-apple-darwin/bin/happy-agent-supervisor
package/package.json CHANGED
@@ -1,18 +1,33 @@
1
1
  {
2
2
  "name": "@slopus/happy-agent-supervisor",
3
- "version": "0.0.0",
4
- "description": "Trusted native sandbox boundary for Happy agent workloads. Placeholder release.",
3
+ "version": "0.0.1-darwin-arm64",
4
+ "description": "Trusted native sandbox boundary for Happy agent workloads. Native binary for darwin-arm64.",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "git+https://github.com/slopus/rig.git",
9
9
  "directory": "packages/happy-agent-supervisor"
10
10
  },
11
+ "engines": {
12
+ "node": ">=22.19.0"
13
+ },
14
+ "os": [
15
+ "darwin"
16
+ ],
17
+ "cpu": [
18
+ "arm64"
19
+ ],
20
+ "bin": {
21
+ "happy-agent-supervisor": "vendor/aarch64-apple-darwin/bin/happy-agent-supervisor"
22
+ },
11
23
  "files": [
24
+ "vendor",
25
+ "SHA256SUMS",
26
+ "LICENSE",
12
27
  "README.md"
13
28
  ],
14
29
  "publishConfig": {
15
30
  "access": "public",
16
- "tag": "placeholder"
31
+ "tag": "platform"
17
32
  }
18
- }
33
+ }