[PR #118] fix: egress filtering when running as non-root user #128

Open
opened 2026-10-18 21:11:04 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/open-webui/open-terminal/pull/118
Author: @SoerenOestervig
Created: 5/5/2026
Status: 🔄 Open

Base: main ← Head: fix/egress-filtering-as-non-root-user


📝 Commits (1)

  • 1f18ec9 fix: egress filtering when running as non-root user

📊 Changes

1 file changed (+5 additions, -3 deletions)

View changed files

📝 entrypoint.sh (+5 -3)

📄 Description

Summary

Egress filtering (OPEN_TERMINAL_ALLOWED_DOMAINS) is broken in the current default image. Two bugs exist:

  1. The container crashes on startup when running as the default unprivileged user
  2. Even if the crash is bypassed, the DNS whitelist never resolves — all outbound traffic is blocked including whitelisted domains

Addressing issue: #119


Bug 1 — Container crash on capability drop

When egress filtering is enabled, the entrypoint configures iptables rules and then attempts to permanently drop CAP_NET_ADMIN using capsh. However, capsh is executed as the unprivileged user, which lacks the CAP_SETPCAP permission required to modify the bounding set. Combined with set -e, this crashes the container immediately:

unable to raise CAP_SETPCAP for BSET changes: Operation not permitted

Fix: Execute capsh via sudo (which the user account has passwordless access to), and use --user=user to securely drop back to the unprivileged user before starting the application.

- exec capsh --drop=cap_net_admin -- -c "exec open-terminal $*"
+ exec sudo capsh --drop=cap_net_admin --user=user -- -c "exec open-terminal $*"

Bug 2 — dnsmasq blocked from reaching upstream DNS

The iptables rules block ALL outbound port 53 traffic. This includes dnsmasq's own queries to the upstream DNS server, which it needs to resolve whitelisted domains. As a result, the allowed ipset is never populated, and all outbound connections fail — even to whitelisted domains.
Fix: Explicitly allow dnsmasq to reach the captured upstream DNS server before applying the blanket port 53 block.

+ sudo iptables -A OUTPUT -p udp -d "$UPSTREAM_DNS" --dport 53 -j ACCEPT
+ sudo iptables -A OUTPUT -p tcp -d "$UPSTREAM_DNS" --dport 53 -j ACCEPT
  sudo iptables -A OUTPUT -p udp --dport 53 -j DROP
  sudo iptables -A OUTPUT -p tcp --dport 53 -j DROP

Test Results

Test v0.11.34 (default, as user) v0.11.34 (forced to run as root*) This PR
Boots without crashing FAIL PASS PASS
Runs as unprivileged user (UID 1000) — (crashed) FAIL (runs as root) PASS
cap_net_admin permanently dropped — (crashed) PASS PASS
DNS: pypi.org resolves via dnsmasq — (crashed) FAIL (no answer) PASS
DNS: google.com blocked — (crashed) PASS (all DNS broken) PASS
HTTP: pypi.org accessible (200) — (crashed) FAIL (000 — timeout) PASS
HTTP: google.com blocked — (crashed) PASS (all traffic broken) PASS
* "Forced to run as root" = launching the container with --user 0:0 to bypass Bug 1. This is not a viable workaround because it means the AI sandbox process runs with full root privileges, defeating the container's security model.

Configuration: --cap-add NET_ADMIN, OPEN_TERMINAL_ALLOWED_DOMAINS=pypi.org

Notes

  • Both bugs must be fixed together. Fixing only Bug 1 results in a container that boots but still has completely non-functional egress filtering.
  • No changes to the Dockerfile or other files — only entrypoint.sh is modified.

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/open-webui/open-terminal/pull/118 **Author:** [@SoerenOestervig](https://github.com/SoerenOestervig) **Created:** 5/5/2026 **Status:** 🔄 Open **Base:** `main` ← **Head:** `fix/egress-filtering-as-non-root-user` --- ### 📝 Commits (1) - [`1f18ec9`](https://github.com/open-webui/open-terminal/commit/1f18ec9e2c94adb783b59e3a45356892f2d6bee9) fix: egress filtering when running as non-root user ### 📊 Changes **1 file changed** (+5 additions, -3 deletions) <details> <summary>View changed files</summary> 📝 `entrypoint.sh` (+5 -3) </details> ### 📄 Description ## Summary Egress filtering (`OPEN_TERMINAL_ALLOWED_DOMAINS`) is broken in the current default image. Two bugs exist: 1. The container crashes on startup when running as the default unprivileged `user` 2. Even if the crash is bypassed, the DNS whitelist never resolves — all outbound traffic is blocked including whitelisted domains Addressing issue: #119 --- ### Bug 1 — Container crash on capability drop When egress filtering is enabled, the entrypoint configures iptables rules and then attempts to permanently drop `CAP_NET_ADMIN` using `capsh`. However, `capsh` is executed as the unprivileged `user`, which lacks the `CAP_SETPCAP` permission required to modify the bounding set. Combined with `set -e`, this crashes the container immediately: ``` unable to raise CAP_SETPCAP for BSET changes: Operation not permitted ``` **Fix:** Execute `capsh` via `sudo` (which the `user` account has passwordless access to), and use `--user=user` to securely drop back to the unprivileged user before starting the application. ```diff - exec capsh --drop=cap_net_admin -- -c "exec open-terminal $*" + exec sudo capsh --drop=cap_net_admin --user=user -- -c "exec open-terminal $*" ``` --- ### Bug 2 — dnsmasq blocked from reaching upstream DNS The iptables rules block ALL outbound port 53 traffic. This includes dnsmasq's own queries to the upstream DNS server, which it needs to resolve whitelisted domains. As a result, the `allowed` ipset is never populated, and all outbound connections fail — even to whitelisted domains. **Fix:** Explicitly allow dnsmasq to reach the captured upstream DNS server before applying the blanket port 53 block. ```diff + sudo iptables -A OUTPUT -p udp -d "$UPSTREAM_DNS" --dport 53 -j ACCEPT + sudo iptables -A OUTPUT -p tcp -d "$UPSTREAM_DNS" --dport 53 -j ACCEPT sudo iptables -A OUTPUT -p udp --dport 53 -j DROP sudo iptables -A OUTPUT -p tcp --dport 53 -j DROP ``` --- ### Test Results **Configuration:** `--cap-add NET_ADMIN`, `OPEN_TERMINAL_ALLOWED_DOMAINS=pypi.org` | Test | `v0.11.34` (default, as `user`) | `v0.11.34` (forced to run as `root`*) | This PR | |---|---|---|---| | Boots without crashing | **FAIL** | PASS | **PASS** | | Runs as unprivileged `user` (UID 1000) | — (crashed) | **FAIL** (runs as root) | **PASS** | | `cap_net_admin` permanently dropped | — (crashed) | PASS | **PASS** | | DNS: `pypi.org` resolves via dnsmasq | — (crashed) | **FAIL** (no answer) | **PASS** | | DNS: `google.com` blocked | — (crashed) | PASS (all DNS broken) | **PASS** | | HTTP: `pypi.org` accessible (200) | — (crashed) | **FAIL** (000 — timeout) | **PASS** | | HTTP: `google.com` blocked | — (crashed) | PASS (all traffic broken) | **PASS** | \* *"Forced to run as root" = launching the container with `--user 0:0` to bypass Bug 1. This is not a viable workaround because it means the AI sandbox process runs with full root privileges, defeating the container's security model.* --- ### Notes - Both bugs must be fixed together. Fixing only Bug 1 results in a container that boots but still has completely non-functional egress filtering. - No changes to the Dockerfile or other files — only `entrypoint.sh` is modified. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
Sign in to join this conversation.
No labels
bug
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
open-webui/open-terminal#128
No description provided.