fix(security): block /proc/*/environ, cmdline, maps from file read (#4609)

The read_file tool and terminal cat can access /proc/self/environ to
recover all process env vars including secrets stripped by the subprocess
blocklist. Output redaction partially mitigates (catches known-format
tokens) but misses custom/proprietary key formats, especially when
values are printed without their key names.

Add /proc/*/environ, /proc/*/cmdline, and /proc/*/maps to the blocked
device paths in _is_blocked_device():

- /proc/*/environ: leaks full process env (API keys, tokens)
- /proc/*/cmdline: leaks command-line args (may contain passwords)
- /proc/*/maps: leaks memory layout (ASLR bypass for exploitation)

Legitimate /proc reads (cpuinfo, meminfo, uptime, version) remain
accessible — the check only blocks per-pid pseudo-files with known
sensitive suffixes.

Complements PR #4432 (PID namespace isolation for child processes)
which prevents children from reading the parent's /proc, but does not
prevent the parent process itself from being read via file tools.

Partially addresses #4427

Changes:
  tools/file_tools.py                  | +6
  tests/tools/test_file_read_guards.py | +18 -1

Co-authored-by: dsr-restyn <dsr-restyn@users.noreply.github.com>
This commit is contained in:
Dakota Secula-Rosell 2026-05-25 08:07:31 -04:00 committed by GitHub
parent 4909dd84c1
commit b7b8bec800
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 23 additions and 1 deletions

View file

@ -78,7 +78,23 @@ class TestDevicePathBlocking(unittest.TestCase):
def test_proc_fd_other_not_blocked(self):
self.assertFalse(_is_blocked_device("/proc/self/fd/3"))
self.assertFalse(_is_blocked_device("/proc/self/maps"))
def test_proc_sensitive_pseudo_files_blocked(self):
"""environ/cmdline/maps under /proc/<pid> must be blocked (issue #4427)."""
for path in (
"/proc/self/environ",
"/proc/12345/environ",
"/proc/self/cmdline",
"/proc/99/cmdline",
"/proc/self/maps",
"/proc/1/maps",
):
self.assertTrue(_is_blocked_device(path), f"{path} should be blocked")
def test_proc_legitimate_files_not_blocked(self):
"""Top-level /proc files like cpuinfo and meminfo must remain accessible."""
for path in ("/proc/cpuinfo", "/proc/meminfo", "/proc/uptime", "/proc/version"):
self.assertFalse(_is_blocked_device(path), f"{path} should not be blocked")
def test_normal_files_not_blocked(self):
self.assertFalse(_is_blocked_device("/tmp/test.py"))

View file

@ -143,6 +143,12 @@ def _is_blocked_device(filepath: str) -> bool:
("/fd/0", "/fd/1", "/fd/2")
):
return True
# /proc/*/environ, /proc/*/cmdline, /proc/*/maps can leak secrets,
# command-line args, and memory layout from the host process (issue #4427)
if normalized.startswith("/proc/") and normalized.endswith(
("/environ", "/cmdline", "/maps")
):
return True
return False