Title: rustpython-host_env for better sandbox support · Issue #7575 · RustPython/RustPython · GitHub
Open Graph Title: rustpython-host_env for better sandbox support · Issue #7575 · RustPython/RustPython
X Title: rustpython-host_env for better sandbox support · Issue #7575 · RustPython/RustPython
Description: Summary If we fully isolate host_env support code to a crate, will that be easier to maintain sandbox feature with lints? I guess so. Worth to do? not sure. Plan: Create rustpython-host-env crate Context RustPython controls host OS acces...
Open Graph Description: Summary If we fully isolate host_env support code to a crate, will that be easier to maintain sandbox feature with lints? I guess so. Worth to do? not sure. Plan: Create rustpython-host-env crate C...
X Description: Summary If we fully isolate host_env support code to a crate, will that be easier to maintain sandbox feature with lints? I guess so. Worth to do? not sure. Plan: Create rustpython-host-env crate C...
Opengraph URL: https://github.com/RustPython/RustPython/issues/7575
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"rustpython-host_env for better sandbox support","articleBody":"## Summary\n\nIf we fully isolate host_env support code to a crate, will that be easier to maintain sandbox feature with lints? I guess so. Worth to do? not sure.\n\n\u003c!-- Short description of the issue. --\u003e\n# Plan: Create `rustpython-host-env` crate\n\n## Context\n\nRustPython controls host OS access via the `host_env` feature flag, enforced by `#[cfg(feature = \"host_env\")]` scattered across hundreds of locations. If a `cfg` is forgotten, host code leaks into sandbox builds silently.\n\nBy isolating host OS API wrappers into a dedicated crate, **the crate boundary itself becomes the sandbox guarantee**. Key constraint: this crate has **zero Python runtime dependency**. All Python-level bindings must be added by the consumer (vm/stdlib).\n\n## Current State\n\n### Already Python-free host abstractions in `crates/common/src/`:\n- `os.rs` — errno handling, exit_code, winerror_to_errno, OsStr ffi conversions\n- `crt_fd.rs` — CRT file descriptor abstraction (Owned/Borrowed types, open/read/write/close)\n- `fileutils.rs` — fstat, fopen, Windows StatStruct\n- `windows.rs` — ToWideString, FromWideString traits\n- `macros.rs` — `suppress_iph!` macro (MSVC invalid parameter handler suppression)\n\n### Pure host functions embedded in vm/stdlib modules:\n\nThese files mix Python bindings with pure host API calls. The host parts should be extracted:\n\n**`vm/src/stdlib/posix.rs`** (2908 lines):\n- `set_inheritable(fd, inheritable)` — pure nix fcntl wrapper\n- `getgroups_impl()` — pure libc/nix wrapper\n- `get_right_permission()`, `get_permissions()` — pure permission logic\n- 400+ libc constant re-exports (`#[pyattr] use libc::*`)\n\n**`vm/src/stdlib/nt.rs`** (2301 lines):\n- `win32_hchmod()`, `win32_lchmod()`, `fchmod_impl()` — pure Windows API calls (currently return PyResult, should return io::Result)\n- Spawn mode constants, `O_*` flags\n\n**`vm/src/stdlib/_signal.rs`** (729 lines):\n- `timeval_to_double()`, `double_to_timeval()`, `itimerval_to_tuple()` — pure math\n- 30+ signal/timer constants\n\n**`vm/src/stdlib/time.rs`** (1616 lines):\n- `asctime_from_tm()` — pure string formatting\n- `get_tz_info()` — pure Windows API\n- Time unit constants (`SEC_TO_MS`, `MS_TO_US`, etc.)\n- `duration_since_system_now()` — host clock access (currently takes vm, can return io::Result instead)\n\n**`vm/src/stdlib/msvcrt.rs`**:\n- `getch()`, `getwch()`, `getche()`, `getwche()`, `kbhit()`, `setmode_binary()` — all pure host\n- Locking constants (`LK_UNLCK`, `LK_LOCK`, etc.)\n\n**`vm/src/stdlib/_winapi.rs`** (2180 lines):\n- `GetACP()`, `GetCurrentProcess()`, `GetLastError()`, `GetVersion()` — pure host\n- 100+ Windows API constants\n\n**`vm/src/stdlib/os.rs`** (2395 lines):\n- `fs_metadata()` — pure `std::fs` wrapper\n- libc flag constants (`O_APPEND`, `O_CREAT`, etc.)\n\n## Dependency Graph (After)\n\n```\nrustpython-host-env (NEW — zero Python dep, independent of common)\n├── Dependencies: libc, nix (unix), windows-sys (win), widestring (win), rustpython-wtf8\n├── From common: os, crt_fd, fileutils, windows, macros\n└── Extracted from vm/stdlib: posix, nt, signal, time, msvcrt, winapi, socket, mmap, ...\n\nrustpython-common (NO host_env dependency — pure algorithmic code only)\n└── cformat, float_ops, hash, int, str, encodings, etc.\n\nrustpython-vm\n├── rustpython-common\n├── rustpython-host-env (optional, feature = \"host_env\")\n├── libc (retained for type definitions \u0026 constants used inline in #[pyattr])\n└── Python bindings call host_env for actual OS operations\n\nrustpython-stdlib\n├── rustpython-vm, rustpython-common\n├── rustpython-host-env (optional, feature = \"host_env\")\n└── libc, nix, socket2, memmap2 (retained for now — future migration target)\n```\n\n`common` and `host_env` are fully independent — no dependency in either direction.\n\n## Phase 1: Create the crate and move modules from common\n\nCreate `crates/host_env/`, **move** host modules from common, and update common to re-export.\n\n### New files:\n\n**`crates/host_env/Cargo.toml`:**\n```toml\n[package]\nname = \"rustpython-host-env\"\ndescription = \"Host OS API abstractions for RustPython (zero Python dependency)\"\nversion.workspace = true\nedition.workspace = true\n\n[dependencies]\nrustpython-wtf8 = { workspace = true }\nlibc = { workspace = true }\nnum-traits = { workspace = true }\ncfg-if = { workspace = true }\n\n[target.'cfg(unix)'.dependencies]\nnix = { workspace = true }\n\n[target.'cfg(windows)'.dependencies]\nwidestring = { workspace = true }\nwindows-sys = { workspace = true, features = [\n \"Win32_Foundation\",\n \"Win32_Globalization\",\n \"Win32_Networking_WinSock\",\n \"Win32_Storage_FileSystem\",\n \"Win32_System_Console\",\n \"Win32_System_Ioctl\",\n \"Win32_System_LibraryLoader\",\n \"Win32_System_SystemServices\",\n \"Win32_System_Time\",\n] }\n```\n\n**`crates/host_env/src/lib.rs`:**\n```rust\n#[macro_use]\nmod macros;\npub use macros::*;\n\npub mod os;\n\n#[cfg(any(unix, windows, target_os = \"wasi\"))]\npub mod crt_fd;\n\n#[cfg(any(not(target_arch = \"wasm32\"), target_os = \"wasi\"))]\npub mod fileutils;\n\n#[cfg(windows)]\npub mod windows;\n\n// New modules — extracted from vm/stdlib (Phase 2)\n#[cfg(unix)]\npub mod posix;\n#[cfg(windows)]\npub mod nt;\npub mod signal;\npub mod time;\n#[cfg(windows)]\npub mod msvcrt;\n#[cfg(windows)]\npub mod winapi;\n```\n\n**Modules moved from common**: `os.rs`, `crt_fd.rs`, `fileutils.rs`, `windows.rs`, `macros.rs`\n\n### Modified files:\n\n**`Cargo.toml` (workspace root):**\n- Add `\"crates/host_env\"` to `[workspace.members]`\n- Add `rustpython-host-env = { path = \"crates/host_env\" }` to `[workspace.dependencies]`\n\n**`crates/common/Cargo.toml`:**\n- Remove `nix`, `windows-sys`, `widestring` from direct dependencies\n- Keep `libc` for type definitions (`wchar_t` in `str.rs`)\n- No `host_env` feature or dependency — common stays purely algorithmic\n\n**`crates/common/src/lib.rs`:**\n- Remove `pub mod os`, `pub mod crt_fd`, `pub mod fileutils`, `pub mod windows` declarations\n- Remove `#[macro_use] mod macros` and `suppress_iph!` macro (moved to host_env)\n- Delete the source files: `os.rs`, `crt_fd.rs`, `fileutils.rs`, `windows.rs`, `macros.rs`\n\n**`crates/vm/Cargo.toml`:**\n```toml\n[features]\nhost_env = [\"rustpython-host-env\"]\n\n[dependencies]\nrustpython-host-env = { workspace = true, optional = true }\n```\n\n**`crates/stdlib/Cargo.toml`:**\n```toml\n[features]\nhost_env = [\"rustpython-vm/host_env\", \"rustpython-host-env\"]\n\n[dependencies]\nrustpython-host-env = { workspace = true, optional = true }\n```\n\n### Verification:\n```bash\ncargo check -p rustpython-host-env\ncargo test\ncargo check -p rustpython-vm --no-default-features --features compiler,gc # sandbox build\n```\n\n## Phase 2: Extract host functions from vm/stdlib modules\n\nExtract pure host API functions and constants from vm's stdlib modules into new modules within `host_env`.\n\n### New modules in `crates/host_env/src/`:\n\n**`posix.rs`** — extracted from `vm/src/stdlib/posix.rs`:\n```rust\nuse std::os::fd::BorrowedFd;\n\npub fn set_inheritable(fd: BorrowedFd\u003c'_\u003e, inheritable: bool) -\u003e nix::Result\u003c()\u003e {\n use nix::fcntl;\n let flags = fcntl::FdFlag::from_bits_truncate(fcntl::fcntl(fd, fcntl::FcntlArg::F_GETFD)?);\n let mut new_flags = flags;\n new_flags.set(fcntl::FdFlag::FD_CLOEXEC, !inheritable);\n if flags != new_flags {\n fcntl::fcntl(fd, fcntl::FcntlArg::F_SETFD(new_flags))?;\n }\n Ok(())\n}\n\npub fn getgroups() -\u003e nix::Result\u003cVec\u003cnix::unistd::Gid\u003e\u003e { ... }\npub fn get_right_permission(mode: u32, file_owner: Uid, file_group: Gid) -\u003e nix::Result\u003cPermissions\u003e { ... }\n```\n\n**`nt.rs`** — extracted from `vm/src/stdlib/nt.rs`:\n```rust\npub fn win32_hchmod(handle: HANDLE, mode: u32) -\u003e io::Result\u003c()\u003e { ... }\npub fn win32_lchmod(path: \u0026OsStr, mode: u32) -\u003e io::Result\u003c()\u003e { ... }\n```\n\n**`signal.rs`** — extracted from `vm/src/stdlib/_signal.rs`:\n```rust\npub fn timeval_to_double(tv: \u0026libc::timeval) -\u003e f64 { ... }\npub fn double_to_timeval(val: f64) -\u003e libc::timeval { ... }\npub fn itimerval_to_tuple(it: \u0026libc::itimerval) -\u003e (f64, f64) { ... }\n```\n\n**`time.rs`** — extracted from `vm/src/stdlib/time.rs`:\n```rust\npub const SEC_TO_MS: i64 = 1000;\npub const MS_TO_US: i64 = 1000;\n// ...\n\npub fn asctime_from_tm(tm: \u0026libc::tm) -\u003e String { ... }\npub fn duration_since_system_now() -\u003e io::Result\u003cDuration\u003e { ... }\n\n#[cfg(windows)]\npub fn get_tz_info() -\u003e TIME_ZONE_INFORMATION { ... }\n```\n\n**`msvcrt.rs`** — extracted from `vm/src/stdlib/msvcrt.rs`:\n```rust\npub fn getch() -\u003e Vec\u003cu8\u003e { ... }\npub fn getwch() -\u003e String { ... }\npub fn kbhit() -\u003e i32 { ... }\npub fn setmode_binary(fd: crt_fd::Borrowed\u003c'_\u003e) { ... }\n\npub const LK_UNLCK: i32 = 0;\npub const LK_LOCK: i32 = 1;\n// ...\n```\n\n**`winapi.rs`** — extracted from `vm/src/stdlib/_winapi.rs`:\n```rust\npub fn get_acp() -\u003e u32 { ... }\npub fn get_current_process() -\u003e HANDLE { ... }\npub fn get_last_error() -\u003e u32 { ... }\npub fn get_version() -\u003e u32 { ... }\n// + Windows API constants\n```\n\n### Modified vm/stdlib files:\n\nEach file is updated to call `rustpython_host_env::` instead of inlining the host calls:\n\n```rust\n// BEFORE (vm/src/stdlib/posix.rs)\npub fn set_inheritable(fd: BorrowedFd\u003c'_\u003e, inheritable: bool) -\u003e nix::Result\u003c()\u003e {\n use nix::fcntl;\n // ... 10 lines of nix API calls\n}\n\n// AFTER (vm/src/stdlib/posix.rs)\npub use rustpython_host_env::posix::set_inheritable;\n```\n\n## Phase 3: vm/stdlib import migration\n\nAll `common::os`, `common::crt_fd`, `common::fileutils`, `common::windows` imports must be updated to `rustpython_host_env::`.\n\n### Import migration targets (vm) — ~20 files:\n\n| File | Current | New |\n|------|---------|-----|\n| `ospath.rs` | `rustpython_common::crt_fd` | `rustpython_host_env::crt_fd` |\n| `stdlib/os.rs` | `common::crt_fd`, `common::os::*` | `rustpython_host_env::` |\n| `stdlib/nt.rs` | `common::windows::*`, `common::crt_fd::*` | `rustpython_host_env::` |\n| `stdlib/_io.rs` | `common::crt_fd::Offset`, `common::fileutils::fstat` | `rustpython_host_env::` |\n| `stdlib/_signal.rs` | `common::crt_fd::*`, `common::fileutils::fstat` | `rustpython_host_env::` |\n| `stdlib/posix.rs` | `common::os::*`, `common::crt_fd::Offset` | `rustpython_host_env::` |\n| `stdlib/_ctypes/function.rs` | `rustpython_common::os::get_errno` | `rustpython_host_env::os::` |\n| `stdlib/_codecs.rs` | `common::windows::ToWideString` | `rustpython_host_env::windows::` |\n| `stdlib/sys.rs`, `winreg.rs`, `winsound.rs` | `common::windows::ToWideString` | `rustpython_host_env::windows::` |\n| `windows.rs` | `rustpython_common::windows::ToWideString` | `rustpython_host_env::windows::` |\n| `exceptions.rs` | `common::os::ErrorExt`, `common::os::winerror_to_errno` | `rustpython_host_env::os::` |\n\n### Import migration targets (stdlib) — ~7 files:\n\n| File | Current | New |\n|------|---------|-----|\n| `socket.rs` | `common::os::ErrorExt`, `common::os::errno_io_error` | `rustpython_host_env::os::` |\n| `mmap.rs` | `rustpython_common::crt_fd` | `rustpython_host_env::crt_fd` |\n| `faulthandler.rs` | `rustpython_common::os::{get_errno, set_errno}` | `rustpython_host_env::os::` |\n| `posixshmem.rs` | `common::os::errno_io_error` | `rustpython_host_env::os::` |\n| `termios.rs` | `common::os::ErrorExt` | `rustpython_host_env::os::` |\n| `overlapped.rs` | `crate::vm::common::os::winerror_to_errno` | `rustpython_host_env::os::` |\n| `openssl.rs` | `rustpython_common::fileutils::fopen` | `rustpython_host_env::fileutils::` |\n\n### External consumers:\n\n| File | Current | New |\n|------|---------|-----|\n| `src/lib.rs` | `rustpython_vm::common::os::exit_code` | `rustpython_host_env::os::exit_code` |\n| `examples/*.rs` | `vm::common::os::exit_code` | Keep via re-export |\n\n## Phase 4 (Future): Extract host functions from stdlib modules\n\nSame pattern as Phase 2, but for `crates/stdlib/src/` modules. These modules heavily use `libc`, `nix`, `socket2`, `memmap2` directly. Extract the pure host layer into `host_env`.\n\n**Target modules and what goes into host_env:**\n\n| stdlib module | host_env module | What to extract |\n|---------------|----------------|-----------------|\n| `socket.rs` (3498 lines) | `host_env::socket` | Socket creation, bind, connect, address conversion, cmsg helpers, poll wrappers. Re-export `socket2` types. |\n| `mmap.rs` (1625 lines) | `host_env::mmap` | mmap/munmap wrappers, madvise, msync. Re-export `memmap2` types. |\n| `select.rs` (745 lines) | `host_env::select` | select/poll/epoll/kqueue wrappers via libc/nix. |\n| `posixsubprocess.rs` (537 lines) | `host_env::subprocess` | fork_exec, pipe, dup2, close-on-exec logic. |\n| `multiprocessing.rs` (1152 lines) | `host_env::multiprocessing` | Semaphore operations (sem_open/wait/post/unlink via libc). |\n| `fcntl.rs` (220 lines) | `host_env::fcntl` | fcntl, ioctl, flock wrappers. |\n| `faulthandler.rs` (1333 lines) | `host_env::faulthandler` | Signal handler registration, stack dump via libc write. |\n| `locale.rs` (332 lines) | `host_env::locale` | strcoll, strxfrm, setlocale wrappers. |\n| `resource.rs` (194 lines) | `host_env::resource` | getrusage, getrlimit, setrlimit wrappers. |\n| `grp.rs` (103 lines) | `host_env::grp` | getgrent/setgrent/endgrent, Group lookup via nix. |\n| `syslog.rs` (148 lines) | `host_env::syslog` | openlog, syslog, closelog, setlogmask wrappers. |\n| `posixshmem.rs` (52 lines) | `host_env::shm` | shm_open, shm_unlink wrappers. |\n| `termios.rs` (280 lines) | `host_env::termios` | Terminal attribute get/set via termios crate. |\n\nAfter this, `nix`, `socket2`, `memmap2`, `rustix` are removed from stdlib's direct dependencies. Only `host_env` provides them.\n\n## Phase 5: Lint enforcement\n\nThree layers of enforcement, from strongest to lightest:\n\n### Layer 1: Crate boundary (compile-time, absolute)\n\nThe strongest guarantee. If a crate doesn't list `rustpython-host-env` in its `[dependencies]`, it physically cannot call any host_env function. This is already enforced by Rust's module system.\n\n**Pure crates (no host_env dependency allowed):**\n- `rustpython-common`\n- `rustpython-compiler`, `rustpython-compiler-core`, `rustpython-compiler-source`\n- `rustpython-codegen`\n- `rustpython-literal`\n- `rustpython-sre_engine`\n- `rustpython-wtf8`\n- `rustpython-derive`, `rustpython-derive-impl`\n\nCI check:\n```bash\n# Verify pure crates don't depend on host_env\nfor crate in common compiler compiler-core compiler-source codegen literal sre_engine wtf8 derive derive-impl; do\n if rg 'rustpython-host-env' \"crates/$crate/Cargo.toml\"; then\n echo \"ERROR: $crate should not depend on host_env\"\n exit 1\n fi\ndone\n```\n\n### Layer 2: clippy disallowed_methods (compile-time, configurable)\n\nBlock direct host API usage in vm/stdlib. Force all host access through `host_env`.\n\n**Workspace-level `clippy.toml`** (project root):\n```toml\ndisallowed-methods = [\n # Filesystem\n { path = \"std::fs::read\", reason = \"use rustpython_host_env for host filesystem access\" },\n { path = \"std::fs::write\", reason = \"use rustpython_host_env\" },\n { path = \"std::fs::read_to_string\", reason = \"use rustpython_host_env\" },\n { path = \"std::fs::read_dir\", reason = \"use rustpython_host_env\" },\n { path = \"std::fs::create_dir\", reason = \"use rustpython_host_env\" },\n { path = \"std::fs::create_dir_all\", reason = \"use rustpython_host_env\" },\n { path = \"std::fs::remove_file\", reason = \"use rustpython_host_env\" },\n { path = \"std::fs::remove_dir\", reason = \"use rustpython_host_env\" },\n { path = \"std::fs::metadata\", reason = \"use rustpython_host_env\" },\n { path = \"std::fs::symlink_metadata\", reason = \"use rustpython_host_env\" },\n { path = \"std::fs::canonicalize\", reason = \"use rustpython_host_env\" },\n { path = \"std::fs::File::open\", reason = \"use rustpython_host_env\" },\n { path = \"std::fs::File::create\", reason = \"use rustpython_host_env\" },\n { path = \"std::fs::OpenOptions::open\", reason = \"use rustpython_host_env\" },\n\n # Environment\n { path = \"std::env::var\", reason = \"use rustpython_host_env\" },\n { path = \"std::env::var_os\", reason = \"use rustpython_host_env\" },\n { path = \"std::env::set_var\", reason = \"use rustpython_host_env\" },\n { path = \"std::env::remove_var\", reason = \"use rustpython_host_env\" },\n { path = \"std::env::vars\", reason = \"use rustpython_host_env\" },\n { path = \"std::env::vars_os\", reason = \"use rustpython_host_env\" },\n { path = \"std::env::current_dir\", reason = \"use rustpython_host_env\" },\n { path = \"std::env::set_current_dir\", reason = \"use rustpython_host_env\" },\n { path = \"std::env::temp_dir\", reason = \"use rustpython_host_env\" },\n\n # Process\n { path = \"std::process::Command::new\", reason = \"use rustpython_host_env\" },\n { path = \"std::process::exit\", reason = \"use rustpython_host_env\" },\n { path = \"std::process::abort\", reason = \"use rustpython_host_env\" },\n { path = \"std::process::id\", reason = \"use rustpython_host_env\" },\n\n # Network\n { path = \"std::net::TcpStream::connect\", reason = \"use rustpython_host_env\" },\n { path = \"std::net::TcpListener::bind\", reason = \"use rustpython_host_env\" },\n { path = \"std::net::UdpSocket::bind\", reason = \"use rustpython_host_env\" },\n]\n```\n\n**`crates/host_env/clippy.toml`** (overrides — host_env is allowed to use everything):\n```toml\ndisallowed-methods = []\n```\n\nClippy resolves `clippy.toml` by walking up from the crate directory, so `host_env`'s local config takes precedence over the workspace root.\n\n**Workspace `Cargo.toml`:**\n```toml\n[workspace.lints.clippy]\ndisallowed_methods = \"deny\"\n```\n\n### Layer 3: Sandbox build verification (CI)\n\nBuild without `host_env` feature to catch any code that accidentally compiles without the feature gate:\n\n```bash\ncargo check -p rustpython-vm --no-default-features --features compiler,gc\ncargo check -p rustpython-stdlib --no-default-features --features compiler\n```\n\n### Layer 4: Whitelist-based module audit (CI script)\n\nMaintain a whitelist of modules in vm/stdlib that are known to NOT use host_env. Any change that adds a `rustpython_host_env` import to a whitelisted module triggers CI failure.\n\n```bash\n# .ci/host_env_whitelist.txt — modules that must stay host-free\n# vm modules:\ncrates/vm/src/stdlib/_abc.rs\ncrates/vm/src/stdlib/_collections.rs\ncrates/vm/src/stdlib/_functools.rs\ncrates/vm/src/stdlib/_operator.rs\ncrates/vm/src/stdlib/_sre.rs\ncrates/vm/src/stdlib/_stat.rs\ncrates/vm/src/stdlib/_string.rs\ncrates/vm/src/stdlib/errno.rs\ncrates/vm/src/stdlib/gc.rs\ncrates/vm/src/stdlib/itertools.rs\ncrates/vm/src/stdlib/marshal.rs\n\n# Check:\nwhile IFS= read -r file; do\n if rg 'rustpython_host_env' \"$file\" 2\u003e/dev/null; then\n echo \"ERROR: $file is whitelisted as host-free but imports host_env\"\n exit 1\n fi\ndone \u003c .ci/host_env_whitelist.txt\n```\n\nThe inverse is also useful — list all files that ARE allowed to use host_env, and reject any new file that uses it without being on the list. This catches accidental host API usage in new modules.\n\n### Layer 5: `#![no_std]` for pure crates\n\nAfter removing host modules from `common`, it could potentially become `#![no_std]` unconditionally (it already has `#![cfg_attr(not(feature = \"std\"), no_std)]`). This is the strongest possible guarantee — no `std::fs`, `std::env`, `std::net`, `std::process` available at all.\n\nCandidate crates for unconditional `#![no_std]`:\n- `rustpython-literal`\n- `rustpython-wtf8`\n- `rustpython-compiler-source`\n\n### Summary of enforcement layers\n\n| Layer | What it catches | Strength | Cost |\n|-------|----------------|----------|------|\n| Crate boundary | Missing host_env dependency | Absolute — compile error | Zero — automatic |\n| clippy disallowed_methods | Direct std::fs/env/net usage | Strong — clippy deny | Low — clippy.toml config |\n| Sandbox build | Missing `#[cfg(feature = \"host_env\")]` | Strong — compile error | Low — CI job |\n| Module whitelist | Unintended host_env usage in pure modules | Medium — CI script | Low — maintain whitelist |\n| `#![no_std]` | Any std usage in pure crates | Absolute — compile error | Medium — may need refactoring |\n\n## Risk Assessment\n\n| Risk | Level | Mitigation |\n|------|-------|------------|\n| Target modules have Python type dependencies | **Low** | Verified: only `libc`, `nix`, `windows-sys`, `rustpython-wtf8` |\n| Internal cross-references break on move | **Low** | `crt_fd`, `os`, `fileutils`, `windows` all move together; `crate::` paths stay valid |\n| `suppress_iph!` macro `$crate` resolution | **Medium** | `$crate` automatically resolves to new crate; `__macro_private` moves alongside |\n| Breaking external consumers | **Medium** | Clean break — consumers must update `common::os` to `host_env::os`. No re-export shim. |\n| Scope of Phase 2 extraction | **Medium** | Start with clearly pure functions; mixed functions can be migrated incrementally |\n","author":{"url":"https://github.com/youknowone","@type":"Person","name":"youknowone"},"datePublished":"2026-04-09T18:07:50.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":3},"url":"https://github.com/7575/RustPython/issues/7575"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:51db6c4f-844e-4636-86b5-7264d25b5ba1 |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | DB18:1C181D:4332C2:5FAD50:6A623AA1 |
| html-safe-nonce | d4285c4c0d61c67e7f3cc4e2b5f27325e79fbc85535ba70487bf501bb162d629 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJEQjE4OjFDMTgxRDo0MzMyQzI6NUZBRDUwOjZBNjIzQUExIiwidmlzaXRvcl9pZCI6IjMyOTA1MDY1NTE4NTQ1MTI4MDIiLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ== |
| visitor-hmac | 32868da23fe0d1962d75bae45d02518a4b5be43cc07ef999f626bc2c139a59e7 |
| hovercard-subject-tag | issue:4234085825 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/RustPython/RustPython/7575/issue_layout |
| twitter:image | https://opengraph.githubassets.com/dfaf00ac79e92675c6035060cb7d0b25c42032a1a5cc1d46b93158134173a155/RustPython/RustPython/issues/7575 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/dfaf00ac79e92675c6035060cb7d0b25c42032a1a5cc1d46b93158134173a155/RustPython/RustPython/issues/7575 |
| og:image:alt | Summary If we fully isolate host_env support code to a crate, will that be easier to maintain sandbox feature with lints? I guess so. Worth to do? not sure. Plan: Create rustpython-host-env crate C... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | youknowone |
| hostname | github.com |
| expected-hostname | github.com |
| None | 81a21eb42fd0e80f3094d5be0f523bc284f5792af0333a266ecada0c39501f0f |
| turbo-cache-control | no-preview |
| go-import | github.com/RustPython/RustPython git https://github.com/RustPython/RustPython.git |
| octolytics-dimension-user_id | 39710557 |
| octolytics-dimension-user_login | RustPython |
| octolytics-dimension-repository_id | 135201145 |
| octolytics-dimension-repository_nwo | RustPython/RustPython |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 135201145 |
| octolytics-dimension-repository_network_root_nwo | RustPython/RustPython |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 76f36d3a9c09a237d489d4eeeaf6db0b604da73f |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width