diff --git a/coderd/coderd.go b/coderd/coderd.go index 37e7d22a6d080..0aab4b26262ea 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -1532,17 +1532,19 @@ func New(options *Options) *API { // Add CSP headers to all static assets and pages. CSP headers only affect // browsers, so these don't make sense on api routes. - cspMW := httpmw.CSPHeaders(options.Telemetry.Enabled(), func() []string { - if api.DeploymentValues.Dangerous.AllowAllCors { - // In this mode, allow all external requests - return []string{"*"} - } - if f := api.WorkspaceProxyHostsFn.Load(); f != nil { - return (*f)() - } - // By default we do not add extra websocket connections to the CSP - return []string{} - }, additionalCSPHeaders) + cspMW := httpmw.CSPHeaders( + api.Experiments, + options.Telemetry.Enabled(), func() []string { + if api.DeploymentValues.Dangerous.AllowAllCors { + // In this mode, allow all external requests + return []string{"*"} + } + if f := api.WorkspaceProxyHostsFn.Load(); f != nil { + return (*f)() + } + // By default we do not add extra websocket connections to the CSP + return []string{} + }, additionalCSPHeaders) // Static file handler must be wrapped with HSTS handler if the // StrictTransportSecurityAge is set. We only need to set this header on diff --git a/coderd/httpmw/csp.go b/coderd/httpmw/csp.go index e6864b7448c41..afc19ddaf0c1f 100644 --- a/coderd/httpmw/csp.go +++ b/coderd/httpmw/csp.go @@ -4,6 +4,8 @@ import ( "fmt" "net/http" "strings" + + "github.com/coder/coder/v2/codersdk" ) // cspDirectives is a map of all csp fetch directives to their values. @@ -37,6 +39,7 @@ const ( CSPDirectiveFormAction CSPFetchDirective = "form-action" CSPDirectiveMediaSrc CSPFetchDirective = "media-src" CSPFrameAncestors CSPFetchDirective = "frame-ancestors" + CSPFrameSource CSPFetchDirective = "frame-src" CSPDirectiveWorkerSrc CSPFetchDirective = "worker-src" ) @@ -55,7 +58,7 @@ const ( // Example: https://github.com/coder/coder/issues/15118 // //nolint:revive -func CSPHeaders(telemetry bool, websocketHosts func() []string, staticAdditions map[CSPFetchDirective][]string) func(next http.Handler) http.Handler { +func CSPHeaders(experiments codersdk.Experiments, telemetry bool, websocketHosts func() []string, staticAdditions map[CSPFetchDirective][]string) func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Content-Security-Policy disables loading certain content types and can prevent XSS injections. @@ -88,13 +91,21 @@ func CSPHeaders(telemetry bool, websocketHosts func() []string, staticAdditions CSPDirectiveMediaSrc: {"'self'"}, // Report all violations back to the server to log CSPDirectiveReportURI: {"/api/v2/csp/reports"}, - CSPFrameAncestors: {"'none'"}, // Only scripts can manipulate the dom. This prevents someone from // naming themselves something like ''. // "require-trusted-types-for" : []string{"'script'"}, } + if experiments.Enabled(codersdk.ExperimentAITasks) { + // AI tasks use iframe embeds of local apps. + // TODO: Handle region domains too, not just path based apps + cspSrcs.Append(CSPFrameAncestors, `'self'`) + cspSrcs.Append(CSPFrameSource, `'self'`) + } else { + cspSrcs.Append(CSPFrameAncestors, `'none'`) + } + if telemetry { // If telemetry is enabled, we report to coder.com. cspSrcs.Append(CSPDirectiveConnectSrc, "https://coder.com") diff --git a/coderd/httpmw/csp_test.go b/coderd/httpmw/csp_test.go index c5000d3a29370..bef6ab196eb6e 100644 --- a/coderd/httpmw/csp_test.go +++ b/coderd/httpmw/csp_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/require" "github.com/coder/coder/v2/coderd/httpmw" + "github.com/coder/coder/v2/codersdk" ) func TestCSPConnect(t *testing.T) { @@ -20,7 +21,7 @@ func TestCSPConnect(t *testing.T) { r := httptest.NewRequest(http.MethodGet, "/", nil) rw := httptest.NewRecorder() - httpmw.CSPHeaders(false, func() []string { + httpmw.CSPHeaders(codersdk.Experiments{}, false, func() []string { return expected }, map[httpmw.CSPFetchDirective][]string{ httpmw.CSPDirectiveMediaSrc: expectedMedia,