diff --git a/api/poetry.lock b/api/poetry.lock index ae3568eeb..e1173a3b6 100644 --- a/api/poetry.lock +++ b/api/poetry.lock @@ -1001,21 +1001,23 @@ bcrypt = ["bcrypt"] [[package]] name = "django-csp" -version = "3.8" +version = "4.0b2" description = "Django Content Security Policy support." optional = false python-versions = "*" files = [ - {file = "django_csp-3.8-py3-none-any.whl", hash = "sha256:19b2978b03fcd73517d7d67acbc04fbbcaec0facc3e83baa502965892d1e0719"}, - {file = "django_csp-3.8.tar.gz", hash = "sha256:ef0f1a9f7d8da68ae6e169c02e9ac661c0ecf04db70e0d1d85640512a68471c0"}, + {file = "django_csp-4.0b2-py3-none-any.whl", hash = "sha256:48b159f9eddd9a2140df483e224c76ec4bf6118010e67d5b62279dd897b6b88a"}, + {file = "django_csp-4.0b2.tar.gz", hash = "sha256:3d4912655d5b97a6b9bd74c3fc78af534d531f5e72c17c4565083b4bd872ff09"}, ] [package.dependencies] -Django = ">=3.2" +django = ">=4.2" [package.extras] +dev = ["django-stubs[compatible-mypy]", "jinja2 (>=2.9.6)", "mypy", "pre-commit", "pytest", "pytest-cov", "pytest-django", "pytest-ruff", "sphinx", "sphinx-rtd-theme", "tox", "tox-gh-actions", "types-setuptools"] jinja2 = ["jinja2 (>=2.9.6)"] tests = ["jinja2 (>=2.9.6)", "pytest", "pytest-cov", "pytest-django", "pytest-ruff"] +typing = ["django-stubs[compatible-mypy]", "jinja2 (>=2.9.6)", "mypy", "pytest", "pytest-django", "types-setuptools"] [[package]] name = "django-debug-toolbar" @@ -3788,4 +3790,4 @@ test = ["pytest"] [metadata] lock-version = "2.0" python-versions = "~3.12" -content-hash = "cde09c98f3477b7dc84d81d25a16e082670ea9a7f6f55f3c3ac59f5f69a3136a" +content-hash = "3a993f9f10f1ef5ae9b89cfd9f8d78e2e2681300e8d36f5b3254e37d08ca0c6f" diff --git a/api/pyproject.toml b/api/pyproject.toml index f7f7c41e4..7bb33b2cd 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -12,7 +12,7 @@ djangorestframework = "~3.15.0" weasyprint = "~62.3" django-phonenumber-field = { version = "^8.0.0", extras = ["phonenumberslite"] } -django-csp = "^3.7" +django-csp = "^4.0b2" django-storages = "^1.13.2" drf-nested-routers = "^0.94.1" django-filter = "^24.1" diff --git a/api/src/reportcreator_api/conf/settings.py b/api/src/reportcreator_api/conf/settings.py index 839da4dae..be7f589ca 100644 --- a/api/src/reportcreator_api/conf/settings.py +++ b/api/src/reportcreator_api/conf/settings.py @@ -19,6 +19,7 @@ import fido2.features import redis +from csp.constants import NONE, SELF, UNSAFE_INLINE from decouple import Csv, config from kombu import Queue @@ -407,22 +408,34 @@ SECURE_REFERRER_POLICY = 'same-origin' X_FRAME_OPTIONS = 'SAMEORIGIN' -CSP_DEFAULT_SRC = ["'none'"] -CSP_IMG_SRC = ["'self'", "data:"] -CSP_FONT_SRC = ["'self'"] -CSP_WORKER_SRC = ["'self'"] -CSP_CONNECT_SRC = ["'self'", "data:"] -CSP_FRAME_SRC = ["'self'"] -CSP_FRAME_ANCESTORS = ["'self'"] -CSP_FORM_ACTION = ["'self'"] -# nuxt, vuetify and markdown preview use inline styles -CSP_STYLE_SRC = ["'self'", "'unsafe-inline'"] -# unsafe-inline: - -CSP_SCRIPT_SRC = [ - "'self'", - "'sha256-vfPLwqW0BNyGGLG6upxgxsXF+K7Jp/V2hJGlbPt7NJY='", # hash of nuxt inline script injected in index.html -] +CONTENT_SECURITY_POLICY = { + 'DIRECTIVES': { + 'default-src': [NONE], + 'img-src': [SELF, 'data:'], + 'font-src': [SELF], + 'worker-src': [SELF], + 'connect-src': [SELF, 'data:'], + 'frame-src': [SELF], + 'frame-ancestors': [SELF], + 'form-action': [SELF], + # nuxt, vuetify and markdown preview use inline styles + 'style-src': [SELF, UNSAFE_INLINE], + # hash of nuxt inline script injected in index.html + 'script-src': [SELF, "'sha256-vfPLwqW0BNyGGLG6upxgxsXF+K7Jp/V2hJGlbPt7NJY='"], + 'require-trusted-types-for': ["'script'"], + 'trusted-types': [ + 'default', # required for vuetify, unhead, mermaid + 'worker-url', # load web workers via import URL + 'vue', # used by vue and markdown + 'dompurify', # used by mermaid + "'allow-duplicates'", # dompurify is used twice as dependency + # monaco-editor policies + 'defaultWorkerFactory', 'tokenizeToString', 'standaloneColorizer', + 'editorViewLayer', 'domLineBreaksComputer', 'domLineBreaksComputer', + 'diffEditorWidget', 'editorGhostText', 'diffReview', 'stickyScrollViewLayer', + ], + }, +} PERMISSIONS_POLICY = { 'publickey-credentials-get': '(self)', @@ -689,7 +702,7 @@ def __bool__(self): 'serviceVersion': 'dev', } if ELASTIC_APM_RUM_ENABLED: - CSP_CONNECT_SRC.append(ELASTIC_APM_RUM_CONFIG['serverUrl']) + CONTENT_SECURITY_POLICY['DIRECTIVES']['connect-src'].append(ELASTIC_APM_RUM_CONFIG['serverUrl']) if DEBUG: diff --git a/frontend/NOTICE b/frontend/NOTICE index ded85b380..0af707afc 100644 --- a/frontend/NOTICE +++ b/frontend/NOTICE @@ -7638,6 +7638,31 @@ MIT SOFTWARE +@types/dompurify 3.0.5 +MIT + MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE + + @types/estree 1.0.5 MIT MIT License @@ -7938,6 +7963,31 @@ MIT SOFTWARE +@types/trusted-types 2.0.7 +MIT + MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE + + @types/unist 2.0.11 MIT MIT License diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 02c329b5c..01e01afce 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -18,6 +18,7 @@ "@pinia/nuxt": "^0.5.1", "@vueuse/nuxt": "^11.0.1", "date-fns": "^4.1.0", + "dompurify": "^3.1.6", "emoji-mart-vue-fast": "^15.0.0", "js-file-download": "^0.4.12", "lodash-es": "^4.17.21", @@ -36,6 +37,7 @@ "devDependencies": { "@nuxt/eslint": "^0.5.5", "@nuxt/test-utils": "^3.10.0", + "@types/dompurify": "^3.0.5", "@types/lodash-es": "^4.17.0", "@types/node": "^20", "@types/uuid": "^10.0.0", @@ -49,7 +51,6 @@ } }, "../packages/markdown": { - "name": "reportcreator-markdown", "version": "0.1.0", "dependencies": { "@codemirror/commands": "~6.6", @@ -3252,6 +3253,15 @@ "node": ">=10.13.0" } }, + "node_modules/@types/dompurify": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz", + "integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==", + "dev": true, + "dependencies": { + "@types/trusted-types": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", @@ -3305,6 +3315,12 @@ "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==" }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "dev": true + }, "node_modules/@types/uuid": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", @@ -5651,6 +5667,11 @@ "url": "https://github.com/fb55/domhandler?sponsor=1" } }, + "node_modules/dompurify": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.6.tgz", + "integrity": "sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ==" + }, "node_modules/domutils": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 376001f20..0ebee3c08 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -37,7 +37,8 @@ "zxcvbn": "^4.4.2", "@github/webauthn-json": "^2.1.1", "@elastic/apm-rum-vue": "^2.1.5", - "date-fns": "^4.1.0" + "date-fns": "^4.1.0", + "dompurify": "^3.1.6" }, "devDependencies": { "@nuxt/eslint": "^0.5.5", @@ -46,6 +47,7 @@ "@types/zxcvbn": "^4.4.2", "@types/lodash-es": "^4.17.0", "@types/uuid": "^10.0.0", + "@types/dompurify": "^3.0.5", "sass": "^1.69.3", "eslint": "^9.0.0", "license-checker-rseidelsohn": "^4.2.8", diff --git a/frontend/src/components/Design/CodeEditor.vue b/frontend/src/components/Design/CodeEditor.vue index 35f52b47a..9610ebf89 100644 --- a/frontend/src/components/Design/CodeEditor.vue +++ b/frontend/src/components/Design/CodeEditor.vue @@ -4,19 +4,20 @@