rs/cors: A net/http CORS Handler for Go Services
Go net/http configurable handler to handle CORS requests
At a glance
- What is it?
- rs/cors is a middleware that implements the W3C CORS specification for Go's net/http, and its defaults are permissive enough to surprise you. Here is how it installs, what the options actually do, and where it stops being the right tool.
- Who is it for?
- Adopt rs/cors if your service is plain Go net/http or a router that accepts an http.Handler, and you want CORS behaviour set by explicit options rather than by hand-written headers. Do not adopt it if you expect it to guess a safe origin list: AllowedOrigins defaults to *, and the README states that combining that with AllowCredentials is refused rather than reflected, so a wildcard-plus-cookies setup will simply not work unless you write an AllowOriginFunc that returns true.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 119 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 1, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What rs/cors solves, and for whom
Browsers enforce the same-origin policy, so a page served from one origin cannot read a response from another unless that response carries the right Access-Control-* headers. rs/cors is a Go handler that writes those headers for you. It implements the Cross Origin Resource Sharing W3 specification as a net/http handler, which means it slots into the standard library's handler chain and into any router that accepts an http.Handler. The README ships runnable examples for net/http, Goji, Martini, Negroni, Alice, HttpRouter, Gorilla, Buffalo, Gin and Chi, which tells you the intended audience: Go backend engineers running an HTTP API that a browser front end calls from a different host, port or scheme. If your API is consumed only by server-side clients or native apps, CORS is noise and you do not need this.
How the handler wraps your mux and decides on an origin
The mechanism is middleware wrapping. You build a cors.Cors value with cors.New(cors.Options{...}), then call its Handler method on your existing handler. Every request passes through the wrapper, which inspects the Origin header and the method before handing off. The README describes the decision inputs in order of precedence: AllowOriginVaryRequestFunc takes the request and the origin and returns both a boolean and a list of headers used in the decision, so those headers can be added to Vary; AllowOriginRequestFunc takes the request and origin and returns a boolean, and the README marks it deprecated in favour of the Vary variant; AllowOriginFunc takes only the origin string. Setting any of these ignores the ones below it, and AllowOriginFunc ignores AllowedOrigins entirely. That precedence chain is the part most people get wrong. If you set AllowOriginFunc, your AllowedOrigins slice is dead configuration, and nothing in the API warns you. The Vary detail matters for caches: a response whose allowance depends on a header should tell intermediaries that the response varies by that header, and only the AllowOriginVaryRequestFunc form lets you express that.
Installing it and serving a first cross-origin request
The README's Getting Started section assumes Go and a configured GOPATH, then has you create a file called server.go. The import path is github.com/rs/cors, and the example wires cors.Default() around a standard ServeMux. Run these two commands from the directory holding server.go; the first fetches the module, the second starts the server on port 8080.
go get github.com/rs/cors
go run server.goThe README's sample handler responds to every path with a JSON body and sets Content-Type to application/json. To see the CORS headers, the README gives this curl invocation, sending an Origin header and dumping response headers with -D -. The response shown in the README is a 200 with Access-Control-Allow-Origin set and the JSON body.
curl -D - -H 'Origin: http://foo.com' http://localhost:8080/For anything beyond a demo, replace cors.Default() with cors.New and an explicit Options value. The README's parameter example shows the shape: a slice of allowed origins, credentials enabled, and Debug turned on for testing.
c := cors.New(cors.Options{
AllowedOrigins: []string{"http://foo.com", "http://foo.com:8080"},
AllowCredentials: true,
Debug: true,
})
handler = c.Handler(handler)Note the comment the README attaches to Debug: it is for testing, and the documentation suggests considering disabling it in production. cors.Default() is convenient, but the README states its defaults accept all origins with simple methods (GET and POST), so a Default() setup is an open policy, not a starting point you can forget about.
The wildcard-plus-credentials trap and the options that bite
The README documents a deliberate security modification. Configuring AllowedOrigins to * together with AllowCredentials to true used to make the library reflect the request's Origin header, working around a protection in the standard that makes clients refuse that combination. That behaviour was removed via issues #55 and #57. The practical consequence: if you need credentials and a wildcard, the library will not silently give you a reflected origin. The README says you can restore the old behaviour with AllowOriginFunc returning true, and points to #55 for the security implications. That is the correct design, and it is also the most common migration complaint, because code that worked by accident now does not. Two other options deserve attention. OptionsPassthrough tells the preflight handler to let later handlers process OPTIONS; the README says to turn it on if your application handles OPTIONS itself, otherwise your own OPTIONS route never runs. OptionsSuccessStatus defaults to http.StatusNoContent (204) and only matters if a client rejects 204. MaxAge defaults to 0, meaning no max age, so preflight results are not cached and every cross-origin call can trigger an extra round trip. On wildcards in AllowedOrigins, the README notes that only one wildcard is permitted per origin and that usage implies a small performance penalty.
Where rs/cors is the wrong tool
rs/cors is a header-writing layer, not an authentication or authorization system. AllowCredentials true means the browser may attach cookies and HTTP auth to the cross-origin request; it does not validate the caller, and the README frames it purely as a CORS option. If your threat model includes a malicious site that can reach your API with the user's cookies, the origin allowlist is your only defence here, and a wildcard defeats it. The library is also the wrong choice when CORS is not actually the problem. A browser error that looks like CORS is often a 500 from the upstream service, a proxy stripping headers, or a preflight rejected because AllowedHeaders does not list a custom header the client sends. rs/cors will faithfully emit headers on a response that fails for another reason. Finally, if your framework already bundles its own CORS middleware with a configuration format your team knows, adding a second layer means two places where an origin list lives, and the README's precedence rules only apply to this library's options.
How it compares to gorilla/handlers and framework middleware
The closest general-purpose alternative in the Go ecosystem is gorilla/handlers, which bundles CORS alongside other HTTP middleware such as logging and compression in one package. The difference is scope and coupling: gorilla/handlers gives you a handlers.CORS function inside a collection, while rs/cors is a single-purpose module whose entire API is the Options struct and the Handler method. If you already use Gorilla's toolkit, the bundled version avoids a second dependency; if you use Chi, Gin or Buffalo, the README ships an example for each, and both approaches wrap the router the same way. The other alternative is writing the headers yourself in a small middleware, which is viable for a fixed single-origin API but loses the preflight handling, the Vary bookkeeping in AllowOriginVaryRequestFunc, and the credentials protection described above. The README's own framing is the honest summary: it is a net/http handler implementing the W3C specification, and its value is that the specification's edge cases are already encoded.
Maintenance, licence and upgrade cost
The repository is not archived, and the last push was on 2026-06-04. The go.mod declares go 1.23.0 and the module path is github.com/rs/cors, so it is a normal Go module consumed through the standard toolchain. The licence is MIT, which permits commercial and closed-source use and requires preserving the copyright notice and permission text; that is a statement about the licence file, not legal advice, and your own counsel should review anything you redistribute. The module has no third-party dependencies in the go.mod shown, which keeps the upgrade surface small: a version bump changes this library and nothing else. The upgrade cost that does exist is behavioural rather than mechanical. The wildcard-with-credentials change described in the README was a breaking change for anyone relying on the old reflected-origin behaviour, and the README's remedy is to write an AllowOriginFunc. Treat any future option changes the same way: read the options struct, not just the version number.
Editorial conclusion
Adopt rs/cors if your service is plain Go net/http or a router that accepts an http.Handler, and you want CORS behaviour set by explicit options rather than by hand-written headers. Do not adopt it if you expect it to guess a safe origin list: AllowedOrigins defaults to *, and the README states that combining that with AllowCredentials is refused rather than reflected, so a wildcard-plus-cookies setup will simply not work unless you write an AllowOriginFunc that returns true. Before shipping, verify three things in your own configuration: the AllowedOrigins slice, the value of AllowCredentials, and whether Debug is still set to true, since the README marks it as a testing aid to consider disabling in production.
Frequently asked questions
What does CORS stand for in rs/cors?
CORS stands for Cross Origin Resource Sharing. The README describes rs/cors as a net/http handler implementing the Cross Origin Resource Sharing W3 specification in Golang.
How do I install rs/cors in a Go project?
The README's Getting Started section says to install it with go get github.com/rs/cors after installing Go and setting up your GOPATH. The module path in go.mod is github.com/rs/cors and it declares go 1.23.0.
Why does rs/cors not reflect the Origin header when AllowedOrigins is * and AllowCredentials is true?
The README states this behaviour was removed via issues #55 and #57 to avoid a known security issue where the library reflected the request Origin header, working around a protection in the standard. If you depend on the old behaviour, the README says you can restore it with an AllowOriginFunc that returns true.
Does rs/cors work with Gin, Chi or Gorilla?
The README lists runnable examples for net/http, Goji, Martini, Negroni, Alice, HttpRouter, Gorilla, Buffalo, Gin and Chi under examples/. Each example is a separate server file in the repository.
What is the default value of AllowedOrigins in rs/cors?
The README states the default value of AllowedOrigins is *. It also notes that if the special * value is present in the list, all origins will be allowed, and that an origin may contain one wildcard replacing 0 or more characters.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/rs-cors)