Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 29 additions & 15 deletions docs/process-container/networking.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ bind only the exact loopback address and port supplied to MXC.
|---|---|---|---|
| Packaged proxy | Package Family Name | `default: "allow"`; `hostLoopback: "deny"` | Package identity |
| Unpackaged AppContainer | Profile | `default: "allow"`; `hostLoopback: "deny"` | AppContainer profile |
| Unpackaged non-AppContainer (development/testing compatibility) | Omit | `default: "allow"`; `hostLoopback: "allow"` | None |
| Unpackaged non-AppContainer (development/testing compatibility) | Omit | `default: "allow"`; `hostLoopback: "allow"` or `"deny"` | None |

All deployments retain the base Model 2 client policy. `allowedProxyPeer` adds proxy identity binding on top of the
common WFP endpoint enforcement. The proxy endpoint is the loopback address and port in
Expand All @@ -102,20 +102,25 @@ client policy and package identity are the same; the enforcement table below sep

`ingress.hostLoopback` is bidirectional. `allowedProxyPeer` authorizes a package family or AppContainer profile without
opening general host-loopback access, so identity-scoped paths keep `hostLoopback: "deny"`. Only an unpackaged
non-AppContainer proxy lacks an accepted peer identity and requires `hostLoopback: "allow"`. This is a weaker
development/testing compatibility deployment, not the shared policy's strict host-loopback-closure guarantee.
The shared setting requests both host-loopback directions, while WFP restricts client-container egress to the
configured proxy endpoint.
non-AppContainer proxy lacks an accepted peer identity. It may keep either host-loopback setting. `"allow"` requests
bidirectional access and retains the PSEC 1.1 ingress prerequisite; `"deny"` removes that prerequisite.
Neither setting adds proxy identity binding, so this remains a development/testing compatibility deployment rather
than the shared policy's strict host-loopback-closure guarantee.
For direct-egress policies, MXC passes `ingress.default` and `ingress.hostLoopback` through the PSEC 1.1 ingress table when
`IsProcessSecurityEnvironmentVersionSupported` reports contract 1.1 or newer and
`QueryProcessSecurityEnvironmentSupport` advertises ingress support. Proxy policies omit that native table because
PSEC rejects it alongside a proxy. Their ingress default still selects `privateNetworkClientServer`, and their
host-loopback setting selects both the `networkLoopback` capability and the `MXC-Loopback` peer rather than an
identity-scoped proxy peer. The peer alone is insufficient: without the capability, the environment can be created
but its client cannot reach the host proxy.
PSEC rejects it alongside a proxy. Their ingress default still selects `privateNetworkClientServer`, and an
identity-less proxy selects both the `networkLoopback` capability and the `MXC-Loopback` peer rather than an
identity-scoped proxy peer, whatever its host-loopback setting. The peer alone is insufficient: without the
capability, the environment can be created but its client cannot reach the host proxy.
The public ingress policy remains required, and host-loopback allow retains the PSEC 1.1 host requirement.
Requests that do not allow host loopback use the
PSEC 1.0 capability mapping; `hostLoopback: "allow"` is rejected when the PSEC 1.1 ingress contract is unavailable.
Both host-loopback settings serialize the same proxy capability and peer, without a native ingress table.
`hostLoopback: "deny"` therefore does not remove a separately encoded bidirectional grant.
It permits the PSEC 1.0 capability mapping when no other requested feature requires a newer contract;
for example, enumeration-only filesystem grants still require PSEC 1.1.
`hostLoopback: "allow"` is rejected when the PSEC 1.1 ingress contract is unavailable.
An identity-less proxy can therefore use a PSEC 1.0 host by keeping `hostLoopback: "deny"` and
requesting only features supported by that host.
Requests the PSEC contract cannot preserve continue to an AppContainer tier.

**Unresolved ingress limitation:** on Windows build 26691.1002, this proxy mapping permits unprivileged connections
Expand Down Expand Up @@ -157,6 +162,12 @@ scoping remains, but Windows cannot verify which host process owns that endpoint
development and debugging. PSEC grants `networkLoopback` to authorize the host-loopback connection without MXC
installing administrator-owned firewall rules. Independently managed host firewall restrictions can still apply.

Set `"hostLoopback": "deny"` instead on a host whose PSEC contract is older than 1.1, or whose
`QueryProcessSecurityEnvironmentSupport` does not advertise ingress support. The request is otherwise identical: MXC
grants the same `networkLoopback` capability and `MXC-Loopback` peer, and WFP applies the same endpoint scoping, so
the client reaches the configured proxy. This removes the PSEC 1.1 ingress prerequisite, not a separately encoded
grant. Other requested features may still require PSEC 1.1. The inbound limitation above applies to both settings.

#### HTTP client guidance

Code inside the ProcessContainer should use WinHTTP or an HTTP library that queries the system for proxy information.
Expand All @@ -169,16 +180,19 @@ the configured loopback proxy address and port and blocks direct public and priv

Model 2 requires `egress.default: "deny"` and `ingress.default: "allow"`. When a non-blank `allowedProxyPeer` names a
package or AppContainer profile, MXC authorizes only that peer and `ingress.hostLoopback` remains denied. An
identity-less host proxy omits `allowedProxyPeer` and requires `ingress.hostLoopback: "allow"`. Direct egress allow and
deny rules do not apply when `runtimeConfig.networkProxy` is present.
identity-less host proxy omits `allowedProxyPeer`; its `ingress.hostLoopback` controls the PSEC 1.1 ingress
prerequisite rather than its eligibility. Direct egress allow and deny rules do not apply when
`runtimeConfig.networkProxy` is present.

The proxy endpoint is runtime metadata, not shared network policy. MXC configures the per-container WinHTTP proxy,
applies WFP endpoint scoping, and grants the private-network capability selected by `ingress.default`.

The identity-scoped and host-loopback paths are mutually exclusive. When `allowedProxyPeer` is present, MXC resolves
the package family or AppContainer profile and grants the private-network capability selected by `ingress.default`.
When it is omitted, MXC uses the configured proxy endpoint without peer identity binding and requires bidirectional
host-loopback access. MXC configures the per-container WinHTTP proxy for either path.
When it is omitted, MXC uses the configured proxy endpoint without peer identity binding. `hostLoopback: "allow"`
then also requests bidirectional host-loopback access and the PSEC 1.1 ingress contract; `"deny"` removes that
prerequisite and permits PSEC 1.0 when the rest of the request supports it. MXC configures the per-container
WinHTTP proxy for every path.

The caller must:

Expand Down
11 changes: 11 additions & 0 deletions docs/process-container/os-version-support.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,17 @@ AppContainer fallback rejects
`privateNetworkClientServer` is bidirectional and no schema 0.8 WFP filter is
available there to block private-network egress.

`ingress.hostLoopback: "allow"` additionally needs PSEC contract 1.1 with
ingress support, which the `BaseContainer` tier does not imply. An identity-less
`runtimeConfig.networkProxy` can run on a PSEC 1.0 host by keeping
`hostLoopback: "deny"`: MXC grants the `networkLoopback` capability and the
`MXC-Loopback` peer for the proxy itself. Both host-loopback settings serialize
the same proxy capability and peer and omit native ingress. `"deny"` removes
the PSEC 1.1 ingress prerequisite, not a separately encoded grant; other
features, such as enumeration-only filesystem grants, can still require 1.1.
`"allow"` requests bidirectional access, but the host-to-container limitation
in the [networking guide](./networking.md#proxy-deployment-choices) still applies.

## UI restrictions

UI restrictions map to Job Object `JOB_OBJECT_UILIMIT_*` flags plus the
Expand Down
18 changes: 11 additions & 7 deletions docs/sandbox-policy/0.8.0/networking/networking.md
Original file line number Diff line number Diff line change
Expand Up @@ -394,8 +394,8 @@ access, after which `egress` rules apply to both public and private outbound des

This capability coupling is a backend mapping, not a change to the shared
meaning of `ingress.default`. The ProcessContainer section identifies which
proxy deployments retain strict endpoint scoping and which compatibility
deployment additionally requires general host-loopback access.
proxy deployments provide peer identity binding and which compatibility
deployment relies on endpoint scoping without that identity.

### D8: Delegation from the invoking user

Expand All @@ -414,8 +414,9 @@ already implemented.

The strict host-loopback guarantee applies to the identity-scoped
ProcessContainer path with OS-scoped proxy enforcement. The identity-less host
proxy path requires broader host-loopback access and is an explicitly documented
PSEC compatibility behavior rather than strict model-2 enforcement. The
proxy path lacks peer identity binding and is an explicitly documented
PSEC compatibility behavior rather than strict model-2 enforcement. It accepts
either host-loopback setting; it does not require general host-loopback access. The
AppContainer fallback rejects schema 0.8 runtime proxy requests because
they cannot preserve either posture.

Expand All @@ -426,8 +427,11 @@ they cannot preserve either posture.
AppContainer proxy uses its profile name. Windows requires `ingress.default: "allow"` to grant the bidirectional
`privateNetworkClientServer` capability. With `allowedProxyPeer`, proxy reachability remains scoped to that peer and
endpoint and `ingress.hostLoopback` stays `"deny"`. An identity-less host proxy cannot use peer scoping and is the
documented development/testing compatibility path that requires `ingress.hostLoopback: "allow"`; it does not
provide the strict host-loopback-closure guarantee.
documented development/testing compatibility path; it does not provide the strict host-loopback-closure guarantee.
Its `ingress.hostLoopback` accepts either setting: `"allow"` requests bidirectional access and requires PSEC 1.1
with ingress support, while `"deny"` removes that prerequisite. Another feature can still require PSEC 1.1.
Both settings serialize the same proxy capability and peer and omit native ingress, so `"deny"` does not remove
a separately encoded grant. The backend guide records the current host-to-container limitation.
- **Model 1:** Grants `internetClient`, allowing direct internet egress under WFP IP/CIDR/port/protocol rules.
Private-network outbound also requires `ingress.default: "allow"` and remains subject to the same `egress` rules.
- **Model 3:** Grants no `internetClient`, private-network capability, or loopback exemptions.
Expand All @@ -440,7 +444,7 @@ they cannot preserve either posture.
| Port filtering | Port filtering via WFP | Port ranges supported. |
| Protocol filtering | Protocol filtering via WFP | Schema values are `tcp`, `udp`, `icmp`, and `any`; WFP maps ICMP by address family. |
| Default-deny | PSEC WFP block-all baseline filter at lower precedence than explicit allows. | A non-empty allow list grants `internetClient` only as the capability prerequisite; WFP still limits egress to explicit allows. With no allows, `internetClient` is absent. |
| Proxy (HTTP/S only) | PSEC per-AppContainer WinHTTP configuration, endpoint filtering, and optional scoped peer access | Identity-scoped proxies keep `hostLoopback: "deny"`; the identity-less development/testing compatibility path requires `"allow"`; schema 0.8 proxy requests do not fall back to AppContainer |
| Proxy (HTTP/S only) | PSEC per-AppContainer WinHTTP configuration, endpoint filtering, and optional scoped peer access | Identity-scoped proxies keep `hostLoopback: "deny"`; the identity-less development/testing compatibility path accepts either setting. `"allow"` requires PSEC 1.1 ingress support; `"deny"` removes that prerequisite. Schema 0.8 proxy requests do not fall back to AppContainer |
| Per-sandbox scoping | AppContainer SID, unique per sandbox instance | |
| Private network | `privateNetworkClientServer` via `ingress.default` | Capability gate; `egress` filters outbound |
| Inbound | Capabilities and loopback rules | Private network uses `ingress.default`; loopback is separate |
Expand Down
14 changes: 10 additions & 4 deletions docs/schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,16 @@ omitted, and `hostLoopback` resolves independently of `ingress.default` rather
than inheriting it, so host-loopback access must be requested explicitly.
A ProcessContainer proxy requires `ingress.default: "allow"`. Identity-scoped
proxies set a non-blank `allowedProxyPeer` and keep `hostLoopback: "deny"`;
identity-less host proxies omit `allowedProxyPeer` and require
`hostLoopback: "allow"`. The identity-less route is a weaker development/testing
compatibility deployment because it opens both host-loopback directions; it is
not the strict proxy-endpoint exception defined by the shared model-2 policy.
identity-less host proxies omit `allowedProxyPeer` and choose either
`hostLoopback` setting. `"allow"` requests bidirectional host-loopback access
and requires the PSEC 1.1 ingress contract. `"deny"` removes that prerequisite,
so PSEC 1.0 can be used unless another requested feature requires a newer
contract. Both settings serialize the same proxy capability and peer and omit
native ingress; neither changes a separately encoded bidirectional grant.
The [backend guide](process-container/networking.md#proxy-deployment-choices)
records the current host-to-container limitation. Neither route is the strict
proxy-endpoint exception defined by the shared model-2 policy, because MXC
cannot verify which host process owns the endpoint.

```json
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,11 @@ pub(super) fn build_psec_v1_security_environment_spec(
);
let mut builder = flatbuffers::FlatBufferBuilder::with_capacity(1024);
let mut capabilities = effective_capabilities(&request.policy);
if request.policy.network_proxy.is_enabled()
&& unrestricted_host_loopback_allowed(&request.policy)
{
// Proxy mode cannot carry the native ingress table that grants loopback.
if identity_less_proxy(&request.policy) {
// Proxy mode cannot carry the native ingress table that grants
// loopback, so an identity-less proxy needs this capability to reach
// its endpoint. WFP still limits client egress to the configured proxy
// address and port, so this does not open general host loopback.
ensure_capability(&mut capabilities, "networkLoopback");
}
let ui_restrictions = crate::job_object::to_job_object_uilimit_mask(
Expand Down Expand Up @@ -154,8 +155,21 @@ pub(super) fn unrestricted_host_loopback_allowed(policy: &ContainerPolicy) -> bo
.is_some_and(|ingress| ingress.host_loopback == NetworkAction::Allow)
}

/// An identity-less proxy: the caller supplied an endpoint but no
/// `allowedProxyPeer`, so MXC knows which address and port to authorize but not
/// which host process owns them. Such a request gets the reserved peer and the
/// loopback capability in place of an identity-scoped peer rule.
///
/// Independent of `ingress.hostLoopback`. A proxy policy never carries the
/// native ingress table, but requesting `allow` still requires PSEC 1.1 ingress
/// support. Keying the capability on the proxy identity instead permits PSEC
/// 1.0 when no other requested feature requires a newer contract.
fn identity_less_proxy(policy: &ContainerPolicy) -> bool {
policy.network_proxy.is_enabled() && policy.allowed_proxy_peer.is_none()
}

fn allowed_appcontainer_peer(policy: &ContainerPolicy) -> Option<String> {
if unrestricted_host_loopback_allowed(policy) {
if unrestricted_host_loopback_allowed(policy) || identity_less_proxy(policy) {
Some(LOOPBACK_NETWORK_PEER.to_string())
} else {
policy.allowed_proxy_peer.clone()
Expand Down
Loading
Loading