Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions eslint.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,7 @@ export default typescript.config([
'@sentry/no-static-translations': 'error',
'@sentry/no-raw-css-in-styled': 'error',
'@sentry/no-styled-shortcut': 'error',
'@sentry/no-trusted-types-sinks': 'error',
'@sentry/no-useless-css-interpolation-semicolon': 'error',
'@sentry/no-unnecessary-use-callback': 'error',
},
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,8 @@
"unrs-resolver"
],
"patchedDependencies": {
"zod@4.3.5": "patches/zod@4.3.5.patch"
"zod@4.3.5": "patches/zod@4.3.5.patch",
"zrender@6.1.0": "patches/zrender@6.1.0.patch"
}
},
"APIMethod": "stub",
Expand Down
83 changes: 83 additions & 0 deletions patches/zrender@6.1.0.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
diff --git a/lib/canvas/Painter.js b/lib/canvas/Painter.js
index 78dc6c8d46eca79366f5190a7c191410f8133816..3c79bc4e8abc8917458a0297159c5a0a307b01c8 100644
--- a/lib/canvas/Painter.js
+++ b/lib/canvas/Painter.js
@@ -111,7 +111,7 @@ var CanvasPainter = (function () {
var rootStyle = root.style;
if (rootStyle) {
util.disableUserSelect(root);
- root.innerHTML = '';
+ root.textContent = '';
}
this.storage = storage;
this._prevDisplayList = [];
@@ -685,7 +685,7 @@ var CanvasPainter = (function () {
});
};
CanvasPainter.prototype.dispose = function () {
- this.root.innerHTML = '';
+ this.root.textContent = '';
this.root =
this.storage =
this._domRoot =
diff --git a/lib/svg/Painter.js b/lib/svg/Painter.js
index 2db019c7e9ac4b13947d0bb9220fdecbfa591d2f..b514a95dd529b76e1bea37953dfd00ed065f4b5a 100644
--- a/lib/svg/Painter.js
+++ b/lib/svg/Painter.js
@@ -187,7 +187,7 @@ var SVGPainter = (function () {
};
SVGPainter.prototype.dispose = function () {
if (this.root) {
- this.root.innerHTML = '';
+ this.root.textContent = '';
}
this._svgDom =
this._viewport =
@@ -198,7 +198,7 @@ var SVGPainter = (function () {
};
SVGPainter.prototype.clear = function () {
if (this._svgDom) {
- this._svgDom.innerHTML = null;
+ this._svgDom.textContent = '';
}
this._oldVNode = null;
};
diff --git a/lib/svg-legacy/Painter.js b/lib/svg-legacy/Painter.js
index 5ca3b0d2fb308a7e9019a5dcb0869ce05ea130db..a818d6be22e8fa7c35634841d02eefd503a24828 100644
--- a/lib/svg-legacy/Painter.js
+++ b/lib/svg-legacy/Painter.js
@@ -258,7 +258,7 @@ var SVGPainter = (function () {
return this._height;
};
SVGPainter.prototype.dispose = function () {
- this.root.innerHTML = '';
+ this.root.textContent = '';
this._svgRoot =
this._backgroundRoot =
this._svgDom =
diff --git a/lib/svg-legacy/helper/ClippathManager.js b/lib/svg-legacy/helper/ClippathManager.js
index d99b65aeec73c96cb3d981a87752467819757517..1383275a0c58c5e86ec029eb05a4eaf18ad5ef44 100644
--- a/lib/svg-legacy/helper/ClippathManager.js
+++ b/lib/svg-legacy/helper/ClippathManager.js
@@ -73,7 +73,7 @@ var ClippathManager = (function (_super) {
}
path.brush(clipPath);
var pathEl = this.getSvgElement(clipPath);
- clipPathEl.innerHTML = '';
+ clipPathEl.textContent = '';
clipPathEl.appendChild(pathEl);
parentEl.setAttribute('clip-path', getIdURL(id));
if (clipPaths.length > 1) {
diff --git a/lib/svg-legacy/helper/GradientManager.js b/lib/svg-legacy/helper/GradientManager.js
index 7d413a137ebdc956c646e293820a745812b98a15..e802f747bae4e6b94271bb840958ee883047f55e 100644
--- a/lib/svg-legacy/helper/GradientManager.js
+++ b/lib/svg-legacy/helper/GradientManager.js
@@ -96,7 +96,7 @@ var GradientManager = (function (_super) {
dom.setAttribute('gradientUnits', gradient.global
? 'userSpaceOnUse'
: 'objectBoundingBox');
- dom.innerHTML = '';
+ dom.textContent = '';
var colors = gradient.colorStops;
for (var i = 0, len = colors.length; i < len; ++i) {
var stop_1 = createElement('stop');
9 changes: 6 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions rspack.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,7 @@ const appConfig: Configuration = {
},
output: {
crossOriginLoading: 'anonymous',
trustedTypes: {policyName: 'sentry-bundler', onPolicyCreationFailure: 'continue'},
// Clean the output dir before emit, but keep the service-worker assets
// emitted by the separate `workerConfig` compiler below. Both compilers
// write to this same `dist` path and run in parallel, so without `keep`
Expand Down
29 changes: 29 additions & 0 deletions src/sentry/conf/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,35 @@ def env(
COOP_REPORT_ONLY = True
COOP_REPORT_TO: str | None = None

# Trusted Types (https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Types_API)
# is delivered on its own Content-Security-Policy-Report-Only header, separate
# from the CSP_* settings, so that turning it on cannot alter the CSP we enforce.
#
# Requires CSP_REPORT_ONLY = False. When the CSP itself is report-only, django-csp
# owns the report-only header name and this middleware cannot claim it; see
# SecurityHeadersMiddleware.add_trusted_types_header.
TRUSTED_TYPES_ENABLED = False

# Policy names the browser will accept `createPolicy()` calls for. Anything not
# listed here fails to register, so keep it in sync with the policies the
# frontend actually installs.
TRUSTED_TYPES_POLICIES = [
"dompurify",
"sentry-script-url",
"sentry-bundler",
]

# Where violation reports are sent. With no report URI the browser still reports
# to the devtools console, which is enough for local work.
TRUSTED_TYPES_REPORT_URI: str | None = None

if ENVIRONMENT == "development":
# Collect locally. Also needs CSP_REPORT_ONLY = False, otherwise django-csp
# owns the report-only header and the middleware stands down.
TRUSTED_TYPES_ENABLED = True
# The dev-server error overlay installs its own policy.
TRUSTED_TYPES_POLICIES += ["rspack-dev-server#overlay"]

STATIC_ROOT = os.path.realpath(os.path.join(PROJECT_ROOT, "static"))
STATIC_URL = "/_static/{version}/"
# webpack assets live at a different URL that is unversioned
Expand Down
43 changes: 43 additions & 0 deletions src/sentry/middleware/security.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import logging

from django.conf import settings
from django.utils.deprecation import MiddlewareMixin
from rest_framework.request import Request
from rest_framework.response import Response

from sentry.utils import json

logger = logging.getLogger(__name__)

TRUSTED_TYPES_HEADER = "Content-Security-Policy-Report-Only"

_warned_about_report_only_csp = False


class SecurityHeadersMiddleware(MiddlewareMixin):
"""
Expand All @@ -17,6 +25,8 @@
response.setdefault("X-Content-Type-Options", "nosniff")
response.setdefault("X-XSS-Protection", "1; mode=block")

self.add_trusted_types_header(response)

# Add COOP and Report-To headers if COOP_ENABLED
if getattr(settings, "COOP_ENABLED", False):
coop_report_to = getattr(settings, "COOP_REPORT_TO", None)
Expand All @@ -41,3 +51,36 @@
)

return response

def add_trusted_types_header(self, response: Response) -> None:
if not getattr(settings, "TRUSTED_TYPES_ENABLED", False):
return

# This middleware runs before CSPMiddleware on the response path, and
# django-csp bails when its header name is already set. So when the CSP is
# itself report-only we would be dropping the entire CSP to deliver
# Trusted Types, which is never worth it.
if getattr(settings, "CSP_REPORT_ONLY", False):
global _warned_about_report_only_csp
if not _warned_about_report_only_csp:
_warned_about_report_only_csp = True
logger.warning(
"trusted_types.disabled_by_report_only_csp",
extra={"header": TRUSTED_TYPES_HEADER},
)
return

if TRUSTED_TYPES_HEADER in response:
return

directives = ["require-trusted-types-for 'script'"]

policies = getattr(settings, "TRUSTED_TYPES_POLICIES", None) or []
if policies:
directives.append("trusted-types " + " ".join(policies))

report_uri = getattr(settings, "TRUSTED_TYPES_REPORT_URI", None)
if report_uri:
directives.append(f"report-uri {report_uri}")

response[TRUSTED_TYPES_HEADER] = "; ".join(directives)
2 changes: 2 additions & 0 deletions static/app/bootstrap/commonInitialization.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {MotionGlobalConfig} from 'framer-motion';
import {IS_ACCEPTANCE_TEST, NODE_ENV} from 'sentry/constants';
import {ConfigStore} from 'sentry/stores/configStore';
import type {Config} from 'sentry/types/system';
import {installTrustedTypesPolicies} from 'sentry/utils/trustedTypes';

if (IS_ACCEPTANCE_TEST || NODE_ENV === 'test') {
MotionGlobalConfig.skipAnimations = true;
Expand All @@ -13,5 +14,6 @@ export function commonInitialization(config: Config) {
import(/* webpackMode: "eager" */ 'sentry/utils/silenceReactUnsafeWarnings');
}

installTrustedTypesPolicies();
ConfigStore.loadInitialData(config);
}
4 changes: 4 additions & 0 deletions static/app/bootstrap/renderDom.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type React from 'react';
import {createRoot} from 'react-dom/client';

import {captureSplashLoader} from 'sentry/utils/splashLoader';

export function renderDom<T extends React.ComponentType<any>>(
Component: T,
container: string,
Expand All @@ -14,6 +16,8 @@ export function renderDom<T extends React.ComponentType<any>>(
return;
}

captureSplashLoader(rootEl);

const root = createRoot(rootEl);
root.render(<Component {...props} />);
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export function DefaultCodeBlock({children, lang}: {children: string; lang?: str
}

export function DefaultHtmlBlock({html}: {html: string}) {
// eslint-disable-next-line @sentry/no-trusted-types-sinks -- callers pass sanitizeHtml() output; see token.tsx
return <span dangerouslySetInnerHTML={{__html: html}} />;
}

Expand Down
Loading
Loading