From edb2314ad3ffc79d41f4d55eb7eda00331f9535a Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Thu, 29 May 2025 09:15:58 -0500 Subject: [PATCH 1/4] feat: allow iframing urls on the same domain as the deployment Used for AI tasks --- coderd/httpmw/csp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/httpmw/csp.go b/coderd/httpmw/csp.go index e6864b7448c41..9d893e7230f56 100644 --- a/coderd/httpmw/csp.go +++ b/coderd/httpmw/csp.go @@ -88,7 +88,7 @@ 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'"}, + CSPFrameAncestors: {"'self'"}, // Only scripts can manipulate the dom. This prevents someone from // naming themselves something like ''. From 648c7070d4db5ed997d5a3cfac934ddc6efd7421 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Thu, 29 May 2025 09:42:37 -0500 Subject: [PATCH 2/4] chore: toggle with experiment --- coderd/coderd.go | 24 +++++++++++++----------- coderd/httpmw/csp.go | 13 +++++++++++-- 2 files changed, 24 insertions(+), 13 deletions(-) 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 9d893e7230f56..37f44cda4e916 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. @@ -55,7 +57,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 +90,20 @@ 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: {"'self'"}, // 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'`) + } else { + cspSrcs.Append(CSPFrameAncestors, `'none'`) + } + if telemetry { // If telemetry is enabled, we report to coder.com. cspSrcs.Append(CSPDirectiveConnectSrc, "https://coder.com") From b3ac3eece9118ed4b37fa2a4ed9b03486e06d4d7 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Thu, 29 May 2025 09:47:01 -0500 Subject: [PATCH 3/4] fixup test --- coderd/httpmw/csp_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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, From 95f3b617ada28ac810e68936386ca02a52bc3441 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Thu, 29 May 2025 09:50:22 -0500 Subject: [PATCH 4/4] also add frame-src --- coderd/httpmw/csp.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/coderd/httpmw/csp.go b/coderd/httpmw/csp.go index 37f44cda4e916..afc19ddaf0c1f 100644 --- a/coderd/httpmw/csp.go +++ b/coderd/httpmw/csp.go @@ -39,6 +39,7 @@ const ( CSPDirectiveFormAction CSPFetchDirective = "form-action" CSPDirectiveMediaSrc CSPFetchDirective = "media-src" CSPFrameAncestors CSPFetchDirective = "frame-ancestors" + CSPFrameSource CSPFetchDirective = "frame-src" CSPDirectiveWorkerSrc CSPFetchDirective = "worker-src" ) @@ -100,6 +101,7 @@ func CSPHeaders(experiments codersdk.Experiments, telemetry bool, websocketHosts // 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'`) }