Summary
Before spawning an MCP server, expand_env_vars substitutes $VAR references in argv from the daemon's full process environment with no allowlist. The expanded values land on the child's argv, which is visible in ps, /proc/<pid>/cmdline, system audit logs, and crash dumps.
Evidence
crates/librefang-runtime-mcp/src/lib.rs:1006 — let args_owned: Vec<String> = args.iter().map(|a| expand_env_vars(a)).collect();
crates/librefang-runtime-mcp/src/lib.rs:2118 — else if let Ok(val) = std::env::var(&var_name) { result.push_str(&val); } (no allowlist)
- Compare
:1015 cmd.env_clear() and :1018 for &var in SAFE_ENV_VARS — env passed to child is correctly allowlisted; argv expansion is not.
Why it's a bug (attack)
Anyone able to register an MCP server (admin, dashboard caller, [[mcp_servers]] template) configures args = ["--token", "$LIBREFANG_VAULT_KEY"] or "$ANTHROPIC_API_KEY". The daemon expands them and places the secret on argv. The whole point of env_clear() + SAFE_ENV_VARS was to deny the child the daemon environment; argv expansion reopens that hole on a far worse channel — argv leaks beyond the subprocess into every observability layer.
Proposed fix
Restrict expand_env_vars to the same SAFE_ENV_VARS allowlist (or to names declared in extra_env); for any other reference, leave the literal $VAR text untouched and log a warning. Alternatively, drop arg-side expansion entirely and require operators to set values through extra_env.
Summary
Before spawning an MCP server,
expand_env_varssubstitutes$VARreferences in argv from the daemon's full process environment with no allowlist. The expanded values land on the child's argv, which is visible inps,/proc/<pid>/cmdline, system audit logs, and crash dumps.Evidence
crates/librefang-runtime-mcp/src/lib.rs:1006—let args_owned: Vec<String> = args.iter().map(|a| expand_env_vars(a)).collect();crates/librefang-runtime-mcp/src/lib.rs:2118—else if let Ok(val) = std::env::var(&var_name) { result.push_str(&val); }(no allowlist):1015 cmd.env_clear()and:1018 for &var in SAFE_ENV_VARS— env passed to child is correctly allowlisted; argv expansion is not.Why it's a bug (attack)
Anyone able to register an MCP server (admin, dashboard caller,
[[mcp_servers]]template) configuresargs = ["--token", "$LIBREFANG_VAULT_KEY"]or"$ANTHROPIC_API_KEY". The daemon expands them and places the secret on argv. The whole point ofenv_clear()+SAFE_ENV_VARSwas to deny the child the daemon environment; argv expansion reopens that hole on a far worse channel — argv leaks beyond the subprocess into every observability layer.Proposed fix
Restrict
expand_env_varsto the sameSAFE_ENV_VARSallowlist (or to names declared inextra_env); for any other reference, leave the literal$VARtext untouched and log a warning. Alternatively, drop arg-side expansion entirely and require operators to set values throughextra_env.