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 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.
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.
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_allowedThe 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:
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.
Before preflight 400 access-control-allow-origin=None
Now preflight 200 access-control-allow-origin=https://preview-42.example.comCORSConfig(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, and learn more about the public surface in the documentation.
Frequently asked questions
Pass cors=CORSConfig(origins=[...], origin_regex=r"...") to AgentOS. The regex must match the whole Origin value, so r"https://docs-[a-z0-9-]+\.example\.com" admits https://docs-feature.example.com and refuses https://docs-feature.example.com.evil.test.
Yes, with the same meaning as before and no deprecation warning. An empty list or None still falls back to the settings defaults. Use one form per AgentOS, since passing both cors and cors_allowed_origins raises a ValueError.
It answers 403 origin_not_allowed to a public run or cancel request whose Origin header falls outside the CORS policy, and rejects a public workflow WebSocket handshake from such an origin with 403 too. Requests that send no Origin header keep their usual authentication and quotas.
Yes. CORSConfig(merge_base_app=True) is the default and merges the origins and the regex from a base_app CORSMiddleware into the policy. Set merge_base_app=False to replace them.





