@vzn/vx 0.0.383 → 0.0.385
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 +5 -5
- package/src/exec/sandbox-runtime.ts +71 -15
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vzn/vx",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.385",
|
|
4
4
|
"description": "The fastest task runner and build cache for JavaScript monorepos.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -21,10 +21,10 @@
|
|
|
21
21
|
"node": ">=18"
|
|
22
22
|
},
|
|
23
23
|
"optionalDependencies": {
|
|
24
|
-
"@vzn/vx-linux-x64": "0.0.
|
|
25
|
-
"@vzn/vx-linux-arm64": "0.0.
|
|
26
|
-
"@vzn/vx-darwin-x64": "0.0.
|
|
27
|
-
"@vzn/vx-darwin-arm64": "0.0.
|
|
24
|
+
"@vzn/vx-linux-x64": "0.0.385",
|
|
25
|
+
"@vzn/vx-linux-arm64": "0.0.385",
|
|
26
|
+
"@vzn/vx-darwin-x64": "0.0.385",
|
|
27
|
+
"@vzn/vx-darwin-arm64": "0.0.385"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@anthropic-ai/sandbox-runtime": "^0.0.76"
|
|
@@ -632,12 +632,14 @@ export async function initSandbox(opts?: {
|
|
|
632
632
|
...bundledJavaAgent(),
|
|
633
633
|
}
|
|
634
634
|
if (!srtUp) await unlinkStaleMuxSockets()
|
|
635
|
+
const listening = srtCleanupAdopted ? undefined : cleanupListeners()
|
|
635
636
|
await SandboxManager.initialize(
|
|
636
637
|
config,
|
|
637
638
|
undefined,
|
|
638
639
|
// enableLogMonitor — macOS-only; populates the SandboxViolationStore.
|
|
639
640
|
true,
|
|
640
641
|
)
|
|
642
|
+
if (listening !== undefined) adoptSrtCleanup(listening)
|
|
641
643
|
srtUp = true
|
|
642
644
|
perTaskRun = opts?.allowAllUnixSockets === true || opts?.gitConfig === true ? config : undefined
|
|
643
645
|
// `initialize()` returns early once SRT is up, and on Linux the
|
|
@@ -674,11 +676,21 @@ export async function resetSandbox(): Promise<void> {
|
|
|
674
676
|
const reset = (async () => {
|
|
675
677
|
const { SandboxManager } = await loadSrt()
|
|
676
678
|
await SandboxManager.reset()
|
|
677
|
-
|
|
678
|
-
perTaskRun = undefined
|
|
679
|
-
availabilityCache.clear()
|
|
680
|
-
straceAvailableCache = undefined
|
|
679
|
+
srtDown()
|
|
681
680
|
})()
|
|
681
|
+
trackReset(reset)
|
|
682
|
+
await reset
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
function srtDown(): void {
|
|
686
|
+
srtUp = false
|
|
687
|
+
perTaskRun = undefined
|
|
688
|
+
availabilityCache.clear()
|
|
689
|
+
straceAvailableCache = undefined
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/** `reset` is the one `initSandbox` waits out before it starts SRT again. */
|
|
693
|
+
function trackReset(reset: Promise<unknown>): void {
|
|
682
694
|
const settled = reset.then(
|
|
683
695
|
() => {},
|
|
684
696
|
() => {},
|
|
@@ -687,7 +699,38 @@ export async function resetSandbox(): Promise<void> {
|
|
|
687
699
|
void settled.then(() => {
|
|
688
700
|
if (resetting === settled) resetting = undefined
|
|
689
701
|
})
|
|
690
|
-
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
const SRT_CLEANUP_EVENTS = ['exit', 'SIGINT', 'SIGTERM'] as const
|
|
705
|
+
let srtCleanupAdopted = false
|
|
706
|
+
|
|
707
|
+
function cleanupListeners(): Map<string, unknown[]> {
|
|
708
|
+
return new Map(SRT_CLEANUP_EVENTS.map((ev) => [ev, process.listeners(ev)]))
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
/**
|
|
712
|
+
* SRT's first `initialize` registers its own once-only `exit`, SIGINT and
|
|
713
|
+
* SIGTERM listeners, each an unawaited `reset()`. That reset kills the
|
|
714
|
+
* bridges at once but clears SRT's init promise only once its proxies
|
|
715
|
+
* have closed, so an `initSandbox` in between had `initialize` return
|
|
716
|
+
* early on the dying session, and the next wrap threw "Linux HTTP bridge
|
|
717
|
+
* socket does not exist" (the bridge-socket row's gate failure, M-35).
|
|
718
|
+
* Each listener stays, once-only as SRT made it; vx now tracks the reset
|
|
719
|
+
* it starts, as its own.
|
|
720
|
+
*/
|
|
721
|
+
function adoptSrtCleanup(before: Map<string, unknown[]>): void {
|
|
722
|
+
for (const ev of SRT_CLEANUP_EVENTS) {
|
|
723
|
+
const had = before.get(ev)!
|
|
724
|
+
for (const l of process.listeners(ev) as ((...a: unknown[]) => unknown)[]) {
|
|
725
|
+
if (had.includes(l)) continue
|
|
726
|
+
srtCleanupAdopted = true
|
|
727
|
+
process.removeListener(ev, l)
|
|
728
|
+
process.once(ev, (...a: unknown[]) => {
|
|
729
|
+
srtDown()
|
|
730
|
+
trackReset(Promise.resolve(l(...a)))
|
|
731
|
+
})
|
|
732
|
+
}
|
|
733
|
+
}
|
|
691
734
|
}
|
|
692
735
|
|
|
693
736
|
export interface SandboxedRunArgs {
|
|
@@ -1794,7 +1837,7 @@ async function runSandboxedOnce(
|
|
|
1794
1837
|
config: args.config,
|
|
1795
1838
|
skip: [taskTmpRoot(), ...srtDefaultWritePaths()],
|
|
1796
1839
|
})
|
|
1797
|
-
if (outside.length > 0) violations.push(outsideWritesHint(outside))
|
|
1840
|
+
if (outside.length > 0) violations.push(outsideWritesHint(outside, baselines.denyRead))
|
|
1798
1841
|
}
|
|
1799
1842
|
|
|
1800
1843
|
// The one denial macOS never logs. MEASURED 2026-09-05, same machine, two
|
|
@@ -1862,20 +1905,33 @@ async function runSandboxedOnce(
|
|
|
1862
1905
|
}
|
|
1863
1906
|
|
|
1864
1907
|
/** The hint for writes refused outside the project, a few paths named. */
|
|
1865
|
-
function outsideWritesHint(paths: readonly string[]): SandboxViolation {
|
|
1908
|
+
function outsideWritesHint(paths: readonly string[], walled: readonly string[]): SandboxViolation {
|
|
1866
1909
|
const shown = paths.slice(0, 5).join(', ')
|
|
1867
1910
|
const more = paths.length > 5 ? ` and ${paths.length - 5} more` : ''
|
|
1868
1911
|
const home = toRealPath(os.homedir())
|
|
1869
1912
|
const dir = path.dirname(paths[0]!)
|
|
1870
1913
|
const spelled = atOrUnder(dir, home) ? `~${dir.slice(home.length)}` : dir
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1914
|
+
const refused =
|
|
1915
|
+
`vx: the sandbox refused writes outside the project, which are not reported as ` +
|
|
1916
|
+
`violations: ${shown}${more}.`
|
|
1917
|
+
// Granting a system temp directory opens it to every write of the task;
|
|
1918
|
+
// the task already has a temp directory of its own. A workspace kept
|
|
1919
|
+
// under one is the workspace, and its directory is the grant.
|
|
1920
|
+
const first = paths[0]!
|
|
1921
|
+
const scratchTemp =
|
|
1922
|
+
hostTempRoots().some((t) => atOrUnder(first, t)) &&
|
|
1923
|
+
!walled.some((w) => atOrUnder(first, toRealPath(w)))
|
|
1924
|
+
const line = scratchTemp
|
|
1925
|
+
? `${refused} The task has its own temp directory, empty at its start: write under ` +
|
|
1926
|
+
`$TMPDIR (os.tmpdir() in Node and Bun) instead of a fixed path.`
|
|
1927
|
+
: `${refused} If the task needs one, grant its directory, e.g. ` +
|
|
1928
|
+
`\`allow: { write: ['${spelled}/'] }\`.`
|
|
1929
|
+
return { timestamp: new Date(), hint: true, line }
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
/** The host's shared temp directories, canonical: what a fixed temp path in a tool names. */
|
|
1933
|
+
function hostTempRoots(): string[] {
|
|
1934
|
+
return [...new Set(['/tmp', '/var/tmp', os.tmpdir()].map(toRealPath))]
|
|
1879
1935
|
}
|
|
1880
1936
|
|
|
1881
1937
|
function hiddenReadsHint(paths: readonly string[], within: string): SandboxViolation {
|