# `Raxol.Core.ChildEnv`
[🔗](https://github.com/DROOdotFOO/raxol/blob/v2.8.0/lib/raxol/core/child_env.ex#L1)

The environment a spawned OS process gets: the node's own, minus the secrets
the BEAM consumes.

An Erlang port inherits the whole environment of the node. Every place raxol
spawns something a model or a third party chose -- a directive's shell
command, an MCP stdio server, a benchmarked agent package -- would otherwise
hand that process the Sleuth key the `fx` tool spends, a share-link secret,
a signing key or a bearer token, for a prompt-injected `printenv` to read.

`port_env/1` builds the `{:env, _}` value for `Port.open/2`: the caller's own
variables, plus each secret the caller did not set, mapped to `false`, which
unsets it in the child. A caller that passes a secret explicitly keeps it,
and a caller's own `false` is passed through as Port's unset marker.
`cmd_env/1` is the same for `System.cmd/3`'s `:env` option, whose unset
marker is `nil`.

## Configuration

Read when a child is spawned, so a runtime change applies to the next one.
The config must be a keyword list and both keys lists of variable names
(strings matching `[A-Za-z_][A-Za-z0-9_]*`); anything else raises an
`ArgumentError` naming the key at the next spawn, instead of scrubbing less
than the operator meant:

    config :raxol_core, Raxol.Core.ChildEnv,
      # More names to unset, e.g. a custom `Wallets.Env` `env_var:`.
      extra_secrets: ["MY_WALLET_KEY"],
      # Names to let through after all, to EVERY child (see below).
      pass: ["RAXOL_SLEUTH_API_KEY"]

`pass:` is global: a passed name reaches every child the node spawns --
directive shell commands, MCP stdio servers, the earn bench's agent
packages -- not only the one that needs it. A nested raxol node that needs
the Sleuth key should get it from its own
`config :raxol_agent, :web3, fx: [sleuth_api_key:
...]` instead; a single spawn site that must hand one child a secret names
it in that call's `env` (`port_env/1` keeps an explicitly given secret).

## Using it

Port honours only the LAST `{:env, _}` in its options, so `port_env/1` must
be the only one, or the last: a later `{:env, _}` drops the scrub silently.
For `System.cmd/3`, pass `cmd_env/1` as `:env`, never `port_env/1`: it
takes `nil`, not `false`, as the unset marker.

## What this does not do

It stops inheritance, not every read. A process running as the same user
can still read the node's launch environment (`ps -E` on macOS,
`/proc/<pid>/environ` on Linux). Keeping a secret away from those means not
putting it in the node's environment at all: a config provider, a 0600 file
or `op`.

Nor does it hide what a caller hands a child on purpose: a variable named
in a spawn's `env` is readable by everything that child runs.

# `cmd_env`

```elixir
@spec cmd_env([{String.t(), String.t() | nil}]) :: [{String.t(), String.t() | nil}]
```

A `System.cmd/3` `:env` value: `env` (string names and values, or `nil` to
unset) followed by every secret it does not name, mapped to `nil`.

# `port_env`

```elixir
@spec port_env([{String.t() | charlist(), String.t() | charlist() | false}]) :: [
  {charlist(), charlist() | false}
]
```

A `Port.open/2` `:env` value: `env` (names and values as strings or
charlists, or `false` to unset) followed by every secret it does not name,
unset.

# `secrets`

```elixir
@spec secrets() :: [String.t()]
```

The variables unset in every child: the built-in set plus `:extra_secrets`, minus `:pass`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
