# One browser origin policy for every AgentOS surface

> Agno AgentOS takes its allowed browser origins as `cors=CORSConfig(...)`, exact origins plus a full-match regex, and `PublicSurface(enforce_browser_origins=True)` turns away public runs from any origin outside that list.

- Published: 2026-10-08
- Author: Yash Pratap Solanky
- Categories: Changelog
- Canonical: https://www.agno.com/blog/one-browser-origin-policy-for-every-agentos-surface
- Markdown: https://www.agno.com/blog/one-browser-origin-policy-for-every-agentos-surface.md

`AgentOS(cors=CORSConfig(origins=..., origin_regex=..., merge_base_app=...))` builds one origin policy, and Agno checks that same policy for CORS preflights, public run and cancel admission, CORS headers on auth errors, workflow WebSockets and the MCP server's origin check.

![A CORSConfig with an exact origin and an origin_regex for preview domains feeds five AgentOS surfaces. CORS preflights give allowed origins the header and others a 400. Public run and cancel answer other origins with a 403 when enforce_browser_origins is on. Auth errors 401, 403, 413 and 429 carry CORS headers for allowed origins. Public workflow WebSockets from other origins get a 403 at the handshake under enforcement. The MCP origin check accepts the same origins under enforcement.](https://www.agno.com/images/v3-1-2-browser-origin-policy.png)

A docs site often has a production domain plus a preview deployment for every branch. The exact list covers production, and the regex covers the previews.

```python
from agno.os import AgentOS, CORSConfig
from agno.os.public import PublicSurface

agent_os = AgentOS(
    id="browser-origin-demo",
    agents=[agent],
    db=db,  # PublicSurface needs a PostgresDb
    public=PublicSurface(agents=[agent], enforce_browser_origins=True),
    cors=CORSConfig(
        origins=["https://docs.example.com"],
        origin_regex=r"https://docs-[a-z0-9-]+\.example\.com",
    ),
)
```

We served that app through FastAPI's `TestClient` with an embedded PostgreSQL and a stub model that answers "Hello", then sent a preflight and a public run from three origins.

```text
https://docs.example.com          preflight 200 allow-origin=https://docs.example.com          POST run 200 Hello
https://docs-feature.example.com  preflight 200 allow-origin=https://docs-feature.example.com  POST run 200 Hello
https://evil.example.com          preflight 400 allow-origin=None                              POST run 403 origin_not_allowed
```

The regex has to match the whole `Origin` value, and `CORSConfig` compiles it when you build the config, so a bad pattern fails at startup. Browsers send `Origin` on same-origin POSTs and WebSockets as well, so a page served from the AgentOS domain itself needs that origin in the list once enforcement is on. A request with no `Origin` header, such as a server calling the API, keeps its usual authentication and quotas.

`cors_allowed_origins` keeps working exactly as it did, with no deprecation warning, and an empty list or `None` still means the settings defaults. `CORSConfig(origins=[])` is how you allow no exact origins at all. One `AgentOS` takes one form:

```text
ValueError: Pass either cors=CORSConfig(origins=...) or cors_allowed_origins, not both.
```

Apps mounted on a `base_app` gain something too. AgentOS dropped the `allow_origin_regex` of a `base_app` `CORSMiddleware` in earlier releases, and it merges it into the policy now. A preflight from `https://preview-42.example.com` against a base app whose middleware allows `https://preview-[a-z0-9-]+\.example\.com` shows the change.

```text
Before  preflight 400 access-control-allow-origin=None
Now     preflight 200 access-control-allow-origin=https://preview-42.example.com
```

`CORSConfig(merge_base_app=False)` replaces the base app's origins. CORS also runs as the outermost middleware, so a 401, 403, 413 or 429 from an allowed origin carries the same CORS headers as a success.

See the [cookbook](https://github.com/agno-agi/agno/blob/main/cookbook/05_agent_os/27_public_pages/browser_origins.py), and learn more about the [public surface](https://docs.agno.com/agent-os/public-surface) in the documentation.
