zozo123 opened a new pull request, #74504: URL: https://github.com/apache/airflow/pull/74504
**Behaviour change.** On an OpenSandbox server with the default `[egress] mode = "dns"`, `OpenSandboxBackend` now refuses a deny-all or allowlist `SandboxSpec` at create, including the default `SandboxSpec()`. The error names the fix: set `[egress] mode = "dns+nft"` in the server config, or pass `SandboxSpec(block_network=False)` for a sandbox that may reach the network. The backend is Experimental, so the stability policy allows this in a minor release. The changelog note under the `Changelog` header announces it, as `stability.rst` requires. **Why.** `dns` is the server's default (`EgressConfig.mode`) and is what every example config ships. In that mode the egress sidecar filters name resolution and nothing else, so a "no network" sandbox can still connect to any numeric address. `_verify_network_policy` passed anyway, because `SandboxSync.get_egress_policy()` returns the same rules in `dns` and `dns+nft` mode. That breaks the `SandboxSpec` contract that a backend which cannot enforce a field must raise rather than silently ignore it. **Release context.** apache-airflow-providers-common-ai 0.11.0 (RC vote #74459, ends 2026-10-11 14:47 UTC) ships the backend without this check. The caveat is on the RC thread: https://github.com/apache/airflow/issues/74459#issuecomment-6064052952. This lands in the next release. **Two questions a reviewer will ask:** - *Why use the SDK's generated `opensandbox.api.egress` client?* The sidecar's `GET /policy` response carries `enforcementMode`, but the high-level `get_egress_policy()` keeps only the rules and drops that field. - *Why a separate client?* It is a short-lived client, closed after one request, so it never touches the SDK's shared transport. Non-200 responses go through the SDK's own `handle_api_error` and `require_parsed`, so an HTTP error is reported as one. A missing or unreadable mode also fails closed. `allow_egress_to_cidrs` is still refused. Its message no longer claims that the mode cannot be read. Supporting CIDRs is possible now, but it needs Modal-style entry validation, so it is left for a follow-up. **Measured** against OpenSandbox server 0.2.3 with egress sidecar v1.1.7, running on Docker (colima), with `opensandbox==1.1.0`. Each sandbox was created through `OpenSandboxBackend.create()`. What a sandbox can reach with the backend as it is on `main`, which is byte-identical to 0.11.0rc1: | From inside the sandbox | `dns`, deny-all | `dns`, allow `pypi.org` | `dns+nft`, deny-all | `dns+nft`, allow `pypi.org` | |---|---|---|---|---| | TCP `1.1.1.1:443`, `8.8.8.8:443` | connects | connects | times out | times out | | HTTP `GET 1.1.1.1:80` | `301` from Cloudflare | `301` from Cloudflare | times out | times out | | HTTPS `HEAD pypi.org/simple/` | fails (no DNS) | `200` | fails (no DNS) | `200` | In another `dns`-mode deny-all sandbox created the same way, the system resolver could not look up `example.com`. A DNS-over-HTTPS query to `https://1.1.1.1/dns-query` returned its address, and `GET https://example.com/` over that address returned `200 OK` with a verified certificate. So the DNS-only filter is easy to get around. With this PR: - **`dns+nft` server:** deny-all and `allow_egress_to=["pypi.org"]` are accepted, with the same reachability as the table above. The system test `example_sandbox_toolset_opensandbox.py` succeeds in 7.30 s. On 2026-10-08, against `dns+nft` with 0.11.0rc1, the timeout, kill and cleanup edge cases also passed. - **`dns` server:** deny-all is refused after 2.6 s and the allowlist after 2.0 s, each with the error above. No sandbox container is left behind. Tests: `test_opensandbox.py` has 82 passing tests. The sidecar's `/policy` response is served by patching `httpx.Client.send`, so the generated client and its parsing run for real. The new and changed cases cover `dns`, an unreported mode, an unreachable sidecar, an HTTP 500, the request's URL, API-key header and timeout, and the CIDR message. All of them fail against the code on `main`. mypy is clean on both files. related: #71676, #74459 --- ##### Was generative AI tooling used to co-author this PR? - [X] Yes (please specify the tool below) Generated-by: Claude Code (Opus 5.5) following [the guidelines](https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#gen-ai-assisted-contributions) --- * Read the **[Pull Request Guidelines](https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#pull-request-guidelines)** for more information. Note: commit author/co-author name and email in commits become permanently public when merged. * For fundamental code changes, an Airflow Improvement Proposal ([AIP](https://cwiki.apache.org/confluence/display/AIRFLOW/Airflow+Improvement+Proposals)) is needed. * When adding dependency, check compliance with the [ASF 3rd Party License Policy](https://www.apache.org/legal/resolved.html#category-x). * For significant user-facing changes create newsfragment: `{pr_number}.significant.rst`, in [airflow-core/newsfragments](https://github.com/apache/airflow/tree/main/airflow-core/newsfragments). You can add this file in a follow-up commit after the PR is created so you know the PR number. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
