Stop allowing credentialed cross-origin requests from every origin #261

Closed
opened 2026-11-10 20:52:24 +01:00 by clawbot · 3 comments
Collaborator

The CORS middleware in internal/middleware/middleware.go allows every origin (AllowOriginFunc returns true) with AllowCredentials: true. The auth cookie is SameSite=Strict, which stops other sites but not other origins on the same site: a page on another subdomain of the server's domain (say blog.example.com beside chat.example.com) has the cookie sent with its requests and can read the answers, so it can read everything the signed-in user sees and send as them.

REPO_POLICIES.md ("CORS"): authenticated endpoints restrict Access-Control-Allow-Origin to an explicit allowlist of known origins; * only for public, unauthenticated read-only APIs.

Nothing uses cross-origin access today. The web client is served from the same origin as the API, neoirc-cli is not a browser, and a browser client on another site cannot sign in, since the cookie is SameSite=Strict.

Decision (repo-manager, reversible): the server allows no cross-origin requests. An allowlist would be a new setting nobody needs now; if sneak wants browser clients served from other origins, an allowlist setting can be added then.

Definition of done

  • The CORS middleware, its use in internal/server/routes.go, corsMaxAge, and the github.com/go-chi/cors dependency are removed (go.mod, go.sum).
  • A cross-origin request gets no Access-Control-Allow-* headers; a preflight OPTIONS gets no special handling.
  • README.md: remove or rewrite every passage about cross-origin requests (the HTTP API notes, "Limits and headers", the internal/middleware/ line under the code layout, and the CSP sentence's "except to a cross-origin preflight") to say the server allows no cross-origin requests.
  • A test in internal/server (external test package) sends a request with an Origin header from another origin, and a preflight, and checks that neither answer carries Access-Control-Allow-Origin or Access-Control-Allow-Credentials.

The web client must keep working: load it in a headless browser in a container and use it (sign in, join, send) before pushing.

Read first: AGENTS.md, CONVENTIONS.md, REPO_POLICIES.md.

Model: opus-5-5

The `CORS` middleware in `internal/middleware/middleware.go` allows every origin (`AllowOriginFunc` returns true) with `AllowCredentials: true`. The auth cookie is `SameSite=Strict`, which stops other sites but not other origins on the same site: a page on another subdomain of the server's domain (say `blog.example.com` beside `chat.example.com`) has the cookie sent with its requests and can read the answers, so it can read everything the signed-in user sees and send as them. `REPO_POLICIES.md` ("CORS"): authenticated endpoints restrict `Access-Control-Allow-Origin` to an explicit allowlist of known origins; `*` only for public, unauthenticated read-only APIs. Nothing uses cross-origin access today. The web client is served from the same origin as the API, `neoirc-cli` is not a browser, and a browser client on another site cannot sign in, since the cookie is `SameSite=Strict`. Decision (repo-manager, reversible): the server allows no cross-origin requests. An allowlist would be a new setting nobody needs now; if sneak wants browser clients served from other origins, an allowlist setting can be added then. ## Definition of done - The `CORS` middleware, its use in `internal/server/routes.go`, `corsMaxAge`, and the `github.com/go-chi/cors` dependency are removed (`go.mod`, `go.sum`). - A cross-origin request gets no `Access-Control-Allow-*` headers; a preflight `OPTIONS` gets no special handling. - `README.md`: remove or rewrite every passage about cross-origin requests (the HTTP API notes, "Limits and headers", the `internal/middleware/` line under the code layout, and the CSP sentence's "except to a cross-origin preflight") to say the server allows no cross-origin requests. - A test in `internal/server` (external test package) sends a request with an `Origin` header from another origin, and a preflight, and checks that neither answer carries `Access-Control-Allow-Origin` or `Access-Control-Allow-Credentials`. The web client must keep working: load it in a headless browser in a container and use it (sign in, join, send) before pushing. Read first: `AGENTS.md`, `CONVENTIONS.md`, `REPO_POLICIES.md`. Model: opus-5-5
clawbot self-assigned this 2026-11-10 20:52:24 +01:00
Author
Collaborator

Built in #269: the CORS middleware and github.com/go-chi/cors are gone, a test covers a cross-origin request and a preflight, and README.md says the server allows no cross-origin requests. CONVENTIONS.md still lists go-chi/cors; it is a shared copy, left unedited.

Model: opus-5-5

Built in https://git.eeqj.de/sneak/neoirc/pulls/269: the `CORS` middleware and `github.com/go-chi/cors` are gone, a test covers a cross-origin request and a preflight, and `README.md` says the server allows no cross-origin requests. `CONVENTIONS.md` still lists `go-chi/cors`; it is a shared copy, left unedited. Model: opus-5-5
Author
Collaborator

Decision changed (repo-manager): keep the CORS middleware and github.com/go-chi/cors. CONVENTIONS.md requires both (the library table, the middleware order, and the "CORS Middleware" template). The fault is AllowCredentials: true with every origin reflected; the template's options fix it.

Amended definition of done, replacing the first three bullets:

  • CORS() in internal/middleware uses the CONVENTIONS.md template's options exactly: AllowedOrigins: []string{"*"}, the template's methods, headers and exposed headers, AllowCredentials: false, MaxAge: 300. AllowOriginFunc and corsMaxAge go.
  • A cross-origin answer carries Access-Control-Allow-Origin: *, never the request's origin, and never Access-Control-Allow-Credentials, so a page on another origin cannot read an answer that needs the cookie.
  • README.md: every passage about cross-origin requests says that, and nothing more. Sending as the user from another origin of the same site is tracked separately, in #271; the README makes no claim about it.
  • The test sends a request with another origin's Origin header, and a preflight, and checks both points above.

Model: opus-5-5

Decision changed (repo-manager): keep the `CORS` middleware and `github.com/go-chi/cors`. `CONVENTIONS.md` requires both (the library table, the middleware order, and the "CORS Middleware" template). The fault is `AllowCredentials: true` with every origin reflected; the template's options fix it. Amended definition of done, replacing the first three bullets: - `CORS()` in `internal/middleware` uses the `CONVENTIONS.md` template's options exactly: `AllowedOrigins: []string{"*"}`, the template's methods, headers and exposed headers, `AllowCredentials: false`, `MaxAge: 300`. `AllowOriginFunc` and `corsMaxAge` go. - A cross-origin answer carries `Access-Control-Allow-Origin: *`, never the request's origin, and never `Access-Control-Allow-Credentials`, so a page on another origin cannot read an answer that needs the cookie. - `README.md`: every passage about cross-origin requests says that, and nothing more. Sending as the user from another origin of the same site is tracked separately, in https://git.eeqj.de/sneak/neoirc/issues/271; the README makes no claim about it. - The test sends a request with another origin's `Origin` header, and a preflight, and checks both points above. Model: opus-5-5
Author
Collaborator

Second amendment (repo-manager), replacing the first bullet of #261 (comment) and the README and test bullets after it. The review at #269 (comment) is right: REPO_POLICIES.md allows Access-Control-Allow-Origin: * only on public, unauthenticated, read-only endpoints, and neoirc has no other known origins. CONVENTIONS.md wants go-chi/cors and CORS() with its template's options. Both hold when CORS() covers only the public read-only routes.

  • CORS() in internal/middleware keeps the template's options (AllowedOrigins: []string{"*"}, AllowCredentials: false, the template's methods, headers and exposed headers, MaxAge 300 through the named constant).
  • It is applied only to GET /api/v1/server and GET /.well-known/healthcheck.json (with router.With), not to the global chain. Every other route, the SPA included, sends no Access-Control-Allow-* header to any origin.
  • README.md: those two routes answer any origin with Access-Control-Allow-Origin: *; no other route allows cross-origin reads.
  • Tests: a request with another origin's Origin header to GET /api/v1/server gets Access-Control-Allow-Origin: * and no Access-Control-Allow-Credentials; the same header on POST /api/v1/messages, GET /api/v1/state and a preflight for POST /api/v1/messages gets no Access-Control-Allow-* header.

Disclosed deviation, for the PR body: CORS() is not in the global middleware chain that CONVENTIONS.md lists, because REPO_POLICIES.md forbids * on authenticated routes.

Model: opus-5-5

Second amendment (repo-manager), replacing the first bullet of https://git.eeqj.de/sneak/neoirc/issues/261#issuecomment-175440 and the README and test bullets after it. The review at https://git.eeqj.de/sneak/neoirc/pulls/269#issuecomment-175648 is right: `REPO_POLICIES.md` allows `Access-Control-Allow-Origin: *` only on public, unauthenticated, read-only endpoints, and neoirc has no other known origins. `CONVENTIONS.md` wants `go-chi/cors` and `CORS()` with its template's options. Both hold when `CORS()` covers only the public read-only routes. - `CORS()` in `internal/middleware` keeps the template's options (`AllowedOrigins: []string{"*"}`, `AllowCredentials: false`, the template's methods, headers and exposed headers, `MaxAge` 300 through the named constant). - It is applied only to `GET /api/v1/server` and `GET /.well-known/healthcheck.json` (with `router.With`), not to the global chain. Every other route, the SPA included, sends no `Access-Control-Allow-*` header to any origin. - `README.md`: those two routes answer any origin with `Access-Control-Allow-Origin: *`; no other route allows cross-origin reads. - Tests: a request with another origin's `Origin` header to `GET /api/v1/server` gets `Access-Control-Allow-Origin: *` and no `Access-Control-Allow-Credentials`; the same header on `POST /api/v1/messages`, `GET /api/v1/state` and a preflight for `POST /api/v1/messages` gets no `Access-Control-Allow-*` header. Disclosed deviation, for the PR body: `CORS()` is not in the global middleware chain that `CONVENTIONS.md` lists, because `REPO_POLICIES.md` forbids `*` on authenticated routes. Model: opus-5-5
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/neoirc#261