# Network Policies

> Cordium documentation. Canonical page: <https://octelium.com/docs/cordium/latest/workspaces/network>.

Every *Workspace* can restrict the network destinations that its processes, including nested containers and AI agents, are allowed to reach via egress rules. The rules are enforced by the *Workspace*'s supervisor outside of the sandbox using nftables, which means that nothing running inside the *Workspace*, even as root, can modify or bypass them.

## Default Behavior

Without any configuration, a *Workspace* can reach the public internet, while all private networks are denied. More precisely:

- **Always denied**, regardless of any configuration: the *Workspace*'s supervisor and pod addresses, the pod's default gateway, loopback addresses outside of the sandbox, and link-local addresses such as the cloud metadata endpoint (i.e. `169.254.169.254`).
- **Denied by default**: the non-public networks, i.e. `10.0.0.0/8`, `100.64.0.0/10`, `172.16.0.0/12`, `192.168.0.0/16`, the other IPv4 special-purpose ranges, multicast, and the IPv6 unique local (`fc00::/7`) and special-purpose ranges. This typically includes the Kubernetes pod and service networks, the Kubernetes API and your private networks.
- **Allowed by default**: every other (i.e. publicly routable) destination.

> **Note:**
>
> *Workspaces* do not need any network access to your private networks in order to access your private resources. Octelium *Services* remain reachable via secretless access through the *Workspace*'s Octelium tunnel, which is authorized and audited on a per-request basis by Octelium *Policies* (read more [here](https://octelium.com/docs/cordium/latest/workspaces/secretless.md)).

## Egress Rules

The `spec.runtime.network.egress` field defines a `defaultAction` and a list of `rules`. Each rule matches a list of CIDRs (IPv4 or IPv6) and, optionally, a list of destination ports, which match both TCP and UDP. Here is an example that keeps the public internet reachable while blocking a few specific destinations:

```yaml
spec:
  runtime:
    network:
      egress:
        defaultAction: ALLOW_PUBLIC
        rules:
          # Block a whole provider range
          - action: DENY
            cidrs:
              - 203.0.113.0/24
          # Block outgoing SMTP everywhere
          - action: DENY
            cidrs:
              - 0.0.0.0/0
              - ::/0
            ports:
              - 25
              - 465
              - 587
          # Allow one internal host on HTTPS only
          - action: ALLOW
            cidrs:
              - 10.20.30.40/32
            ports:
              - 443
```

The rules are evaluated as follows:

- The rules are unordered. A destination matched by a `DENY` rule is denied even if it is also matched by an `ALLOW` rule.
- A destination that is not matched by any rule falls back to the `defaultAction`:
  - `ALLOW_PUBLIC` (the default) allows the publicly routable destinations and denies the non-public ones listed above.
  - `DENY` denies everything that is not explicitly allowed.
- The always-denied destinations listed above are denied even if they are matched by an `ALLOW` rule.

A *Workspace* can have up to 64 rules, each with up to 64 CIDRs and 64 ports. Invalid CIDRs, rules without CIDRs or without an action, and invalid ports are rejected when the *Workspace* or *Template* is created or updated.

## Default-Deny Workspaces

For untrusted code and autonomous AI agents, you can deny everything by default and only allow the destinations that are actually needed (e.g. your package registries). Here is an example:

```yaml
spec:
  runtime:
    network:
      egress:
        defaultAction: DENY
        rules:
          # The DNS resolver of the sandbox
          - action: ALLOW
            cidrs:
              - 8.8.8.8/32
            ports:
              - 53
          # Your package registry mirror
          - action: ALLOW
            cidrs:
              - 198.51.100.10/32
            ports:
              - 443
```

Keep the following in mind when using `DENY` as the default action:

- **DNS**: the sandbox resolves names via `8.8.8.8`, which therefore needs to be allowed on port `53`, as shown above.
- **Image pulls and builds are not affected** since they happen before the policy is applied. Repository clones, dotfiles and devcontainer feature downloads, however, happen inside the sandbox, which means that your git host and registries need to be allowed, or reached through an Octelium *Service* instead.
- **Octelium connectivity**: the egress rules also apply to the tunnel of the *Workspace*'s own `octelium connect` process. If the *Workspace* needs secretless access to Octelium *Services*, allow the public addresses of your *Cluster*'s Gateways (you can list them via `octeliumctl get gateway -o yaml`). Everything that flows through the tunnel is then governed by Octelium *Policies* instead.

> **Note:**
>
> For AI agents, a default-deny network policy combined with secretless access to a small set of authorized Octelium *Services* (e.g. an LLM gateway, a read-only database and your git host) gives you a sandbox whose every external interaction is identity-based, authorized on a per-request basis and audited (read more [here](https://octelium.com/docs/cordium/latest/examples/security/untrusted-code.md)).

## Templates and Updates

Egress rules can be defined in *Templates* as well as in *Workspaces*. The rules of both are combined, which means that a *Template*'s `DENY` rules cannot be overridden by an `ALLOW` rule of a *Workspace* created from it. This lets *Space* admins define baseline restrictions for all the *Workspaces* of a *Template* while still letting individual *Workspaces* tighten them further.

The network policy is applied every time the *Workspace* starts. Changes to a *Workspace*'s or *Template*'s rules take effect on the next run. If the policy cannot be enforced, the *Workspace* is not started and its run fails with a `NetworkPolicy` failure.
