@nathapp/nax-agent 0.91.4 → 0.91.5
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/CHANGELOG.md +4 -0
- package/dist/config/schemas-sandbox.js +2 -1
- package/dist/runtime/node-glob.js +22 -3
- package/dist/sandbox/network.d.ts +3 -0
- package/dist/sandbox/network.js +6 -0
- package/dist/sandbox/policy-builder.js +3 -4
- package/dist/sandbox/registry.js +5 -1
- package/dist/sandbox/session-sandbox.d.ts +1 -2
- package/dist/sandbox/session-sandbox.js +8 -2
- package/dist/sandbox/types.d.ts +4 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,10 @@ All notable changes to `@nathapp/nax-agent` are recorded here. The format follow
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/). While the version is `0.x`, a minor
|
|
5
5
|
release may change the public API.
|
|
6
6
|
|
|
7
|
+
## [0.91.5] - 2026-10-23
|
|
8
|
+
|
|
9
|
+
- No changes in this package. Released in lockstep with the other nax packages.
|
|
10
|
+
|
|
7
11
|
## [0.91.4] - 2026-10-23
|
|
8
12
|
|
|
9
13
|
- No changes in this package. Released in lockstep with the other nax packages.
|
|
@@ -41,7 +41,8 @@ export const SandboxConfigSchema = z.object({
|
|
|
41
41
|
/**
|
|
42
42
|
* US-002 (#2538): let a sandboxed command reach loopback ports.
|
|
43
43
|
*
|
|
44
|
-
*
|
|
44
|
+
* Absent: on when `allowedDomains` is set (see `effectiveAllowLocalBinding`),
|
|
45
|
+
* off otherwise. It only changes anything alongside an
|
|
45
46
|
* `allowedDomains` list: with an open network srt's macOS profile already
|
|
46
47
|
* allows all traffic. srt reads it once per process, from the config given
|
|
47
48
|
* to `SandboxManager.initialize`.
|
|
@@ -1,9 +1,28 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Node glob normalized to the agent contract and Bun's symlink traversal.
|
|
3
|
+
*
|
|
4
|
+
* Every dot segment hides a result (ruling D21), with one exemption: a pattern
|
|
5
|
+
* whose first segment is literally `.nax` may surface results whose first
|
|
6
|
+
* result-relative segment is `.nax` (nax's own artifacts live there). Dot
|
|
7
|
+
* segments below `.nax/` stay hidden, and so does every other hidden path.
|
|
8
|
+
*/
|
|
2
9
|
import { globSync, lstatSync, statSync } from "node:fs";
|
|
3
10
|
import { glob } from "node:fs/promises";
|
|
4
11
|
import { relative, resolve, sep } from "node:path";
|
|
12
|
+
const NAX_DIR = ".nax";
|
|
13
|
+
/** True when the pattern's first `/` segment is exactly `.nax`: no glob metacharacter, no escape. */
|
|
14
|
+
function exemptsNaxDir(pattern) {
|
|
15
|
+
return pattern.replace(/^(?:\.\/)+/, "").split("/")[0] === NAX_DIR;
|
|
16
|
+
}
|
|
17
|
+
/** Whether a result-relative path has a hidden dot segment, ignoring a leading `.nax` the pattern asked for. */
|
|
18
|
+
function hasHiddenSegment(path, naxExempt) {
|
|
19
|
+
return path
|
|
20
|
+
.split(sep)
|
|
21
|
+
.some((segment, index) => segment.startsWith(".") && !(naxExempt && index === 0 && segment === NAX_DIR));
|
|
22
|
+
}
|
|
5
23
|
function scanOptions(pattern, opts) {
|
|
6
24
|
const cwd = resolve(opts.cwd);
|
|
25
|
+
const naxExempt = exemptsNaxDir(pattern);
|
|
7
26
|
if (!statSync(cwd).isDirectory()) {
|
|
8
27
|
throw Object.assign(new Error(`ENOTDIR: not a directory, glob '${cwd}'`), { code: "ENOTDIR", path: cwd });
|
|
9
28
|
}
|
|
@@ -24,7 +43,7 @@ function scanOptions(pattern, opts) {
|
|
|
24
43
|
};
|
|
25
44
|
const exclude = (entry) => {
|
|
26
45
|
const path = relative(cwd, resolve(cwd, entry.parentPath, entry.name));
|
|
27
|
-
if (path
|
|
46
|
+
if (hasHiddenSegment(path, naxExempt))
|
|
28
47
|
return true;
|
|
29
48
|
// Node can optimize brace/class segments without reporting their symlink
|
|
30
49
|
// entries. Check lexical ancestors too, allowing only the literal prefix.
|
|
@@ -45,7 +64,7 @@ function filePath(entry, cwd, absolute, pattern) {
|
|
|
45
64
|
statSync(full, { throwIfNoEntry: false })?.isFile()))
|
|
46
65
|
return null;
|
|
47
66
|
const path = relative(cwd, full);
|
|
48
|
-
if (path
|
|
67
|
+
if (hasHiddenSegment(path, exemptsNaxDir(pattern)))
|
|
49
68
|
return null;
|
|
50
69
|
const dotPrefix = pattern.match(/^(?:\.\/)+/)?.[0] ?? "";
|
|
51
70
|
return absolute ? resolve(full) : `${dotPrefix}${path}`;
|
|
@@ -13,6 +13,7 @@ import { SANDBOX_GLOB_CHARS } from "../config/schemas-sandbox.js";
|
|
|
13
13
|
import { CLAUDE_SETTINGS_FILES } from "./claude-settings.js";
|
|
14
14
|
import { BUILTIN_CACHE_WRITE_ROOTS, BUILTIN_CREDENTIAL_READ_DENIES, MACOS_CACHE_WRITE_ROOT, SRT_MACOS_TMPDIR, SRT_MACOS_TMPDIR_DENIES, } from "./defaults.js";
|
|
15
15
|
import { WORKTREE_COMMON_WRITE_DIRS, WORKTREE_CONFIG_FILE } from "./git-guards.js";
|
|
16
|
+
import { effectiveAllowLocalBinding } from "./network.js";
|
|
16
17
|
function expandHome(p, home) {
|
|
17
18
|
if (p === "~")
|
|
18
19
|
return home;
|
|
@@ -162,10 +163,8 @@ export function buildSandboxPolicy(input) {
|
|
|
162
163
|
...config.filesystem.denyRead.map((p) => resolve(root, expandHome(p, home))),
|
|
163
164
|
]);
|
|
164
165
|
const allowed = config.network.allowedDomains;
|
|
165
|
-
//
|
|
166
|
-
|
|
167
|
-
// only admits `true` (see SandboxNetworkPolicy), and srt treats absent as off.
|
|
168
|
-
const loopback = config.network.allowLocalBinding === true;
|
|
166
|
+
// Resolve the config default before constructing a true-or-absent policy.
|
|
167
|
+
const loopback = effectiveAllowLocalBinding(config.network);
|
|
169
168
|
return {
|
|
170
169
|
writeRoots,
|
|
171
170
|
denyWrite,
|
package/dist/sandbox/registry.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* serves one project, so one network config per process holds.
|
|
5
5
|
*/
|
|
6
6
|
import { getSafeLogger } from "#src/infra/index";
|
|
7
|
+
import { effectiveAllowLocalBinding } from "./network.js";
|
|
7
8
|
import { probeSandbox } from "./probe.js";
|
|
8
9
|
import { createSrtBackend } from "./srt-backend.js";
|
|
9
10
|
export const _sandboxRegistryDeps = {
|
|
@@ -14,7 +15,10 @@ let backend;
|
|
|
14
15
|
let probed;
|
|
15
16
|
let warnedUnavailable = false;
|
|
16
17
|
export function sandboxBackendFor(config) {
|
|
17
|
-
backend ??= _sandboxRegistryDeps.createBackend(
|
|
18
|
+
backend ??= _sandboxRegistryDeps.createBackend({
|
|
19
|
+
...config.network,
|
|
20
|
+
allowLocalBinding: effectiveAllowLocalBinding(config.network),
|
|
21
|
+
});
|
|
18
22
|
return backend;
|
|
19
23
|
}
|
|
20
24
|
export function probeSandboxOnce(target, storyId = "_sandbox") {
|
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
/** nax#2500: a launcher's sandbox as plain data for a backend that runs its own shell. */
|
|
2
1
|
import type { CommandLauncher, SessionSandbox } from "./types.ts";
|
|
3
|
-
export declare function sessionSandboxFrom(launcher: CommandLauncher | undefined, root: string): Promise<SessionSandbox | undefined>;
|
|
2
|
+
export declare function sessionSandboxFrom(launcher: CommandLauncher | undefined, root: string, writeExemption?: string): Promise<SessionSandbox | undefined>;
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
/** nax#2500: a launcher's sandbox as plain data for a backend that runs its own shell. */
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
export async function sessionSandboxFrom(launcher, root, writeExemption) {
|
|
2
4
|
if (launcher === undefined || launcher.state.kind === "disabled")
|
|
3
5
|
return undefined;
|
|
4
6
|
if (launcher.state.kind === "unavailable")
|
|
5
7
|
return { kind: "unavailable", reason: launcher.state.reason };
|
|
6
8
|
if (launcher.policyAt === undefined)
|
|
7
9
|
return { kind: "unavailable", reason: "the launcher exposes no policy" };
|
|
8
|
-
return {
|
|
10
|
+
return {
|
|
11
|
+
kind: "policy",
|
|
12
|
+
policy: await launcher.policyAt(root),
|
|
13
|
+
...(writeExemption === undefined ? {} : { writeExemption: resolve(root, writeExemption) }),
|
|
14
|
+
};
|
|
9
15
|
}
|
package/dist/sandbox/types.d.ts
CHANGED
|
@@ -14,7 +14,8 @@ export interface SandboxNetworkPolicy {
|
|
|
14
14
|
* US-002: srt may let a command bind to -- and reach -- loopback ports, so a
|
|
15
15
|
* DB-backed test run sees a local server that is up (#2538).
|
|
16
16
|
*
|
|
17
|
-
*
|
|
17
|
+
* The config default is resolved by `effectiveAllowLocalBinding` before
|
|
18
|
+
* policy construction. `true` only. srt's own default is off, so absent and `false` are the same
|
|
18
19
|
* thing to it, and a policy that cannot express "off" cannot contradict the
|
|
19
20
|
* config it was built from.
|
|
20
21
|
*/
|
|
@@ -34,6 +35,8 @@ export interface SandboxPolicy {
|
|
|
34
35
|
export type SessionSandbox = {
|
|
35
36
|
readonly kind: "policy";
|
|
36
37
|
readonly policy: SandboxPolicy;
|
|
38
|
+
/** Absolute path of the session's single writable output. */
|
|
39
|
+
readonly writeExemption?: string;
|
|
37
40
|
} | {
|
|
38
41
|
readonly kind: "unavailable";
|
|
39
42
|
readonly reason: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nathapp/nax-agent",
|
|
3
|
-
"version": "0.91.
|
|
3
|
+
"version": "0.91.5",
|
|
4
4
|
"description": "nax's native coding agent: session contract, native loop, tools, permissions, sandbox, command-safety.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "William Khoo",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@anthropic-ai/sandbox-runtime": "0.0.77",
|
|
49
49
|
"@modelcontextprotocol/sdk": "^1.32.1",
|
|
50
|
-
"@nathapp/nax-ai": "0.91.
|
|
50
|
+
"@nathapp/nax-ai": "0.91.5",
|
|
51
51
|
"zod": "^4.3.6"
|
|
52
52
|
},
|
|
53
53
|
"publishConfig": {
|