diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 347f7fc0..2259f715 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -123,7 +123,7 @@ jobs: PY - name: Set up Node for native package preparation verification if: ${{ contains(fromJSON(needs.classify.outputs.go_plan).test, 'cyberagent-workbench/internal/dependencycache') }} - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: '24.22.1' - name: Prepare integrity-pinned package preparation fixtures @@ -283,7 +283,7 @@ jobs: --output-dir "$RUNNER_TEMP/store-shard-${{ matrix.shard }}" - name: Upload Store shard evidence if: ${{ always() }} - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: store-shard-${{ matrix.shard }} path: ${{ runner.temp }}/store-shard-${{ matrix.shard }}/ @@ -334,7 +334,7 @@ jobs: go-version-file: go.mod check-latest: true cache: true - - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: '24.22.1' - name: Install reviewed real language servers @@ -383,7 +383,7 @@ jobs: working-directory: web steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: '24.22.1' cache: npm @@ -416,7 +416,7 @@ jobs: run: npm run test:journeys - name: Upload workbench journey results if: ${{ !cancelled() && needs.classify.outputs.journeys == 'true' }} - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: workbench-journeys path: output/playwright/workbench-journeys @@ -499,7 +499,7 @@ jobs: sha256sum "$EMBEDDED_WASI_FIXTURE" - name: Preserve a mismatched rebuilt WASI module if: failure() && steps.verify-wasi.outcome == 'failure' - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: rebuilt-wasi-fixture path: analyzers/target/wasm32-wasip1/release/cyberagent-analyzer-fixture.wasm @@ -547,7 +547,7 @@ jobs: arm64:arm64:arm64|x86_64:amd64:amd64) ;; *) echo "Runner, Go and expected native architectures differ" >&2; exit 1 ;; esac - - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: '24.22.1' cache: npm @@ -592,7 +592,7 @@ jobs: run: bash scripts/ci/verify-macos-agent-browser-ui.sh 3341c0e3a35b9f84a38b913465c0252e10f95649 - name: Upload native Chrome experiment and actual browser PNGs if: ${{ always() }} - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: darwin-chrome-native-${{ github.sha }} path: build/darwin-chrome-evidence/ @@ -617,7 +617,7 @@ jobs: run: python3 scripts/package-desktop-dmg.py "v1.0.0-ci.$GITHUB_RUN_ID" "$GITHUB_SHA" "$RELEASE_ARCH" - name: Upload downloadable macOS preview if: ${{ needs.classify.outputs.packaging == 'true' }} - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: TraverseBoard-macos-preview-${{ matrix.arch }} path: build/macos-public/* @@ -625,7 +625,7 @@ jobs: retention-days: 30 - name: Upload macOS preview disk image if: ${{ needs.classify.outputs.packaging == 'true' }} - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: TraverseBoard-macos-dmg-preview-${{ matrix.arch }} path: build/macos-dmg/* @@ -658,7 +658,7 @@ jobs: ./scripts/tests/prepare-windows-fixed-go.tests.ps1 ./scripts/tests/desktop-toolchain.tests.ps1 ./scripts/prepare-windows-fixed-go.ps1 -ExpectedGoVersion $env:TRAVERSE_CI_GO_VERSION - - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: '24.22.1' cache: npm @@ -974,7 +974,7 @@ jobs: } - name: Upload real Edge UI evidence receipt if: always() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: ui-evidence-edge-smoke path: ${{ runner.temp }}/ui-evidence-smoke diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml index 48b5722e..26dacda2 100644 --- a/.github/workflows/release-desktop.yml +++ b/.github/workflows/release-desktop.yml @@ -200,7 +200,7 @@ jobs: cache: true - name: Set up pinned Node 24.22.1 - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: '24.22.1' cache: npm @@ -261,7 +261,7 @@ jobs: go-version-file: go.mod cache: true - name: Set up pinned Node 24.22.1 - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: '24.22.1' cache: npm @@ -284,14 +284,14 @@ jobs: RELEASE_ARCH: ${{ matrix.arch }} run: python3 scripts/package-desktop-dmg.py "$RELEASE_VERSION" "$RELEASE_REVISION" "$RELEASE_ARCH" - name: Upload exact Mac preview archive and verification sidecars - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: TraverseBoard-macos-${{ matrix.arch }}-${{ needs.dependency-boundaries.outputs.revision }} path: build/macos-public/* if-no-files-found: error retention-days: 30 - name: Upload exact Mac preview disk image and checksum - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: TraverseBoard-macos-dmg-${{ matrix.arch }}-${{ needs.dependency-boundaries.outputs.revision }} path: build/macos-dmg/* @@ -340,7 +340,7 @@ jobs: cache: true - name: Set up pinned Node 24.22.1 - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: '24.22.1' cache: npm @@ -430,7 +430,7 @@ jobs: - name: Upload isolated direct-EXE signing request if: ${{ needs.dependency-boundaries.outputs.signing_prepare == 'true' }} - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: ${{ needs.dependency-boundaries.outputs.artifact_name }}-direct-exe-signing-request if-no-files-found: error @@ -649,7 +649,7 @@ jobs: - name: Upload exact Desktop verification and direct-EXE candidate if: ${{ needs.dependency-boundaries.outputs.signing_prepare != 'true' }} - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: ${{ needs.dependency-boundaries.outputs.artifact_name }} if-no-files-found: error @@ -675,7 +675,7 @@ jobs: - name: Preserve failed packaged validation diagnostics if: ${{ failure() }} - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: windows-packaged-validation-${{ github.run_id }} path: build/desktop/standard-code-packaged-e2e.json @@ -684,7 +684,7 @@ jobs: - name: Upload isolated development MSIX validation artifact if: ${{ needs.dependency-boundaries.outputs.signing_prepare != 'true' && (github.event_name == 'pull_request' || needs.dependency-boundaries.outputs.prerelease == 'true' || vars.STORE_PACKAGE_IDENTITY_NAME == '' || vars.STORE_PACKAGE_PUBLISHER == '' || vars.STORE_PUBLISHER_DISPLAY_NAME == '' || vars.STORE_PACKAGE_VERSION == '') }} - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: ${{ needs.dependency-boundaries.outputs.artifact_name }}-development-msix if-no-files-found: error @@ -699,7 +699,7 @@ jobs: - name: Upload isolated Partner Center submission artifact if: ${{ github.event_name != 'pull_request' && needs.dependency-boundaries.outputs.signing_prepare != 'true' && needs.dependency-boundaries.outputs.prerelease == 'false' && vars.STORE_PACKAGE_IDENTITY_NAME != '' && vars.STORE_PACKAGE_PUBLISHER != '' && vars.STORE_PUBLISHER_DISPLAY_NAME != '' && vars.STORE_PACKAGE_VERSION != '' }} - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: ${{ needs.dependency-boundaries.outputs.artifact_name }}-partner-center if-no-files-found: error @@ -1008,7 +1008,7 @@ jobs: [System.Text.UTF8Encoding]::new($false)) - name: Upload exact attestation bundles and index - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: ${{ needs.portable-zip.outputs.artifact_name }}-attestations if-no-files-found: error diff --git a/web/package-lock.json b/web/package-lock.json index 9d8c1bd7..03e7da3c 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -20,6 +20,7 @@ "@tiptap/starter-kit": "3.32.0", "@xterm/addon-fit": "0.11.0", "@xterm/xterm": "6.0.0", + "dompurify": "3.4.17", "highlight.js": "11.11.1", "lucide-react": "1.31.0", "monaco-editor": "0.56.0", @@ -42,7 +43,7 @@ "openapi-typescript": "7.13.0", "typescript": "5.9.3", "vite": "8.3.4", - "vitest": "4.1.11" + "vitest": "4.1.12" } }, "node_modules/@adobe/css-tools": { @@ -1388,16 +1389,16 @@ } }, "node_modules/@vitest/expect": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.11.tgz", - "integrity": "sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.12.tgz", + "integrity": "sha512-vFlTt+YEu9WBpNjdGayNmlMVzrWPi1+sv853/7t2FU7hpO5gAFBKHzQmkCq5Xjpr+evegF2994LQT3hF3mRlqQ==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.11", - "@vitest/utils": "4.1.11", + "@vitest/spy": "4.1.12", + "@vitest/utils": "4.1.12", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" }, @@ -1406,13 +1407,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.11.tgz", - "integrity": "sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.12.tgz", + "integrity": "sha512-fKShNpKZzupWZuY0z7egydvL0sqW4INEZBEExMBMAa6izlENEVHwoXgdaTCaBZ5fwlr4xf2o1wXC83n9RpO+NA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.11", + "@vitest/spy": "4.1.12", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -1433,9 +1434,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.11.tgz", - "integrity": "sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.12.tgz", + "integrity": "sha512-XW9O8+Mxx4VpCtkYdMJLZQQwsVryP9wk7cPKDxtXFfwSak6yRh5+2fep/Y0XF/ElBvJHq8bcYdi6aeuai0opZA==", "dev": true, "license": "MIT", "dependencies": { @@ -1446,13 +1447,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.11.tgz", - "integrity": "sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.12.tgz", + "integrity": "sha512-NKlVKCr5QnCe8jijdMnmkpHHU+WTqiLFR6G0pHji9uRjXK9xJl1k1nTw6F3pOOj2PDcsDt2GKQMI7wIsaT491w==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.11", + "@vitest/utils": "4.1.12", "pathe": "^2.0.3" }, "funding": { @@ -1460,14 +1461,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.11.tgz", - "integrity": "sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.12.tgz", + "integrity": "sha512-ko1y1Qgb/slzQ9mr+b89Cuqud8WG1TKtsPD8r7ZUBdvVOGt85Af2HwCqHujrhwJq95hs/9RM7fcAPg57o1vlzw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.11", - "@vitest/utils": "4.1.11", + "@vitest/pretty-format": "4.1.12", + "@vitest/utils": "4.1.12", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -1476,9 +1477,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.11.tgz", - "integrity": "sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.12.tgz", + "integrity": "sha512-RGq9x4JNGNTZHEJxvN0oSiJjeSCExm0n2gZKKJZHY9cX5AVA6M7VJILBT3ZJ/tJ/0J9uWT7fETV9/btoi//vSg==", "dev": true, "license": "MIT", "funding": { @@ -1486,13 +1487,13 @@ } }, "node_modules/@vitest/utils": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.11.tgz", - "integrity": "sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.12.tgz", + "integrity": "sha512-Q3awXrwOfEV+qc3WmO5ONyE0yO7Q1gQ9/oyHxGLZ/pl7N3NO/ZztqQwKLuwMkV+CA1JYSIeO3gkHnRDIo4AVaA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.11", + "@vitest/pretty-format": "4.1.12", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" }, @@ -1840,9 +1841,9 @@ "peer": true }, "node_modules/dompurify": { - "version": "3.4.16", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.16.tgz", - "integrity": "sha512-sqo+pNp3qRhCIpbgRi1y8Tgk27Bo2Ry7w0dC1NBeNTdZChWjz9Xb/KOoZbRP/R6pQZ80Qw8YhXw13hWWBbMRnQ==", + "version": "3.4.17", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.17.tgz", + "integrity": "sha512-B/pSlYVHQ58kqISD/yDjuJ1yfzI34m1gF4lAod2bwSRhiN4K5Cl3H8Af7hqB2PmTL911tdEudnnXn5FnhRoz0w==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" @@ -4281,9 +4282,9 @@ } }, "node_modules/undici": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.1.tgz", - "integrity": "sha512-RYONW2MeafgYlkVOKYKkA/Ag7BmXqgIWCa8t1m0JcxrQg9pI9lEqRhAOruOBCbAohOa/gkCF+iPi9hrgvTzu6Q==", + "version": "7.30.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.30.1.tgz", + "integrity": "sha512-03KrMbjIFDWQR5RNPrSw3bd1r12JpyFQl9/yxjUbJtqkjmllqC5lFjZh6ujeuJCKr8EOQvRW872Oy/az+nLYxg==", "dev": true, "license": "MIT", "engines": { @@ -4498,19 +4499,19 @@ } }, "node_modules/vitest": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.11.tgz", - "integrity": "sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.12.tgz", + "integrity": "sha512-5eWdz2yb3ajxWF3C8C4TjkXl7FGm3n2E/VlazALrQHzK7nYMSGFnCZto0bbPNl77AwkOZVLk0YnBAejUu0NiWA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.1.11", - "@vitest/mocker": "4.1.11", - "@vitest/pretty-format": "4.1.11", - "@vitest/runner": "4.1.11", - "@vitest/snapshot": "4.1.11", - "@vitest/spy": "4.1.11", - "@vitest/utils": "4.1.11", + "@vitest/expect": "4.1.12", + "@vitest/mocker": "4.1.12", + "@vitest/pretty-format": "4.1.12", + "@vitest/runner": "4.1.12", + "@vitest/snapshot": "4.1.12", + "@vitest/spy": "4.1.12", + "@vitest/utils": "4.1.12", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", @@ -4538,12 +4539,12 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.11", - "@vitest/browser-preview": "4.1.11", - "@vitest/browser-webdriverio": "4.1.11", - "@vitest/coverage-istanbul": "4.1.11", - "@vitest/coverage-v8": "4.1.11", - "@vitest/ui": "4.1.11", + "@vitest/browser-playwright": "4.1.12", + "@vitest/browser-preview": "4.1.12", + "@vitest/browser-webdriverio": "^4.1.11", + "@vitest/coverage-istanbul": "4.1.12", + "@vitest/coverage-v8": "4.1.12", + "@vitest/ui": "4.1.12", "happy-dom": "*", "jsdom": "*", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" diff --git a/web/package.json b/web/package.json index 89d9de27..2f3eed72 100644 --- a/web/package.json +++ b/web/package.json @@ -27,6 +27,7 @@ "@tiptap/starter-kit": "3.32.0", "@xterm/addon-fit": "0.11.0", "@xterm/xterm": "6.0.0", + "dompurify": "3.4.17", "highlight.js": "11.11.1", "lucide-react": "1.31.0", "monaco-editor": "0.56.0", @@ -49,16 +50,16 @@ "openapi-typescript": "7.13.0", "typescript": "5.9.3", "vite": "8.3.4", - "vitest": "4.1.11" + "vitest": "4.1.12" }, "overrides": { "@redocly/openapi-core": { "js-yaml": "4.3.2" }, "brace-expansion": "5.0.12", - "dompurify": "3.4.16", + "dompurify": "3.4.17", "nanoid": "3.3.18", "postcss": "8.5.25", - "undici": "7.29.1" + "undici": "7.30.1" } } diff --git a/web/src/lib/monaco-sanitizer.test.ts b/web/src/lib/monaco-sanitizer.test.ts new file mode 100644 index 00000000..a6fb29d0 --- /dev/null +++ b/web/src/lib/monaco-sanitizer.test.ts @@ -0,0 +1,21 @@ +import DOMPurify from "dompurify"; +// @ts-expect-error Monaco ships this runtime sanitizer without declarations. +import { safeSetInnerHtml, sanitizeHtml } from "monaco-editor/base/browser/domSanitize.js"; + +describe("Monaco sanitizer", () => { + it.each(["trusted HTML", "DOM fragment"])("uses the installed DOMPurify for %s", (output) => { + const sanitize = vi.spyOn(DOMPurify, "sanitize"); + const dirty = '

Kept formatting

' + + 'unsafe' + + 'safe'; + const target = document.createElement("div"); + + if (output === "trusted HTML") target.innerHTML = String(sanitizeHtml(dirty)); + else safeSetInnerHtml(target, dirty); + + expect(sanitize).toHaveBeenCalled(); + expect(target.querySelector("strong")).toHaveTextContent("formatting"); + expect(target.querySelector('script, [onerror], a[href^="javascript:"]')).toBeNull(); + expect(target.querySelector('a[href="https://example.com"]')).toHaveTextContent("safe"); + }); +}); diff --git a/web/vite.config.ts b/web/vite.config.ts index 7faf222b..fd8574df 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -1,7 +1,23 @@ +import { fileURLToPath } from "node:url"; import react from "@vitejs/plugin-react"; -import { loadEnv } from "vite"; +import { loadEnv, normalizePath, type Plugin } from "vite"; import { defineConfig } from "vitest/config"; +// npm overrides cannot replace Monaco's embedded copy. Redirect its sanitizer import +// in both Vite and the separate dependency prebundler to the installed DOMPurify. +const monacoSanitizer = normalizePath(fileURLToPath(import.meta.resolve("monaco-editor/base/browser/domSanitize.js"))); +const monacoDOMPurify: Plugin = { + name: "monaco-dompurify", + enforce: "pre", + async resolveId(source, importer) { + if (source === "./dompurify/dompurify.js" && importer && normalizePath(importer) === monacoSanitizer) { + const resolved = await this.resolve("dompurify", importer, { skipSelf: true }); + if (!resolved) throw new Error("Cannot resolve installed DOMPurify for the Monaco sanitizer"); + return resolved; + } + }, +}; + export default defineConfig(({ mode }) => { const env = loadEnv(mode, ".", ""); const apiTarget = env.CYBERAGENT_API_TARGET || "http://127.0.0.1:8765"; @@ -19,7 +35,10 @@ export default defineConfig(({ mode }) => { }; return { - plugins: [react()], + plugins: [react(), monacoDOMPurify], + optimizeDeps: { + rolldownOptions: { plugins: [monacoDOMPurify] }, + }, server: { host: "127.0.0.1", port: 5173, @@ -39,6 +58,7 @@ export default defineConfig(({ mode }) => { }, test: { include: ["src/**/*.{test,spec}.{ts,tsx}"], + server: { deps: { inline: ["monaco-editor"] } }, environment: "jsdom", globals: true, setupFiles: "./src/test/setup.ts",