From 93e930ba4170359de4e9f41f43f3bd6eb428a691 Mon Sep 17 00:00:00 2001 From: "Jiaxiao (mossaka) Zhou" Date: Wed, 11 Mar 2026 17:24:14 +0000 Subject: [PATCH] fix(security): clear LD_PRELOAD after one-shot-token library loads The one-shot-token LD_PRELOAD library now unsets LD_PRELOAD and LD_LIBRARY_PATH from the environment after initialization. The library remains loaded in the current process's address space so getenv interception continues to work, but child processes no longer inherit these variables. This fixes Deno 2.x's scoped --allow-run permissions which reject spawning subprocesses when LD_PRELOAD is set in the environment. Fixes #1001 Co-Authored-By: Claude Opus 4.6 (1M context) --- containers/agent/one-shot-token/src/lib.rs | 47 ++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/containers/agent/one-shot-token/src/lib.rs b/containers/agent/one-shot-token/src/lib.rs index 395b43a2..9940cc97 100644 --- a/containers/agent/one-shot-token/src/lib.rs +++ b/containers/agent/one-shot-token/src/lib.rs @@ -198,6 +198,7 @@ fn init_token_list(state: &mut TokenState) { ); } state.initialized = true; + clear_ld_preload(state.debug_enabled); return; } @@ -225,6 +226,26 @@ fn init_token_list(state: &mut TokenState) { ); } state.initialized = true; + clear_ld_preload(state.debug_enabled); +} + +/// Unset LD_PRELOAD and LD_LIBRARY_PATH from the environment so child processes +/// don't inherit them. The library is already loaded in this process's address space, +/// so getenv interception continues to work. This fixes Deno 2.x's scoped --allow-run +/// permissions which reject spawning subprocesses when LD_PRELOAD is set. +/// See: https://github.com/github/gh-aw-firewall/issues/1001 +fn clear_ld_preload(debug_enabled: bool) { + // SAFETY: unsetenv is a standard POSIX function. We pass valid C strings. + unsafe { + let ld_preload = CString::new("LD_PRELOAD").unwrap(); + libc::unsetenv(ld_preload.as_ptr()); + let ld_library_path = CString::new("LD_LIBRARY_PATH").unwrap(); + libc::unsetenv(ld_library_path.as_ptr()); + } + + if debug_enabled { + eprintln!("[one-shot-token] Cleared LD_PRELOAD and LD_LIBRARY_PATH from environment"); + } } /// Check if a token name is sensitive @@ -433,4 +454,30 @@ mod tests { assert!(!state.initialized); } + #[test] + fn test_clear_ld_preload_removes_env_vars() { + // Set LD_PRELOAD and LD_LIBRARY_PATH in the environment + unsafe { + let key = CString::new("LD_PRELOAD").unwrap(); + let val = CString::new("/tmp/test.so").unwrap(); + libc::setenv(key.as_ptr(), val.as_ptr(), 1); + + let key2 = CString::new("LD_LIBRARY_PATH").unwrap(); + let val2 = CString::new("/tmp/lib").unwrap(); + libc::setenv(key2.as_ptr(), val2.as_ptr(), 1); + } + + // Call clear_ld_preload + clear_ld_preload(false); + + // Verify they were unset + unsafe { + let key = CString::new("LD_PRELOAD").unwrap(); + assert!(call_real_getenv(key.as_ptr()).is_null()); + + let key2 = CString::new("LD_LIBRARY_PATH").unwrap(); + assert!(call_real_getenv(key2.as_ptr()).is_null()); + } + } + }