@vzn/vx 0.0.640 → 0.0.642

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.
Files changed (76) hide show
  1. package/launcher.sh +9 -2
  2. package/package.json +5 -12
  3. package/skills/vx/SKILL.md +4 -4
  4. package/src/bin.ts +8 -6
  5. package/src/cache/archive.ts +735 -107
  6. package/src/cache/cache.ts +133 -29
  7. package/src/cache/fold-memo.ts +58 -0
  8. package/src/cache/git-inputs.ts +213 -31
  9. package/src/cache/index.ts +9 -2
  10. package/src/cache/inputs.ts +244 -108
  11. package/src/cache/key-fold.ts +90 -69
  12. package/src/cache/layer.ts +9 -7
  13. package/src/cache/layered-cache.ts +74 -9
  14. package/src/cache/output-index.ts +25 -4
  15. package/src/cache/remote-lane.ts +27 -10
  16. package/src/cache/schema.ts +13 -3
  17. package/src/cache/scoped-cache.ts +11 -3
  18. package/src/cache/umask.ts +29 -0
  19. package/src/cli/docs.ts +71 -7
  20. package/src/cli/help.ts +11 -5
  21. package/src/cli/plan-format.ts +4 -1
  22. package/src/cli/run.ts +10 -3
  23. package/src/cli/select.ts +2 -0
  24. package/src/cli/upgrade.ts +37 -2
  25. package/src/cli/watch-filter.ts +1 -6
  26. package/src/cli/watch-fs.ts +2 -1
  27. package/src/exec/runner.ts +5 -0
  28. package/src/exec/sandbox-runtime.ts +81 -1
  29. package/src/graph/priorities.ts +14 -1
  30. package/src/handoff.ts +5 -0
  31. package/src/history/history.ts +43 -33
  32. package/src/index.ts +11 -1
  33. package/src/orchestrator/deferred-outputs.ts +3 -2
  34. package/src/orchestrator/execute-task.ts +20 -14
  35. package/src/orchestrator/hit-restore.ts +111 -29
  36. package/src/orchestrator/index.ts +1 -0
  37. package/src/orchestrator/local-shortcircuit.ts +8 -0
  38. package/src/orchestrator/lockfile-claim.ts +54 -8
  39. package/src/orchestrator/miss-save.ts +27 -17
  40. package/src/orchestrator/output-host.ts +9 -1
  41. package/src/orchestrator/plan.ts +4 -0
  42. package/src/orchestrator/plugin-host.ts +35 -5
  43. package/src/orchestrator/plugin.ts +29 -3
  44. package/src/orchestrator/prepare.ts +74 -5
  45. package/src/orchestrator/projects.ts +6 -1
  46. package/src/orchestrator/run-report.ts +30 -9
  47. package/src/orchestrator/run.ts +62 -38
  48. package/src/orchestrator/sandbox-request.ts +1 -48
  49. package/src/orchestrator/save-lane.ts +17 -10
  50. package/src/orchestrator/signals.ts +7 -10
  51. package/src/orchestrator/stable-keys.ts +83 -25
  52. package/src/orchestrator/task-hash.ts +46 -12
  53. package/src/report/events.ts +12 -0
  54. package/src/report/framed-output.ts +2 -1
  55. package/src/report/logger.ts +58 -15
  56. package/src/report/status-line.ts +45 -4
  57. package/src/report/summary.ts +11 -6
  58. package/src/util/errors.ts +1 -1
  59. package/src/util/hidden-prompt.ts +74 -0
  60. package/src/util/index.ts +13 -0
  61. package/src/util/paths.ts +58 -5
  62. package/src/util/secrets.ts +150 -0
  63. package/src/workspace/affected.ts +63 -13
  64. package/src/workspace/config-eval.ts +2 -2
  65. package/src/workspace/config-schema.ts +37 -3
  66. package/src/workspace/project-loader.ts +6 -4
  67. package/src/workspace/workspace.ts +17 -11
  68. package/docs/caching.md +0 -2352
  69. package/docs/cli.md +0 -3280
  70. package/docs/execution.md +0 -675
  71. package/docs/features.md +0 -178
  72. package/docs/patterns.md +0 -219
  73. package/docs/schema.md +0 -2461
  74. package/docs/security.md +0 -252
  75. package/src/cli/docs-corpus.ts +0 -21
  76. package/src/cli/docs-text.d.ts +0 -5
package/docs/security.md DELETED
@@ -1,252 +0,0 @@
1
- # Security model
2
-
3
- What vx trusts, what it checks, what the sandbox stops and what it does
4
- not, and how to report a problem. Report a vulnerability privately
5
- through GitHub's private vulnerability reporting on
6
- [vznjs/vx](https://github.com/vznjs/vx/security) (Security → Report a
7
- vulnerability), never in a public issue.
8
-
9
- ## What vx trusts
10
-
11
- - **Your configs.** `vx.config.ts` and `.vx/workspace.ts` are programs vx
12
- runs in its own process. A config can do anything you can: vx
13
- validates what it returns, not what it does. The workspace root is
14
- the nearest `.vx/workspace.*`, and vx never looks for it above the git
15
- top-level, so a file planted in a shared directory such as `/tmp`
16
- cannot claim your checkout and run as you. Outside git, one another
17
- user owns is refused, root aside, judged by the file itself (a link,
18
- not what it names) and its `.vx` (`tests/workspace-root-file.test.ts`).
19
- No task output reaches `.vx` or a `vx.lock.json`: a clean never deletes
20
- them and a hit never restores them (`tests/output-never-dotvx.test.ts`).
21
- What a config imports
22
- must already be installed: vx runs Bun with `--no-install`, so an
23
- import no `node_modules` provides is refused, never fetched from the
24
- registry (L-22).
25
- - **Your projects lock.** A run executes `.vx/projects.lock.json` as
26
- written and never re-evaluates or re-validates it, so the lock is code
27
- too. It is minified and marked generated, which collapses it in a
28
- pull request's diff: a change to it alone changes what runs while every
29
- config and `vx.lock.json` reads the same. Run `vx lock --check` in CI
30
- before anything else; it fails on any entry that differs from what its
31
- config evaluates to and on one with no config (`tests/lock.test.ts`).
32
- - **Your project's own vx.** A global vx hands every call to the
33
- workspace root's `node_modules/.bin/vx` (cli.md § Which vx runs), so even
34
- `vx --version` in a repo you do not trust runs that repo's code. One
35
- another user owns is never run.
36
- - **Your plugins.** A plugin is code you imported. It runs in vx's
37
- process with a config's reach; vx checks the shape of what it hands
38
- back and isolates a telemetry plugin's failures, nothing more.
39
- - **Your lockfile and your repo.** vx parses a lockfile (and
40
- `turbo.json` or an Nx graph when migrating) into keys and refuses one
41
- it cannot read, naming the file. A lockfile that names another
42
- install is a different key, never a code path.
43
- - **Whoever can write your remote cache.** A cache key names the
44
- inputs, not the bytes, so a store that answers a key with bytes of its
45
- choosing is trusted as a writer, as it is in every action cache. Use a
46
- store only your builds write to, or `turboCache()`'s signature key,
47
- which refuses an artifact a keyholder did not sign. A job you do not
48
- trust (a fork's pull request) reads at most: `--cache-policy=local:rw,remote:r`,
49
- or the tools' own `TURBO_REMOTE_CACHE_READ_ONLY`, `TURBO_CACHE` and
50
- `NX_SKIP_REMOTE_CACHE`, which the cache plugins honour (L-34).
51
-
52
- ## Cache poisoning
53
-
54
- A pull request that can write the cache the default branch reads can
55
- plant bytes main later replays (CREEP, CVE-2025-36852). The boundary is
56
- the credential, enforced by the cache server: the default branch's job
57
- holds a token that writes the default keys, and a pull request's job
58
- holds one that cannot (read-only, or confined by the server to its own
59
- namespace), or none.
60
-
61
- `cache.scope` is the client half. `'pr-123'` reads the default keys,
62
- then its own, and writes only its own, which `github()` sets on
63
- Actions (`pr-<n>`, `ref-<name>`; the default branch sets none)
64
- and `VX_CACHE_SCOPE` or `--cache-scope` sets on any CI. Whether a run
65
- writes is `cache.policy`'s call: off CI the default reads the remote and
66
- writes nothing, since a key holds only declared inputs and a laptop's
67
- undeclared tool or file would replay on CI. A laptop that should never
68
- write gets a read-only token too. The local cache follows the same
69
- key rule, so on one disk a PR's entries sit under its scope's keys and
70
- a persistent runner never serves them to main.
71
- It keeps honest runs apart and stops a non-hermetic PR build from
72
- landing where main reads; it is not a boundary, since the PR's own
73
- config decides it and any run holding a write credential can write any
74
- key.
75
-
76
- A boundary the server holds, with bazel-remote and `@vzn/vx-reapi`: reads
77
- are open, writes need a password, and only main's push job has it.
78
-
79
- ```sh
80
- htpasswd -cB /etc/bazel-remote/htpasswd ci # the write credential
81
- bazel-remote --dir /data --max_size 50 --grpc_address 0.0.0.0:9092 \
82
- --htpasswd_file /etc/bazel-remote/htpasswd --allow_unauthenticated_reads
83
- ```
84
-
85
- ```ts
86
- // .vx/workspace.ts: the header only where the secret is set
87
- reapi({
88
- endpoint: 'grpcs://cache.example.com:9092',
89
- headers: process.env.CACHE_AUTH ? { authorization: `Basic ${process.env.CACHE_AUTH}` } : {},
90
- })
91
- ```
92
-
93
- ```yaml
94
- # .github/workflows/ci.yml: CACHE_AUTH (base64 of ci:<password>) is a
95
- # secret of the `cache-write` environment, whose only deployment branch
96
- # is main, so no other branch's workflow can read it
97
- jobs:
98
- build:
99
- if: github.event_name != 'push' || github.ref != 'refs/heads/main'
100
- steps:
101
- - run: vx run build --all
102
- build-main:
103
- if: github.event_name == 'push' && github.ref == 'refs/heads/main'
104
- environment: cache-write
105
- env:
106
- CACHE_AUTH: ${{ secrets.CACHE_AUTH }}
107
- steps:
108
- - run: vx run build --all
109
- ```
110
-
111
- A pull request's job has no `CACHE_AUTH`, so the server refuses its
112
- writes whatever its config says. Never give the write secret to a
113
- `pull_request_target`, `issue_comment` or `workflow_run` job that checks
114
- out a pull request's code: it runs with main's ref and secrets, and
115
- `github()` scopes it `pr-<n>` only on the client side.
116
-
117
- A boundary that holds whoever can write the store is a signing cache
118
- plugin: main's CI signs each upload, and a reader refuses, as it streams,
119
- a download no trusted key signed, so the hit is a miss. Core needs no
120
- change for it; the plugins guide (§ Your own cache › Signed artifacts) has a
121
- complete one, run by `tests/signed-cache-guide.unsafe.test.ts`.
122
-
123
- ## What vx checks on bytes it did not write
124
-
125
- Every artifact that arrives from a remote, and every one read back from
126
- the local store:
127
-
128
- - ends in a CRC-32 of its entries: a byte changed in transit or at rest
129
- makes the hit a miss, never a replay of the damaged bytes;
130
- - on arrival, records the cache key it was packed under, and is
131
- refused under another (a local read-back is not re-checked);
132
- - on arrival, carries only the files the task declares as outputs;
133
- - lands each file under the task's project or declared workspace path:
134
- traversal (`..`, `.`, absolute names) is refused before anything
135
- lands, a refused archive leaves nothing behind, and links and devices
136
- are never written; a seeded fuzz of hostile archives holds this;
137
- - is bounded: a decompression bomb, an oversized header or body, and a
138
- remote answer larger than asked for are refused as they are read.
139
-
140
- Remote execution (`@vzn/vx-reapi`) holds each blob to its digest, and
141
- writes only the server's outputs a declared glob names, inside the
142
- workspace, through no link that leads out of it, and through no link
143
- into a place no declared glob names.
144
-
145
- ## What the sandbox stops
146
-
147
- A task with `exec.sandbox` runs where only what it declares exists:
148
-
149
- - reads of the workspace outside the task's project and grants fail;
150
- - writes outside its `allow.write` grants fail (declared outputs grant
151
- none);
152
- - the network is closed except to the domains granted, one union per
153
- run: a task granted any domain reaches every domain the run grants;
154
- with none, it reaches no listener on the host's loopback and no unix
155
- socket outside the workspace (L-46);
156
- - its temp directory, port-bridge socket and trace log are its own
157
- (mode 0700), unreachable from another task and another local user;
158
- - the host's credential stores (`~/.ssh`, `~/.gnupg`, `~/.aws`,
159
- `~/.npmrc`, `~/.netrc`, the token files of deploy and agent CLIs such
160
- as Vercel, Wrangler, Fly or Claude, and the like; the list is in the
161
- schema's sandbox section) are unreadable unless `allow.read` names one
162
- (L-41), cannot be hard-linked into an output for vx to pack, and a denied read's `fix:` hint never suggests granting one
163
- (`tests/sandbox-credential-stores.unsafe.test.ts`, `tests/sandbox-fix.test.ts`);
164
- - vx's own cache directory, wherever `cache.dir` puts it in the
165
- workspace, is a wall like `.vx/cache`: a broad grant stops at it, and a write
166
- grant that would bind it is refused (L-24);
167
- - a symlinked output must resolve inside its project: vx packs outputs
168
- outside the sandbox, and a link to another project's file would have
169
- carried that file into the cache (L-23). An output directory linked out
170
- of the project refuses the task by name (X-88). Inside the project, a
171
- link must name a file the task could read (a read or write grant, its
172
- `node_modules`) or one of its own outputs, so `dist/x -> ../.env` under
173
- `read: ['src/']` is refused, and Linux judges making the link by the
174
- link, never its target (`tests/sandbox-output-link.unsafe.test.ts`).
175
- - vx's own environment, which holds the variables a task is not given
176
- (a token, a key), is unreadable: the task cannot see vx's
177
- `/proc/<pid>/environ` on Linux or read it through `ps eww` on macOS
178
- (`tests/sandbox-parent-env.unsafe.test.ts`).
179
-
180
- An undeclared touch of the task's own files fails the task, and a failed
181
- task is never cached. See [Sandboxing tasks](https://vznjs.github.io/vx/guides/sandboxing/).
182
-
183
- The sandbox costs each task it runs; a cache hit runs nothing and costs
184
- nothing. `tests/sandbox-parent-env.unsafe.test.ts` prints the cost of one
185
- `true` task, min of 20, on every CI run. On GitHub-hosted runners on
186
- 2026-10-13 it printed +269 ms on Linux and +97 ms on macOS.
187
-
188
- ## What it does not stop
189
-
190
- - **A task with no `exec.sandbox`.** It runs with your permissions:
191
- it reads anything you can. vx hides its own environment from it on
192
- every command: on Linux vx makes itself non-dumpable (about 0.8 ms), on
193
- macOS it zeroes the values `ps eww` reads, every variable but the
194
- essentials every task gets (about 0.7 ms). It starts its helpers with `PATH`
195
- alone and each `git` with only what git reads
196
- (`tests/hide-env.unsafe.test.ts`, `tests/git-env.test.ts`). That hides
197
- the environment only, not files.
198
- - **Reads outside the workspace root.** `~/.cache`, `/etc` and the like
199
- stay readable (tools need them) and fold into no key. The credential
200
- stores under home (`~/.ssh`, `~/.aws`, `~/.npmrc` and the like) are
201
- not: a task reads one only when its `allow.read` names it (L-41).
202
- - **Resource use.** CPU, memory and disk are bounded by timeouts and the
203
- artifact ceiling, not by the sandbox.
204
- - **A weaker sandbox where the host cannot nest one**:
205
- `weakerWhenNested` in a container, a task that itself sandboxes on
206
- macOS. `vx info` says what the host supports.
207
-
208
- ## Secrets
209
-
210
- The value, of 6 characters or more, of a variable whose name holds
211
- `TOKEN`, `SECRET`, `KEY`, `PASSWORD`, `PASSWD` or `CREDENTIAL` (not one
212
- ending `_FILE`, `_PATH` or `_DIR`, nor `GIT_CONFIG_KEY_<n>`), or that a
213
- task lists in `exec.env.secret`, and the password of a URL
214
- (`scheme://user:<password>@`) in any variable's value, is printed as `***` wherever vx shows, stores or
215
- exports it: task output, the stdout a hit replays, commands, an
216
- executor's error, telemetry,
217
- `vx show` (L-26). Values in the run history are digests under a per-store
218
- salt, and what follows `--` on the command line reaches telemetry as a
219
- count, not a quote, a secret before it masked (L-44). A remote executor (`@vzn/vx-reapi`) receives a
220
- task's `exec.env.define` values and the `inputs.env` values its
221
- local child gets, never `passThrough`; those values sit unmasked in the
222
- action's Command, which the remote stores in its CAS, as Bazel's
223
- `--action_env` does, so a secret a remote task needs is trusted to that
224
- remote. `vx lock` refuses to write a lock holding a secret value: the lock is
225
- committed (L-42).
226
-
227
- Masking covers the text vx shows and stores, not a task's outputs: they
228
- are cached and uploaded as the task wrote them, so a build that bakes a
229
- secret into a file (a server bundle reading `process.env.API_KEY`) hands
230
- it to every reader of the remote cache. Keep secrets out of cached
231
- outputs, or give that task no `cache`. vx does not scan outputs for
232
- secret values: a name-based match would refuse public `*_KEY` values too.
233
-
234
- ## Releases
235
-
236
- Release binaries carry a build-provenance attestation
237
- (`gh attestation verify vx-<target> --repo vznjs/vx`); `vx upgrade`
238
- installs a binary only after npm's registry signature over the platform
239
- package's `name@version:integrity` verifies against npm's key, pinned
240
- in vx, and the tarball matches that integrity: the host that serves the
241
- bytes cannot make the signature, so a swapped tarball is refused before
242
- anything is written, and a key id vx does not pin is refused with a hint
243
- that the installer needs updating (owner, 2026-10-13; pnpm's installer
244
- checks the same). The new binary is staged in a fresh directory, never a
245
- name another user could plant a link at. A vx a package manager
246
- installed is upgraded by that manager, which checks its own integrity.
247
- npm packages
248
- publish with provenance; every third-party action in CI runs from a
249
- full commit SHA; and a workflow hands an event's or a dispatcher's value
250
- to a script through `env`, never pasted in (a release tag is checked as a
251
- version first); every workflow names its token's permissions, and CI's
252
- is read-only. A test holds each rule.
@@ -1,21 +0,0 @@
1
- // The pages `vx docs` searches: core's reference, imported as text so the
2
- // compiled binary carries them and an npm install reads them from the
3
- // package (package.json `files`). Loaded only by `vx docs`.
4
- import caching from '../../docs/caching.md' with { type: 'text' }
5
- import cli from '../../docs/cli.md' with { type: 'text' }
6
- import execution from '../../docs/execution.md' with { type: 'text' }
7
- import features from '../../docs/features.md' with { type: 'text' }
8
- import patterns from '../../docs/patterns.md' with { type: 'text' }
9
- import schema from '../../docs/schema.md' with { type: 'text' }
10
- import security from '../../docs/security.md' with { type: 'text' }
11
-
12
- /** Page name (its file, no `.md`) → markdown. */
13
- export const PAGES: Readonly<Record<string, string>> = {
14
- cli,
15
- schema,
16
- caching,
17
- execution,
18
- patterns,
19
- security,
20
- features,
21
- }
@@ -1,5 +0,0 @@
1
- // A markdown page imported `with { type: 'text' }` is its text (docs-corpus.ts).
2
- declare module '*.md' {
3
- const text: string
4
- export default text
5
- }