@vzn/vx 0.0.199 → 0.0.200
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vzn/vx",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.200",
|
|
4
4
|
"description": "A fast, correct task runner and build cache for JavaScript monorepos.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -17,10 +17,10 @@
|
|
|
17
17
|
"node": ">=18"
|
|
18
18
|
},
|
|
19
19
|
"optionalDependencies": {
|
|
20
|
-
"@vzn/vx-linux-x64": "0.0.
|
|
21
|
-
"@vzn/vx-linux-arm64": "0.0.
|
|
22
|
-
"@vzn/vx-darwin-x64": "0.0.
|
|
23
|
-
"@vzn/vx-darwin-arm64": "0.0.
|
|
20
|
+
"@vzn/vx-linux-x64": "0.0.200",
|
|
21
|
+
"@vzn/vx-linux-arm64": "0.0.200",
|
|
22
|
+
"@vzn/vx-darwin-x64": "0.0.200",
|
|
23
|
+
"@vzn/vx-darwin-arm64": "0.0.200"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@anthropic-ai/sandbox-runtime": "^0.0.76"
|
|
@@ -52,7 +52,7 @@ export function srtDefaultWritePaths(): string[] {
|
|
|
52
52
|
const lower = (s: string): string => s.toLowerCase()
|
|
53
53
|
|
|
54
54
|
/** Whether `rel` (from the scan root, `/`-separated) is one of rg's hits for SRT's patterns. */
|
|
55
|
-
function isHit(rel: string): boolean {
|
|
55
|
+
function isHit(rel: string, gitConfig: boolean): boolean {
|
|
56
56
|
const segs = rel.split('/').map(lower)
|
|
57
57
|
if (DANGEROUS_FILES.has(segs[segs.length - 1]!)) return true
|
|
58
58
|
const inside = (dir: string): boolean => {
|
|
@@ -63,9 +63,13 @@ function isHit(rel: string): boolean {
|
|
|
63
63
|
return false
|
|
64
64
|
}
|
|
65
65
|
if (DANGEROUS_DIRS.some(inside) || inside('.git/hooks')) return true
|
|
66
|
-
// `**/.git/config`,
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
// `**/.git/config`, unless the task grants `gitConfig` (B-41).
|
|
67
|
+
return (
|
|
68
|
+
!gitConfig &&
|
|
69
|
+
segs.length >= 2 &&
|
|
70
|
+
segs[segs.length - 2] === '.git' &&
|
|
71
|
+
segs[segs.length - 1] === 'config'
|
|
72
|
+
)
|
|
69
73
|
}
|
|
70
74
|
|
|
71
75
|
/** The deny path SRT derives from a hit (`linuxGetMandatoryDenyPaths`), or none. */
|
|
@@ -93,7 +97,11 @@ function denyOf(cwd: string, rel: string): string | undefined {
|
|
|
93
97
|
* path with a glob character is dropped from `denyWrite` on Linux, and
|
|
94
98
|
* the write it guards must not be left open.
|
|
95
99
|
*/
|
|
96
|
-
export function scopedMandatoryDenies(
|
|
100
|
+
export function scopedMandatoryDenies(
|
|
101
|
+
cwd: string,
|
|
102
|
+
writePaths: readonly string[],
|
|
103
|
+
gitConfig = false,
|
|
104
|
+
): string[] {
|
|
97
105
|
const roots = new Set<string>()
|
|
98
106
|
for (const raw of writePaths) {
|
|
99
107
|
const w = raw.replace(/\/+$/, '') || '/'
|
|
@@ -112,7 +120,7 @@ export function scopedMandatoryDenies(cwd: string, writePaths: readonly string[]
|
|
|
112
120
|
const childRel = rel === '' ? e.name : `${rel}/${e.name}`
|
|
113
121
|
if (e.isDirectory()) {
|
|
114
122
|
if (depth + 1 < SEARCH_DEPTH) visit(path.join(abs, e.name), childRel, depth + 1)
|
|
115
|
-
} else if (isHit(childRel)) {
|
|
123
|
+
} else if (isHit(childRel, gitConfig)) {
|
|
116
124
|
const d = denyOf(cwd, childRel)
|
|
117
125
|
if (d !== undefined) denies.add(d)
|
|
118
126
|
}
|
|
@@ -130,7 +138,7 @@ export function scopedMandatoryDenies(cwd: string, writePaths: readonly string[]
|
|
|
130
138
|
}
|
|
131
139
|
if (dir) {
|
|
132
140
|
if (depth < SEARCH_DEPTH) visit(root, rel, depth)
|
|
133
|
-
} else if (depth > 0 && isHit(rel)) {
|
|
141
|
+
} else if (depth > 0 && isHit(rel, gitConfig)) {
|
|
134
142
|
const d = denyOf(cwd, rel)
|
|
135
143
|
if (d !== undefined) denies.add(d)
|
|
136
144
|
}
|
|
@@ -412,14 +412,16 @@ function trackTaskTmpdir(dir: string): void {
|
|
|
412
412
|
let srtUp = false
|
|
413
413
|
/**
|
|
414
414
|
* The run's config while one of its tasks lifts SRT's `socket(AF_UNIX)`
|
|
415
|
-
* block
|
|
416
|
-
* one task's `unixSockets` or port list lifted
|
|
417
|
-
* of the run, and a task that asked for
|
|
418
|
-
* or ssh-agent socket (L-6)
|
|
419
|
-
*
|
|
415
|
+
* block or grants `gitConfig`, else undefined. SRT reads both from its
|
|
416
|
+
* run-wide config, so one task's `unixSockets` or port list lifted the
|
|
417
|
+
* block for every sandboxed task of the run, and a task that asked for
|
|
418
|
+
* neither reached the host's docker or ssh-agent socket (L-6); the
|
|
419
|
+
* per-task `allowGitConfig` vx passed was never read at all (B-41). Such a
|
|
420
|
+
* run sets both per task, for the span of its wrap, which is when SRT
|
|
421
|
+
* reads them.
|
|
420
422
|
*/
|
|
421
|
-
let
|
|
422
|
-
/** Wraps of a `
|
|
423
|
+
let perTaskRun: Parameters<SrtModule['SandboxManager']['updateConfig']>[0] | undefined
|
|
424
|
+
/** Wraps of a `perTaskRun`, one at a time: each holds SRT's config for its task. */
|
|
423
425
|
let wrapTurn: Promise<unknown> = Promise.resolve()
|
|
424
426
|
|
|
425
427
|
/**
|
|
@@ -540,6 +542,8 @@ export async function initSandbox(opts?: {
|
|
|
540
542
|
* sets it for each task's own wrap (L-6).
|
|
541
543
|
*/
|
|
542
544
|
allowAllUnixSockets?: boolean
|
|
545
|
+
/** Whether any task of the run grants `gitConfig`: SRT reads it run-wide, so it is set per wrap (B-41). */
|
|
546
|
+
gitConfig?: boolean
|
|
543
547
|
}): Promise<void> {
|
|
544
548
|
// A reset a server's exit started unawaited: a watch cycle stops its
|
|
545
549
|
// server and starts its run at once, and an init under that reset
|
|
@@ -571,7 +575,7 @@ export async function initSandbox(opts?: {
|
|
|
571
575
|
true,
|
|
572
576
|
)
|
|
573
577
|
srtUp = true
|
|
574
|
-
|
|
578
|
+
perTaskRun = opts?.allowAllUnixSockets === true || opts?.gitConfig === true ? config : undefined
|
|
575
579
|
// `initialize()` returns early once SRT is up, and on Linux the
|
|
576
580
|
// availability probe brought it up with an EMPTY config before the run's
|
|
577
581
|
// own call — so the run's allowlist and unix-socket allowance never
|
|
@@ -607,7 +611,7 @@ export async function resetSandbox(): Promise<void> {
|
|
|
607
611
|
const { SandboxManager } = await loadSrt()
|
|
608
612
|
await SandboxManager.reset()
|
|
609
613
|
srtUp = false
|
|
610
|
-
|
|
614
|
+
perTaskRun = undefined
|
|
611
615
|
availabilityCache.clear()
|
|
612
616
|
straceAvailableCache = undefined
|
|
613
617
|
})()
|
|
@@ -942,10 +946,11 @@ export async function wrapSandboxedCommand(
|
|
|
942
946
|
customConfig!.filesystem!.allowWrite!.push(toRealPath(tmp))
|
|
943
947
|
if (scopedDenyScan) {
|
|
944
948
|
customConfig!.filesystem!.denyWrite!.push(
|
|
945
|
-
...scopedMandatoryDenies(
|
|
946
|
-
|
|
947
|
-
...customConfig!.filesystem!.allowWrite
|
|
948
|
-
|
|
949
|
+
...scopedMandatoryDenies(
|
|
950
|
+
process.cwd(),
|
|
951
|
+
[...srtDefaultWritePaths(), ...customConfig!.filesystem!.allowWrite!],
|
|
952
|
+
args.config.gitConfig === true,
|
|
953
|
+
),
|
|
949
954
|
)
|
|
950
955
|
}
|
|
951
956
|
// Seatbelt re-allows a read inside a denied region only by name; on Linux
|
|
@@ -967,6 +972,7 @@ export async function wrapSandboxedCommand(
|
|
|
967
972
|
inner,
|
|
968
973
|
customConfig,
|
|
969
974
|
ports.length > 0 || asksUnixSockets(args.config),
|
|
975
|
+
args.config.gitConfig === true,
|
|
970
976
|
)
|
|
971
977
|
if (process.platform === 'darwin') {
|
|
972
978
|
const rules = [
|
|
@@ -1099,19 +1105,21 @@ function asksUnixSockets(c: Pick<ResolvedSandboxConfig, 'unixSockets'>): boolean
|
|
|
1099
1105
|
return c.unixSockets === true || (c.unixSockets !== undefined && c.unixSockets.length > 0)
|
|
1100
1106
|
}
|
|
1101
1107
|
|
|
1102
|
-
/** SRT's wrap, with the socket lift this task asked for or none (L-6). */
|
|
1108
|
+
/** SRT's wrap, with the socket lift and the git-config grant this task asked for, or none (L-6, B-41). */
|
|
1103
1109
|
function wrapForTask(
|
|
1104
1110
|
SandboxManager: SrtModule['SandboxManager'],
|
|
1105
1111
|
command: string,
|
|
1106
1112
|
customConfig: Parameters<SrtModule['SandboxManager']['wrapWithSandbox']>[2],
|
|
1107
1113
|
sockets: boolean,
|
|
1114
|
+
gitConfig: boolean,
|
|
1108
1115
|
): Promise<string> {
|
|
1109
|
-
const run =
|
|
1116
|
+
const run = perTaskRun
|
|
1110
1117
|
if (run === undefined) return SandboxManager.wrapWithSandbox(command, undefined, customConfig)
|
|
1111
1118
|
const turn = wrapTurn.then(() => {
|
|
1112
1119
|
SandboxManager.updateConfig({
|
|
1113
1120
|
...run,
|
|
1114
1121
|
network: { ...run.network, allowAllUnixSockets: sockets },
|
|
1122
|
+
filesystem: { ...run.filesystem, allowGitConfig: gitConfig },
|
|
1115
1123
|
})
|
|
1116
1124
|
return SandboxManager.wrapWithSandbox(command, undefined, customConfig)
|
|
1117
1125
|
})
|
|
@@ -64,6 +64,8 @@ export interface SandboxRunUnion {
|
|
|
64
64
|
deniedDomains: string[]
|
|
65
65
|
/** Whether SRT's all-or-nothing `socket(AF_UNIX)` filter is lifted for the run. */
|
|
66
66
|
unixSockets: boolean
|
|
67
|
+
/** Whether any task grants `gitConfig`, which SRT reads run-wide and vx sets per wrap (B-41). */
|
|
68
|
+
gitConfig: boolean
|
|
67
69
|
/** Whether EVERY sandboxed task accepts the weaker nested profile. */
|
|
68
70
|
weakerNested: boolean
|
|
69
71
|
}
|
|
@@ -75,6 +77,7 @@ export function sandboxRunUnion(nodes: Iterable<TaskNode>): SandboxRunUnion | nu
|
|
|
75
77
|
const domains = new Set<string>()
|
|
76
78
|
const denied = new Set<string>()
|
|
77
79
|
let unixSockets = false
|
|
80
|
+
let gitConfig = false
|
|
78
81
|
for (const n of sandboxed) {
|
|
79
82
|
const allow = n.config.exec?.sandbox?.allow
|
|
80
83
|
const net = allow?.network
|
|
@@ -87,8 +90,9 @@ export function sandboxRunUnion(nodes: Iterable<TaskNode>): SandboxRunUnion | nu
|
|
|
87
90
|
if (sockets === true || (Array.isArray(sockets) && sockets.length > 0)) unixSockets = true
|
|
88
91
|
const lb = allow?.localBinding
|
|
89
92
|
if (Array.isArray(lb) && lb.length > 0 && process.platform === 'linux') unixSockets = true
|
|
93
|
+
if (allow?.gitConfig === true) gitConfig = true
|
|
90
94
|
}
|
|
91
|
-
return { domains: [...domains], deniedDomains: [...denied], unixSockets, weakerNested }
|
|
95
|
+
return { domains: [...domains], deniedDomains: [...denied], unixSockets, gitConfig, weakerNested }
|
|
92
96
|
}
|
|
93
97
|
|
|
94
98
|
/**
|
|
@@ -115,7 +119,7 @@ export function sandboxRunUnion(nodes: Iterable<TaskNode>): SandboxRunUnion | nu
|
|
|
115
119
|
export function prepareSandbox(nodes: Iterable<TaskNode>): SandboxArmer | null {
|
|
116
120
|
const union = sandboxRunUnion(nodes)
|
|
117
121
|
if (union === null) return null
|
|
118
|
-
const { domains, deniedDomains, unixSockets, weakerNested } = union
|
|
122
|
+
const { domains, deniedDomains, unixSockets, gitConfig, weakerNested } = union
|
|
119
123
|
let pending: Promise<void> | undefined
|
|
120
124
|
let armed = false
|
|
121
125
|
return {
|
|
@@ -131,6 +135,7 @@ export function prepareSandbox(nodes: Iterable<TaskNode>): SandboxArmer | null {
|
|
|
131
135
|
allowedDomains: domains,
|
|
132
136
|
deniedDomains,
|
|
133
137
|
...(unixSockets ? { allowAllUnixSockets: true } : {}),
|
|
138
|
+
...(gitConfig ? { gitConfig: true } : {}),
|
|
134
139
|
})
|
|
135
140
|
} catch (err) {
|
|
136
141
|
// A throw from the runtime itself (its bridge needs socat, which
|