Skip to content

feat: aggkit-proxy: add CORS support for REST and WebSocket endpoints - #1807

Merged
joanestebanr merged 3 commits into
developfrom
tnobayashi/cors
Aug 20, 2026
Merged

feat: aggkit-proxy: add CORS support for REST and WebSocket endpoints#1807
joanestebanr merged 3 commits into
developfrom
tnobayashi/cors

Conversation

@joanestebanr

@joanestebanr joanestebanr commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

🔄 Changes Summary

  • New REST.CORS config on the shared RESTConfig/HTTPServer (common/config.go, common/httpserver.go): Enabled, AllowedOrigins, AllowedMethods, AllowedHeaders, AllowCredentials, MaxAge. Wraps the gin engine with rs/cors when enabled. Disabled by default — no behavior change for existing deployments.
  • [REST.CORS] section added to the proxy's default config (proxy/config/default.go), disabled, with AllowedMethods/AllowedHeaders covering the common REST verbs and any header, ready to flip on.
  • The bridgetracker's WebSocket endpoint now enforces the same origin policy: browsers never CORS-preflight the Upgrade request, so Access-Control-* headers can't restrict it — instead the handshake itself is rejected (403) for a disallowed origin, via a new CORSConfig.OriginAllowed check wired from REST.CORS down into bridgetracker.ConfigwsHandler's websocket.Upgrader.CheckOrigin. With CORS disabled (default), the WS endpoint stays fully public, same as before.
  • Fixed two bugs uncovered while adding tests, both in how corsHandler maps AllowedOrigins onto rs/cors:
    • rs/cors always answers Access-Control-Allow-Origin: * when AllowedOrigins contains "*", even with AllowCredentials set — browsers reject that combination outright. Now routed through AllowOriginFunc so the origin is actually reflected, matching the documented intent.
    • rs/cors treats an empty AllowedOrigins as its own default of "allow every origin" — the opposite of what CORSConfig.AllowedOrigins documents and of OriginAllowed's fail-closed behavior for the WebSocket handshake. Enabling CORS without filling in AllowedOrigins used to silently open the REST server to every origin while denying the WS endpoint under the same config. Now forced to deny-all via AllowOriginFunc, consistent with the docs and with WS.
  • docs/common_config.md and docs/bridgetracker/API.md updated accordingly.

⚠️ Breaking Changes

  • 🛠️ Config: New optional [REST.CORS] section on every RESTConfig-backed service (proxy, aggsender-rpc, bridgeservice, autoclaim-api, …), not just the proxy. Fully backward-compatible: Enabled defaults to false.

📋 Config Updates

[REST.CORS]
Enabled = false
# Empty denies every origin once Enabled is true; use ["*"] to allow any origin.
AllowedOrigins = []
AllowedMethods = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"]
AllowedHeaders = ["*"]
AllowCredentials = false
MaxAge = "12h"

✅ Testing

  • 🤖 Automatic: common/httpserver_test.go (CORS disabled/enabled, preflight, AllowCredentials+wildcard reflection, empty-AllowedOrigins deny-all, corsHandler mapping), common/config_test.go (CORSConfig.OriginAllowed table), bridgetracker/websocket_test.go (TestWSCORSOriginCheck: handshake allowed/rejected per origin policy). make test-unit, make lint both pass.

🐞 Issues

📝 Notes

  • CORS is added at the shared RESTConfig/HTTPServer level rather than proxy-specific, so any service built on it gets the capability for free.
  • WebSocket origin enforcement is a genuine server-side access check (unlike CORS headers, which are a browser-side courtesy) — see the new "Cross-origin access" section in docs/bridgetracker/API.md.

🤖 Generated with Claude Code

tnobayashi and others added 2 commits August 14, 2026 14:55
…ake too

WebSocket upgrades aren't gated by Access-Control-* response headers the way
REST responses are (browsers never CORS-preflight the Upgrade request), so
REST.CORS.AllowedOrigins previously had no effect on the tracker's WebSocket
endpoint: it accepted any origin unconditionally, regardless of how REST CORS
was configured.

- common.CORSConfig gains OriginAllowed(origin), for callers that must reject
  cross-origin access themselves instead of relying on rs/cors response
  headers. Disabled CORS (the default) allows any origin, preserving current
  behavior; enabled, it mirrors rs/cors's own matching (case-insensitive,
  "*", single embedded wildcard per entry).
- bridgetracker.Config gains a programmatic CORS field, wired from the
  proxy's REST.CORS by the binary (proxy/cmd/run.go) and threaded through to
  a per-instance websocket.Upgrader whose CheckOrigin calls OriginAllowed.
- Also fixes an unrelated bug in the CORS commit this branches from: rs/cors
  always answers Access-Control-Allow-Origin: * when AllowedOrigins contains
  "*", even with AllowCredentials set, which browsers reject outright.
  corsHandler now routes that combination through AllowOriginFunc so the
  origin is actually reflected, matching the documented intent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 13c8c89f2d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread common/httpserver.go
…ow all

rs/cors treats an empty AllowedOrigins as its own zero-value default of
"allow every origin" (its docs say as much), which is the opposite of what
CORSConfig.AllowedOrigins documents ("Ignored ... when empty") and of
OriginAllowed's fail-closed behavior used by the WebSocket handshake.
Concretely: enabling CORS without filling in AllowedOrigins silently opened
the REST server to every origin while the WebSocket endpoint denied every
origin under the same config — an inconsistency an operator would only
discover by testing both surfaces.

corsHandler now forces deny-all via AllowOriginFunc when AllowedOrigins is
empty, matching the documented intent and OriginAllowed.

Also updates the proxy's default [REST.CORS] section: AllowedMethods and
AllowedHeaders now cover the common REST verbs and any header (still
Enabled = false, so this changes nothing until an operator opts in), and
AllowedOrigins gets a comment pointing at ["*"] for anyone who wants to
allow every origin instead of denying by default.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@joanestebanr
joanestebanr enabled auto-merge (squash) August 20, 2026 16:27
@joanestebanr
joanestebanr merged commit 2f9ce7c into develop Aug 20, 2026
37 of 38 checks passed
@joanestebanr
joanestebanr deleted the tnobayashi/cors branch August 20, 2026 16:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: aggkit-proxy: Add CORS headers

3 participants