# Web Tabs Open any dashboard you run, Grafana, Uptime Kuma, Portainer, a status page on port 4000, as a tab beside your agent sessions. Codeman becomes one mission control instead of Codeman plus a pile of browser tabs. A web tab is **not a session**. There is no PTY, no tmux, and no respawn behind it, the same way a docker case is not a run mode. ## Adding one 1. Click the chevron next to **Run**. 2. Under **Web / URL**, pick **Add URL**. 3. Name it, paste the URL, optionally hit **Test**, and **Save**. It opens immediately and appears in the dropdown from then on. Web tabs share the tab strip with sessions, continue the same `Alt+1` to `Alt+9` numbering, and carry a globe icon so they never read as a running agent. **Closing a tab is not deleting it.** The tab's `x` closes; the `x` on its **dropdown row** deletes the saved dashboard. Each dropdown row also has a gear for editing the URL. Switching tabs does not reload a dashboard. Frames stay alive in the background, so one that took a while to authenticate is still there when you come back. Past six live frames, the least recently viewed is dropped to bound memory. ## Why dashboards are proxied A plain cross-origin iframe fails three ways at once in the setup Codeman actually ships in: | Blocker | What happens | | ------------------- | ----------------------------------------------------------------------------------------------- | | **Mixed content** | Production is HTTPS, and browsers hard-block `http://` iframes on an HTTPS page. No override, and none at all on iOS Safari. | | **Framing refusal** | Grafana, Portainer, Home Assistant and many others send `X-Frame-Options: DENY`. | | **Codeman's CSP** | `default-src 'self'` blocks a cross-origin frame before it starts. | So by default the dashboard is served **through Codeman's own origin**: the browser loads a path on Codeman, and Codeman relays to the dashboard, stripping the framing refusal, rewriting redirects, cookies and root-absolute URLs, and relaying WebSockets so live panels still update. A useful side effect: the dashboard is fetched **by the Codeman server**, so a tailnet-only or localhost-only dashboard works from any device that can reach Codeman, including a phone that is not on your tailnet. There is also a `direct` mode, a plain cross-origin iframe, which is cheaper but only works for an HTTPS dashboard that permits framing. ## The Test button, and what it does not test **Test** probes from the server and tells you which mode applies. It verifies **server-to-upstream reachability and nothing else**. It does not exercise the browser sandbox, cookies, CORS, CSP, or any reverse proxy in front of Codeman. A passing Test does not guarantee the embedded page renders. ## The sandbox, and when to turn it off Because a proxied dashboard is served from Codeman's own address, the browser considers it same-origin with Codeman. Unchecked, its JavaScript could read the Codeman page and call the API that spawns agents. So the frame is sandboxed **without** same-origin access by default. The page runs in an opaque origin: it cannot touch Codeman, and it gets no cookies or local storage of its own. Unchecking **Open sandboxed** grants a real origin. Do that only for a dashboard you fully trust, and only when you need it, which in practice means one with its own login that stores a session in a cookie. Either way, Codeman never forwards its own credentials upstream. The `Authorization` header and the `codeman_session` cookie are stripped on the way out, so `CODEMAN_PASSWORD` cannot leak into a dashboard. ## Known incompatibility: cookie-authenticated reverse proxies If Codeman itself sits behind Cloudflare Access, Authelia, oauth2-proxy, or similar, a **sandboxed** tab may render unstyled or broken while the Codeman page around it works fine. The reason: an opaque-origin frame's stylesheet, script, and API requests do not carry the proxy's authentication cookie. The proxy redirects them to the login provider, and CORS or CSP kills them there. Trusted mode keeps a real origin and the cookie, so it works. Test cannot catch this, because it checks the server's reach, not the browser's. ## Security notes The proxy authenticates on an in-memory capability embedded in the path, which is why it is exempt from the cookie and Origin checks that every API route enforces. That exemption is fenced to safe methods and non-API paths, and there is a test pinning it in place. Two failure modes that only appear inside a sandboxed frame, and that curl can never reproduce, are handled: runtime-built root-absolute URLs escaping the injected base, and same-host requests being CORS-checked with a null origin. Both present as the dashboard's own "Failed to fetch" while the page itself renders fine. ## Read next - [The Dashboard](The-Dashboard) - the tab strip these share. - [Security](Security) - why the sandbox default is what it is. - [`docs/web-tabs.md`](https://github.com/Ark0N/Codeman/blob/master/docs/web-tabs.md) - the full reference.