fix(shell): pass .NET/NuGet + Windows app-data env to exec_shell (#1857)
`exec_shell` runs with `env_clear()` plus a strict allowlist. On Windows there is no sandbox, so commands run directly — but the allowlist dropped `APPDATA`, `LOCALAPPDATA`, `ProgramData`, `ProgramFiles*`, and the `DOTNET_*` / `NUGET_*` variables that `dotnet restore` and NuGet rely on to locate their package cache, HTTP cache, and config. Restore therefore failed through the tool while working in the user's own shell, where the full environment is present. Add the .NET/NuGet and Windows app-data path variables to the shell allowlist (`DOTNET_*` via prefix, like `LC_*`). NuGet credential vars (`NuGetPackageSourceCredentials_*`) still fall outside the allowlist and are not exported. Also benefits npm/pip on Windows, which use the same paths. https://claude.ai/code/session_01MQrnh6wHfrEYN5BBdMarC1
This commit is contained in:
@@ -169,6 +169,23 @@ fn is_allowed_parent_env_key(key: &OsStr) -> bool {
|
||||
| "EXTENSIONSDKDIR"
|
||||
| "DEVENVDIR"
|
||||
| "VISUALSTUDIOVERSION"
|
||||
// Windows app-data + .NET/NuGet paths. `dotnet restore` (and npm,
|
||||
// pip, etc.) resolve their package caches, HTTP cache, and config
|
||||
// under %APPDATA% / %LOCALAPPDATA% / %ProgramData% / %ProgramFiles%.
|
||||
// The sanitized child env dropped these, so restore failed through
|
||||
// `exec_shell` even though it worked in the user's own shell, where
|
||||
// the full environment is present (#1857). `DOTNET_*` (below) covers
|
||||
// DOTNET_ROOT and the CLI flags.
|
||||
| "APPDATA"
|
||||
| "LOCALAPPDATA"
|
||||
| "PROGRAMDATA"
|
||||
| "ALLUSERSPROFILE"
|
||||
| "PROGRAMFILES"
|
||||
| "PROGRAMFILES(X86)"
|
||||
| "PROGRAMW6432"
|
||||
| "PROCESSOR_ARCHITECTURE"
|
||||
| "NUGET_PACKAGES"
|
||||
| "NUGET_HTTP_CACHE_PATH"
|
||||
// Standard proxy variables are needed by shell tasks in
|
||||
// corporate and WSL environments where direct internet egress is
|
||||
// blocked. They intentionally exclude token/API-key-shaped vars.
|
||||
@@ -178,6 +195,10 @@ fn is_allowed_parent_env_key(key: &OsStr) -> bool {
|
||||
| "ALL_PROXY"
|
||||
| "FTP_PROXY"
|
||||
) || normalized.starts_with("LC_")
|
||||
// .NET CLI / SDK configuration (DOTNET_ROOT, DOTNET_CLI_*,
|
||||
// DOTNET_NOLOGO, DOTNET_CLI_TELEMETRY_OPTOUT, …). Paths and flags
|
||||
// only — no secret-shaped values (#1857).
|
||||
|| normalized.starts_with("DOTNET_")
|
||||
}
|
||||
|
||||
/// Allowlist for MCP stdio launches. Strict superset of
|
||||
@@ -354,6 +375,38 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn child_env_allowlist_includes_dotnet_and_windows_appdata_keys() {
|
||||
// #1857: dotnet restore / NuGet need these to find caches and config.
|
||||
for key in [
|
||||
"APPDATA",
|
||||
"LOCALAPPDATA",
|
||||
"PROGRAMDATA",
|
||||
"ALLUSERSPROFILE",
|
||||
"PROGRAMFILES",
|
||||
"PROGRAMFILES(X86)",
|
||||
"PROGRAMW6432",
|
||||
"PROCESSOR_ARCHITECTURE",
|
||||
"NUGET_PACKAGES",
|
||||
"DOTNET_ROOT",
|
||||
"DOTNET_CLI_TELEMETRY_OPTOUT",
|
||||
"DOTNET_NOLOGO",
|
||||
// Case-insensitive: the real Windows var is `ProgramFiles`.
|
||||
"ProgramFiles",
|
||||
"dotnet_root",
|
||||
] {
|
||||
assert!(
|
||||
is_allowed_parent_env_key(OsStr::new(key)),
|
||||
"child env allowlist should include {key}"
|
||||
);
|
||||
}
|
||||
// Guard: NuGet credential env vars must still be dropped.
|
||||
assert!(
|
||||
!is_allowed_parent_env_key(OsStr::new("NuGetPackageSourceCredentials_feed")),
|
||||
"NuGet credential vars must not be exported to child processes"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mcp_env_allowlist_includes_python_bootstrap_keys() {
|
||||
for key in [
|
||||
|
||||
Reference in New Issue
Block a user