Environment variables
Three separate sets of variables matter, and conflating them is a security bug:
- What locac reads: your keys, your home directory, your shell.
- What locac sets for the commands it spawns: the run's identity, and nothing more.
- What a spawned command inherits: a short allow-list, because everything locac spawns is running target code.
What locac reads
Provider credentials
Every built-in provider reads one key variable and one base-URL override. The full table is on the Providers page; the key variables are:
ANTHROPIC_API_KEY · OPENAI_API_KEY · GROQ_API_KEY · OPENROUTER_API_KEY ·
TOGETHER_API_KEY · XAI_API_KEY · DEEPSEEK_API_KEY · MOONSHOT_API_KEY · ZAI_API_KEY ·
VLLM_API_KEY · LMSTUDIO_API_KEY · OLLAMA_API_KEY
DEEPSEEK_API_KEY and MOONSHOT_API_KEY each serve two providers: that vendor's
Anthropic-shaped and OpenAI-shaped endpoints both read the same variable.
The matching base-URL overrides are ANTHROPIC_BASE_URL, OPENAI_BASE_URL, GROQ_BASE_URL,
OPENROUTER_BASE_URL, TOGETHER_BASE_URL, XAI_BASE_URL, ZAI_BASE_URL, DEEPSEEK_BASE_URL,
DEEPSEEK_OPENAI_BASE_URL, MOONSHOT_BASE_URL, MOONSHOT_OPENAI_BASE_URL,
MOONSHOT_OPENAI_CN_BASE_URL, VLLM_BASE_URL, LMSTUDIO_BASE_URL, OLLAMA_OPENAI_BASE_URL, and
OLLAMA_HOST for the native Ollama protocol.
A key can also be referenced from the config file rather than inlined, which keeps the secret in your shell profile and out of a file you might paste:
{ "apiKey": "$ANTHROPIC_API_KEY" }
Both "$VAR" and "${VAR}" are expanded once, at run time. An unset variable resolves to nothing
rather than to an empty string, so the run fails loudly instead of sending a blank credential.
locac's own
| Variable | Default | What it does |
|---|---|---|
LOCAC_HOME | ~/.locac | Where config, skills, roles, keybindings, session databases and extracted runtime assets live. Never the target repository |
LOCAC_SHELL | auto-discovered | Path to a bash-compatible shell for bash, execute and the sandbox. Same as --shell-path |
LOCAC_PASSTHROUGH_ENV | unset | Comma-separated extra variable names to admit into spawned commands. See the allow-list below |
LOCAC_HOME is the anchor of the whole trust model. Everything locac treats as operator
input, from configuration and prompt overrides to skills, roles and key bindings, is read from there and
nowhere else. The repository under audit is attacker-authored, so a config read from it would let
the target choose your provider, your base URL and your API key.
Shell resolution
LOCAC_SHELL sits second in a five-step chain:
--shell-path, orshellPathfrom the config (CLI and per-project overrides already merged)LOCAC_SHELLbashonPATH, unless it is the WSL launcher, which is skipped in favour of a native bash- Common Windows install locations
- No bash: fall back to
sh, else PowerShell (-NoProfile -NonInteractive), elsecmd /c
An explicit-but-missing path does not hard-fail. It warns once and falls through to auto-discovery, so a stale config entry can never brick a run:
locac: shellPath "C:\Program Files\Git\bin\bash.exe" not found; falling back to auto-discovery
The PowerShell fallback is deliberately not given -ExecutionPolicy Bypass: the target repo is
attacker-authored, and its .ps1 files should stay exactly as hard to run as your host says they
are.
Editor
| Variable | Read by | Order |
|---|---|---|
EDITOR, VISUAL | locac skills edit <name> | $EDITOR, then $VISUAL |
VISUAL, EDITOR | The TUI's Ctrl+G draft handoff | $VISUAL, then $EDITOR |
The two orders differ on purpose. Ctrl+G hands your whole draft to an editor and takes back what
you save, which wants the POSIX definition of VISUAL, the full-screen editor. skills edit
just opens a file.
With neither set, skills edit prints the path instead of failing:
/home/you/.locac/skills/my-skill/SKILL.md
(set $EDITOR to open it automatically)
A value with arguments works: EDITOR="code -w" reaches the spawn as two words.
Terminal
| Variable | Effect |
|---|---|
NO_COLOR | Set to anything, and all ANSI colour is suppressed |
TERM=dumb | Same |
Colour is emitted only when stdout is a real TTY and neither of those applies, so piping to a file or a pager already gives you plain text without setting anything.
What locac sets for spawned commands
Every command locac spawns, whether the bash tool, execute, a sandbox-wrapped argv or a target
build, inherits these:
| Variable | Value |
|---|---|
LOCAC_SESSION_ID | The current session id. Absent when there is no session |
LOCAC_MODEL | The active model id |
LOCAC_PROVIDER | The active provider id |
LOCAC_EFFORT | The reasoning level actually going out on the wire. Absent, not empty, when the endpoint carries none |
LOCAC_EFFORT is omitted rather than set to "" because a script asking [ -n "$LOCAC_EFFORT" ]
should be able to tell "the operator chose no effort" from "this endpoint has no effort knob".
These let a PoC stamp its output with the run it belongs to, and let a build detect that it is running under the harness, without a side-channel file.
What is deliberately not there is the design:
- Never the API key. One
envin a Makefile or a postinstall script would exfiltrate it. - Never the session database path. The store lives outside the sandbox root, so naming it to attacker-authored code buys the attacker a target and locac nothing.
What a spawned command inherits
Handing target code {...process.env} hands it your provider keys, your cloud and CI tokens, and
LOCAC_HOME, which points at the file holding the key. So locac allow-lists its own environment
down to what a process needs in order to start, and drops everything else.
Matching is case-insensitive, because Windows spells these Path, SystemRoot, ComSpec, and
a case-sensitive list would strip PATH itself there.
POSIX: PATH · HOME · SHELL · TMPDIR · USER · LOGNAME · LANG · LC_ALL ·
LC_CTYPE · TERM · TZ
Windows (a process will not even start without the first three): SYSTEMROOT · SYSTEMDRIVE ·
WINDIR · COMSPEC · PATHEXT · TEMP · TMP · USERPROFILE · HOMEDRIVE · HOMEPATH ·
APPDATA · LOCALAPPDATA · PROGRAMDATA · PROGRAMFILES · PROGRAMFILES(X86) · PROGRAMW6432 ·
COMMONPROGRAMFILES · COMMONPROGRAMFILES(X86) · COMMONPROGRAMW6432 · ALLUSERSPROFILE ·
PUBLIC · DRIVERDATA · NUMBER_OF_PROCESSORS · PROCESSOR_ARCHITECTURE · OS
Note what is absent: no ANTHROPIC_API_KEY, no GITHUB_TOKEN, no AWS_*, no LOCAC_HOME.
Re-admitting a variable
Real builds need real variables. LOCAC_PASSTHROUGH_ENV is the operator's escape hatch, never the
target's, because the target cannot set it:
export LOCAC_PASSTHROUGH_ENV="JAVA_HOME,MAVEN_OPTS,CARGO_HOME"
locac run "build the project and audit the deserialization path" --auto
Names are comma-separated, trimmed and matched case-insensitively.
Add only what the build actually needs. Every name you add is a name the target's Makefile can
read.
Next
Configuration covers the config file these variables interact with, and the
Flag reference lists their command-line equivalents: --shell-path for
LOCAC_SHELL, --api-key and --base-url for the provider variables, and --config to point at a
config file outside LOCAC_HOME entirely.