Program Arguments
Every other block in a fragment decides what a program may reach. This one changes what it does, so it is deliberately the narrowest construct in the language: one verb, and no way to remove, replace or reorder anything you typed.
fragment "codex-app" { args { append ["--ignore-certificate-errors-spki-list=$WARDCELL_PROXY_CA_SPKI"] }}Why it exists
Section titled “Why it exists”One class of program cannot be configured any other way. Chromium — and so
every Electron application — reads no certificate authority from its
environment, and gives each child process a Seatbelt profile of its own. Under
wardcell that means it needs --no-sandbox to start at all, and
--ignore-certificate-errors-spki-list before its traffic can be inspected.
Both are command-line switches with no environment equivalent.
Nothing else needs it. The JVM takes $WARDCELL_PROXY_CA_TRUSTSTORE through
env, cargo takes CARGO_HTTP_CAINFO, and every CLI in the bundled tree is
configured the same way. If a variable will do, use one.
The rules
Section titled “The rules”Added, never removed. wardcell run -- Codex.app --foo runs with --foo
intact, in the position you put it. A policy cannot remove, replace or reorder
what you typed.
In front of your own arguments. A fragment is selected on the program, so
the switch it adds belongs to the program. wardcell run -- codex login status
runs codex --sandbox=… login status, not codex login status --sandbox=… —
the second is refused, because that option belongs to codex and not to login status. The front is also the only position that cannot land between one of
your options and its value, since a policy has no way to know which of your
switches take one.
Never the program itself. A policy decides what a program may reach, not which program runs.
Your value wins. An argument whose switch is already present is dropped, so
--user-data-dir=/mine is not joined by a second one from policy. This is the
only place a policy reads your command line, and it reads it to stay out of the
way. A positional argument is never compared — a file you happen to call
no-sandbox does not suppress a switch.
Including a switch you spelled differently, where the fragment says so. A
program may accept one option under two names, which no comparison of switch
names can see: to codex, -s and --sandbox are one option, and being
handed both is an error that stops it starting. unless_given names the other
spellings, and the whole block is skipped when you used any of them:
args { unless_given ["-s"] # the same option, spelled short append ["--sandbox=danger-full-access"] # what to set when you did not}It governs the block, not one entry — so a fragment adding several unrelated
switches gives each its own args block.
A short entry matches its value attached as well as separate: -s read-only,
-s=read-only and -sread-only are one option to the program and three
unrelated strings to anything comparing switch names. You do not need to name
the long spelling — an appended --sandbox=… already carries that name, so the
first-one-wins rule drops it on its own.
Every run says what was added, on stderr, naming the fragment:
wardcell: policy added argument --no-sandbox (shared/electron-app)wardcell: policy added argument --ignore-certificate-errors-spki-list=xU+2VP… (apps/codex-app)This is not optional and it is the reason the block is safe to have. Every
other rule in a policy restricts a run, so a quiet run is a safe one;
--no-sandbox makes the program weaker, and a quiet run would hide that.
A workspace policy may not use it. A .wardcell file sits in a directory
the run can usually write, so a sandbox could otherwise edit the policy its own
next run is built from. Filesystem grants from layer 3 are checked against
base.sbx and can only widen reach; an appended --gpu-launcher=… would run a
program of the sandbox’s choosing. wardcell rejects it.
Only switches. append takes arguments beginning with -. A policy
configures how a program runs; what it operates on is yours to say, and a bare
path added to your command line would change that.
Fragments union, first one wins. Several fragments may each append, in
fragment order, and a switch already appended is not appended again — so two
fragments asking for --no-sandbox produce one. Two fragments asking for the
same switch with different values also produce one, the earlier, with no
error. Keep a switch in a single fragment.
Tokens
Section titled “Tokens”Arguments expand the same tokens an env value does, which is what makes
per-run values possible:
| Token | |
|---|---|
$WARDCELL_PROXY_CA_SPKI | base64(SHA-256(SubjectPublicKeyInfo)) of this run’s CA — what Chromium’s --ignore-certificate-errors-spki-list takes |
$WARDCELL_PROXY_CA_CERT / _BUNDLE / _TRUSTSTORE | the same CA as a file, in the three forms a runtime might read |
$WARDCELL_PLUGIN_DIR | the run’s shared plugin directory, read-only to the sandbox — where a plugin materializes trust and other files |
$APP_BUNDLE, $HOME, $WORKDIR, $TMPDIR, $WARDCELL_ZONE, $WARDCELL_WORKDIR_ID | as everywhere else |
$WARDCELL_PROXY_CA_SPKI changes every run, because the CA does. That is the
point: no user could type it, which is why the switch it feeds was unusable
before this block existed.
Getting interception back for a Chromium application
Section titled “Getting interception back for a Chromium application”--ignore-certificate-errors-spki-list is not
--ignore-certificate-errors. It does not switch validation off; it trusts
exactly one key — the one wardcell generated for this run. With it, allow_http
and allow_requests work normally and every request is visible and gated:
fragment "vscode-app" { args { append ["--ignore-certificate-errors-spki-list=$WARDCELL_PROXY_CA_SPKI"] }
net { allow_http "marketplace.visualstudio.com" { allow_requests ["GET /**"] } }}