@landstrip/landstrip-api 0.18.43 → 0.19.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 CHANGED
@@ -1,64 +1,49 @@
1
- # landstrip
1
+ # @landstrip/landstrip-api
2
2
 
3
- `landstrip` runs commands in an OS-level sandbox using Landlock on Linux,
4
- Seatbelt on macOS, and AppContainer or restricted users on Windows.
3
+ Node.js wrapper, native binaries, and TypeScript types for the Landstrip sandbox.
4
+ Requires Node.js 18 or newer; binaries are provided for Linux, macOS, and Windows
5
+ on x64 and arm64.
5
6
 
6
- ## Install
7
+ ## Install and run
7
8
 
8
9
  ```sh
9
10
  npm install --save-dev @landstrip/landstrip-api
10
11
  npx landstrip run -p policy.json -- cargo test
11
12
  ```
12
13
 
13
- The npm package installs a Node.js wrapper and a native binary for the current
14
- platform.
14
+ See the [quick start](../../README.md#quick-start) for an example policy and
15
+ [landstrip(1)](../landstrip/man/man1/landstrip.1) for CLI options, policy rules,
16
+ and platform limits.
15
17
 
16
- ## Usage
18
+ ## Node.js API
17
19
 
18
- ```sh
19
- landstrip run -p policy.json -- cargo test
20
- landstrip policy validate -p policy.json
21
- landstrip policy resolve -p policy.json
22
- landstrip doctor
23
- ```
24
-
25
- Run `landstrip --help` or see [landstrip(1)](../landstrip/man/man1/landstrip.1) for the full
26
- command reference.
27
-
28
- ## Agent extensions
20
+ ```js
21
+ const { execFileSync } = require('node:child_process');
22
+ const { binaryPath } = require('@landstrip/landstrip-api');
29
23
 
30
- | Integration | Package | Documentation |
31
- | ----------- | ---------------------- | -------------------------------------------------------------------- |
32
- | OpenCode | `opencode-landstrip` | [OpenCode](packages/opencode-landstrip/README.md) |
33
- | Pi | `pi-landstrip` | [Pi](packages/pi-landstrip/README.md) |
24
+ execFileSync(binaryPath(), ['run', '-p', 'policy.json', '--', 'cargo', 'test'], {
25
+ stdio: 'inherit',
26
+ });
27
+ ```
34
28
 
35
- ## Policy
29
+ `binaryPath()` returns the installed native binary's path and throws if the
30
+ platform is unsupported or its binary package is missing. `packageName()` returns
31
+ the platform's binary package name. Both accept optional `platform` and `arch`
32
+ arguments, defaulting to the current process.
36
33
 
37
- ```json
38
- {
39
- "filesystem": {
40
- "allowWrite": ["."],
41
- "denyWrite": ["**/.env", "**/*.pem"],
42
- "denyRead": ["~/.ssh"],
43
- "allowRead": ["~/.ssh/config"]
44
- },
45
- "network": {
46
- "allowNetwork": false,
47
- "allowLocalBinding": false
48
- }
49
- }
50
- ```
34
+ The package exports [`LandstripTrap` and `LandstripControlResponse`](lib/index.d.ts)
35
+ for structured events and Linux broker replies. Runtime traps and approval
36
+ queries are Linux-only; macOS's `--trap` reports setup and launch failures.
37
+ Descriptor lifetimes and the reply protocol are covered in the manual.
51
38
 
52
- Pass policies with `-p, --policy <FILE>`. Later policies merge over earlier
53
- ones. Platform behavior and limits are documented in the manual.
39
+ Integration helpers are exported through [`/shared`](lib/shared.d.ts) and
40
+ [`/proxy`](lib/proxy.d.ts).
54
41
 
55
42
  ## Development
56
43
 
57
- ```sh
58
- make ci
59
- ```
44
+ Run `make ci` from the repository root.
60
45
 
61
- ## Licensing
46
+ ## License
62
47
 
63
- `@landstrip/landstrip-api` is licensed under the Apache 2.0 license. See
64
- [LICENSE](LICENSE) for more information.
48
+ [Apache-2.0](LICENSE). The bundled native binary is
49
+ [LGPL-3.0-or-later](../landstrip/LICENSE).
package/lib/index.d.ts CHANGED
@@ -10,7 +10,7 @@ export type LandstripMechanism =
10
10
  /**
11
11
  * `query` holds the syscall until the launcher answers with a
12
12
  * {@link LandstripControlResponse}; `info` is terminal. Queries need a socket on
13
- * `--trap-fd`, and only the Linux broker raises them.
13
+ * `--trap`, and only the Linux broker raises them.
14
14
  */
15
15
  export type LandstripTrapState = 'query' | 'info';
16
16
 
@@ -83,6 +83,7 @@ export type LandstripPolicyErrorCode =
83
83
  | 'POLICY_PARSE_FAILED'
84
84
  | 'POLICY_IO_FAILED'
85
85
  | 'POLICY_UNRESTRICTED_READ'
86
+ | 'POLICY_DENY_READ_ALWAYS_UNSUPPORTED'
86
87
  | 'POLICY_TCP_BIND_UNSUPPORTED'
87
88
  | 'POLICY_UNIX_SOCKET_UNSUPPORTED'
88
89
  | 'POLICY_UNIX_SOCKET_PATH'
@@ -118,7 +119,7 @@ export type LandstripInternalTrap =
118
119
 
119
120
  /**
120
121
  * One landstrip event. Failure and completed-denial events are written to
121
- * stderr; pending Linux query events are written only to `--trap-fd`.
122
+ * stderr; pending Linux query events are written only to `--trap`.
122
123
  */
123
124
  export type LandstripTrap =
124
125
  | LandstripFilesystemTrap
package/lib/proxy.js CHANGED
@@ -4,6 +4,7 @@
4
4
  const { lookup } = require('node:dns/promises');
5
5
  const { Agent, createServer: createHttpServer, request: requestHttp } = require('node:http');
6
6
  const { connect, isIP } = require('node:net');
7
+ const { pipeline } = require('node:stream');
7
8
 
8
9
  const { canonicalizeHost } = require('./shared');
9
10
 
@@ -262,7 +263,9 @@ function startFilterProxy(options) {
262
263
  upstreamResponse.statusMessage,
263
264
  stripHopByHopHeaders(upstreamResponse.headers),
264
265
  );
265
- upstreamResponse.pipe(clientResponse);
266
+ pipeline(upstreamResponse, clientResponse, (error) => {
267
+ if (error) upstreamRequest.destroy();
268
+ });
266
269
  });
267
270
  upstreamRequest.once('error', () => {
268
271
  if (!clientResponse.headersSent) {
@@ -272,8 +275,12 @@ function startFilterProxy(options) {
272
275
  }
273
276
  });
274
277
  clientRequest.once('aborted', () => upstreamRequest.destroy());
278
+ clientRequest.once('error', () => upstreamRequest.destroy());
279
+ clientResponse.once('error', () => upstreamRequest.destroy());
275
280
  clientResponse.once('close', () => {
276
- if (!clientResponse.writableEnded) upstreamRequest.destroy();
281
+ clientRequest.unpipe(upstreamRequest);
282
+ upstreamRequest.destroy();
283
+ agent.destroy();
277
284
  });
278
285
  clientRequest.pipe(upstreamRequest);
279
286
  }
package/lib/shared.js CHANGED
@@ -68,22 +68,22 @@ function sessionScopeFor(filePath, baseDirectory) {
68
68
  }
69
69
 
70
70
  function canonicalizeHost(host) {
71
+ if (/[\s\p{Cc}/\\@?#]/u.test(host)) return null;
72
+
71
73
  const bracketed = host.startsWith('[');
72
74
  if (bracketed !== host.endsWith(']')) return null;
73
-
74
- // A trailing dot ("pastebin.com.") is the same host to DNS but would slip
75
- // past a literal deny entry; strip a single trailing dot and reject the rest.
76
- const value = bracketed ? host.slice(1, -1) : host.replace(/\.$/, '');
77
- if (!value || value.endsWith('.')) return null;
75
+ const value = bracketed ? host.slice(1, -1) : host;
78
76
 
79
77
  if (ipaddr.isValid(value)) {
80
78
  if (bracketed && ipaddr.parse(value).kind() !== 'ipv6') return null;
81
79
  return ipaddr.process(value).toString();
82
80
  }
83
- if (bracketed) return null;
81
+ if (bracketed || /[:%]/.test(value)) return null;
84
82
 
85
- const ascii = domainToASCII(value);
86
- if (!ascii) return null;
83
+ // A trailing dot ("pastebin.com.") is the same host to DNS but would slip
84
+ // past a literal deny entry; strip a single trailing dot and reject the rest.
85
+ const ascii = domainToASCII(value).replace(/\.$/, '');
86
+ if (!ascii || ascii.endsWith('.')) return null;
87
87
 
88
88
  try {
89
89
  const parsed = new URL(`http://${ascii}/`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@landstrip/landstrip-api",
3
- "version": "0.18.43",
3
+ "version": "0.19.0",
4
4
  "description": "Sandbox runner using Landlock, Seatbelt, and AppContainer",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://github.com/landstrip/landstrip#readme",
@@ -40,12 +40,12 @@
40
40
  "ipaddr.js": "^2.5.0"
41
41
  },
42
42
  "optionalDependencies": {
43
- "@landstrip/landstrip-darwin-arm64": "0.18.43",
44
- "@landstrip/landstrip-darwin-x64": "0.18.43",
45
- "@landstrip/landstrip-linux-x64": "0.18.43",
46
- "@landstrip/landstrip-linux-arm64": "0.18.43",
47
- "@landstrip/landstrip-win32-x64": "0.18.43",
48
- "@landstrip/landstrip-win32-arm64": "0.18.43"
43
+ "@landstrip/landstrip-darwin-arm64": "0.19.0",
44
+ "@landstrip/landstrip-darwin-x64": "0.19.0",
45
+ "@landstrip/landstrip-linux-x64": "0.19.0",
46
+ "@landstrip/landstrip-linux-arm64": "0.19.0",
47
+ "@landstrip/landstrip-win32-x64": "0.19.0",
48
+ "@landstrip/landstrip-win32-arm64": "0.19.0"
49
49
  },
50
50
  "publishConfig": {
51
51
  "access": "public"