Reject wildcard CORS origin when credentials are enabled - #52
Conversation
Coverage Report for CI Build 32197183823Coverage decreased (-0.3%) to 97.091%Details
Uncovered ChangesNo uncovered changes found. Coverage Regressions7 previously-covered lines in 2 files lost coverage.
Coverage Stats
💛 - Coveralls |
40c0257 to
906e698
Compare
5b0489e to
9ee60a7
Compare
|
the underlying issue is real, wildcard plus credentials reflects any origin with remark42 uses exactly that combination on purpose, there's a comment above pls keep the rejection as the default, but add an explicit opt-in with a name that makes the risk obvious, so a caller who knowingly wants origin reflection can still have it. Also note this is based on |
906e698 to
9079085
Compare
Previously, CORS(CorsAllowCredentials(true)) kept the default "*" origin list and reflected any request Origin back together with Access-Control-Allow-Credentials: true, which lets any site read cookie-authenticated responses. The config doc already claimed the combination was not allowed. CORS now panics at construction when "*" is among the allowed origins and credentials are on. This is a breaking change for callers relying on the old behaviour, who have to enumerate their origins instead.
Rejecting "*" with credentials stays the default, but it no longer removes the capability. A service that has to accept credentialed requests from arbitrary third-party origins, an embeddable widget being the obvious case, can ask for origin reflection by name, and the name and doc comment say what it costs.
9079085 to
98408c6
Compare
|
Added the opt-in as Also retargeted this to master and rebased off |
rest.CORS refuses "*" together with credentials since go-pkgz/rest#52, so the bump and the option have to land together: the option does not exist in v1.22.0 and the panic fires at construction, inside routes(), which makes it a startup failure rather than a request-time one. The wildcard stays. The comment widget is embedded on arbitrary third-party sites, so the set of origins is not knowable, which is why the escape hatch was asked for upstream instead of accepting the panic. What it costs is unchanged and now written next to the call: any site a signed-in user visits can read authenticated responses, so state-changing requests have to keep being protected by something other than the origin, X-XSRF-Token today. Pinned to master for now rather than a release, as the fixes are not tagged yet. The bump also carries testify to v1.12.0, which drops go-spew and go-difflib from the module graph.
rest.CORS refuses "*" together with credentials since go-pkgz/rest#52, so the bump and the option have to land together: the option does not exist in v1.22.0 and the panic fires at construction, inside routes(), which makes it a startup failure rather than a request-time one. The wildcard stays. The comment widget is embedded on arbitrary third-party sites, so the set of origins is not knowable, which is why the escape hatch was asked for upstream instead of accepting the panic. What it costs is unchanged and now written next to the call: any site a signed-in user visits can read authenticated responses, so state-changing requests have to keep being protected by something other than the origin, X-XSRF-Token today. Pinned to master for now rather than a release, as the fixes are not tagged yet. The bump also carries testify to v1.12.0, which drops go-spew and go-difflib from the module graph.
…entials rest.CORS refuses "*" together with credentials since go-pkgz/rest#52, so the bump and the option have to land together: the option does not exist in v1.22.0 and the panic fires at construction, inside routes(), which makes it a startup failure rather than a request-time one. The wildcard stays. The comment widget is embedded on arbitrary third-party sites, so the set of origins is not knowable, which is why the escape hatch was asked for upstream instead of accepting the panic. What it costs is unchanged and now written next to the call: any site a signed-in user visits can read authenticated responses, so state-changing requests have to keep being protected by something other than the origin, X-XSRF-Token today. The example module is tidied in the same commit, as it reaches go-pkgz/rest through the replace directive and its indirect graph would otherwise keep the old pin and fail the readonly module check in CI. The bump also carries testify to v1.12.0, which drops go-spew and go-difflib from the module graph.
…entials rest.CORS refuses "*" together with credentials since go-pkgz/rest#52, so the bump and the option have to land together: the option does not exist in v1.22.0 and the panic fires at construction, inside routes(), which makes it a startup failure rather than a request-time one. The wildcard stays. The comment widget is embedded on arbitrary third-party sites, so the set of origins is not knowable, which is why the escape hatch was asked for upstream instead of accepting the panic. What it costs is unchanged and now written next to the call: any site a signed-in user visits can read authenticated responses, so state-changing requests have to keep being protected by something other than the origin, X-XSRF-Token today. The example module is tidied in the same commit, as it reaches go-pkgz/rest through the replace directive and its indirect graph would otherwise keep the old pin and fail the readonly module check in CI. The bump also carries testify to v1.12.0, which drops go-spew and go-difflib from the module graph.
…entials (#2157) rest.CORS refuses "*" together with credentials since go-pkgz/rest#52, so the bump and the option have to land together: the option does not exist in v1.22.0 and the panic fires at construction, inside routes(), which makes it a startup failure rather than a request-time one. The wildcard stays. The comment widget is embedded on arbitrary third-party sites, so the set of origins is not knowable, which is why the escape hatch was asked for upstream instead of accepting the panic. What it costs is unchanged and now written next to the call: any site a signed-in user visits can read authenticated responses, so state-changing requests have to keep being protected by something other than the origin, X-XSRF-Token today. The example module is tidied in the same commit, as it reaches go-pkgz/rest through the replace directive and its indirect graph would otherwise keep the old pin and fail the readonly module check in CI. The bump also carries testify to v1.12.0, which drops go-spew and go-difflib from the module graph.
Stacked on #49, so the diff here is the CORS change alone. The base retargets to master once #49 merges.
This is a breaking change and it breaks remark42 at boot. Details below.
CORSnow panics at construction when"*"is among the allowed origins andCorsAllowCredentials(true)is set, including the case where the origin list is left at its default.Previously that combination reflected any request
Originback together withAccess-Control-Allow-Credentials: true. That is precisely the configuration the same-origin policy exists to prevent: any site a victim visits can issue credentialed requests to the service and read the responses. TheCORSConfigdocumentation already stated that credentials cannot be combined with"*", so the code was contradicting its own contract rather than implementing a deliberate choice.Who this breaks
Any caller doing
rest.CORS(rest.CorsAllowCredentials(true))without naming origins, or passing"*"explicitly alongside credentials, panics when the middleware is constructed.remark42 is affected.
corsMiddleware()inapp/rest/api/middleware.gowiresCorsAllowedOrigins("*")together withCorsAllowCredentials(true), and it is built insideroutes()during server start, so this panics at boot rather than per request. Every deployment that does not set--proxy-corsis affected. remark42 has to enumerate its allowed origins before it can take a release containing this.Worth stating plainly: that same configuration is a live exposure in remark42 today, so the fix is worth the coordination rather than the other way round.
Why a panic
The failure is deliberately loud and at construction time, so a misconfiguration cannot reach production silently. The alternative of quietly dropping the credentials header would break authentication at runtime in a way that is far harder to trace back to its cause, and would do so on the request path rather than at startup.
Callers that need credentials list their origins instead:
The wildcard remains available without credentials, which is unchanged and safe.
The test that asserted the old behaviour is replaced with cases covering the default list, an explicit
"*", and"*"alongside other origins.