fix(security): trust check for project-level .rpg.toml (#824 H5) - #831
Conversation
Adds a unit test that creates a world-writable `.rpg.toml` in a temp dir, points CWD at it, and asserts `load_project_config()` skips the file. The test fails on main: today the loader has no permission/ownership check for project configs, so a world-writable file is silently loaded. The fix in the next commit makes this test pass. Refs #824 H5.
A `.rpg.toml` discovered by walking CWD up to `$HOME` is loaded with no permission or ownership check. Because `PROMPT1` and friends flow through backtick-shell expansion in the REPL (`src/repl/mod.rs:282`), cloning an untrusted repo and starting rpg inside it can execute arbitrary shell on first prompt render. Add `check_project_config_trust()`, modelled on the pgpass check at `src/connection.rs:1566–1582`: * file must be owned by the current uid; * mode must satisfy `& 0o022 == 0` (no group/other write). Untrusted files are skipped with a stderr warning and an actionable `chmod 0600` hint; the REPL still starts. User-level config under `~/.config/rpg/` is implicitly trusted and unaffected. The check is `#[cfg(unix)]`; on Windows the loader proceeds (POSIX modes are not portable) — same gating as pgpass. This is the minimum-viable fix. A TOFU-style allowlist at `~/.config/rpg/trusted_project_configs` (direnv-style opt-in) is left as a TODO under #824 H5. Refs #824.
CodeQL's cleartext-logging rule (#831 review) flagged `meta.uid()` and `geteuid()` flowing into the error string and on to `rpg_eprintln!`. Same pattern as #817 in `connection.rs`: uid values are sensitive in CodeQL's taint model and must not reach a logger. Drop the numeric uids from the error message — the diagnostic value was low (the user can `ls -l` the file to see ownership) and the trust check itself is unchanged. The mode error message is reformatted slightly to keep the chmod hint, which is the actually useful bit. Existing test (`load_project_config_skips_world_writable_file`) still passes; assertion is on file rejection, not the error string. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Addressed CodeQL cleartext-logging finding (security/code-scanning/140) in 6e5b1a9. The flagged taint flow was Fix: drop the numeric uids from the error message. Diagnostic value was low ( Existing test |
REV review — PR #831Tight, well-scoped fix for #824 H5: trust check on project Findings
VerdictPASS End-to-end test evidenceUnit-test evidenceLive end-to-end evidence — negative case (world-writable, rejected)Built a fresh The trust check fires with the exact Live end-to-end evidence — positive case (0o600, loaded)After fixing perms: No warning — the config was accepted and the loader proceeded without complaint. |
Summary
Fixes finding H5 from #824. A
.rpg.tomldiscovered by walking CWD up to$HOMEis loaded with no permission or ownership check. BecausePROMPT1and friends flow through backtick-shell expansion in the REPL (src/repl/mod.rs:282), cloning an untrusted repo and starting rpg inside it can execute arbitrary shell on first prompt render.Threat model
.rpg.tomlsettingPROMPT1 = "\evil-cmd`db=> "`.rpg. rpg walks up from CWD, finds.rpg.toml, loads it. The REPL renders the prompt and executesevil-cmdviash -c.Analogous tools (direnv
.envrc, VS Code workspace settings) require explicit trust for exactly this reason. pgpass already has a permission check atsrc/connection.rs:1566–1582; the far more powerful.rpg.tomldid not.Minimum-viable fix
New
check_project_config_trust()insrc/config.rs, modelled on the pgpass check:& 0o022 == 0(no group/other write).Untrusted files are skipped with a stderr warning and an actionable
chmod 0600hint; the REPL still starts. User-level config under~/.config/rpg/is implicitly trusted and unaffected (matches how psql treats~/.psqlrc).The check is
#[cfg(unix)]; on Windows the loader proceeds because POSIX modes are not portably exposed — same gating as the pgpass check.Deferred work
The richer TOFU-style allowlist at
~/.config/rpg/trusted_project_configs(direnv-style opt-in trust) suggested in #824 is out of scope for this PR; it is left as aTODO(#824 H5)incheck_project_config_trust's docstring. Implementing this minimum-viable check first lets us ship the fix immediately and design the allowlist UX deliberately.H6 (env scrubbing on the backtick subshell) is being addressed in a parallel PR.
Test plan
Red/green TDD per CLAUDE.md.
config::tests::load_project_config_skips_world_writable_file— creates a temp dir with a world-writable.rpg.toml, points CWD at it, assertsload_project_config()does not load the file. Verified failing onmain(the world-writable config was loaded).cargo test --lib --bins— 2064 passed, 0 failed (1 new test added).cargo fmt --check— clean.cargo clippy --all-targets --all-features -- -D warnings— clean.cargo check --target wasm32-unknown-unknown— builds.Refs #824.