Skip to content

Commit 241e92f

Browse files
committed
[server] Add Go 1.25 CORS protection
1 parent c64d178 commit 241e92f

File tree

4 files changed

+61
-4
lines changed

4 files changed

+61
-4
lines changed

build.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ type builder struct {
2929
*InstanceStats
3030
m *minify.M
3131
routes []InstanceRoute
32+
router *http.ServeMux
3233
}
3334

3435
type InstanceStats struct {

config.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ type Config struct {
3030
// Whether html templates are minified at load time. Default `true`.
3131
Minify bool `json:"minify,omitempty" arg:"-m,--minify" default:"true"`
3232

33+
CrossOrigin CrossOriginConfig `json:"crossorigin"`
34+
3335
Databases []DotDBConfig `json:"databases" arg:"-"`
3436
Flags []DotFlagsConfig `json:"flags" arg:"-"`
3537
Directories []DotDirConfig `json:"directories" arg:"-"`
@@ -53,6 +55,12 @@ type Config struct {
5355
Logger *slog.Logger `json:"-" arg:"-"`
5456
}
5557

58+
type CrossOriginConfig struct {
59+
Disabled bool `json:"disabled" arg:"--disable-cors" default:"false"`
60+
TrustedOrigins []string `json:"trusted_origins" arg:"--trusted-origin,separate"`
61+
InsecureBypassPatterns []string `json:"insecure_bypass_patterns" arg:"--insecure-bypass-pattern,separate"`
62+
}
63+
5664
// FillDefaults sets default values for unset fields
5765
func (config *Config) Defaults() *Config {
5866
if config.TemplatesDir == "" {

instance.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,11 @@ import (
4040
//
4141
// See also [Server] which manages instances and enables reloading them.
4242
type Instance struct {
43+
id int64
44+
handler http.Handler
45+
4346
config Config
44-
id int64
4547

46-
router *http.ServeMux
4748
files map[string]*fileInfo
4849
templates *template.Template
4950
funcs template.FuncMap
@@ -199,6 +200,17 @@ func (config *Config) Instance(cfgs ...Option) (*Instance, *InstanceStats, []Ins
199200
slog.Int("staticFilesAlternateEncodings", build.StaticFilesAlternateEncodings),
200201
))
201202

203+
if !config.CrossOrigin.Disabled {
204+
handler := http.NewCrossOriginProtection()
205+
for _, origin := range config.CrossOrigin.TrustedOrigins {
206+
handler.AddTrustedOrigin(origin)
207+
}
208+
for _, pattern := range config.CrossOrigin.InsecureBypassPatterns {
209+
handler.AddInsecureBypassPattern(pattern)
210+
}
211+
build.handler = handler.Handler(build.router)
212+
}
213+
202214
return build.Instance, build.InstanceStats, build.routes, nil
203215
}
204216

@@ -251,7 +263,7 @@ func (instance *Instance) ServeHTTP(w http.ResponseWriter, r *http.Request) {
251263
log := instance.config.Logger.With(slog.Group("serve",
252264
slog.String("requestid", rid),
253265
))
254-
log.LogAttrs(r.Context(), slog.LevelDebug, "serving request",
266+
log.LogAttrs(ctx, slog.LevelDebug, "serving request",
255267
slog.String("user-agent", r.Header.Get("User-Agent")),
256268
slog.String("method", r.Method),
257269
slog.String("requestPath", r.URL.Path),
@@ -261,7 +273,7 @@ func (instance *Instance) ServeHTTP(w http.ResponseWriter, r *http.Request) {
261273
r = r.WithContext(ctx)
262274
metrics := httpsnoop.CaptureMetrics(instance.router, w, r)
263275

264-
log.LogAttrs(r.Context(), levelDebug2, "request served",
276+
log.LogAttrs(ctx, levelDebug2, "request served",
265277
slog.Group("response",
266278
slog.Duration("duration", metrics.Duration),
267279
slog.Int("statusCode", metrics.Code),

test/tests/cors.hurl

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Test CORS protection with untrusted origin
2+
OPTIONS http://localhost:8080/
3+
Origin: http://evil.com
4+
Access-Control-Request-Method: GET
5+
6+
HTTP 405
7+
8+
# Test CORS protection with simple cross-origin request
9+
GET http://localhost:8080/
10+
Origin: http://evil.com
11+
12+
HTTP 200
13+
[Asserts]
14+
header "Access-Control-Allow-Origin" not exists
15+
16+
# Test preflight with trusted origin (but none are trusted, so 405)
17+
# Since no trusted origins, even "null" or same should be protected if specified.
18+
# But for localhost:8080, if hurl is localhost:8080 to server localhost:8080, same origin, no protection needed.
19+
# But to test, using cross-origin.
20+
21+
# Test OPTIONS preflight with Access-Control-Request-Method
22+
OPTIONS http://localhost:8080/assets/file.txt
23+
Origin: http://example.com
24+
Access-Control-Request-Method: GET
25+
26+
HTTP 405
27+
28+
# Test GET with cross-origin
29+
GET http://localhost:8080/assets/file.txt
30+
Origin: http://example.com
31+
32+
HTTP 200
33+
Content-Type: text/plain; charset=utf-8
34+
[Asserts]
35+
header "Access-Control-Allow-Origin" not exists
36+
body == "testing"

0 commit comments

Comments
 (0)