@landstrip/landstrip 0.16.23 → 0.17.1

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.
Files changed (3) hide show
  1. package/README.md +80 -22
  2. package/lib/index.d.ts +151 -0
  3. package/package.json +12 -6
package/README.md CHANGED
@@ -22,6 +22,20 @@ npx landstrip -p policy.json cargo test
22
22
  The npm package installs a small Node.js wrapper and a platform-specific native
23
23
  binary package.
24
24
 
25
+
26
+ ### Agent extensions
27
+
28
+ The bundled extensions integrate Landstrip with Pi and OpenCode:
29
+
30
+ ```sh
31
+ pi install npm:pi-landstrip
32
+ opencode plugin install opencode-landstrip
33
+ ```
34
+
35
+ See [pi-landstrip](packages/pi-landstrip/README.md) and
36
+ [opencode-landstrip](packages/opencode-landstrip/README.md) for configuration
37
+ details.
38
+
25
39
  ## Platforms
26
40
 
27
41
  | Area | macOS | Linux | Windows |
@@ -42,8 +56,9 @@ statically, e.g. for many filesystem mutator syscalls, or when denials must be
42
56
  reported back to the launcher. The broker intercepts `openat`/`openat2` via
43
57
  seccomp user-notifications, resolves the real path, and validates it
44
58
 
45
- Finally there's some logic to deduce that Landlock and Seccomp (mostly for
46
- mutator syscall, which take fd) are fairly disjoint entities.
59
+ Landlock and seccomp cover mostly disjoint filesystem operations: Landlock
60
+ handles kernel-enforced path access, while seccomp mediates unsupported mutators
61
+ and reports broker decisions.
47
62
 
48
63
  ### Windows
49
64
 
@@ -135,18 +150,20 @@ For a filesystem-only sandbox with unrestricted direct network access, set:
135
150
  policy enforcement in place. On Windows this grants the AppContainer its network
136
151
  capabilities; without it the container denies all network access.
137
152
 
138
- ## Denial Traps
153
+ ## Traps
139
154
 
140
- Sandbox denials are reported as JSON objects, one per line, each with a fixed
141
- `kind` discriminant and a stable `code`. Consumers route on `kind` for coarse
142
- grouping and on `code` for the policy denial class. Traps go to standard error
143
- by default; `--trap-fd FD` writes them to an already-open descriptor instead.
155
+ Every landstrip event a sandbox denial, and every failure that keeps the tool
156
+ from running is reported as a JSON object, one per line, with a fixed `kind`
157
+ discriminant and a stable `code`. Consumers route on `kind` for the shape of the
158
+ record and on `code` for what happened. Failure traps and completed denial traps
159
+ go to standard error by default. On Linux, pending query traps go only to
160
+ `--trap-fd FD`, which writes them to an already-open descriptor.
144
161
 
145
162
  ```sh
146
163
  landstrip --trap-fd 3 -p policy.json cargo test 3>landstrip-traps.txt
147
164
  ```
148
165
 
149
- The two trap kinds are:
166
+ The trap kinds are:
150
167
 
151
168
  - `filesystem` (`code` `FILESYSTEM_DENIED`): `operation` is `read` or `write`,
152
169
  `path` is the resolved path, `requested_path` is the tool's original path when
@@ -154,21 +171,43 @@ The two trap kinds are:
154
171
  `process` carry routing context.
155
172
  - `network` (`code` `NETWORK_DENIED`): `operation` is `connect` or `bind` and
156
173
  `target` is `address:port`, with `syscall`, `errno`, and `process` context.
157
-
158
- `reason` is a platform-independent classification of the decision, derived from
159
- the policy and the requested path:
174
+ - `launch` (`code` `LAUNCH_FAILED`): the sandbox was installed but the tool did
175
+ not start. `program` is the tool, `errno` its symbolic errno where the platform
176
+ has one, and `message` the system's text.
177
+ - `usage` (`code` `USAGE_ERROR`): the command line was rejected. Exits with
178
+ status 2, and reaches standard error only — the trap descriptor is part of the
179
+ arguments that failed to parse.
180
+ - `internal`: everything that fails before the tool runs. `code` names the stage:
181
+ `POLICY_PARSE_FAILED`, `POLICY_IO_FAILED`, `SANDBOX_SETUP_FAILED`,
182
+ `SUPERVISE_FAILED`, `PLATFORM_UNSUPPORTED`, `INTEGER_TOO_LARGE`, a
183
+ `POLICY_*` validation rejection (`POLICY_UNRESTRICTED_READ`,
184
+ `POLICY_TCP_BIND_UNSUPPORTED`, `POLICY_UNIX_SOCKET_UNSUPPORTED`,
185
+ `POLICY_UNIX_SOCKET_PATH`, `POLICY_DENY_WRITE_SYMLINK_ANCESTOR`,
186
+ `POLICY_INVALID_PORT`, `POLICY_EMPTY_PATH`, `POLICY_HOME_UNAVAILABLE`,
187
+ `POLICY_TRAVERSAL_DEPTH`), or `INTERNAL_ERROR` for a failure the code space
188
+ does not name.
189
+
190
+ A code names the stage that failed, not the operating system that reported it:
191
+ the same `LAUNCH_FAILED` or `SANDBOX_SETUP_FAILED` is raised by every backend
192
+ that has that stage. The platform detail rides along in the record instead.
193
+
194
+ `mechanism` records the kernel layer an event is attributed to: `landlock`,
195
+ `seccomp`, `seatbelt`, or `appcontainer`. Per-denial traps are always `seccomp`,
196
+ the only layer with a per-denial callback; Landlock enforces in-kernel without
197
+ one. `SANDBOX_SETUP_FAILED` carries the mechanism that could not be installed.
198
+
199
+ `reason` is a platform-independent classification of a filesystem decision,
200
+ derived from the policy and the requested path:
160
201
 
161
202
  - `allow_miss`: the path matched no allow root and was denied by default.
162
203
  - `deny_match`: the path matched an explicit deny root that overrides an allow.
163
204
 
164
- `mechanism` records the kernel layer that detected the denial. Per-denial traps
165
- are always `seccomp`, the only layer with a per-denial callback; Landlock
166
- enforces in-kernel without one.
167
-
168
205
  ```json
169
206
  {
170
207
  "kind": "filesystem",
171
208
  "code": "FILESYSTEM_DENIED",
209
+ "state": "info",
210
+ "query_id": "0",
172
211
  "operation": "write",
173
212
  "path": "/repo/out",
174
213
  "requested_path": "out",
@@ -183,6 +222,8 @@ enforces in-kernel without one.
183
222
  {
184
223
  "kind": "network",
185
224
  "code": "NETWORK_DENIED",
225
+ "state": "info",
226
+ "query_id": "0",
186
227
  "operation": "connect",
187
228
  "target": "127.0.0.1:9999",
188
229
  "syscall": "connect",
@@ -190,15 +231,32 @@ enforces in-kernel without one.
190
231
  "mechanism": "seccomp",
191
232
  "process": { "pid": 1234, "exe": "/usr/bin/nc", "cwd": "/repo" }
192
233
  }
234
+ {
235
+ "kind": "launch",
236
+ "code": "LAUNCH_FAILED",
237
+ "program": "/usr/bin/cargo",
238
+ "errno": "ENOENT",
239
+ "message": "No such file or directory (os error 2)"
240
+ }
241
+ {
242
+ "kind": "internal",
243
+ "code": "SANDBOX_SETUP_FAILED",
244
+ "mechanism": "landlock",
245
+ "message": "not enforced by the kernel (Linux 5.13+ with CONFIG_SECURITY_LANDLOCK required, and not disabled via the lsm= boot parameter)"
246
+ }
193
247
  ```
194
248
 
195
- Traps are informational; the configured policy always applies. landstrip is
196
- otherwise quiet on success — standard error belongs to landstrip, standard
197
- output to the sandboxed tool. Usage, policy, launch, and platform errors are
198
- printed as plain text; usage errors exit with status 2. Writing to `--trap-fd`
199
- is best-effort: it needs an already-open descriptor (3 or greater; 0-2 are
200
- reserved), and if the write fails the denial is dropped while the policy stays
201
- in effect.
249
+ Denial traps are informational; the configured policy always applies. landstrip
250
+ is otherwise quiet on success — standard error belongs to landstrip, standard
251
+ output to the sandboxed tool. Failure traps are accompanied by a human-readable
252
+ log line; the JSON is what machines should read. Usage errors exit with status 2,
253
+ every other landstrip failure with 1; the tool's own status is passed through
254
+ otherwise.
255
+
256
+ Writing to `--trap-fd` is best-effort: it needs an already-open descriptor (3 or
257
+ greater; 0-2 are reserved), and if the write fails the trap is dropped while the
258
+ policy stays in effect. On Linux, a broker launch failure also reaches
259
+ `--trap-fd` while the descriptor remains open.
202
260
 
203
261
  ## Development
204
262
 
package/lib/index.d.ts ADDED
@@ -0,0 +1,151 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+
3
+ /** The kernel layer an event is attributed to. */
4
+ export type LandstripMechanism =
5
+ | 'landlock'
6
+ | 'seccomp'
7
+ | 'seatbelt'
8
+ | 'appcontainer';
9
+
10
+ /**
11
+ * `query` holds the syscall until the launcher answers with a
12
+ * {@link LandstripControlResponse}; `info` is terminal. Queries need a socket on
13
+ * `--trap-fd`, and only the Linux broker raises them.
14
+ */
15
+ export type LandstripTrapState = 'query' | 'info';
16
+
17
+ /** Why a filesystem access was mediated. */
18
+ export type LandstripTrapReason = 'allow_miss' | 'deny_match';
19
+
20
+ export interface LandstripProcess {
21
+ pid: number;
22
+ exe: string | null;
23
+ cwd: string | null;
24
+ }
25
+
26
+ /** A filesystem access the policy denies. */
27
+ export interface LandstripFilesystemTrap {
28
+ kind: 'filesystem';
29
+ code: 'FILESYSTEM_DENIED';
30
+ state: LandstripTrapState;
31
+ /** Decimal `u64`; `"0"` marks a terminal `info` event. */
32
+ query_id: string;
33
+ operation: 'read' | 'write';
34
+ /** The resolved path. */
35
+ path: string;
36
+ /** The path the tool asked for, before resolution. */
37
+ requested_path: string;
38
+ syscall: string;
39
+ errno: string;
40
+ flags: string[];
41
+ reason: LandstripTrapReason;
42
+ /** The policy edit that would permit this access. */
43
+ suggested_grant: { allowRead?: string; allowWrite?: string };
44
+ process: LandstripProcess;
45
+ mechanism: 'seccomp';
46
+ }
47
+
48
+ /** A network access the policy denies. */
49
+ export interface LandstripNetworkTrap {
50
+ kind: 'network';
51
+ code: 'NETWORK_DENIED';
52
+ state: LandstripTrapState;
53
+ /** Decimal `u64`; `"0"` marks a terminal `info` event. */
54
+ query_id: string;
55
+ operation: 'connect' | 'bind';
56
+ /** `address:port`. */
57
+ target: string;
58
+ syscall: string;
59
+ errno: string;
60
+ mechanism: 'seccomp';
61
+ process: LandstripProcess;
62
+ }
63
+
64
+ /** The sandbox was installed but the tool did not start. */
65
+ export interface LandstripLaunchTrap {
66
+ kind: 'launch';
67
+ code: 'LAUNCH_FAILED';
68
+ program: string;
69
+ /** Absent where the platform has no POSIX errno for the failure. */
70
+ errno?: string;
71
+ message: string;
72
+ }
73
+
74
+ /** The command line was rejected. Reaches stderr only; landstrip exits 2. */
75
+ export interface LandstripUsageTrap {
76
+ kind: 'usage';
77
+ code: 'USAGE_ERROR';
78
+ message: string;
79
+ }
80
+
81
+ /** A policy the platform sandbox cannot enforce, rejected before launch. */
82
+ export type LandstripPolicyErrorCode =
83
+ | 'POLICY_PARSE_FAILED'
84
+ | 'POLICY_IO_FAILED'
85
+ | 'POLICY_UNRESTRICTED_READ'
86
+ | 'POLICY_TCP_BIND_UNSUPPORTED'
87
+ | 'POLICY_UNIX_SOCKET_UNSUPPORTED'
88
+ | 'POLICY_UNIX_SOCKET_PATH'
89
+ | 'POLICY_DENY_WRITE_SYMLINK_ANCESTOR'
90
+ | 'POLICY_INVALID_PORT'
91
+ | 'POLICY_EMPTY_PATH'
92
+ | 'POLICY_HOME_UNAVAILABLE'
93
+ | 'POLICY_TRAVERSAL_DEPTH';
94
+
95
+ /** The stage that failed before the tool ran. */
96
+ export type LandstripInternalCode =
97
+ | LandstripPolicyErrorCode
98
+ | 'SANDBOX_SETUP_FAILED'
99
+ | 'SUPERVISE_FAILED'
100
+ | 'PLATFORM_UNSUPPORTED'
101
+ | 'INTEGER_TOO_LARGE'
102
+ | 'INTERNAL_ERROR';
103
+
104
+ /** Everything that fails before the tool runs. */
105
+ export type LandstripInternalTrap =
106
+ | {
107
+ kind: 'internal';
108
+ code: 'SANDBOX_SETUP_FAILED';
109
+ mechanism: LandstripMechanism;
110
+ message: string;
111
+ }
112
+ | {
113
+ kind: 'internal';
114
+ code: Exclude<LandstripInternalCode, 'SANDBOX_SETUP_FAILED'>;
115
+ mechanism?: never;
116
+ message: string;
117
+ };
118
+
119
+ /**
120
+ * One landstrip event. Failure and completed-denial events are written to
121
+ * stderr; pending Linux query events are written only to `--trap-fd`.
122
+ */
123
+ export type LandstripTrap =
124
+ | LandstripFilesystemTrap
125
+ | LandstripNetworkTrap
126
+ | LandstripLaunchTrap
127
+ | LandstripUsageTrap
128
+ | LandstripInternalTrap;
129
+
130
+ /** Every code landstrip reports, across all trap kinds. */
131
+ export type LandstripCode = LandstripTrap['code'];
132
+
133
+ /**
134
+ * The answer to a `state: "query"` trap, written back to the trap socket as one
135
+ * JSON line. `query_id` is an opaque decimal `u64` copied verbatim from the
136
+ * trap. An unanswered query holds the sandboxed syscall.
137
+ */
138
+ export interface LandstripControlResponse {
139
+ query_id: string;
140
+ action: 'allow' | 'deny';
141
+ }
142
+
143
+ /**
144
+ * Path to the native landstrip binary for the running platform.
145
+ *
146
+ * @throws if the platform is unsupported, or the binary package is not installed.
147
+ */
148
+ export function binaryPath(platform?: string, arch?: string): string;
149
+
150
+ /** Name of the binary package for the given platform. */
151
+ export function packageName(platform?: string, arch?: string): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@landstrip/landstrip",
3
- "version": "0.16.23",
3
+ "version": "0.17.1",
4
4
  "description": "Sandbox runner using Landlock, Seatbelt, and AppContainer",
5
5
  "license": "Apache-2.0 AND LGPL-2.1-or-later",
6
6
  "homepage": "https://github.com/landstrip/landstrip#readme",
@@ -12,7 +12,13 @@
12
12
  "url": "https://github.com/landstrip/landstrip/issues"
13
13
  },
14
14
  "main": "lib/index.js",
15
- "exports": "./lib/index.js",
15
+ "types": "lib/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./lib/index.d.ts",
19
+ "default": "./lib/index.js"
20
+ }
21
+ },
16
22
  "bin": {
17
23
  "landstrip": "bin/landstrip.js"
18
24
  },
@@ -24,10 +30,10 @@
24
30
  "LICENSE-LGPL-2.1"
25
31
  ],
26
32
  "optionalDependencies": {
27
- "@landstrip/landstrip-darwin-arm64": "0.16.23",
28
- "@landstrip/landstrip-darwin-x64": "0.16.23",
29
- "@landstrip/landstrip-linux-x64": "0.16.23",
30
- "@landstrip/landstrip-win32-x64": "0.16.23"
33
+ "@landstrip/landstrip-darwin-arm64": "0.17.1",
34
+ "@landstrip/landstrip-darwin-x64": "0.17.1",
35
+ "@landstrip/landstrip-linux-x64": "0.17.1",
36
+ "@landstrip/landstrip-win32-x64": "0.17.1"
31
37
  },
32
38
  "publishConfig": {
33
39
  "access": "public"