@vzn/vx 0.0.87 → 0.0.88
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/cache/cache.ts +28 -3
- package/src/exec/sandbox-runtime.ts +59 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vzn/vx",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.88",
|
|
4
4
|
"description": "An open, extensible monorepo task runner.",
|
|
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.88",
|
|
21
|
+
"@vzn/vx-linux-arm64": "0.0.88",
|
|
22
|
+
"@vzn/vx-darwin-x64": "0.0.88",
|
|
23
|
+
"@vzn/vx-darwin-arm64": "0.0.88"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@anthropic-ai/sandbox-runtime": "^0.0.76"
|
package/src/cache/cache.ts
CHANGED
|
@@ -1127,10 +1127,35 @@ export class Cache implements CacheLayer {
|
|
|
1127
1127
|
*/
|
|
1128
1128
|
async ingest(hash: string, body: Blob | Response, meta: IngestMeta): Promise<void> {
|
|
1129
1129
|
const tmpPath = this.tempPath(hash)
|
|
1130
|
+
// The compressed bytes are bounded too, before the decode's ceiling ever
|
|
1131
|
+
// sees them: a remote body that never ends (a hostile or broken server)
|
|
1132
|
+
// was written to the temp until the disk was full (L-5). No artifact
|
|
1133
|
+
// under the ceiling compresses past zstd's own bound on it.
|
|
1134
|
+
const cap = this.artifactCeiling + (this.artifactCeiling >> 8) + 64 * 1024
|
|
1135
|
+
const past = (): CorruptArtifactError =>
|
|
1136
|
+
new CorruptArtifactError(
|
|
1137
|
+
hash,
|
|
1138
|
+
`remote body runs past ${cap} bytes (the artifact ceiling's bound)`,
|
|
1139
|
+
)
|
|
1130
1140
|
try {
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1141
|
+
if (body instanceof Blob) {
|
|
1142
|
+
if (body.size > cap) throw past()
|
|
1143
|
+
await Bun.write(tmpPath, body)
|
|
1144
|
+
} else if (Number(body.headers.get('content-length') ?? 0) > cap) {
|
|
1145
|
+
throw past()
|
|
1146
|
+
} else {
|
|
1147
|
+
let n = 0
|
|
1148
|
+
const counted = body.body?.pipeThrough(
|
|
1149
|
+
new TransformStream<Uint8Array, Uint8Array>({
|
|
1150
|
+
transform(chunk, controller) {
|
|
1151
|
+
n += chunk.byteLength
|
|
1152
|
+
if (n > cap) controller.error(past())
|
|
1153
|
+
else controller.enqueue(chunk)
|
|
1154
|
+
},
|
|
1155
|
+
}),
|
|
1156
|
+
)
|
|
1157
|
+
await Bun.write(tmpPath, new Response(counted ?? null))
|
|
1158
|
+
}
|
|
1134
1159
|
} catch (err) {
|
|
1135
1160
|
await unlink(tmpPath).catch(() => undefined)
|
|
1136
1161
|
throw err
|
|
@@ -538,6 +538,8 @@ export interface ResolvedSandboxConfig {
|
|
|
538
538
|
gitConfig?: boolean
|
|
539
539
|
weakerWhenNested?: boolean
|
|
540
540
|
weakerNetworkIsolation?: boolean
|
|
541
|
+
/** macOS: the walls a glob grant reaches, by kind (`darwinWallRules`). */
|
|
542
|
+
wallsReached?: { read: readonly string[]; write: readonly string[] }
|
|
541
543
|
ignore?: {
|
|
542
544
|
read?: readonly string[]
|
|
543
545
|
write?: readonly string[]
|
|
@@ -645,6 +647,11 @@ export function resolveSandboxConfig(
|
|
|
645
647
|
walls,
|
|
646
648
|
),
|
|
647
649
|
}
|
|
650
|
+
if (process.platform === 'darwin') {
|
|
651
|
+
const read = wallsGlobsReach(r.allowRead, walls)
|
|
652
|
+
const write = wallsGlobsReach(r.allowWrite, walls)
|
|
653
|
+
if (read.length + write.length > 0) r.wallsReached = { read, write }
|
|
654
|
+
}
|
|
648
655
|
if (a.network !== undefined) r.network = a.network
|
|
649
656
|
if (cfg.deny?.network !== undefined) r.denyNetwork = cfg.deny.network
|
|
650
657
|
if (a.systemInfo !== undefined) r.systemInfo = a.systemInfo
|
|
@@ -788,7 +795,10 @@ export async function wrapSandboxedCommand(
|
|
|
788
795
|
ports.length > 0 ? `${portBridgeInner(ports, tag)} ${grouped.command}` : grouped.command
|
|
789
796
|
let wrapped = await SandboxManager.wrapWithSandbox(inner, undefined, customConfig)
|
|
790
797
|
if (process.platform === 'darwin') {
|
|
791
|
-
const rules =
|
|
798
|
+
const rules = [
|
|
799
|
+
...macProfileRules(args.config),
|
|
800
|
+
...darwinWallRules(args.config, baselines.allowRead),
|
|
801
|
+
]
|
|
792
802
|
if (rules.length > 0) wrapped = injectProfileRules(wrapped, rules)
|
|
793
803
|
}
|
|
794
804
|
// Linux: the shell execs bwrap, so bwrap is the spawn itself and its
|
|
@@ -1528,7 +1538,54 @@ function injectProfileRules(wrapped: string, rules: readonly string[]): string {
|
|
|
1528
1538
|
* that broke it learned so from its OWN tool. Measured, one task per
|
|
1529
1539
|
* spelling, each writing files it declares:
|
|
1530
1540
|
*
|
|
1531
|
-
* write: ['g/**
|
|
1541
|
+
* write: ['g/**
|
|
1542
|
+
* The walls a glob grant can reach: each one under (or at) a glob's
|
|
1543
|
+
* literal head. On Linux `expandGrants` drops such a hit before the bind
|
|
1544
|
+
* (B-1); seatbelt matches a glob as a path regex, with no hit to drop.
|
|
1545
|
+
*/
|
|
1546
|
+
function wallsGlobsReach(grants: readonly string[], walls: readonly string[]): string[] {
|
|
1547
|
+
const heads = grants
|
|
1548
|
+
.filter((g) => !isMountableLiteral(g))
|
|
1549
|
+
.map((g) => path.dirname(g.slice(0, g.search(MOUNT_WILDCARDS))))
|
|
1550
|
+
return walls.filter((w) =>
|
|
1551
|
+
heads.some((h) => w === h || w.startsWith(h === path.sep ? h : h + path.sep)),
|
|
1552
|
+
)
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
/**
|
|
1556
|
+
* macOS: the walls a glob grant reaches, denied at the profile's tail
|
|
1557
|
+
* (SBPL is last-match-wins). SRT re-emits a wall's deny after the allows
|
|
1558
|
+
* only under a LITERAL allow, so a root project's `read: ['**\/*.ts']`
|
|
1559
|
+
* read nested projects' sources its key excludes — item 1010's stale hit,
|
|
1560
|
+
* by glob, on darwin (B-12). A literal grant at or inside the wall, the
|
|
1561
|
+
* task's or a baseline's (a linked dependency), is carved out: on Linux
|
|
1562
|
+
* too it binds. Reads deny the data, not the metadata, as SRT's own wall
|
|
1563
|
+
* denies do: a walk may still stat the directory.
|
|
1564
|
+
*/
|
|
1565
|
+
export function darwinWallRules(
|
|
1566
|
+
c: Pick<ResolvedSandboxConfig, 'wallsReached' | 'allowRead' | 'allowWrite'>,
|
|
1567
|
+
baseAllowRead: readonly string[],
|
|
1568
|
+
): string[] {
|
|
1569
|
+
const rules: string[] = []
|
|
1570
|
+
const deny = (op: string, walls: readonly string[], literals: readonly string[]): void => {
|
|
1571
|
+
for (const w of walls) {
|
|
1572
|
+
const at = `(subpath "${sbplResolvedPath(w, 'wall')}")`
|
|
1573
|
+
const kept = literals
|
|
1574
|
+
.filter((l) => isMountableLiteral(l) && (l === w || l.startsWith(w + path.sep)))
|
|
1575
|
+
.map((l) => `(require-not (subpath "${sbplResolvedPath(l, 'grant')}"))`)
|
|
1576
|
+
rules.push(
|
|
1577
|
+
kept.length === 0
|
|
1578
|
+
? `(deny ${op} ${at})`
|
|
1579
|
+
: `(deny ${op} (require-all ${at} ${kept.join(' ')}))`,
|
|
1580
|
+
)
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
deny('file-read-data', c.wallsReached?.read ?? [], [...c.allowRead, ...baseAllowRead])
|
|
1584
|
+
deny('file-write*', c.wallsReached?.write ?? [], c.allowWrite)
|
|
1585
|
+
return rules
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
/**'] ok — collapsed to the directory
|
|
1532
1589
|
* write: ['g/a.txt'] ok — a literal is widened to its directory
|
|
1533
1590
|
* write: ['g/*'] FAILED: `bash: g/a.txt: Read-only file system`
|
|
1534
1591
|
* write: ['g/*.txt'] FAILED, same
|