Run env in your terminal and count. Tokens, cloud keys, a PS1, a
proxy, something an installer left behind years ago. If a build could
read all of that, two machines would build different bytes and never say
why, and every token would be one echo away from a CI log.
So a vx task starts almost empty.
Three layers
A short fixed list always gets through: PATH, HOME, SHELL, USER,
TMPDIR, locale, terminal and color variables, CI, NODE_OPTIONS and
a few more. Everything else is something you name:
// packages/api/vx.config.ts
import { defineProject } from '@vzn/vx/config'
export default defineProject({
tasks: {
env: {
exec: {
command:
'echo "FOO=$FOO" && echo "NODE_ENV=$NODE_ENV" && echo "DEPLOY_TOKEN=$DEPLOY_TOKEN" && echo "GH_PAT=$GH_PAT"',
env: {
passThrough: ['DEPLOY_TOKEN', 'GH_PAT'],
define: { NODE_ENV: 'production' },
secret: ['GH_PAT'],
},
},
},
},
})
-
passThroughtakes a value from your environment. It does not enter the cache key, so a laptop and a CI runner can still share a hit. -
definesets a literal value. It lives in the config, so it is part of the key. -
secretmarks a name to mask, for names vx would not guess.
Now run it with four variables set:
$ FOO=leaked DEPLOY_TOKEN=s3cr3t-value-123 GH_PAT=ghp_abcdef123456 vx run @demo/api#env
┌─ @demo/api#env > $ echo "FOO=$FOO" && echo "NODE_ENV=$NODE_ENV" && echo "DEPLOY_TOKEN=$DEPLOY_TOKEN" && echo "GH_PAT=$GH_PAT"
FOO=
NODE_ENV=production
DEPLOY_TOKEN=***
GH_PAT=***
└─ @demo/api#env ── (3ms) success
FOO never reached the task. NODE_ENV came from the config. Both
tokens arrived, and both printed as ***.
Masked everywhere vx prints
A value whose name holds TOKEN, SECRET, KEY, PASSWORD, PASSWD
or CREDENTIAL is masked without being asked. GH_PAT holds none of
those words, so it is listed in secret. Masking covers more than the
live log: the output the cache keeps and a hit replays, the command a
remote cache receives, vx show, vx why, vx last and telemetry. A multi-line key is masked line by line,
and a value split across two chunks of output is still caught.
When a variable changes the output
Some variables do change what a build produces, like NODE_OPTIONS
loading a loader. vx does not guess which. Say so, and the value joins
the key:
cache: { inputs: { files: ['src/**'], env: ['NODE_OPTIONS'] }, outputs: { files: ['dist/**'] } }
Hygiene, not a wall
Dropping variables keeps builds the same from machine to machine. It is
not a wall: an unsandboxed task runs as you and reads any file you can.
vx does hide its own environment from it: on Linux vx makes itself
non-dumpable (VX_DEBUG_DUMPABLE=1 turns that off for strace and debuggers),
and on macOS it zeroes the values ps eww reads, every variable but the
essentials every task gets. That guards the environment only, not your
files. To keep a task away from what it was not given, sandbox it with
exec.sandbox.
What the task gets and what the key sees are separate on purpose. The
whole set of rules is in the schema.
Originally published on the vx blog. vx is open-source (MIT) task orchestration with a build cache: vznjs.github.io/vx · GitHub.
Written with AI assistance.
Top comments (0)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.