@vzn/vx 0.0.400 → 0.0.402
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 +19 -5
- package/src/index.ts +1 -0
- package/src/orchestrator/index.ts +1 -0
- package/src/orchestrator/plugin.ts +54 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vzn/vx",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.402",
|
|
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.402",
|
|
25
|
+
"@vzn/vx-linux-arm64": "0.0.402",
|
|
26
|
+
"@vzn/vx-darwin-x64": "0.0.402",
|
|
27
|
+
"@vzn/vx-darwin-arm64": "0.0.402"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@anthropic-ai/sandbox-runtime": "^0.0.76"
|
|
@@ -1891,7 +1891,8 @@ async function runSandboxedOnce(
|
|
|
1891
1891
|
config: args.config,
|
|
1892
1892
|
skip: [taskTmpRoot(), ...srtDefaultWritePaths()],
|
|
1893
1893
|
})
|
|
1894
|
-
if (outside.length > 0)
|
|
1894
|
+
if (outside.length > 0)
|
|
1895
|
+
violations.push(outsideWritesHint(outside, baselines.denyRead, args.reportWithin))
|
|
1895
1896
|
}
|
|
1896
1897
|
|
|
1897
1898
|
// The one denial macOS never logs. MEASURED 2026-09-05, same machine, two
|
|
@@ -1959,12 +1960,22 @@ async function runSandboxedOnce(
|
|
|
1959
1960
|
}
|
|
1960
1961
|
|
|
1961
1962
|
/** The hint for writes refused outside the project, a few paths named. */
|
|
1962
|
-
function outsideWritesHint(
|
|
1963
|
+
function outsideWritesHint(
|
|
1964
|
+
paths: readonly string[],
|
|
1965
|
+
walled: readonly string[],
|
|
1966
|
+
within: string,
|
|
1967
|
+
): SandboxViolation {
|
|
1963
1968
|
const shown = paths.slice(0, 5).join(', ')
|
|
1964
1969
|
const more = paths.length > 5 ? ` and ${paths.length - 5} more` : ''
|
|
1965
1970
|
const home = toRealPath(os.homedir())
|
|
1966
1971
|
const dir = path.dirname(paths[0]!)
|
|
1967
|
-
|
|
1972
|
+
// In the workspace, from the project, as a committed config spells it:
|
|
1973
|
+
// the absolute path held only on the machine that printed it.
|
|
1974
|
+
const spelled = walled.some((w) => atOrUnder(dir, toRealPath(w)))
|
|
1975
|
+
? path.relative(toRealPath(within), dir) || '.'
|
|
1976
|
+
: atOrUnder(dir, home)
|
|
1977
|
+
? `~${dir.slice(home.length)}`
|
|
1978
|
+
: dir
|
|
1968
1979
|
const refused =
|
|
1969
1980
|
`vx: the sandbox refused writes outside the project, which are not reported as ` +
|
|
1970
1981
|
`violations: ${shown}${more}.`
|
|
@@ -1979,10 +1990,13 @@ function outsideWritesHint(paths: readonly string[], walled: readonly string[]):
|
|
|
1979
1990
|
? `${refused} The task has its own temp directory, empty at its start: write under ` +
|
|
1980
1991
|
`$TMPDIR (os.tmpdir() in Node and Bun) instead of a fixed path.`
|
|
1981
1992
|
: `${refused} If the task needs one, grant its directory, e.g. ` +
|
|
1982
|
-
`\`allow: { write: [
|
|
1993
|
+
`\`allow: { write: [${jsString(`${spelled}/`)}] }\`.`
|
|
1983
1994
|
return { timestamp: new Date(), hint: true, line }
|
|
1984
1995
|
}
|
|
1985
1996
|
|
|
1997
|
+
/** A path as a JS string literal a config can take: a quote in it is escaped. */
|
|
1998
|
+
const jsString = (p: string): string => (p.includes("'") ? JSON.stringify(p) : `'${p}'`)
|
|
1999
|
+
|
|
1986
2000
|
/** The host's shared temp directories, canonical: what a fixed temp path in a tool names. */
|
|
1987
2001
|
function hostTempRoots(): string[] {
|
|
1988
2002
|
return [...new Set(['/tmp', '/var/tmp', os.tmpdir()].map(toRealPath))]
|
|
@@ -1997,7 +2011,7 @@ function hiddenReadsHint(paths: readonly string[], within: string): SandboxViola
|
|
|
1997
2011
|
line:
|
|
1998
2012
|
`vx: the sandbox hid paths outside the project that exist on this machine, which are ` +
|
|
1999
2013
|
`not reported as violations: ${shown}${more}. If the task reads one, grant it, e.g. ` +
|
|
2000
|
-
`\`allow: { read: [
|
|
2014
|
+
`\`allow: { read: [${jsString(path.relative(toRealPath(within), paths[0]!))}] }\`.`,
|
|
2001
2015
|
}
|
|
2002
2016
|
}
|
|
2003
2017
|
|
package/src/index.ts
CHANGED
|
@@ -483,29 +483,69 @@ export function definePlugin(origin: PluginOrigin, hooks: PluginHooks): VxPlugin
|
|
|
483
483
|
return { ...hooks, name, [PLUGIN_PACKAGE]: name } as VxPlugin
|
|
484
484
|
}
|
|
485
485
|
|
|
486
|
+
/** What `refuseUnknownOptions` holds an option's value to. */
|
|
487
|
+
type OptionKind<V> = [V] extends [string]
|
|
488
|
+
? 'string'
|
|
489
|
+
: [V] extends [number]
|
|
490
|
+
? 'number'
|
|
491
|
+
: [V] extends [boolean]
|
|
492
|
+
? 'boolean'
|
|
493
|
+
: [V] extends [(...args: never[]) => unknown]
|
|
494
|
+
? 'function'
|
|
495
|
+
: [V] extends [object]
|
|
496
|
+
? 'object'
|
|
497
|
+
: 'any'
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Every option a factory takes, each with the one kind its type allows
|
|
501
|
+
* (`'any'` for a union of kinds, such as `false | { … }`). Derived from
|
|
502
|
+
* the options interface, so the type checker refuses a missing option, an
|
|
503
|
+
* extra one or a wrong kind.
|
|
504
|
+
*/
|
|
505
|
+
export type PluginOptionKinds<T> = {
|
|
506
|
+
readonly [K in keyof Required<T>]-?: OptionKind<Required<T>[K]>
|
|
507
|
+
}
|
|
508
|
+
|
|
486
509
|
/**
|
|
487
|
-
* Refuse an option a plugin factory does not take,
|
|
488
|
-
* unknown config field. Bun strips a config's
|
|
489
|
-
* (`reapi({ endpont })`) reached the factory,
|
|
490
|
-
*
|
|
491
|
-
*
|
|
510
|
+
* Refuse an option a plugin factory does not take, or a value of the wrong
|
|
511
|
+
* kind, as core refuses an unknown config field. Bun strips a config's
|
|
512
|
+
* types, so a misspelt option (`reapi({ endpont })`) reached the factory,
|
|
513
|
+
* which read it as unset and quietly declined, and a string where a number
|
|
514
|
+
* or a boolean belongs (`process.env.X`) was misread or threw a bare
|
|
515
|
+
* TypeError. `factory` names the call in the message (`reapi()`).
|
|
492
516
|
*/
|
|
493
|
-
export function refuseUnknownOptions(
|
|
517
|
+
export function refuseUnknownOptions<T>(
|
|
494
518
|
factory: string,
|
|
495
519
|
options: unknown,
|
|
496
|
-
|
|
520
|
+
kinds: PluginOptionKinds<T>,
|
|
497
521
|
): void {
|
|
498
522
|
if (options === undefined) return
|
|
499
523
|
if (options === null || typeof options !== 'object' || Array.isArray(options)) {
|
|
500
524
|
throw new UserError(`${factory}: options must be an object`)
|
|
501
525
|
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
526
|
+
const known = kinds as Readonly<Record<string, string>>
|
|
527
|
+
const names = Object.keys(known)
|
|
528
|
+
for (const [key, value] of Object.entries(options)) {
|
|
529
|
+
const kind = known[key]
|
|
530
|
+
if (kind === undefined) {
|
|
531
|
+
const near = nearest(key, names)
|
|
532
|
+
throw new UserError(
|
|
533
|
+
`${factory} has unknown option "${key}" (allowed: ${[...names].sort().join(', ')})` +
|
|
534
|
+
(near === undefined ? '' : ` — did you mean ${near}?`),
|
|
535
|
+
)
|
|
536
|
+
}
|
|
537
|
+
if (value === undefined || kind === 'any') continue
|
|
538
|
+
const ok =
|
|
539
|
+
kind === 'object'
|
|
540
|
+
? value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
541
|
+
: typeof value === kind
|
|
542
|
+
if (!ok) {
|
|
543
|
+
const got =
|
|
544
|
+
typeof value === 'function' ? 'a function' : (JSON.stringify(value) ?? String(value))
|
|
545
|
+
throw new UserError(
|
|
546
|
+
`${factory} option "${key}" must be ${kind === 'object' ? 'an' : 'a'} ${kind}, got ${got}`,
|
|
547
|
+
)
|
|
548
|
+
}
|
|
509
549
|
}
|
|
510
550
|
}
|
|
511
551
|
|