fix: harden security beyond PR #249 — command injection, deps, path injection - #264
fix: harden security beyond PR #249 — command injection, deps, path injection#264WODE25500 wants to merge 8 commits into
Conversation
…s, path injection
Five additional security hardening changes identified during a full
repository security audit:
1. Replace os.system() with subprocess.run() in Sleep plugin
(plugins/openclaw/slash_sleep.py) to prevent shell command injection
via unsanitized arguments.
2. Raise dependency floors to address known CVEs:
- vllm >= 0.8.4 (was 0.4.0; CVE-2025-32433 in transitive deps)
- datasets >= 3.0 (was 2.18.0; remote code execution via
load_dataset with untrusted configs)
- Declare openai-codex-sdk as an explicit optional dep (codex extra)
to prevent dependency confusion / undeclared-import attacks.
3. Sanitize task_id before use in tempfile.mkdtemp prefix
(skillopt/envs/spreadsheetbench/rollout.py) to prevent directory
creation at attacker-chosen paths via crafted task identifiers.
4. Extend WebUI security tests from 2 to 8, covering --share warning,
auth via CLI args / env vars, default-no-auth, and path traversal
rejection in scan_outputs().
5. Sync requirements.txt commented versions with pyproject.toml floors.
All 1445 existing tests pass; 6 new regression tests added.
|
The shell-free subprocess invocation and path hardening are useful. Re-reviewing In Both call Please reject incomplete credentials before building/launching the UI. Tests should cover user-only, password-only, incomplete environment configuration, and a complete pair, with the incomplete cases asserting that |
Supplying only --auth-user or only --auth-pass (or only one of SKILLOPT_WEBUI_USER / SKILLOPT_WEBUI_PASS) previously left auth=None and still launched the UI — a deployment could expose the training controls without login. Now reject before building/launching (sys.exit 1); launch() is never called for incomplete credentials. Added user-only / pass-only / env-incomplete regressions.
|
Yifan Yang (@Yif-Yang) — fixed on |
|
Thanks for I am keeping the security-hardening review open rather than treating those passing cases as proof that the complete WebUI boundary is covered. Please extend the negative integration matrix through the real Any further security-sensitive reproduction details should be coordinated privately under the repository's |
|
Understood, and agreed. Any further security-sensitive reproduction details (real credentials, private filesystem paths, or exploit payloads) will be coordinated privately per |
…oint Lift scan_outputs out of the build_ui closure so the Output Explorer callback is directly testable, and add callback-level tests that call it with traversal args (denied, returns []) and a valid in-tree output area (digested, reads config.yaml). This replaces the prior approximation tests that only re-checked relative_to() in isolation.
|
Thanks for the re-review. I reworked the Output Explorer tests so they exercise the actual data-consumption path instead of re-checking the containment helper in isolation.
Full webui suite: 21 passed. (If desired I can extend the same callback-level approach to the remaining UI callbacks.) |
The codex optional extra and the vllm/datasets floor bumps are dependency hygiene / CVE-floor changes, not part of the command-injection and path- injection hardening. Keep this PR surgical (the four security fixes + WebUI tests); the dependency changes are preserved in the branch history (commit 6f0030d) for a separate dependency PR. The gradio floor comment stays as-is (already synced to pyproject's 5.50.0 floor).
UI callbacks must not trust Gradio component values: config preview and launch preflight now resolve paths through a shared boundary helper and only accept configs/ files, while scan_outputs also rejects symlink escapes at every directory/file it reads. Config preview was promoted to a module-level callback so the registered consumption path is directly testable.
|
Yifan Yang (@Yif-Yang) Re-reviewing the complete WebUI boundary, I found the config-preview callback ( Fix on
Added callback-level regressions for relative traversal, absolute traversal, a valid in-tree config, and launch-preflight rejection; the full WebUI test set (17 security + build/env preflight) passes locally: 25 passed. CI on the new head is awaiting maintainer approval. |
|
Re-reviewed The independent security follow-up matrix is now 11 passed, 2 failed, improved from 7 passed, 6 failed. The remaining failures exercise authentication-configuration rejection through the real Please finish the authentication configuration matrix across CLI/environment sources and their precedence, explicitly distinguishing absent configuration from invalid configured values. Invalid authentication configuration must stop before launching the UI. Keep valid configured authentication and the intentionally unconfigured local-use case as separate positive controls. I am not including the remaining security-sensitive reproduction inputs in this public thread. Please coordinate those privately under the repository's The PR's own security file passes 17 tests locally; the two Gradio build/preflight modules were skipped in this verification environment, so I am not claiming that dependency matrix passed. Official CI remains awaiting maintainer approval. This acknowledges the completed path fixes, but is not approval to merge the remaining security boundary. |
`resolve_auth()` replaces the `args.auth_user or os.environ.get(...)` pair: - Precedence is explicit and per field: a CLI flag wins over its environment variable. - "Not supplied" and "supplied but blank" are distinct states. Previously `--auth-user "" --auth-pass ""`, or a pair of blank environment variables, left both sides falsy and launched the UI with no authentication at all, and an explicitly blank flag fell back to the environment. - Incomplete configuration still stops before the UI launches, now through a single error path that names the source that was set. `main()` keeps the fail-closed `sys.exit(1)` and its message now separates "configure both to require login" from "omit both to run without". Tests: the `main()` matrix covers CLI/env/field-mixed acceptance, per-field precedence, and the incomplete and blank rejections. Four of the new cases fail against the previous resolution.
|
Yifan Yang (@Yif-Yang) — fixed on
Verification on Linux / Python 3.11 running this repository's own CI commands — scratch branch off this head, deleted afterwards, workflow file not part of this PR:
Same caveat you raised, stated plainly: that run installs only On coordinating the rest privately: the configuration shape is what I would need to reproduce it — which source supplied which field, and which of them were empty. That is not credential or payload material. If your matrix still fails after |
The rejection matrix asserted that launch() was never called, which leaves a refactor free to construct the UI and then refuse. The probe now hands back the mocked build_ui so the rejection cases also assert it was never called.
|
Yifan Yang (@Yif-Yang) — one more from a self-review pass, test-side only, on The rejection matrix asserted that Re-verified on Linux / Python 3.11 (scratch branch off this head, deleted afterwards; workflow file not part of this PR):
The positive controls are unchanged: valid CLI, valid env, and field-mixed configurations all launch with |
The rejection matrix listed ten shapes. The contract is a table over four inputs - CLI and environment, each supplying a username and a password, each either absent, supplied-but-blank or supplied - so enumerate all 81 combinations against a restated decision table instead. Ten shapes covered the families I could think of; the sweep is what covers the boundary. Against the pre-fix resolution this reports 24 failures, every one of them a launch with missing, blank or environment-substituted credentials. Against the current resolution it reports none. Precedence keeps its own test: the sweep uses the same literal on both sources, so it cannot pin which one actually won.
|
Yifan Yang (@Yif-Yang) — this round is about the matrix itself rather than a new input, on I replaced the ten-shape rejection list with the whole decision table: four inputs — CLI and environment, each supplying a username and a password — each either absent, supplied-but-blank or supplied, so all 81 combinations go through the real
All 24 were launches with missing, blank or environment-substituted credentials — the family you were pointing at. If your remaining two cases are configuration-shape cases rather than something structurally different, they should fall inside those 81. The table now lives in Honest caveat: Precedence still has its own test, because the sweep uses the same literal on both sources and cannot tell which one actually won. Re-verified on Linux / Python 3.11 (scratch branch, deleted afterwards; workflow file not part of this PR):
|
Summary
Post-#249 security hardening from a full-repo audit. Kept surgical: the dependency/CVE floor changes and the
codexoptional extra that were originally bundled here are split OUT of this PR so it contains only the security fixes.Changes
Command injection fix - Replace
os.system()withsubprocess.run()in the Sleep plugin (plugins/openclaw/slash_sleep.py). The command is passed as a list with no shell, so unsanitized arguments can no longer be injected.Path injection fix - Sanitize
task_idbefore use intempfile.mkdtempprefix (skillopt/envs/spreadsheetbench/rollout.py) to prevent directory creation at attacker-chosen paths.WebUI hardening:
scan_outputsguarded so it digests only underPROJECT_ROOT(path-traversal and symlink escapes are denied at the point each directory/file is read).PROJECT_ROOT/configs/(load_config,validate_training_config).--auth-user/--auth-pass(orSKILLOPT_WEBUI_USER/SKILLOPT_WEBUI_PASS) basic auth, resolved per field with explicit precedence (a flag wins over its variable). "Not supplied" and "supplied but blank" are different states: incomplete or blank configuration, from any mix of sources, stops before the UI is even built. A blank pair previously left both sides falsy and launched with no authentication at all. A warning is emitted when--shareis used.Test plan
python -m pytest tests/test_webui_security.py- 100 passedtest_webui_build_gradio.py,test_webui_env_preflight.py) need thewebuiextra and have not been re-run since this changeaction_requiredpending maintainer approvalNote for maintainers
The dependency changes originally here (
codexoptional extra,vllm>=0.8.4,datasets>=3.0) are intentionally split out of this security PR so it stays surgical. They are preserved in the branch history (commit6f0030d) and belong in a separate dependency/CVE-floor PR.