@@ -23,29 +23,39 @@ func (s cspDirectives) Append(d CSPFetchDirective, values ...string) {
23
23
type CSPFetchDirective string
24
24
25
25
const (
26
- cspDirectiveDefaultSrc = "default-src"
27
- cspDirectiveConnectSrc = "connect-src"
28
- cspDirectiveChildSrc = "child-src"
29
- cspDirectiveScriptSrc = "script-src"
30
- cspDirectiveFontSrc = "font-src"
31
- cspDirectiveStyleSrc = "style-src"
32
- cspDirectiveObjectSrc = "object-src"
33
- cspDirectiveManifestSrc = "manifest-src"
34
- cspDirectiveFrameSrc = "frame-src"
35
- cspDirectiveImgSrc = "img-src"
36
- cspDirectiveReportURI = "report-uri"
37
- cspDirectiveFormAction = "form-action"
38
- cspDirectiveMediaSrc = "media-src"
39
- cspFrameAncestors = "frame-ancestors"
40
- cspDirectiveWorkerSrc = "worker-src"
26
+ CSPDirectiveDefaultSrc CSPFetchDirective = "default-src"
27
+ CSPDirectiveConnectSrc CSPFetchDirective = "connect-src"
28
+ CSPDirectiveChildSrc CSPFetchDirective = "child-src"
29
+ CSPDirectiveScriptSrc CSPFetchDirective = "script-src"
30
+ CSPDirectiveFontSrc CSPFetchDirective = "font-src"
31
+ CSPDirectiveStyleSrc CSPFetchDirective = "style-src"
32
+ CSPDirectiveObjectSrc CSPFetchDirective = "object-src"
33
+ CSPDirectiveManifestSrc CSPFetchDirective = "manifest-src"
34
+ CSPDirectiveFrameSrc CSPFetchDirective = "frame-src"
35
+ CSPDirectiveImgSrc CSPFetchDirective = "img-src"
36
+ CSPDirectiveReportURI CSPFetchDirective = "report-uri"
37
+ CSPDirectiveFormAction CSPFetchDirective = "form-action"
38
+ CSPDirectiveMediaSrc CSPFetchDirective = "media-src"
39
+ CSPFrameAncestors CSPFetchDirective = "frame-ancestors"
40
+ CSPDirectiveWorkerSrc CSPFetchDirective = "worker-src"
41
41
)
42
42
43
43
// CSPHeaders returns a middleware that sets the Content-Security-Policy header
44
- // for coderd. It takes a function that allows adding supported external websocket
45
- // hosts. This is primarily to support the terminal connecting to a workspace proxy.
44
+ // for coderd.
45
+ //
46
+ // Arguments:
47
+ // - websocketHosts: a function that returns a list of supported external websocket hosts.
48
+ // This is to support the terminal connecting to a workspace proxy.
49
+ // The origin of the terminal request does not match the url of the proxy,
50
+ // so the CSP list of allowed hosts must be dynamic and match the current
51
+ // available proxy urls.
52
+ // - staticAdditions: a map of CSP directives to append to the default CSP headers.
53
+ // Used to allow specific static additions to the CSP headers. Allows some niche
54
+ // use cases, such as embedding Coder in an iframe.
55
+ // Example: https://github.com/coder/coder/issues/15118
46
56
//
47
57
//nolint:revive
48
- func CSPHeaders (telemetry bool , websocketHosts func () []string ) func (next http.Handler ) http.Handler {
58
+ func CSPHeaders (telemetry bool , websocketHosts func () []string , staticAdditions map [ CSPFetchDirective ][] string ) func (next http.Handler ) http.Handler {
49
59
return func (next http.Handler ) http.Handler {
50
60
return http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
51
61
// Content-Security-Policy disables loading certain content types and can prevent XSS injections.
@@ -55,30 +65,30 @@ func CSPHeaders(telemetry bool, websocketHosts func() []string) func(next http.H
55
65
// The list of CSP options: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/default-src
56
66
cspSrcs := cspDirectives {
57
67
// All omitted fetch csp srcs default to this.
58
- cspDirectiveDefaultSrc : {"'self'" },
59
- cspDirectiveConnectSrc : {"'self'" },
60
- cspDirectiveChildSrc : {"'self'" },
68
+ CSPDirectiveDefaultSrc : {"'self'" },
69
+ CSPDirectiveConnectSrc : {"'self'" },
70
+ CSPDirectiveChildSrc : {"'self'" },
61
71
// https://github.com/suren-atoyan/monaco-react/issues/168
62
- cspDirectiveScriptSrc : {"'self'" },
63
- cspDirectiveStyleSrc : {"'self' 'unsafe-inline'" },
72
+ CSPDirectiveScriptSrc : {"'self'" },
73
+ CSPDirectiveStyleSrc : {"'self' 'unsafe-inline'" },
64
74
// data: is used by monaco editor on FE for Syntax Highlight
65
- cspDirectiveFontSrc : {"'self' data:" },
66
- cspDirectiveWorkerSrc : {"'self' blob:" },
75
+ CSPDirectiveFontSrc : {"'self' data:" },
76
+ CSPDirectiveWorkerSrc : {"'self' blob:" },
67
77
// object-src is needed to support code-server
68
- cspDirectiveObjectSrc : {"'self'" },
78
+ CSPDirectiveObjectSrc : {"'self'" },
69
79
// blob: for loading the pwa manifest for code-server
70
- cspDirectiveManifestSrc : {"'self' blob:" },
71
- cspDirectiveFrameSrc : {"'self'" },
80
+ CSPDirectiveManifestSrc : {"'self' blob:" },
81
+ CSPDirectiveFrameSrc : {"'self'" },
72
82
// data: for loading base64 encoded icons for generic applications.
73
83
// https: allows loading images from external sources. This is not ideal
74
84
// but is required for the templates page that renders readmes.
75
85
// We should find a better solution in the future.
76
- cspDirectiveImgSrc : {"'self' https: data:" },
77
- cspDirectiveFormAction : {"'self'" },
78
- cspDirectiveMediaSrc : {"'self'" },
86
+ CSPDirectiveImgSrc : {"'self' https: data:" },
87
+ CSPDirectiveFormAction : {"'self'" },
88
+ CSPDirectiveMediaSrc : {"'self'" },
79
89
// Report all violations back to the server to log
80
- cspDirectiveReportURI : {"/api/v2/csp/reports" },
81
- cspFrameAncestors : {"'none'" },
90
+ CSPDirectiveReportURI : {"/api/v2/csp/reports" },
91
+ CSPFrameAncestors : {"'none'" },
82
92
83
93
// Only scripts can manipulate the dom. This prevents someone from
84
94
// naming themselves something like '<svg onload="alert(/cross-site-scripting/)" />'.
@@ -87,7 +97,7 @@ func CSPHeaders(telemetry bool, websocketHosts func() []string) func(next http.H
87
97
88
98
if telemetry {
89
99
// If telemetry is enabled, we report to coder.com.
90
- cspSrcs .Append (cspDirectiveConnectSrc , "https://coder.com" )
100
+ cspSrcs .Append (CSPDirectiveConnectSrc , "https://coder.com" )
91
101
}
92
102
93
103
// This extra connect-src addition is required to support old webkit
@@ -102,7 +112,7 @@ func CSPHeaders(telemetry bool, websocketHosts func() []string) func(next http.H
102
112
// We can add both ws:// and wss:// as browsers do not let https
103
113
// pages to connect to non-tls websocket connections. So this
104
114
// supports both http & https webpages.
105
- cspSrcs .Append (cspDirectiveConnectSrc , fmt .Sprintf ("wss://%[1]s ws://%[1]s" , host ))
115
+ cspSrcs .Append (CSPDirectiveConnectSrc , fmt .Sprintf ("wss://%[1]s ws://%[1]s" , host ))
106
116
}
107
117
108
118
// The terminal requires a websocket connection to the workspace proxy.
@@ -112,15 +122,19 @@ func CSPHeaders(telemetry bool, websocketHosts func() []string) func(next http.H
112
122
for _ , extraHost := range extraConnect {
113
123
if extraHost == "*" {
114
124
// '*' means all
115
- cspSrcs .Append (cspDirectiveConnectSrc , "*" )
125
+ cspSrcs .Append (CSPDirectiveConnectSrc , "*" )
116
126
continue
117
127
}
118
- cspSrcs .Append (cspDirectiveConnectSrc , fmt .Sprintf ("wss://%[1]s ws://%[1]s" , extraHost ))
128
+ cspSrcs .Append (CSPDirectiveConnectSrc , fmt .Sprintf ("wss://%[1]s ws://%[1]s" , extraHost ))
119
129
// We also require this to make http/https requests to the workspace proxy for latency checking.
120
- cspSrcs .Append (cspDirectiveConnectSrc , fmt .Sprintf ("https://%[1]s http://%[1]s" , extraHost ))
130
+ cspSrcs .Append (CSPDirectiveConnectSrc , fmt .Sprintf ("https://%[1]s http://%[1]s" , extraHost ))
121
131
}
122
132
}
123
133
134
+ for directive , values := range staticAdditions {
135
+ cspSrcs .Append (directive , values ... )
136
+ }
137
+
124
138
var csp strings.Builder
125
139
for src , vals := range cspSrcs {
126
140
_ , _ = fmt .Fprintf (& csp , "%s %s; " , src , strings .Join (vals , " " ))
0 commit comments