Skip to content

Commit ea8f405

Browse files
authored
Revert "[WSLC] Add cooperative HTTP/HTTPS proxy support" (#697)
Revert "[WSLC] Add cooperative HTTP/HTTPS proxy support (#652)" This reverts commit 3b910d9.
1 parent 500ed40 commit ea8f405

11 files changed

Lines changed: 37 additions & 729 deletions

File tree

docs/schema.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,6 @@ production configs and the dev schema when working on experimental features:
5555
"proxy": { "localhost": 8080 } // Loopback proxy port (processcontainer; bubblewrap; seatbelt)
5656
// (use { "builtinTestServer": true } for the bundled
5757
// testing-only proxy; requires --allow-testing-features)
58-
// WSLC supports the cooperative proxy too, but only via
59-
// { "url": "http://proxy.example:8080" } (own-netns:
60-
// localhost/builtinTestServer are unreachable, rejected)
6158
},
6259

6360
"processContainer": { // Process-based container-specific

docs/wsl/wsl-container-getting-started.md

Lines changed: 0 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -227,66 +227,6 @@ no separate `--setup-wslc` step is required.
227227
| `"allowOutbound": true` | Bridged networking (full access) |
228228
| `"allowOutbound": false` | No networking (isolated) |
229229

230-
### Network proxy (cooperative, unprivileged)
231-
232-
WSLC supports a **cooperative HTTP/HTTPS proxy**: setting `network.proxy`
233-
routes a container's egress through a proxy you provide. WSLC's kernel has
234-
**no in-kernel `iptables`**, so — exactly like the Bubblewrap backend — there
235-
is no netfilter drop-floor; enforcement is *cooperative*, applied by handing
236-
the workload proxy environment variables that well-behaved clients honor.
237-
238-
**How it works**
239-
240-
1. When `network.proxy` is set, the runner translates it into the
241-
`HTTP_PROXY`, `HTTPS_PROXY`, `http_proxy`, and `https_proxy` environment
242-
variables inside the container (via `WslcSetProcessSettingsEnvVariables`).
243-
Any caller-supplied values for these keys — including `NO_PROXY` /
244-
`no_proxy` — are **stripped** from the *initial* process environment first.
245-
Because WSLC merges the process environment onto the image's baked-in
246-
`ENV`, the runner also sets `NO_PROXY` / `no_proxy` to the **empty string**,
247-
so an image-baked exemption (e.g. `ENV NO_PROXY=*`) cannot silently disable
248-
the proxy. This sanitizes the process's *starting* environment only; see the
249-
cooperative-model caveat below.
250-
2. Cooperative tools (curl, wget, Python `requests`, Node `https`, etc.) honor
251-
the env vars and their traffic flows through the proxy.
252-
253-
**Only the `url` form is supported.** A WSLC container runs in its own network
254-
namespace (a separate WSL system VM), so a host- or distro-loopback proxy is
255-
**not reachable** from inside the container. The proxy must be a routable
256-
address the container can reach:
257-
258-
```json
259-
{
260-
"version": "0.6.0-alpha",
261-
"containment": "wslc",
262-
"process": { "commandLine": "curl -fsSL https://example.com && echo OK" },
263-
"network": {
264-
"defaultPolicy": "allow",
265-
"proxy": { "url": "http://proxy.example:8080" }
266-
},
267-
"experimental": { "wslc": { "image": "alpine:latest" } }
268-
}
269-
```
270-
271-
The `localhost` and `builtinTestServer` proxy forms are **rejected at
272-
config-parse time** for WSLC (they imply a host-loopback / MXC-run proxy that
273-
the container cannot reach). The proxy also requires `defaultPolicy: "allow"`
274-
and no `allowedHosts` / `blockedHosts`: the container must have outbound
275-
networking to reach the proxy, and host lists are not forwarded to it — configs
276-
that combine the proxy with a `block` default or host lists are **rejected**.
277-
278-
**Caveats**
279-
280-
- **Cooperative model, not enforcement.** Only clients that honor the proxy
281-
env vars are routed through the proxy. Tools that bypass them (raw sockets,
282-
custom HTTP clients, statically-linked binaries that ignore the env) are
283-
**not** contained. WSLC cannot provide a hard network floor because its
284-
kernel lacks `iptables`. For strict network isolation, use
285-
`"allowOutbound": false` (no networking) instead.
286-
- **Consumer-provided proxy.** MXC does not start a proxy for WSLC; you supply
287-
a reachable one via `url`. Any host filtering is the proxy's responsibility —
288-
the runner does not forward `allowedHosts` / `blockedHosts` to it.
289-
290230
### Filesystem mounts
291231

292232
Paths in `filesystem.readwritePaths` and `filesystem.readonlyPaths` are mounted
@@ -309,7 +249,6 @@ the container.
309249

310250
- [`tests/examples/wslc_hello_world.json`](../../tests/examples/wslc_hello_world.json) — Hello world with Alpine
311251
- [`tests/configs/wslc_network_isolated.json`](../../tests/configs/wslc_network_isolated.json) — Network isolation
312-
- [`tests/configs/wslc_network_proxy.json`](../../tests/configs/wslc_network_proxy.json) — Cooperative HTTP proxy (`network.proxy.url`)
313252
- [`tests/configs/wslc_custom_registry_ghcr.json`](../../tests/configs/wslc_custom_registry_ghcr.json) — Pull from GitHub Container Registry
314253
- [`tests/configs/wslc_custom_registry_quay.json`](../../tests/configs/wslc_custom_registry_quay.json) — Pull from Quay.io
315254
- [`tests/configs/wslc_tar_import_rootfs.json`](../../tests/configs/wslc_tar_import_rootfs.json) — Import rootfs tar

sdk/node/src/types.ts

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -210,24 +210,11 @@ export interface NetworkConfig {
210210
/** Hostnames or IP addresses to block (firewall mode only) */
211211
blockedHosts?: string[];
212212
/** Proxy configuration (supported on Windows ProcessContainer, Linux Bubblewrap,
213-
* macOS Seatbelt, and WSLC). On Bubblewrap/Seatbelt/WSLC it is a cooperative
214-
* env-var proxy (HTTP_PROXY/HTTPS_PROXY): well-behaved HTTP clients honor it,
215-
* raw-socket clients can bypass it. `builtinTestServer` activates a bundled,
216-
* testing-only proxy; the SDK rejects it unless `allowTestingFeatures: true` is
217-
* set in SandboxSpawnOptions (which maps to the native `--allow-testing-features`
218-
* flag).
219-
*
220-
* WSLC imposes additional parse-time constraints (a violating config is
221-
* rejected before it runs):
222-
* - Only the `{ url }` form is accepted — its containers run in their own
223-
* network namespace, so the `localhost` / `builtinTestServer` loopback
224-
* forms are unreachable and rejected.
225-
* - The `url` scheme must be `http` or `https`.
226-
* - `defaultPolicy` must be `"allow"` and both `allowedHosts` and
227-
* `blockedHosts` must be empty/unset — WSLC has no in-kernel iptables, so
228-
* it cannot enforce host lists, and the container needs outbound
229-
* networking to reach the proxy at all.
230-
* Enforcement is cooperative (no in-kernel iptables). */
213+
* and macOS Seatbelt). On Bubblewrap/Seatbelt it is a cooperative env-var proxy
214+
* (HTTP_PROXY/HTTPS_PROXY): well-behaved HTTP clients honor it, raw-socket clients
215+
* can bypass it. `builtinTestServer` activates a bundled, testing-only proxy; the
216+
* SDK rejects it unless `allowTestingFeatures: true` is set in SandboxSpawnOptions
217+
* (which maps to the native `--allow-testing-features` flag). */
231218
proxy?: { builtinTestServer: true } | { localhost: number } | { url: string };
232219
/** Automatically remove firewall rules after execution (default: true). Deprecated: use lifecycle.preservePolicy. */
233220
removeRulesOnExit?: boolean;

src/backends/bubblewrap/common/src/bwrap_command.rs

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,21 @@ use std::collections::HashSet;
1111

1212
use wxc_common::filesystem_resolve::FsIntent;
1313
use wxc_common::models::{ExecutionRequest, NetworkPolicy, ProxyAddress};
14-
use wxc_common::proxy_env::{is_managed_proxy_key, PROXY_SET_KEYS};
14+
15+
/// Env var keys that the proxy block manages. Listed here so we can strip
16+
/// any conflicting entries the caller supplied via `request.env` (callers
17+
/// must not be able to defeat the cooperative proxy by injecting their own
18+
/// proxy env vars).
19+
const PROXY_ENV_KEYS: &[&str] = &[
20+
"HTTP_PROXY",
21+
"HTTPS_PROXY",
22+
"ALL_PROXY",
23+
"http_proxy",
24+
"https_proxy",
25+
"all_proxy",
26+
"NO_PROXY",
27+
"no_proxy",
28+
];
1529

1630
/// Read-only host paths bind-mounted into every Bubblewrap sandbox as the
1731
/// deny-by-default baseline. Mirrors the seatbelt backend's
@@ -217,7 +231,7 @@ pub fn build_args_classified(
217231
if let Some((key, value)) = env_str.split_once('=') {
218232
// When the proxy is active, drop any caller-supplied proxy env
219233
// entries so they cannot override the values we set below.
220-
if proxy_address.is_some() && is_managed_proxy_key(key) {
234+
if proxy_address.is_some() && PROXY_ENV_KEYS.contains(&key) {
221235
continue;
222236
}
223237
args.extend(["--setenv".into(), key.into(), value.into()]);
@@ -240,8 +254,15 @@ pub fn build_args_classified(
240254
// destinations.
241255
if let Some(addr) = proxy_address {
242256
let url = addr.to_url();
243-
for key in PROXY_SET_KEYS {
244-
args.extend(["--setenv".into(), (*key).into(), url.clone()]);
257+
for key in [
258+
"HTTP_PROXY",
259+
"HTTPS_PROXY",
260+
"ALL_PROXY",
261+
"http_proxy",
262+
"https_proxy",
263+
"all_proxy",
264+
] {
265+
args.extend(["--setenv".into(), key.into(), url.clone()]);
245266
}
246267
}
247268

src/backends/wslc/common/src/wsl_container_runner.rs

Lines changed: 6 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -975,57 +975,17 @@ impl WSLContainerRunner {
975975
return sdk_error("WslcSetProcessSettingsCmdLine failed", hr, "");
976976
}
977977

978-
// Route egress through the cooperative proxy: WSLc has no in-kernel
979-
// iptables, so per-host policy is enforced at the proxy layer by
980-
// injecting HTTP(S)_PROXY (and scrubbing caller-supplied proxy vars).
981-
// See wxc_common::proxy_env.
982-
let effective_env: Vec<String> = if request.policy.network_proxy.is_enabled() {
983-
// url-only (also enforced at parse time). Fail fast rather than
984-
// inject an empty HTTP_PROXY= for the localhost/builtinTestServer
985-
// forms, which carry no routable URL.
986-
let proxy_url = match request
987-
.policy
988-
.network_proxy
989-
.address
990-
.as_ref()
991-
.and_then(|addr| addr.original_url.clone())
992-
{
993-
Some(url) => url,
994-
None => {
995-
return ScriptResponse::error(
996-
"WSLC: network.proxy requires the 'url' form (a routable proxy URL); \
997-
the localhost and builtinTestServer forms are not supported because a \
998-
WSLc container runs in its own network namespace.",
999-
);
1000-
}
1001-
};
1002-
let _ = writeln!(
1003-
logger,
1004-
"[WSLC] Cooperative network proxy configured: {}",
1005-
wxc_common::proxy_env::redact_proxy_url(&proxy_url)
1006-
);
1007-
wxc_common::proxy_env::apply_cooperative_proxy_env(&request.env, &proxy_url)
1008-
} else {
1009-
request.env.clone()
1010-
};
1011-
1012-
// Env buffers must outlive WslcCreateContainer: the SDK stores the
1013-
// pointers into process_settings (it does not copy), and reads them at
1014-
// container-create time. Hoisting to function scope keeps them alive —
1015-
// mirrors the cmdline/_cwd_cstr handling. Scoping them inside the `if`
1016-
// below frees them early and causes a use-after-free (0xC0000005).
1017-
let _env_cstrings: Vec<Vec<u8>>;
1018-
let _env_ptrs: Vec<PCSTR>;
1019-
if !effective_env.is_empty() {
1020-
_env_cstrings = effective_env
978+
if !request.env.is_empty() {
979+
let env_cstrings: Vec<Vec<u8>> = request
980+
.env
1021981
.iter()
1022982
.map(|e| format!("{}\0", e).into_bytes())
1023983
.collect();
1024-
_env_ptrs = _env_cstrings.iter().map(|e| e.as_ptr() as PCSTR).collect();
984+
let env_ptrs: Vec<PCSTR> = env_cstrings.iter().map(|e| e.as_ptr() as PCSTR).collect();
1025985
let hr = sdk.WslcSetProcessSettingsEnvVariables(
1026986
&mut process_settings,
1027-
_env_ptrs.as_ptr(),
1028-
_env_ptrs.len(),
987+
env_ptrs.as_ptr(),
988+
env_ptrs.len(),
1029989
);
1030990
if hr != S_OK {
1031991
return sdk_error("WslcSetProcessSettingsEnvVariables failed", hr, "");

0 commit comments

Comments
 (0)