vike-cli secrets
Inspection of the one credential store — where it is, which key names it holds, which accounts those names resolve to, an empty grid to start one from — and the one narrow writer: set a single key, value from stdin or an environment variable, never from the command line.
There is one credential store, <project>/settings/secrets.env, and secrets is how you look at it
without opening it in an editor. Three subcommands read; one writes, and only in one shape: set
upserts a single named key into a store that already exists. There is deliberately no rotate, no
delete, no --out on template and no way to create a store from here: it is the user's only copy
of live venue credentials, so creating, moving or rewriting it is their decision, made with an
editor. path prints the location so they can.
Subcommands and flags
| invocation | what it does |
|---|---|
secrets list | the key names in the store — never a value — plus the accounts those names resolve to |
secrets path | the store's path, whether it is there, and whether its mode exposes it |
secrets template | an empty credential grid on stdout — per roster venue, the SIM/DEMO/LIVE × _API_KEY/_API_SECRET/_API_PASSPHRASE rows plus that venue's attribution keys. Not every name the workspace reads: the legacy MAINNET tier and the bespoke shapes are absent by design, and the emitted header says so |
secrets set KEY | upsert one credential into the project's existing store, the value read from stdin (one line) — see set |
--from-env NAME | set only: take the value from the environment variable NAME instead of stdin, verbatim |
--file PATH | inspect this store instead of the project's (list, path). Refused on set: a write never takes an operator-supplied destination |
--venue ID | template only: emit one roster venue's rows. Passing it to list or path is a named usage error rather than a silently ignored flag |
-h, --help | the usage text, on stdout, exit 0 |
Which file, exactly
path answers the question an operator must be able to settle without reading source: which file
are my keys actually coming from? The settings directory is found by walking up from the working
directory, and VIKE_SETTINGS_DIR names it outright, so the answer depends on where you are
standing. Both inspecting subcommands take the same resolved directory the dispatcher hands every
other subcommand, so this output cannot drift from what a daemon loads.
store: /srv/vike-tradehub/settings/secrets.env (present)Absent has three answers, not two
Path::exists maps every error to false, so a project root this process cannot search once
reported the store as absent and then printed the fresh-install advice for what was really a
permissions problem. crates/vike-cli/src/cmd/secrets.rs's Presence uses Path::try_exists
instead: Ok(false) is NotFound and nothing else. Present, absent, and could not be
determined each print different words — the third shares no word with the other two, because an
operator greps this line. An undetermined answer is a finding, not a refusal: it prints the path,
says what was not established, and still exits 0, since path is what you run when something is
already broken.
When the store is genuinely absent, path prints the three commands that create one — mkdir -p,
$EDITOR, and chmod 600 … # it is plaintext credentials — and, if a pre-one-store .env is
sitting beside it, vike_secrets::legacy_store_warning says so rather than letting nothing here
reads any other location stand unqualified.
Permissions are reported, never enforced
Both subcommands warn about an over-permissive mode. list always did, because it opens the store
and vike_secrets::resolve hands the finding back with the credentials; path never did, because it
opens nothing — so the command documented as the safe first one was the one that stayed silent about
a world-writable credential file. Measured on a clean install at modes 600, 640, 644, 664 and 666,
path printed zero warnings at every one while list warned from 640 up. path now asks the
same question through vike_secrets::permission_warning, which lstats the file and follows a
symlink only far enough to readlink and stat it — never its contents. A finding is never a
refusal, so no exit code changes.
list — names, and the accounts behind them
The header names the file before the keys, because where did these come from is the question the
list itself cannot answer. Then the key names, then the accounts, derived from those same names
through vike_model::account_keys::accounts_in_store. That second block exists because two spellings
look identical to a reader and are not:
HYPERLIQUID_LIVE_API_KEY__ALT an account named ALT
HYPERLIQUID_LIVE_API_KEY_ALT one underscore, and NOT an accountThe grammar splits at a double underscore (vike_model::account_keys::ACCOUNT_SEPARATOR), so the
single-underscore spelling leaves the whole string as one base name belonging to the default account
— a credential that is silently inert. An account row is proof the labelled key was understood. The
row renders as venue/TIER label, with the unlabelled account shown as (default) rather than
AccountLabel's own DEFAULT, which is the one spelling AccountLabel::parse refuses.
This is a store disclosure and says nothing about arming
An account listed here is an account whose credentials exist. Whether it mounts is a separate,
policy question, answered one level below the venue ceiling: vike_config::VenuePolicy::account
resolves an account's mode as the lower of the venue's own line and that account's line in
policy.toml's [accounts] table, and vike_mount::accounts_to_mount mounts a labelled account
only when the answer lands above paper.
A labelled account with no line of its own therefore resolves paper: the venue line was written
when the venue had one account, and reading it as consent for a second is the silent escalation the
ceiling exists to prevent. Names the grammar recognises as no credential contribute no row, and that
is a classification rather than a gap: attribution codes, the POLY_* L2 trio, Dukascopy's numbered
sub-account, Aster's TESTNET tier.
template — a shape, and a shape is safe to paste
Every name is derived from the venue roster (vike_model::venues::VENUES) crossed with the current
tiers and suffixes in vike_model::credential_keys, so a venue added to the roster appears the same
day; an unknown --venue is an error naming the roster rather than an empty emission. The header
states the rule people miss: arming is not a credential question alone — policy.venues.<venue>
is a ceiling consulted before the credential is read, and it defaults to paper for every venue.
The header's other ⚠ block states what the grid cannot contain, and that gap is structural rather
than an oversight: a venue whose credentials have a bespoke shape is derivable from no central
table, so its keys live only in that bridge's own crates/bridges/<venue>/src/config.rs and are
written by hand — the FX brokers' login/password pair, the on-chain venues' _PRIVATE_KEY, the
POLY_* trio. The pre-rename MAINNET tier is omitted for a different reason: the loader still
reads it, so it stays in the registry's grid, but teaching a spelling we are retiring to somebody
writing their first store would be its own kind of wrong. vike_model::credential_keys::starter_keys
draws both lines; lookup_keys — which this command never calls — is what answers every name a
computed lookup in this workspace can ask for.
Output goes to stdout and there is no --out flag, on purpose: the moment this command owns a
path it can truncate a live store. Redirecting it yourself is your own keystroke against your own
path.
set — the one writer, and the shape it is held to
printf %s "$SECRET" | vike-cli secrets set BINANCE_DEMO_API_KEY # the value on stdin, one line
vike-cli secrets set BINANCE_LIVE_API_KEY --from-env BINANCE_KEY # the value from $BINANCE_KEYThe CLI stayed read-only for a long time on four arguments — the store is the user's only copy, a
writer invites scripts to regenerate the file wholesale, a value in a shell history is a leak, and
nothing on the agent surface may ever write a credential — and the writer that finally exists is the
one shape those arguments allow. crates/vike-cli/src/cmd/secrets.rs's run_set is the code, and
the order it does things in is the argument:
- The key is validated first, before anything is read or asked of stdin, against the key grid
vike_model::credential_keys::key_ownercan read — so a refused key never consumed the secret you piped on its way to the error. An unknown name is refused by name, with the nearest real names beside it: the store is a flat file, so a hand-typedBINANCE_LIVE_API_KEY_would otherwise be written as happily as the real one and the venue would stay on paper with no error anywhere. The one exception is a labelled second-account key such asHYPERLIQUID_LIVE_API_KEY__ALT— read, listed, and not settable from here, because the grid is a fixed enumeration and a label is not. Its refusal offers nothing to copy: the nearest name is the default account's live key, and writing this value there would overwrite the credential that account signs with. - The store must already exist.
setupserts into the project's store — the same filepathprints, under the sameVIKE_SETTINGS_DIR— and creates none: with no store the command refuses and prints the two lines that make one (templateredirected, thenchmod 600). A store that is there and cannot be read is an error, never "not configured", the same distinctionlistdraws. A permissive mode is reported on stderr and the write proceeds. - The value is taken last, from stdin or from the named variable, and never from the command
line: a second positional — and, deliberately, any unrecognised token, a mistyped flag included
— is refused with a message that quotes nothing you typed, because the token is the secret and
an error echoing it would put it into the scrollback this refusal exists to keep it out of. Stdin
is trimmed of the newline a shell or editor adds; a
--from-envvalue is taken verbatim, so a credential with real leading or trailing whitespace survives, quoted. Both refuse empty (an empty value arms nothing, and reporting success for it is the failure class this project deletes settings keys over) and both refuse more than one line: a multi-line value written verbatim became a truncated credential plus a secondKEY=VALUEthe store never validated — an injection, not an untidiness — so--from-envnames the variable and stops. - The write is a second call site of the one upsert,
vike_secrets::save_credentials: the named key is replaced in place or appended, every other line, comment and blank stays byte-identical, the store's mode and a symlinked store's link are preserved, and the result lands atomically. There is no second writer to drift from the GUI's. - The write is recorded in the change journal, as the CLI actor, by key name and store — never by value — and a journal failure does not fail the call: the credential is on disk, and an error path for a write that succeeded is worse than a missing ledger line.
What it prints on success is one line, KEY replaced in PATH or KEY appended in PATH, and nothing
else. vike-cli secrets list afterwards shows the name; no invocation of this command ever shows a
value.
The agent surface has no counterpart. vike-cli mcp advertises no tool that writes a credential,
and a test holds the tool roster to that — an agent that needs a key armed is told to ask the
operator, who runs the line above.
See also
policy.toml— the arming ceiling consulted first- Accounts — the key grammar and the per-account arming rows
- Risk and arming — ceiling, then credential, then risk gate
- Environment variables — including
VIKE_SETTINGS_DIR
config
Two verbs over one settings directory: show discloses every setting and where its value came from, check judges the same tree and makes the exit code the product.
backend
Mint a vike-tradehub node's authentication keys on the daemon's own box with `backend setup`, then attach a client machine to a running node with `backend connect`, `status`, and `disconnect`.