@slopus/happy-agent-supervisor 0.0.0 → 0.0.1-darwin-x64
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 +21 -0
- package/README.md +236 -5
- package/SHA256SUMS +1 -0
- package/package.json +19 -4
- package/vendor/x86_64-apple-darwin/bin/happy-agent-supervisor +0 -0
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
|
-
#
|
|
1
|
+
# Happy Agent Supervisor
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
+
559b77454948858ac720beab4f1a25db8e728cf1ea89ed38610a9ef2c01a0506 vendor/x86_64-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.
|
|
4
|
-
"description": "Trusted native sandbox boundary for Happy agent workloads.
|
|
3
|
+
"version": "0.0.1-darwin-x64",
|
|
4
|
+
"description": "Trusted native sandbox boundary for Happy agent workloads. Native binary for darwin-x64.",
|
|
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
|
+
"x64"
|
|
19
|
+
],
|
|
20
|
+
"bin": {
|
|
21
|
+
"happy-agent-supervisor": "vendor/x86_64-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": "
|
|
31
|
+
"tag": "platform"
|
|
17
32
|
}
|
|
18
|
-
}
|
|
33
|
+
}
|
|
Binary file
|