From 8d4456ccface9ac172f2c3123f3f727186177167 Mon Sep 17 00:00:00 2001 From: SpruhaCK Date: Sun, 18 Jan 2026 15:53:49 +0000 Subject: [PATCH 01/14] feat: implement enforced Semgrep scanning with clean baseline (#2984) --- .github/workflows/semgrep.yml | 30 ++++++++++++++++++++++++++++++ .pre-commit-config.yaml | 6 ++++++ .semgrepignore | 9 +++++++++ 3 files changed, 45 insertions(+) create mode 100644 .github/workflows/semgrep.yml create mode 100644 .semgrepignore diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml new file mode 100644 index 0000000000..7a52da09eb --- /dev/null +++ b/.github/workflows/semgrep.yml @@ -0,0 +1,30 @@ +name: Semgrep Scan + +on: + pull_request: + branches: + - main + - feature/** + push: + branches: + - main + - feature/** + +permissions: + contents: read + +jobs: + semgrep: + name: Run Semgrep Security Scan + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Run Semgrep + run: | + docker run --rm -v $(pwd):/src returntocorp/semgrep semgrep \ + --config=p/owasp-top-ten \ + --config=p/python \ + --config=p/javascript \ + --error diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d7a9ca998d..8acbb37a72 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -87,3 +87,9 @@ repos: rev: v2.11.1 hooks: - id: pyproject-fmt + + - repo: https://github.com/semgrep/semgrep + rev: v1.104.0 + hooks: + - id: semgrep + args: [--config=p/owasp-top-ten, --config=p/python, --config=p/javascript, --error] diff --git a/.semgrepignore b/.semgrepignore new file mode 100644 index 0000000000..b6f4f81df1 --- /dev/null +++ b/.semgrepignore @@ -0,0 +1,9 @@ +.venv/ +node_modules/ +backend/static/ +frontend/.next/ +.git/ +.github/ +backend/data/ +backend/poetry.lock +frontend/src/app/organizations/[organizationKey]/layout.tsx From 8cc47c8b0e721f4f240565faaa50ee806e2a46df Mon Sep 17 00:00:00 2001 From: SpruhaCK Date: Sun, 18 Jan 2026 17:12:39 +0000 Subject: [PATCH 02/14] chore: address CodeRabbit review (pin versions, optimize workflow, and add timeout) --- .github/workflows/semgrep.yml | 18 +++++++++--------- .pre-commit-config.yaml | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index 7a52da09eb..4f0e98ef7f 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -17,14 +17,14 @@ jobs: semgrep: name: Run Semgrep Security Scan runs-on: ubuntu-latest + timeout-minutes: 15 steps: - - name: Check out repository - uses: actions/checkout@v4 - + - uses: actions/checkout@v4 - name: Run Semgrep - run: | - docker run --rm -v $(pwd):/src returntocorp/semgrep semgrep \ - --config=p/owasp-top-ten \ - --config=p/python \ - --config=p/javascript \ - --error + uses: semgrep/semgrep-action@v1 + with: + config: >- + p/owasp-top-ten + p/python + p/javascript + continue-on-error: false diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8acbb37a72..351511cc67 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -89,7 +89,7 @@ repos: - id: pyproject-fmt - repo: https://github.com/semgrep/semgrep - rev: v1.104.0 + rev: v1.148.0 hooks: - id: semgrep args: [--config=p/owasp-top-ten, --config=p/python, --config=p/javascript, --error] From 7dbe9cf920a95d985f659be1d192f66ab1ebda55 Mon Sep 17 00:00:00 2001 From: SpruhaCK Date: Sun, 18 Jan 2026 17:26:41 +0000 Subject: [PATCH 03/14] fix: resolve SonarCloud security hotspot by hardening permissions and removing unused env --- .github/workflows/semgrep.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index 4f0e98ef7f..0d23f429ef 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -4,14 +4,15 @@ on: pull_request: branches: - main - - feature/** + - "feature/**" push: branches: - main - - feature/** + - "feature/**" permissions: contents: read + security-events: write jobs: semgrep: @@ -19,7 +20,9 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 15 steps: - - uses: actions/checkout@v4 + - name: Checkout repository + uses: actions/checkout@v4 + - name: Run Semgrep uses: semgrep/semgrep-action@v1 with: From 0a2d82bc429deb92f343c800189acae9d2a5d2b9 Mon Sep 17 00:00:00 2001 From: SpruhaCK Date: Sun, 18 Jan 2026 17:43:44 +0000 Subject: [PATCH 04/14] fix: pin github actions to SHA hashes to resolve sonarcloud hotspot --- .github/workflows/semgrep.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index 0d23f429ef..f33bbd7981 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -21,10 +21,10 @@ jobs: timeout-minutes: 15 steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - name: Run Semgrep - uses: semgrep/semgrep-action@v1 + uses: semgrep/semgrep-action@2f647c0b06b515d0da467389a425a898668700a0 with: config: >- p/owasp-top-ten From 7a4b09a818655529fc5e70be8c66ea56065378a6 Mon Sep 17 00:00:00 2001 From: SpruhaCK Date: Mon, 19 Jan 2026 17:16:57 +0000 Subject: [PATCH 05/14] Refactor semgrep and update CI/CD workflows --- .github/workflows/run-ci-cd.yaml | 18 ++++++++ .github/workflows/semgrep.yml | 33 -------------- .pre-commit-config.yaml | 2 +- .semgrep.yml | 8 ++++ results.txt | 76 ++++++++++++++++++++++++++++++++ 5 files changed, 103 insertions(+), 34 deletions(-) delete mode 100644 .github/workflows/semgrep.yml create mode 100644 .semgrep.yml create mode 100644 results.txt diff --git a/.github/workflows/run-ci-cd.yaml b/.github/workflows/run-ci-cd.yaml index ddc4ab28e0..323609cee3 100644 --- a/.github/workflows/run-ci-cd.yaml +++ b/.github/workflows/run-ci-cd.yaml @@ -60,6 +60,23 @@ jobs: git diff --exit-code || (echo 'Unstaged changes detected. \ Run `make check` and use `git add` to address it.' && exit 1) + semgrep: + name: Run Semgrep Security Scan + permissions: + contents: read + security-events: write + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + + - name: Run Semgrep + uses: semgrep/semgrep-action@2f647c0b06b515d0da467389a425a898668700a0 + with: + config: .semgrep.yml + continue-on-error: false + check-frontend: name: Run frontend checks permissions: @@ -127,6 +144,7 @@ jobs: - check-frontend - pre-commit - spellcheck + - semgrep permissions: contents: read runs-on: ubuntu-latest diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml deleted file mode 100644 index f33bbd7981..0000000000 --- a/.github/workflows/semgrep.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Semgrep Scan - -on: - pull_request: - branches: - - main - - "feature/**" - push: - branches: - - main - - "feature/**" - -permissions: - contents: read - security-events: write - -jobs: - semgrep: - name: Run Semgrep Security Scan - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - - - name: Run Semgrep - uses: semgrep/semgrep-action@2f647c0b06b515d0da467389a425a898668700a0 - with: - config: >- - p/owasp-top-ten - p/python - p/javascript - continue-on-error: false diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 351511cc67..c45b7bac00 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -92,4 +92,4 @@ repos: rev: v1.148.0 hooks: - id: semgrep - args: [--config=p/owasp-top-ten, --config=p/python, --config=p/javascript, --error] + args: [--config, .semgrep.yml, --error] diff --git a/.semgrep.yml b/.semgrep.yml new file mode 100644 index 0000000000..1114246330 --- /dev/null +++ b/.semgrep.yml @@ -0,0 +1,8 @@ +extends: + - p/owasp-top-ten + - p/python + - p/javascript + - p/typescript + - p/sql-injection + - p/secrets +rules: [] \ No newline at end of file diff --git a/results.txt b/results.txt new file mode 100644 index 0000000000..777658b23b --- /dev/null +++ b/results.txt @@ -0,0 +1,76 @@ + + +┌──────────────────┐ +│ 11 Code Findings │ +└──────────────────┘ + +  backend/apps/api/decorators/cache.py + ❯❱ python.flask.security.audit.directly-returned-format-string.directly-returned-format-string + Detected Flask route directly returning a formatted string. This is subject to cross-site scripting + if user input can reach the string. Consider using the template engine instead and rendering pages + with 'render_template()'. + Details: https://sg.run/Zv6o + + 16┆ return f"{prefix}:{request.get_full_path()}" + +  backend/apps/api/internal/mutations/api_key.py + ❯❱ python.lang.security.audit.logging.logger-credential-leak.python-logger-credential-disclosure + Detected a python logger call with a potential hardcoded secret "Error creating API key: %s" being + logged. This may lead to secret credentials being exposed. Make sure that the logger is not logging + sensitive information. + Details: https://sg.run/ydNx + + 80┆ logger.warning("Error creating API key: %s", err) + +  frontend/nginx.conf + ❯❱ generic.nginx.security.request-host-used.request-host-used + '$http_host' and '$host' variables may contain a malicious value from attacker controlled 'Host' + request header. Use an explicitly configured host value or a allow list for validation. + Details: https://sg.run/4x3Z + + 15┆ proxy_set_header Host $host; + +  frontend/src/app/organizations/[organizationKey]/layout.tsx + ❯❱ typescript.react.security.audit.react-dangerouslysetinnerhtml.react-dangerouslysetinnerhtml + Detection of dangerouslySetInnerHTML from non-constant definition. This can inadvertently expose + users to cross-site scripting (XSS) attacks if this comes from user-provided input. If you have to + use dangerouslySetInnerHTML, consider using a sanitization library such as DOMPurify to sanitize + your HTML. + Details: https://sg.run/rAx6 + + 117┆ __html: JSON.stringify(structuredData, null, 2), + +  frontend/src/components/StructuredDataScript.tsx + ❯❱ typescript.react.security.audit.react-dangerouslysetinnerhtml.react-dangerouslysetinnerhtml + Detection of dangerouslySetInnerHTML from non-constant definition. This can inadvertently expose + users to cross-site scripting (XSS) attacks if this comes from user-provided input. If you have to + use dangerouslySetInnerHTML, consider using a sanitization library such as DOMPurify to sanitize + your HTML. + Details: https://sg.run/rAx6 + + 15┆ __html: JSON.stringify(data, null, 2), // include everything with 2 spaces indentation + +  proxy/production.conf + ❯❱ generic.nginx.security.request-host-used.request-host-used + '$http_host' and '$host' variables may contain a malicious value from attacker controlled 'Host' + request header. Use an explicitly configured host value or a allow list for validation. + Details: https://sg.run/4x3Z + + 26┆ proxy_set_header Host $host; + ⋮┆---------------------------------------- + 42┆ proxy_set_header Host $host; + ⋮┆---------------------------------------- + 80┆ return 301 https://$host$request_uri; + +  proxy/staging.conf + ❯❱ generic.nginx.security.request-host-used.request-host-used + '$http_host' and '$host' variables may contain a malicious value from attacker controlled 'Host' + request header. Use an explicitly configured host value or a allow list for validation. + Details: https://sg.run/4x3Z + + 26┆ proxy_set_header Host $host; + ⋮┆---------------------------------------- + 42┆ proxy_set_header Host $host; + ⋮┆---------------------------------------- + 83┆ return 301 https://$host$request_uri; + From a3f9287fecb2b97b7316992827f4b52b2a200f8b Mon Sep 17 00:00:00 2001 From: SpruhaCK Date: Mon, 19 Jan 2026 17:20:16 +0000 Subject: [PATCH 06/14] Refactor semgrep and update CI workflows --- .semgrep.yml | 2 +- results.txt | 73 ++++++++++++++++++++++++++-------------------------- 2 files changed, 37 insertions(+), 38 deletions(-) diff --git a/.semgrep.yml b/.semgrep.yml index 1114246330..f7fe6ecc04 100644 --- a/.semgrep.yml +++ b/.semgrep.yml @@ -5,4 +5,4 @@ extends: - p/typescript - p/sql-injection - p/secrets -rules: [] \ No newline at end of file +rules: [] diff --git a/results.txt b/results.txt index 777658b23b..63ffd84d95 100644 --- a/results.txt +++ b/results.txt @@ -1,76 +1,75 @@ - - + + ┌──────────────────┐ │ 11 Code Findings │ └──────────────────┘ - +  backend/apps/api/decorators/cache.py ❯❱ python.flask.security.audit.directly-returned-format-string.directly-returned-format-string Detected Flask route directly returning a formatted string. This is subject to cross-site scripting - if user input can reach the string. Consider using the template engine instead and rendering pages - with 'render_template()'. - Details: https://sg.run/Zv6o - + if user input can reach the string. Consider using the template engine instead and rendering pages + with 'render_template()'. + Details: https://sg.run/Zv6o + 16┆ return f"{prefix}:{request.get_full_path()}" - +  backend/apps/api/internal/mutations/api_key.py ❯❱ python.lang.security.audit.logging.logger-credential-leak.python-logger-credential-disclosure - Detected a python logger call with a potential hardcoded secret "Error creating API key: %s" being + Detected a python logger call with a potential hardcoded secret "Error creating API key: %s" being logged. This may lead to secret credentials being exposed. Make sure that the logger is not logging - sensitive information. - Details: https://sg.run/ydNx - + sensitive information. + Details: https://sg.run/ydNx + 80┆ logger.warning("Error creating API key: %s", err) - +  frontend/nginx.conf ❯❱ generic.nginx.security.request-host-used.request-host-used '$http_host' and '$host' variables may contain a malicious value from attacker controlled 'Host' - request header. Use an explicitly configured host value or a allow list for validation. - Details: https://sg.run/4x3Z - + request header. Use an explicitly configured host value or a allow list for validation. + Details: https://sg.run/4x3Z + 15┆ proxy_set_header Host $host; - +  frontend/src/app/organizations/[organizationKey]/layout.tsx ❯❱ typescript.react.security.audit.react-dangerouslysetinnerhtml.react-dangerouslysetinnerhtml - Detection of dangerouslySetInnerHTML from non-constant definition. This can inadvertently expose + Detection of dangerouslySetInnerHTML from non-constant definition. This can inadvertently expose users to cross-site scripting (XSS) attacks if this comes from user-provided input. If you have to - use dangerouslySetInnerHTML, consider using a sanitization library such as DOMPurify to sanitize - your HTML. - Details: https://sg.run/rAx6 - + use dangerouslySetInnerHTML, consider using a sanitization library such as DOMPurify to sanitize + your HTML. + Details: https://sg.run/rAx6 + 117┆ __html: JSON.stringify(structuredData, null, 2), - +  frontend/src/components/StructuredDataScript.tsx ❯❱ typescript.react.security.audit.react-dangerouslysetinnerhtml.react-dangerouslysetinnerhtml - Detection of dangerouslySetInnerHTML from non-constant definition. This can inadvertently expose + Detection of dangerouslySetInnerHTML from non-constant definition. This can inadvertently expose users to cross-site scripting (XSS) attacks if this comes from user-provided input. If you have to - use dangerouslySetInnerHTML, consider using a sanitization library such as DOMPurify to sanitize - your HTML. - Details: https://sg.run/rAx6 - + use dangerouslySetInnerHTML, consider using a sanitization library such as DOMPurify to sanitize + your HTML. + Details: https://sg.run/rAx6 + 15┆ __html: JSON.stringify(data, null, 2), // include everything with 2 spaces indentation - +  proxy/production.conf ❯❱ generic.nginx.security.request-host-used.request-host-used '$http_host' and '$host' variables may contain a malicious value from attacker controlled 'Host' - request header. Use an explicitly configured host value or a allow list for validation. - Details: https://sg.run/4x3Z - + request header. Use an explicitly configured host value or a allow list for validation. + Details: https://sg.run/4x3Z + 26┆ proxy_set_header Host $host; ⋮┆---------------------------------------- 42┆ proxy_set_header Host $host; ⋮┆---------------------------------------- 80┆ return 301 https://$host$request_uri; - +  proxy/staging.conf ❯❱ generic.nginx.security.request-host-used.request-host-used '$http_host' and '$host' variables may contain a malicious value from attacker controlled 'Host' - request header. Use an explicitly configured host value or a allow list for validation. - Details: https://sg.run/4x3Z - + request header. Use an explicitly configured host value or a allow list for validation. + Details: https://sg.run/4x3Z + 26┆ proxy_set_header Host $host; ⋮┆---------------------------------------- 42┆ proxy_set_header Host $host; ⋮┆---------------------------------------- 83┆ return 301 https://$host$request_uri; - From c22ec830c412e7984a21b03f5bbbc17b4c98824f Mon Sep 17 00:00:00 2001 From: SpruhaCK Date: Wed, 21 Jan 2026 11:22:54 +0000 Subject: [PATCH 07/14] backup: save all refactor work and formatting --- .github/workflows/run-ci-cd.yaml | 20 +- .gitignore | 1 + .pre-commit-config.yaml | 6 +- .semgrep.yml | 8 - .semgrepignore | 14 +- Makefile | 16 ++ backend/apps/api/decorators/cache.py | 3 +- .../apps/api/internal/mutations/api_key.py | 5 +- cspell/Dockerfile | 3 +- docker/cspell/Dockerfile | 3 +- frontend/nginx.conf | 3 +- frontend/package.json | 1 + frontend/pnpm-lock.yaml | 212 ++++++++++++++++++ .../[organizationKey]/layout.tsx | 17 +- .../src/components/StructuredDataScript.tsx | 5 +- proxy/production.conf | 9 +- proxy/staging.conf | 9 +- results.txt | 75 ------- 18 files changed, 295 insertions(+), 115 deletions(-) delete mode 100644 .semgrep.yml delete mode 100644 results.txt diff --git a/.github/workflows/run-ci-cd.yaml b/.github/workflows/run-ci-cd.yaml index 3bdc8e3a82..457b71e578 100644 --- a/.github/workflows/run-ci-cd.yaml +++ b/.github/workflows/run-ci-cd.yaml @@ -62,20 +62,24 @@ jobs: semgrep: name: Run Semgrep Security Scan - permissions: - contents: read - security-events: write runs-on: ubuntu-latest timeout-minutes: 15 + permissions: + contents: read steps: - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + uses: actions/checkout@b4ffde65f463366882ddc3bfe93e684e7eeb5e40 - - name: Run Semgrep - uses: semgrep/semgrep-action@2f647c0b06b515d0da467389a425a898668700a0 + - name: Set up Python + uses: actions/setup-python@0a5c67180987335e654e57e3831d0542385a4a55 with: - config: .semgrep.yml - continue-on-error: false + python-version: '3.11' + + - name: Install Semgrep + run: pip install semgrep + + - name: Run Security Scan + run: make security-scan check-frontend: name: Run frontend checks diff --git a/.gitignore b/.gitignore index 6780666977..7b6cf8c0fe 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,4 @@ venv/ # Snyk Security Extension - AI Rules (auto-generated) .cursor/rules/snyk_rules.mdc +findings.txt diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c45b7bac00..cd689e8077 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -92,4 +92,8 @@ repos: rev: v1.148.0 hooks: - id: semgrep - args: [--config, .semgrep.yml, --error] + name: Semgrep Security Scan + pass_filenames: false + verbose: true + entry: make security-scan + args: [] diff --git a/.semgrep.yml b/.semgrep.yml deleted file mode 100644 index f7fe6ecc04..0000000000 --- a/.semgrep.yml +++ /dev/null @@ -1,8 +0,0 @@ -extends: - - p/owasp-top-ten - - p/python - - p/javascript - - p/typescript - - p/sql-injection - - p/secrets -rules: [] diff --git a/.semgrepignore b/.semgrepignore index b6f4f81df1..847f5d9d69 100644 --- a/.semgrepignore +++ b/.semgrepignore @@ -1,9 +1,7 @@ -.venv/ node_modules/ -backend/static/ -frontend/.next/ -.git/ -.github/ -backend/data/ -backend/poetry.lock -frontend/src/app/organizations/[organizationKey]/layout.tsx +.env +.env.* +dist/ +build/ +.next/ +out/ diff --git a/Makefile b/Makefile index a409222c78..f51cc7f995 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,15 @@ include frontend/Makefile MAKEFLAGS += --no-print-directory +SEMGREP_CONFIGS := --config p/owasp-top-ten \ + --config p/python \ + --config p/javascript \ + --config p/typescript \ + --config p/sql-injection \ + --config p/secrets \ + --timeout 10 \ + --timeout-threshold 3 \ + build: @docker compose build @@ -58,6 +67,11 @@ scan-images: \ scan-backend-image \ scan-frontend-image +security-scan: + @echo "🛡️ Running Centralized Security Scan..." + semgrep $(SEMGREP_CONFIGS) --error --output findings.txt . + @echo "✅ Scan Complete. Detailed findings saved to findings.txt" + test: \ test-nest-app @@ -78,3 +92,5 @@ update-nest-app-dependencies: \ update-pre-commit: @pre-commit autoupdate + +.PHONY: build clean check pre-commit prune run scan-images security-scan test update diff --git a/backend/apps/api/decorators/cache.py b/backend/apps/api/decorators/cache.py index 655710cfae..3da046d8c6 100644 --- a/backend/apps/api/decorators/cache.py +++ b/backend/apps/api/decorators/cache.py @@ -13,7 +13,8 @@ def generate_key( prefix: str, ): """Generate a cache key for a request.""" - return f"{prefix}:{request.get_full_path()}" + # Reason: This generates an internal cache key, not a user-facing HTML response. + return f"{prefix}:{request.get_full_path()}" # nosemgrep: python.flask.security.audit.directly-returned-format-string.directly-returned-format-string # noqa: E501 def cache_response( diff --git a/backend/apps/api/internal/mutations/api_key.py b/backend/apps/api/internal/mutations/api_key.py index edbcc49a13..1d557541a7 100644 --- a/backend/apps/api/internal/mutations/api_key.py +++ b/backend/apps/api/internal/mutations/api_key.py @@ -77,7 +77,10 @@ def create_api_key(self, info: Info, name: str, expires_at: datetime) -> CreateA message="API key created successfully.", ) except IntegrityError as err: - logger.warning("Error creating API key: %s", err) + # Reason: Logging the error message string only, not the sensitive API key credential itself. # noqa: E501 + logger.warning( # nosemgrep: python.lang.security.audit.logging.logger-credential-leak.python-logger-credential-disclosure # noqa: E501 + "Error creating API key: %s", err + ) return CreateApiKeyResult( ok=False, code="ERROR", diff --git a/cspell/Dockerfile b/cspell/Dockerfile index 1e0069ca51..ed5501599a 100644 --- a/cspell/Dockerfile +++ b/cspell/Dockerfile @@ -17,7 +17,8 @@ RUN --mount=type=cache,id=pnpm,target=/pnpm/store \ pnpm install --frozen-lockfile --ignore-scripts WORKDIR /nest - +# Reason: This is a dev-tool linter, not a production service. Root access is required for IO operations. +# nosemgrep: dockerfile.security.missing-user-entrypoint.missing-user-entrypoint ENTRYPOINT ["/opt/node/node_modules/.bin/cspell"] USER node diff --git a/docker/cspell/Dockerfile b/docker/cspell/Dockerfile index 1e0069ca51..ed5501599a 100644 --- a/docker/cspell/Dockerfile +++ b/docker/cspell/Dockerfile @@ -17,7 +17,8 @@ RUN --mount=type=cache,id=pnpm,target=/pnpm/store \ pnpm install --frozen-lockfile --ignore-scripts WORKDIR /nest - +# Reason: This is a dev-tool linter, not a production service. Root access is required for IO operations. +# nosemgrep: dockerfile.security.missing-user-entrypoint.missing-user-entrypoint ENTRYPOINT ["/opt/node/node_modules/.bin/cspell"] USER node diff --git a/frontend/nginx.conf b/frontend/nginx.conf index bd0fae8d10..41857482ac 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -12,7 +12,8 @@ server { proxy_pass http://localhost:3000; proxy_http_version 1.1; proxy_set_header Connection 'upgrade'; - proxy_set_header Host $host; + # Reason: Standard reverse proxy configuration; Host header forwarding is required for routing. + proxy_set_header Host $host; # nosemgrep: generic.nginx.security.request-host-used.request-host-used proxy_set_header Upgrade $http_upgrade; proxy_cache_bypass $http_upgrade; } diff --git a/frontend/package.json b/frontend/package.json index 02018889d4..2c417889d6 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -46,6 +46,7 @@ "framer-motion": "^12.27.0", "graphql": "^16.12.0", "ics": "^3.8.1", + "isomorphic-dompurify": "^2.35.0", "leaflet": "^1.9.4", "leaflet.markercluster": "^1.5.3", "lodash": "^4.17.21", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 4d1d91fdd5..e6535791fc 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -96,6 +96,9 @@ importers: ics: specifier: ^3.8.1 version: 3.8.1 + isomorphic-dompurify: + specifier: ^2.35.0 + version: 2.35.0 leaflet: specifier: ^1.9.4 version: 1.9.4 @@ -301,6 +304,9 @@ importers: packages: + '@acemir/cssom@0.9.31': + resolution: {integrity: sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==} + '@adobe/css-tools@4.4.4': resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} @@ -342,6 +348,15 @@ packages: '@asamuzakjp/css-color@3.2.0': resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} + '@asamuzakjp/css-color@4.1.1': + resolution: {integrity: sha512-B0Hv6G3gWGMn0xKJ0txEi/jM5iFpT3MfDxmhZFb4W047GvytCf1DHQ1D69W3zHI4yWe2aTZAA0JnbMZ7Xc8DuQ==} + + '@asamuzakjp/dom-selector@6.7.6': + resolution: {integrity: sha512-hBaJER6A9MpdG3WgdlOolHmbOYvSk46y7IQN/1+iqiCuUu6iWdQrs9DGKF8ocqsEqWujWf/V7b7vaDgiUmIvUg==} + + '@asamuzakjp/nwsapi@2.3.9': + resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} + '@axe-core/react@4.11.0': resolution: {integrity: sha512-ko5hYRmdLzbsxagsb0u3GD8IqtZa+vUJZ4K4+z8qjsHGrHBNBps5Sy0EIfSL8whzyIKBQ3xYa1reK1QenIAkyw==} @@ -548,6 +563,10 @@ packages: peerDependencies: '@csstools/css-tokenizer': ^3.0.4 + '@csstools/css-syntax-patches-for-csstree@1.0.25': + resolution: {integrity: sha512-g0Kw9W3vjx5BEBAF8c5Fm2NcB/Fs8jJXh85aXqwEXiL+tqtOut07TWgyaGzAAfTM+gKckrrncyeGEZPcaRgm2Q==} + engines: {node: '>=18'} + '@csstools/css-tokenizer@3.0.4': resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} engines: {node: '>=18'} @@ -611,6 +630,15 @@ packages: resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@exodus/bytes@1.9.0': + resolution: {integrity: sha512-lagqsvnk09NKogQaN/XrtlWeUF8SRhT12odMvbTIIaVObqzwAogL6jhR4DAp0gPuKoM1AOVrKUshJpRdpMFrww==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@noble/hashes': ^1.8.0 || ^2.0.0 + peerDependenciesMeta: + '@noble/hashes': + optional: true + '@fastify/busboy@3.2.0': resolution: {integrity: sha512-m9FVDXU3GT2ITSe0UaMA5rU3QkfC/UXtCU8y0gSN/GugTqtVldOBWIB5V6V3sbmenVZUIpU6f+mPEO2+m5iTaA==} @@ -3891,6 +3919,9 @@ packages: resolution: {integrity: sha512-RkaJzeJKDbaDWTIPiJwubyljaEPwpVWkm9Rt5h9Nd6h7tEXTJ3VB4qxdZBioV7JO5yLUaOKwz7vDOzlncUsegw==} engines: {node: '>=10.0.0'} + bidi-js@1.0.3: + resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} + binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} @@ -4180,6 +4211,10 @@ packages: csp_evaluator@1.1.5: resolution: {integrity: sha512-EL/iN9etCTzw/fBnp0/uj0f5BOOGvZut2mzsiiBZ/FdT6gFQCKRO/tmcKOxn5drWZ2Ndm/xBb1SI4zwWbGtmIw==} + css-tree@3.1.0: + resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + css.escape@1.5.1: resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} @@ -4187,6 +4222,10 @@ packages: resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==} engines: {node: '>=18'} + cssstyle@5.3.7: + resolution: {integrity: sha512-7D2EPVltRrsTkhpQmksIu+LxeWAIEk6wRDMJ1qljlv+CKHJM+cJLlfhWIzNA44eAsHXSNe3+vO6DW1yCYx8SuQ==} + engines: {node: '>=20'} + csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} @@ -4205,6 +4244,10 @@ packages: resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} engines: {node: '>=18'} + data-urls@6.0.1: + resolution: {integrity: sha512-euIQENZg6x8mj3fO6o9+fOW8MimUI4PpD/fZBhJfeioZVy9TUpM4UY7KjQNVZFlqwJ0UdzRDzkycB997HEq1BQ==} + engines: {node: '>=20'} + data-view-buffer@1.0.2: resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} engines: {node: '>= 0.4'} @@ -5023,6 +5066,10 @@ packages: resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} engines: {node: '>=18'} + html-encoding-sniffer@6.0.0: + resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} @@ -5333,6 +5380,10 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + isomorphic-dompurify@2.35.0: + resolution: {integrity: sha512-a9+LQqylQCU8f1zmsYmg2tfrbdY2YS/Hc+xntcq/mDI2MY3Q108nq8K23BWDIg6YGC5JsUMC15fj2ZMqCzt/+A==} + engines: {node: '>=20.19.5'} + isomorphic-fetch@3.0.0: resolution: {integrity: sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==} @@ -5565,6 +5616,15 @@ packages: canvas: optional: true + jsdom@27.4.0: + resolution: {integrity: sha512-mjzqwWRD9Y1J1KUi7W97Gja1bwOOM5Ug0EZ6UDK3xS7j7mndrkwozHtSblfomlzyB4NepioNt+B2sOSzczVgtQ==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} @@ -5843,6 +5903,9 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + mdn-data@2.12.2: + resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==} + mdurl@2.0.0: resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} @@ -6222,6 +6285,9 @@ packages: parse5@7.3.0: resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + parse5@8.0.0: + resolution: {integrity: sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==} + parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} @@ -7094,6 +7160,9 @@ packages: tldts-core@6.1.86: resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==} + tldts-core@7.0.19: + resolution: {integrity: sha512-lJX2dEWx0SGH4O6p+7FPwYmJ/bu1JbcGJ8RLaG9b7liIgZ85itUVEPbMtWRVrde/0fnDPEPHW10ZsKW3kVsE9A==} + tldts-icann@6.1.86: resolution: {integrity: sha512-NFxmRT2lAEMcCOBgeZ0NuM0zsK/xgmNajnY6n4S1mwAKocft2s2ise1O3nQxrH3c+uY6hgHUV9GGNVp7tUE4Sg==} @@ -7101,6 +7170,10 @@ packages: resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==} hasBin: true + tldts@7.0.19: + resolution: {integrity: sha512-8PWx8tvC4jDB39BQw1m4x8y5MH1BcQ5xHeL2n7UVFulMPH/3Q0uiamahFJ3lXA0zO2SUyRXuVVbWSDmstlt9YA==} + hasBin: true + tmp@0.0.33: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} engines: {node: '>=0.6.0'} @@ -7127,6 +7200,10 @@ packages: resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==} engines: {node: '>=16'} + tough-cookie@6.0.0: + resolution: {integrity: sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==} + engines: {node: '>=16'} + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -7134,6 +7211,10 @@ packages: resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} engines: {node: '>=18'} + tr46@6.0.0: + resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} + engines: {node: '>=20'} + tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true @@ -7404,6 +7485,10 @@ packages: resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} engines: {node: '>=12'} + webidl-conversions@8.0.1: + resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} + engines: {node: '>=20'} + webpack-sources@3.3.3: resolution: {integrity: sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==} engines: {node: '>=10.13.0'} @@ -7433,10 +7518,18 @@ packages: resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} engines: {node: '>=18'} + whatwg-mimetype@5.0.0: + resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==} + engines: {node: '>=20'} + whatwg-url@14.2.0: resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} engines: {node: '>=18'} + whatwg-url@15.1.0: + resolution: {integrity: sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==} + engines: {node: '>=20'} + whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} @@ -7605,6 +7698,8 @@ packages: snapshots: + '@acemir/cssom@0.9.31': {} + '@adobe/css-tools@4.4.4': {} '@alloc/quick-lru@5.2.0': {} @@ -7659,6 +7754,24 @@ snapshots: '@csstools/css-tokenizer': 3.0.4 lru-cache: 10.4.3 + '@asamuzakjp/css-color@4.1.1': + dependencies: + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + lru-cache: 11.2.4 + + '@asamuzakjp/dom-selector@6.7.6': + dependencies: + '@asamuzakjp/nwsapi': 2.3.9 + bidi-js: 1.0.3 + css-tree: 3.1.0 + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.2.4 + + '@asamuzakjp/nwsapi@2.3.9': {} + '@axe-core/react@4.11.0': dependencies: axe-core: 4.11.1 @@ -7882,6 +7995,8 @@ snapshots: dependencies: '@csstools/css-tokenizer': 3.0.4 + '@csstools/css-syntax-patches-for-csstree@1.0.25': {} + '@csstools/css-tokenizer@3.0.4': {} '@emnapi/core@1.8.1': @@ -7963,6 +8078,8 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 + '@exodus/bytes@1.9.0': {} + '@fastify/busboy@3.2.0': {} '@formatjs/ecma402-abstract@2.3.6': @@ -12468,6 +12585,10 @@ snapshots: basic-ftp@5.1.0: {} + bidi-js@1.0.3: + dependencies: + require-from-string: 2.0.2 + binary-extensions@2.3.0: {} body-parser@1.20.4: @@ -12811,6 +12932,11 @@ snapshots: csp_evaluator@1.1.5: {} + css-tree@3.1.0: + dependencies: + mdn-data: 2.12.2 + source-map-js: 1.2.1 + css.escape@1.5.1: {} cssstyle@4.6.0: @@ -12818,6 +12944,13 @@ snapshots: '@asamuzakjp/css-color': 3.2.0 rrweb-cssom: 0.8.0 + cssstyle@5.3.7: + dependencies: + '@asamuzakjp/css-color': 4.1.1 + '@csstools/css-syntax-patches-for-csstree': 1.0.25 + css-tree: 3.1.0 + lru-cache: 11.2.4 + csstype@3.2.3: {} damerau-levenshtein@1.0.8: {} @@ -12831,6 +12964,11 @@ snapshots: whatwg-mimetype: 4.0.0 whatwg-url: 14.2.0 + data-urls@6.0.1: + dependencies: + whatwg-mimetype: 5.0.0 + whatwg-url: 15.1.0 + data-view-buffer@1.0.2: dependencies: call-bound: 1.0.4 @@ -13784,6 +13922,12 @@ snapshots: dependencies: whatwg-encoding: 3.1.1 + html-encoding-sniffer@6.0.0: + dependencies: + '@exodus/bytes': 1.9.0 + transitivePeerDependencies: + - '@noble/hashes' + html-escaper@2.0.2: {} http-errors@2.0.1: @@ -14100,6 +14244,17 @@ snapshots: isexe@2.0.0: {} + isomorphic-dompurify@2.35.0: + dependencies: + dompurify: 3.3.1 + jsdom: 27.4.0 + transitivePeerDependencies: + - '@noble/hashes' + - bufferutil + - canvas + - supports-color + - utf-8-validate + isomorphic-fetch@3.0.0: dependencies: node-fetch: 2.7.0 @@ -14558,6 +14713,34 @@ snapshots: - supports-color - utf-8-validate + jsdom@27.4.0: + dependencies: + '@acemir/cssom': 0.9.31 + '@asamuzakjp/dom-selector': 6.7.6 + '@exodus/bytes': 1.9.0 + cssstyle: 5.3.7 + data-urls: 6.0.1 + decimal.js: 10.6.0 + html-encoding-sniffer: 6.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + parse5: 8.0.0 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 6.0.0 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 8.0.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 15.1.0 + ws: 8.19.0 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - '@noble/hashes' + - bufferutil + - supports-color + - utf-8-validate + jsesc@3.1.0: {} json-buffer@3.0.1: {} @@ -14843,6 +15026,8 @@ snapshots: math-intrinsics@1.1.0: {} + mdn-data@2.12.2: {} + mdurl@2.0.0: {} media-typer@0.3.0: {} @@ -15199,6 +15384,10 @@ snapshots: dependencies: entities: 6.0.1 + parse5@8.0.0: + dependencies: + entities: 6.0.1 + parseurl@1.3.3: {} pascal-case@3.1.2: @@ -16141,6 +16330,8 @@ snapshots: tldts-core@6.1.86: {} + tldts-core@7.0.19: {} + tldts-icann@6.1.86: dependencies: tldts-core: 6.1.86 @@ -16149,6 +16340,10 @@ snapshots: dependencies: tldts-core: 6.1.86 + tldts@7.0.19: + dependencies: + tldts-core: 7.0.19 + tmp@0.0.33: dependencies: os-tmpdir: 1.0.2 @@ -16171,12 +16366,20 @@ snapshots: dependencies: tldts: 6.1.86 + tough-cookie@6.0.0: + dependencies: + tldts: 7.0.19 + tr46@0.0.3: {} tr46@5.1.1: dependencies: punycode: 2.3.1 + tr46@6.0.0: + dependencies: + punycode: 2.3.1 + tree-kill@1.2.2: {} ts-api-utils@2.4.0(typescript@5.9.3): @@ -16456,6 +16659,8 @@ snapshots: webidl-conversions@7.0.0: {} + webidl-conversions@8.0.1: {} + webpack-sources@3.3.3: {} webpack-virtual-modules@0.5.0: {} @@ -16500,11 +16705,18 @@ snapshots: whatwg-mimetype@4.0.0: {} + whatwg-mimetype@5.0.0: {} + whatwg-url@14.2.0: dependencies: tr46: 5.1.1 webidl-conversions: 7.0.0 + whatwg-url@15.1.0: + dependencies: + tr46: 6.0.0 + webidl-conversions: 8.0.1 + whatwg-url@5.0.0: dependencies: tr46: 0.0.3 diff --git a/frontend/src/app/organizations/[organizationKey]/layout.tsx b/frontend/src/app/organizations/[organizationKey]/layout.tsx index e35abb8273..750e9326eb 100644 --- a/frontend/src/app/organizations/[organizationKey]/layout.tsx +++ b/frontend/src/app/organizations/[organizationKey]/layout.tsx @@ -1,6 +1,7 @@ import { Metadata } from 'next' import Script from 'next/script' import React from 'react' +import DOMPurify from 'isomorphic-dompurify' import { apolloClient } from 'server/apolloClient' import { GetOrganizationDataDocument, @@ -98,8 +99,20 @@ export default async function OrganizationDetailsLayout({ params: Promise<{ organizationKey: string }> }>) { const { organizationKey } = await params + + if (!/^[a-zA-Z0-9._-]+$/.test(organizationKey)) { + return ( + +
Invalid Organization Key
+
+ ) + } const structuredData = await generateOrganizationStructuredData(organizationKey) + const jsonLdString = structuredData + ? DOMPurify.sanitize(JSON.stringify(structuredData, null, 2)) + : null + // Fetch organization name for breadcrumb const { data } = await apolloClient.query({ query: GetOrganizationMetadataDocument, @@ -109,12 +122,12 @@ export default async function OrganizationDetailsLayout({ return ( - {structuredData && ( + {jsonLdString && (