Server config included in process args can expose secrets from env.yaml
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 1.2k
- Forks
- 349
- Avg merge
- 1d 21h
- Merged PRs (30d)
- 318
Description
Server config is included in process arguments
Describe the bug
Gym includes the full server config in the shell command it uses to start each server. That makes config values visible in the process arguments. A secret stored literally in env.yaml, or otherwise materialized in the in-memory config, is included too. Since model and judge configs can contain API keys, those values should not be put there.
The bug only applies when the secret value is actually present in the serialized in-memory config (as opposed to something like n unresolved OmegaConf reference like ${oc.env:MODEL_API_KEY}).
I found this while checking a local Gym run with ps. Process arguments can also be collected by CI diagnostics, cluster monitoring, and similar tools. No real credential is used or included in this report.
How long the config stays visible depends on the shell and logging wrapper. I measured these cases with a fake value and a clean pre-launch process-table baseline:
- On Linux with Bash 5.2, the value is present in a Bash argv while setup is running. Without
nemo_gym_log_dir, Bash replaces the launcher after setup, so the value is absent whileapp.pyruns. - With
nemo_gym_log_dirset,run_command()wraps the command inset -o pipefail; (...) | tee -a. On Linux, the value remains in a Bash argv whileapp.pyruns. - On macOS with Bash 3.2, the value remained in a Bash argv while
app.pyran in both logging modes.
Steps/code to reproduce the bug
I reproduced this on Gym main at commit 44181d3e4acc7856b7e3596a7ae135de5a039ea3.
-
Set up the repository environment normally:
uv sync --extra dev -
Run the attached reproducer from the Gym repository root:
PYTHONPATH="$PWD" uv run python reproduce_config_argv_exposure.py
The reproduction script uses the fake value fake-api-key-for-gym-reproducer. It follows Gym's real RunHelper.start() server-launch path and stops immediately before the process is created, so it does not actually start a server.
Output on main:
fake API key: fake-api-key-for-gym-reproducer
fake API key present in process command: True
captured command fragment: api_key: fake-api-key-for-gym-reproducer
fake API key passed through child environment: False
The attached reproduce_literal_env_yaml.py also calls Gym's real config parser with a temporary env.yaml. It confirms the fake key reaches the launch serialization, while an ${oc.env:...} doesn't:
fake API key loaded from env.yaml: True
fake API key present in launch serialization: True
fake API key resolved from an environment reference: True
referenced fake API key present in launch serialization: False
The attached reproduce_linux_process_window.py separately checks the setup and active-app windows using the same Popen(..., executable="/bin/bash", shell=True) call and logging wrapper as Gym. It should print something similar to below:
logging unset
matching processes before launch: []
setup started: True
matching processes while setup runs: [(7, 'bash')]
app started: True
matching processes while app runs: []
logging enabled
matching processes before launch: []
setup started: True
matching processes while setup runs: [(9, 'bash'), (10, 'bash')]
app started: True
matching processes while app runs: [(9, 'bash')]
Expected behavior
The serialized config, especially secret values, should not be part of the shell command.
Config
The reproducer uses this minimal in-memory config:
policy_model:
responses_api_models:
demo:
entrypoint: app.py
api_key: fake-api-key-for-gym-reproducer
Environment details
- Gym commit:
44181d3e4acc7856b7e3596a7ae135de5a039ea3 - OS: macOS 26.5.2 (25F84)
- Python: 3.13.14
- Environment created with
uv sync --extra devfrom the repository lock - Full
uv pip list --format freezeoutput is attached asuv-pip-list.txt - No GPU or sandbox provider is needed for reproduction
- Also reproduced on linux with env: Linux 6.8.0 aarch64, Bash 5.2.37, Python 3.12 container image digest
sha256:e5931cdb4a8cec0ad083277c16a39114f14123b8b6c858c8c9689b677789975c
Additional context
The issue is in RunHelper.start() in nemo_gym/cli/env.py. It serializes the global config and adds it to the command passed to run_command(), which then uses Popen(..., shell=True).
Gym could instead pass NEMO_GYM_CONFIG_DICT and NEMO_GYM_CONFIG_PATH through the environment mapping given to Popen.
With that change, the same reproducer prints:
fake API key: fake-api-key-for-gym-reproducer
fake API key present in process command: False
fake API key passed through child environment: True
Moving the config into the child environment keeps it out of the more visible process command line. Someone who can inspect another process's environment may still be able to read it.
A tested patch is available on rystewart-nvidia:secure-server-config. It passes the two config values through Popen(env=...) instead and adds a regression test that checks a fake key is absent from the command. The branch also lets callers name credential variables that Gym should remove from the orchestrator environment before Ray starts. That keeps unrelated Ray workers from inheriting those variables.
uv-pip-list.txt
reproduce_literal_env_yaml.py
reproduce_linux_process_window.py
reproduce_config_argv_exposure.py
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
The issue points to RunHelper.start() in nemo_gym/cli/env.py and the run_command() Popen path; read those entry points first. Review the regression test mentioned in the issue and consider the work done when serialized secrets no longer appear in the launch command while the documented config handoff still works.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 35/100