Zion Boggan zionboggan.com ↗

Enable CORS on registry read-only endpoints

The browser Sealed File Inspector at oversight-protocol.github.io and
the site at oversightprotocol.dev were blocked by the browser when
fetching /.well-known/oversight-registry and /evidence/{file_id}
because the registry returned no Access-Control-Allow-Origin header
and rejected preflight OPTIONS with 405.

Add FastAPI CORSMiddleware with a whitelist of the two public
origins plus localhost dev ports. allow_methods is restricted to
GET and OPTIONS so POST endpoints (/register, /dns_event, /attribute)
cannot be called from a browser even by an allowed origin. No
credentials. Operators can whitelist extra origins via the
OVERSIGHT_CORS_ORIGINS environment variable.

Confirmed via FastAPI TestClient:
  - allowed origin receives Access-Control-Allow-Origin on GET
  - preflight OPTIONS returns 200 with the origin echoed
  - unlisted origin gets no ACAO (browser will block)
  - OPTIONS for POST /register returns 400 (POST not permitted
    from any browser origin)

All 32 conformance harness checks still green.
f440f68   Zion Boggan committed on Apr 22, 2026 (2 months ago)
registry/server.py +30 -0
@@ -32,6 +32,7 @@ from cryptography.hazmat.primitives.asymmetric.ed25519 import (
)
from cryptography.hazmat.primitives import serialization
from fastapi import FastAPI, Request, HTTPException
+from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import Response, JSONResponse
from pydantic import BaseModel
@@ -246,6 +247,35 @@ async def lifespan(app: FastAPI):
app = FastAPI(title="OVERSIGHT Registry", version="0.2.1", lifespan=lifespan)
+# CORS: the public browser inspector at https://oversight-protocol.github.io/oversight/
+# and the site at https://oversightprotocol.dev call the read-only endpoints
+# (/health, /.well-known/oversight-registry, /evidence/{file_id}). Seal, register,
+# and dns_event are never called from a browser, so restrict allowed methods to
+# GET and OPTIONS. Credentials are not used. Additional origins can be allowed
+# with OVERSIGHT_CORS_ORIGINS (comma-separated).
+_default_cors_origins = [
+ "https://oversight-protocol.github.io",
+ "https://oversightprotocol.dev",
+ "https://www.oversightprotocol.dev",
+ "http://localhost:8000",
+ "http://127.0.0.1:8000",
+ "http://localhost:8787",
+ "http://127.0.0.1:8787",
+]
+_extra_origins = [
+ o.strip()
+ for o in os.environ.get("OVERSIGHT_CORS_ORIGINS", "").split(",")
+ if o.strip()
+]
+app.add_middleware(
+ CORSMiddleware,
+ allow_origins=_default_cors_origins + _extra_origins,
+ allow_credentials=False,
+ allow_methods=["GET", "OPTIONS"],
+ allow_headers=["Accept", "Content-Type"],
+ max_age=3600,
+)
+
class RegistrationRequest(BaseModel):
manifest: dict