From e14421019564985de69db2b4261d8dce5d475607 Mon Sep 17 00:00:00 2001 From: Payton McIntosh Date: Sat, 24 Oct 2026 14:21:14 +0100 Subject: [PATCH 1/6] Bump patched audit pins and except zod for 14 days pnpm audit failed on 20 new advisories. Every one except zod has a patched release, so the pinned versions move: vite 7.3.7, vitest 4.1.12 (which carries @vitest/mocker 4.1.12), markdown-it 14.3.3, dompurify 3.4.17, ip-address 10.7.4 and fast-uri 3.1.9, in both resolutions and pnpm.overrides, with pnpm-lock.yaml and bun.lock regenerated. GHSA-mrhx-c7xc-m3w5 is patched only in zod 4.6.6, and the workspace is on zod 3, so the fix is a major-version migration tracked in https://github.com/leynos/wildside/issues/561. A ledger entry with a 14-day expiry covers the gap, with its exploitability argument and a contract test that voids it once zod 4.6.6 resolves or any other package starts depending on zod. --- bun.lock | 50 ++--- frontend-pwa/package.json | 4 +- package.json | 22 +- pnpm-lock.yaml | 206 +++++++++--------- security/audit-exceptions.json | 9 + .../audit_exception_test.py | 10 +- .../workflow_contracts/zod_exception_test.py | 97 +++++++++ 7 files changed, 256 insertions(+), 142 deletions(-) create mode 100644 tests/workflow_contracts/zod_exception_test.py diff --git a/bun.lock b/bun.lock index 55210120f..613364b22 100644 --- a/bun.lock +++ b/bun.lock @@ -15,8 +15,8 @@ "typedoc": "0.28.20", "typescript": "5.9.2", "validator": "^13.15.23", - "vite": "^7.3.5", - "vitest": "^4.1.11", + "vite": "^7.3.7", + "vitest": "^4.1.12", }, }, "frontend-pwa": { @@ -39,8 +39,8 @@ "orval": "~8.27.0", "tailwindcss": "^3", "typescript": "^5", - "vite": "^7.3.5", - "vitest": "^4.1.11", + "vite": "^7.3.7", + "vitest": "^4.1.12", }, }, "packages/tokens": { @@ -71,15 +71,15 @@ "basic-ftp": "6.2.1", "brace-expansion": "5.0.12", "browserslist": "4.28.7", - "dompurify": "3.4.16", + "dompurify": "3.4.17", "esbuild": "0.28.1", - "fast-uri": "3.1.8", + "fast-uri": "3.1.9", "glob": "13.0.6", - "ip-address": "10.7.2", + "ip-address": "10.7.4", "js-yaml": "4.3.2", "katex": "0.18.2", "linkify-it": "5.0.2", - "markdown-it": "14.3.2", + "markdown-it": "14.3.3", "mermaid": "11.16.1", "nanoid": "3.3.18", "postcss": "8.5.23", @@ -515,19 +515,19 @@ "@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="], - "@vitest/expect": ["@vitest/expect@4.1.11", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.11", "@vitest/utils": "4.1.11", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw=="], + "@vitest/expect": ["@vitest/expect@4.1.12", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.12", "@vitest/utils": "4.1.12", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-vFlTt+YEu9WBpNjdGayNmlMVzrWPi1+sv853/7t2FU7hpO5gAFBKHzQmkCq5Xjpr+evegF2994LQT3hF3mRlqQ=="], - "@vitest/mocker": ["@vitest/mocker@4.1.11", "", { "dependencies": { "@vitest/spy": "4.1.11", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ=="], + "@vitest/mocker": ["@vitest/mocker@4.1.12", "", { "dependencies": { "@vitest/spy": "4.1.12", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-fKShNpKZzupWZuY0z7egydvL0sqW4INEZBEExMBMAa6izlENEVHwoXgdaTCaBZ5fwlr4xf2o1wXC83n9RpO+NA=="], - "@vitest/pretty-format": ["@vitest/pretty-format@4.1.11", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw=="], + "@vitest/pretty-format": ["@vitest/pretty-format@4.1.12", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-XW9O8+Mxx4VpCtkYdMJLZQQwsVryP9wk7cPKDxtXFfwSak6yRh5+2fep/Y0XF/ElBvJHq8bcYdi6aeuai0opZA=="], - "@vitest/runner": ["@vitest/runner@4.1.11", "", { "dependencies": { "@vitest/utils": "4.1.11", "pathe": "^2.0.3" } }, "sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw=="], + "@vitest/runner": ["@vitest/runner@4.1.12", "", { "dependencies": { "@vitest/utils": "4.1.12", "pathe": "^2.0.3" } }, "sha512-NKlVKCr5QnCe8jijdMnmkpHHU+WTqiLFR6G0pHji9uRjXK9xJl1k1nTw6F3pOOj2PDcsDt2GKQMI7wIsaT491w=="], - "@vitest/snapshot": ["@vitest/snapshot@4.1.11", "", { "dependencies": { "@vitest/pretty-format": "4.1.11", "@vitest/utils": "4.1.11", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog=="], + "@vitest/snapshot": ["@vitest/snapshot@4.1.12", "", { "dependencies": { "@vitest/pretty-format": "4.1.12", "@vitest/utils": "4.1.12", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-ko1y1Qgb/slzQ9mr+b89Cuqud8WG1TKtsPD8r7ZUBdvVOGt85Af2HwCqHujrhwJq95hs/9RM7fcAPg57o1vlzw=="], - "@vitest/spy": ["@vitest/spy@4.1.11", "", {}, "sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA=="], + "@vitest/spy": ["@vitest/spy@4.1.12", "", {}, "sha512-RGq9x4JNGNTZHEJxvN0oSiJjeSCExm0n2gZKKJZHY9cX5AVA6M7VJILBT3ZJ/tJ/0J9uWT7fETV9/btoi//vSg=="], - "@vitest/utils": ["@vitest/utils@4.1.11", "", { "dependencies": { "@vitest/pretty-format": "4.1.11", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ=="], + "@vitest/utils": ["@vitest/utils@4.1.12", "", { "dependencies": { "@vitest/pretty-format": "4.1.12", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-Q3awXrwOfEV+qc3WmO5ONyE0yO7Q1gQ9/oyHxGLZ/pl7N3NO/ZztqQwKLuwMkV+CA1JYSIeO3gkHnRDIo4AVaA=="], "@zenuml/core": ["@zenuml/core@3.43.2", "", { "dependencies": { "@floating-ui/react": "^0.27.8", "@headlessui/react": "^2.2.1", "@headlessui/tailwindcss": "^0.2.2", "antlr4": "~4.11.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "color-string": "^2.0.1", "dompurify": "^3.2.5", "highlight.js": "^10.7.3", "html-to-image": "^1.11.13", "immer": "^10.1.1", "jotai": "^2.12.2", "lodash": "^4.17.21", "marked": "^4.3.0", "pako": "^2.1.0", "pino": "^8.8.0", "radash": "^12.1.0", "ramda": "^0.28.0", "react": "^19.0.0", "react-dom": "^19.0.0", "tailwind-merge": "^3.1.0", "tailwindcss": "^3.4.17" } }, "sha512-p08Wu7wlTb2sHNjE7NrUhlEA9c/TLhi9T13lysHhEwxa1VFsdkwJr5x4wK622VtH2Lq3t7TDNXELvcjWp2kp0Q=="], @@ -777,7 +777,7 @@ "dlv": ["dlv@1.1.3", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="], - "dompurify": ["dompurify@3.4.16", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-sqo+pNp3qRhCIpbgRi1y8Tgk27Bo2Ry7w0dC1NBeNTdZChWjz9Xb/KOoZbRP/R6pQZ80Qw8YhXw13hWWBbMRnQ=="], + "dompurify": ["dompurify@3.4.17", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-B/pSlYVHQ58kqISD/yDjuJ1yfzI34m1gF4lAod2bwSRhiN4K5Cl3H8Af7hqB2PmTL911tdEudnnXn5FnhRoz0w=="], "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], @@ -841,7 +841,7 @@ "fast-redact": ["fast-redact@3.5.0", "", {}, "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A=="], - "fast-uri": ["fast-uri@3.1.8", "", {}, "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg=="], + "fast-uri": ["fast-uri@3.1.9", "", {}, "sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ=="], "fastparse": ["fastparse@1.1.2", "", {}, "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ=="], @@ -931,7 +931,7 @@ "internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="], - "ip-address": ["ip-address@10.7.2", "", {}, "sha512-7H/2gFSIitxc0hG3nOI1glS8QLo/EHBFFLk8vEUjXY/xu0AdL8jZ9U1IzO2PUm0d2D/ofQcAifb0g6OBkt8U7w=="], + "ip-address": ["ip-address@10.7.4", "", {}, "sha512-DWBMKyeM25D70nrIiTkgQth59/G+naKOTep6huF9j+GmeK3DMD60Zvxg5d5qQnz+38AHgou6ean2LECX+lrKDg=="], "is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="], @@ -1025,7 +1025,7 @@ "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], - "markdown-it": ["markdown-it@14.3.2", "", { "dependencies": { "argparse": "^2.0.1", "entities": "^4.5.0", "linkify-it": "^5.0.2", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", "uc.micro": "^2.1.0" }, "bin": { "markdown-it": "bin/markdown-it.mjs" } }, "sha512-sHHjZ5fJKlgrG4qns2YwVcdNep35h5fERrfkD2YNsb9UFk0UIHarbiTaHKVMlPuWAoiilyK8Fv/jAm11slsY7Q=="], + "markdown-it": ["markdown-it@14.3.3", "", { "dependencies": { "argparse": "^2.0.1", "entities": "^4.5.0", "linkify-it": "^5.0.2", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", "uc.micro": "^2.1.0" }, "bin": { "markdown-it": "bin/markdown-it.mjs" } }, "sha512-L//tDAkjTjJCU6pBUSBTgs3Kg4InVaZuUZog/b6V12UKhcDQ7S78877ibbCanRExwPuYP1CZD7Gd2dxSy1GXRg=="], "markdownlint": ["markdownlint@0.40.0", "", { "dependencies": { "micromark": "4.0.2", "micromark-core-commonmark": "2.0.3", "micromark-extension-directive": "4.0.0", "micromark-extension-gfm-autolink-literal": "2.1.0", "micromark-extension-gfm-footnote": "2.1.0", "micromark-extension-gfm-table": "2.1.1", "micromark-extension-math": "3.1.0", "micromark-util-types": "2.0.2", "string-width": "8.1.0" } }, "sha512-UKybllYNheWac61Ia7T6fzuQNDZimFIpCg2w6hHjgV1Qu0w1TV0LlSgryUGzM0bkKQCBhy2FDhEELB73Kb0kAg=="], @@ -1177,7 +1177,7 @@ "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], - "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + "picomatch": ["picomatch@4.0.7", "", {}, "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA=="], "pify": ["pify@2.3.0", "", {}, "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="], @@ -1439,9 +1439,9 @@ "validator": ["validator@13.15.35", "", {}, "sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw=="], - "vite": ["vite@7.3.6", "", { "dependencies": { "esbuild": "^0.27.0 || ^0.28.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg=="], + "vite": ["vite@7.3.7", "", { "dependencies": { "esbuild": "^0.27.0 || ^0.28.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-AHEzTORMbi7/7aXp7w7KZ5+DCUdSeLsJz0grY8xHpc9DHdYb0vQu6DqJfm2tOw3gDExNgPgnJTXxM6syYFHaCA=="], - "vitest": ["vitest@4.1.11", "", { "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", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@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", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/coverage-istanbul", "@vitest/coverage-v8", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "./vitest.mjs" } }, "sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw=="], + "vitest": ["vitest@4.1.12", "", { "dependencies": { "@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", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@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": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/coverage-istanbul", "@vitest/coverage-v8", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "./vitest.mjs" } }, "sha512-5eWdz2yb3ajxWF3C8C4TjkXl7FGm3n2E/VlazALrQHzK7nYMSGFnCZto0bbPNl77AwkOZVLk0YnBAejUu0NiWA=="], "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], @@ -1579,9 +1579,11 @@ "tailwindcss/jiti": ["jiti@1.21.7", "", { "bin": { "jiti": "bin/jiti.js" } }, "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A=="], + "tinyglobby/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + "unbzip2-stream/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], - "vitest/picomatch": ["picomatch@4.0.7", "", {}, "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA=="], + "vite/tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], "vitest/tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], @@ -1591,8 +1593,6 @@ "yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - "@orval/core/tinyglobby/picomatch": ["picomatch@4.0.7", "", {}, "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA=="], - "@types/babel__core/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], "@types/babel__core/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="], diff --git a/frontend-pwa/package.json b/frontend-pwa/package.json index 3a8c4a3bf..c16fc9eba 100644 --- a/frontend-pwa/package.json +++ b/frontend-pwa/package.json @@ -27,8 +27,8 @@ "orval": "~8.27.0", "tailwindcss": "^3", "typescript": "^5", - "vite": "^7.3.5", - "vitest": "^4.1.11" + "vite": "^7.3.7", + "vitest": "^4.1.12" }, "dependencies": { "@app/types": "workspace:*", diff --git a/package.json b/package.json index 9ddb22d69..ce4efbe31 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,8 @@ "typedoc": "0.28.20", "typescript": "5.9.2", "validator": "^13.15.23", - "vite": "^7.3.5", - "vitest": "^4.1.11" + "vite": "^7.3.7", + "vitest": "^4.1.12" }, "name": "myapp", "private": true, @@ -43,15 +43,15 @@ "basic-ftp": "6.2.1", "brace-expansion": "5.0.12", "browserslist": "4.28.7", - "dompurify": "3.4.16", + "dompurify": "3.4.17", "esbuild": "0.28.1", - "fast-uri": "3.1.8", + "fast-uri": "3.1.9", "glob": "13.0.6", - "ip-address": "10.7.2", + "ip-address": "10.7.4", "js-yaml": "4.3.2", "katex": "0.18.2", "linkify-it": "5.0.2", - "markdown-it": "14.3.2", + "markdown-it": "14.3.3", "mermaid": "11.16.1", "nanoid": "3.3.18", "postcss": "8.5.23", @@ -66,7 +66,7 @@ "overrides": { "@isaacs/brace-expansion": "5.0.1", "brace-expansion@<5.0.12": "5.0.12", - "fast-uri": "3.1.8", + "fast-uri": "3.1.9", "ajv": "8.20.0", "glob": "13.0.6", "@babel/core": "7.29.7", @@ -75,7 +75,7 @@ "katex": "0.18.2", "linkify-it": "5.0.2", "lodash": "4.18.1", - "markdown-it": "14.3.2", + "markdown-it": "14.3.3", "mermaid": "11.16.1", "minimatch": "10.2.3", "lodash-es": "4.18.1", @@ -85,8 +85,8 @@ "browserslist": "4.28.7", "postcss-selector-parser": "7.1.6", "basic-ftp": "6.2.1", - "dompurify": "3.4.16", - "ip-address": "10.7.2", + "dompurify": "3.4.17", + "ip-address": "10.7.4", "nanoid@<3.3.18": "3.3.18", "tar-fs": "3.1.1", "validator": "13.15.23", @@ -102,7 +102,7 @@ "uuid": "14.0.0", "postcss": "8.5.23", "launch-editor": "2.14.1", - "vite": "7.3.5" + "vite": "7.3.7" } } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 84b2d3c44..8c7d2e062 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,15 +10,15 @@ overrides: basic-ftp: 6.2.1 brace-expansion: 5.0.12 browserslist: 4.28.7 - dompurify: 3.4.16 + dompurify: 3.4.17 esbuild: 0.28.1 - fast-uri: 3.1.8 + fast-uri: 3.1.9 glob: 13.0.6 - ip-address: 10.7.2 + ip-address: 10.7.4 js-yaml: 4.3.2 katex: 0.18.2 linkify-it: 5.0.2 - markdown-it: 14.3.2 + markdown-it: 14.3.3 mermaid: 11.16.1 nanoid: 3.3.18 postcss: 8.5.23 @@ -48,7 +48,7 @@ overrides: smol-toml@<1.9.0: 1.9.0 source-map-js@<1.2.2: 1.2.2 launch-editor: 2.14.1 - vite: 7.3.5 + vite: 7.3.7 importers: @@ -85,11 +85,11 @@ importers: specifier: 13.15.23 version: 13.15.23 vite: - specifier: 7.3.5 - version: 7.3.5(@types/node@24.3.1)(jiti@2.6.1)(yaml@2.8.3) + specifier: 7.3.7 + version: 7.3.7(@types/node@24.3.1)(jiti@2.6.1)(yaml@2.8.3) vitest: - specifier: ^4.1.11 - version: 4.1.11(@types/node@24.3.1)(vite@7.3.5(@types/node@24.3.1)(jiti@2.6.1)(yaml@2.8.3)) + specifier: ^4.1.12 + version: 4.1.12(@types/node@24.3.1)(vite@7.3.7(@types/node@24.3.1)(jiti@2.6.1)(yaml@2.8.3)) frontend-pwa: dependencies: @@ -126,7 +126,7 @@ importers: version: 18.3.7(@types/react@18.3.24) '@vitejs/plugin-react': specifier: ^4 - version: 4.7.0(vite@7.3.5(@types/node@22.18.12)(jiti@2.6.1)(yaml@2.8.3)) + version: 4.7.0(vite@7.3.7(@types/node@22.18.12)(jiti@2.6.1)(yaml@2.8.3)) daisyui: specifier: ^4 version: 4.12.24(postcss@8.5.23) @@ -140,11 +140,11 @@ importers: specifier: ^5 version: 5.9.2 vite: - specifier: 7.3.5 - version: 7.3.5(@types/node@22.18.12)(jiti@2.6.1)(yaml@2.8.3) + specifier: 7.3.7 + version: 7.3.7(@types/node@22.18.12)(jiti@2.6.1)(yaml@2.8.3) vitest: - specifier: ^4.1.11 - version: 4.1.11(@types/node@22.18.12)(vite@7.3.5(@types/node@22.18.12)(jiti@2.6.1)(yaml@2.8.3)) + specifier: ^4.1.12 + version: 4.1.12(@types/node@22.18.12)(vite@7.3.7(@types/node@22.18.12)(jiti@2.6.1)(yaml@2.8.3)) packages/tokens: devDependencies: @@ -161,8 +161,8 @@ importers: specifier: ^5.4.4 version: 5.6.0 vite: - specifier: 7.3.5 - version: 7.3.5(@types/node@22.18.12)(jiti@2.6.1)(yaml@2.8.3) + specifier: 7.3.7 + version: 7.3.7(@types/node@22.18.12)(jiti@2.6.1)(yaml@2.8.3) packages/types: devDependencies: @@ -1118,36 +1118,36 @@ packages: resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: - vite: 7.3.5 + vite: 7.3.7 - '@vitest/expect@4.1.11': - resolution: {integrity: sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==} + '@vitest/expect@4.1.12': + resolution: {integrity: sha512-vFlTt+YEu9WBpNjdGayNmlMVzrWPi1+sv853/7t2FU7hpO5gAFBKHzQmkCq5Xjpr+evegF2994LQT3hF3mRlqQ==} - '@vitest/mocker@4.1.11': - resolution: {integrity: sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==} + '@vitest/mocker@4.1.12': + resolution: {integrity: sha512-fKShNpKZzupWZuY0z7egydvL0sqW4INEZBEExMBMAa6izlENEVHwoXgdaTCaBZ5fwlr4xf2o1wXC83n9RpO+NA==} peerDependencies: msw: ^2.4.9 - vite: 7.3.5 + vite: 7.3.7 peerDependenciesMeta: msw: optional: true vite: optional: true - '@vitest/pretty-format@4.1.11': - resolution: {integrity: sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==} + '@vitest/pretty-format@4.1.12': + resolution: {integrity: sha512-XW9O8+Mxx4VpCtkYdMJLZQQwsVryP9wk7cPKDxtXFfwSak6yRh5+2fep/Y0XF/ElBvJHq8bcYdi6aeuai0opZA==} - '@vitest/runner@4.1.11': - resolution: {integrity: sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==} + '@vitest/runner@4.1.12': + resolution: {integrity: sha512-NKlVKCr5QnCe8jijdMnmkpHHU+WTqiLFR6G0pHji9uRjXK9xJl1k1nTw6F3pOOj2PDcsDt2GKQMI7wIsaT491w==} - '@vitest/snapshot@4.1.11': - resolution: {integrity: sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==} + '@vitest/snapshot@4.1.12': + resolution: {integrity: sha512-ko1y1Qgb/slzQ9mr+b89Cuqud8WG1TKtsPD8r7ZUBdvVOGt85Af2HwCqHujrhwJq95hs/9RM7fcAPg57o1vlzw==} - '@vitest/spy@4.1.11': - resolution: {integrity: sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==} + '@vitest/spy@4.1.12': + resolution: {integrity: sha512-RGq9x4JNGNTZHEJxvN0oSiJjeSCExm0n2gZKKJZHY9cX5AVA6M7VJILBT3ZJ/tJ/0J9uWT7fETV9/btoi//vSg==} - '@vitest/utils@4.1.11': - resolution: {integrity: sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==} + '@vitest/utils@4.1.12': + resolution: {integrity: sha512-Q3awXrwOfEV+qc3WmO5ONyE0yO7Q1gQ9/oyHxGLZ/pl7N3NO/ZztqQwKLuwMkV+CA1JYSIeO3gkHnRDIo4AVaA==} '@zenuml/core@3.40.1': resolution: {integrity: sha512-YhhQcxHcXzW8rcW5bzJfU67y4zDIojK3MEL9aSF7G/8zPlfAFdzZhkl0WWHhxdmE/yK4zZ6f7dqjgImw/jJrYA==} @@ -1707,8 +1707,8 @@ packages: dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} - dompurify@3.4.16: - resolution: {integrity: sha512-sqo+pNp3qRhCIpbgRi1y8Tgk27Bo2Ry7w0dC1NBeNTdZChWjz9Xb/KOoZbRP/R6pQZ80Qw8YhXw13hWWBbMRnQ==} + dompurify@3.4.17: + resolution: {integrity: sha512-B/pSlYVHQ58kqISD/yDjuJ1yfzI34m1gF4lAod2bwSRhiN4K5Cl3H8Af7hqB2PmTL911tdEudnnXn5FnhRoz0w==} dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} @@ -1819,8 +1819,8 @@ packages: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} - fast-uri@3.1.8: - resolution: {integrity: sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==} + fast-uri@3.1.9: + resolution: {integrity: sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ==} fastparse@1.1.2: resolution: {integrity: sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==} @@ -2003,8 +2003,8 @@ packages: resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} engines: {node: '>=12'} - ip-address@10.7.2: - resolution: {integrity: sha512-7H/2gFSIitxc0hG3nOI1glS8QLo/EHBFFLk8vEUjXY/xu0AdL8jZ9U1IzO2PUm0d2D/ofQcAifb0g6OBkt8U7w==} + ip-address@10.7.4: + resolution: {integrity: sha512-DWBMKyeM25D70nrIiTkgQth59/G+naKOTep6huF9j+GmeK3DMD60Zvxg5d5qQnz+38AHgou6ean2LECX+lrKDg==} engines: {node: '>= 12'} is-alphabetical@2.0.1: @@ -2202,8 +2202,8 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - markdown-it@14.3.2: - resolution: {integrity: sha512-sHHjZ5fJKlgrG4qns2YwVcdNep35h5fERrfkD2YNsb9UFk0UIHarbiTaHKVMlPuWAoiilyK8Fv/jAm11slsY7Q==} + markdown-it@14.3.3: + resolution: {integrity: sha512-L//tDAkjTjJCU6pBUSBTgs3Kg4InVaZuUZog/b6V12UKhcDQ7S78877ibbCanRExwPuYP1CZD7Gd2dxSy1GXRg==} hasBin: true markdownlint-cli@0.48.0: @@ -3025,8 +3025,8 @@ packages: resolution: {integrity: sha512-4yoz1kEWqUjzi5zsPbAS/903QXSYp0UOtHsPpp7p9rHAw/W+dkInskAE386Fat3oKRROwO98d9ZB0G4cObgUyw==} engines: {node: '>= 0.10'} - vite@7.3.5: - resolution: {integrity: sha512-KuOaNhcnGFN2zIPGA7wRmzF+lJA1sea7rHq17aiJ++9lzY1WWG6Jpwqwe1KNbRVPIqHmr8GLYx7jbrQcN/7/ww==} + vite@7.3.7: + resolution: {integrity: sha512-AHEzTORMbi7/7aXp7w7KZ5+DCUdSeLsJz0grY8xHpc9DHdYb0vQu6DqJfm2tOw3gDExNgPgnJTXxM6syYFHaCA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -3065,23 +3065,23 @@ packages: yaml: optional: true - vitest@4.1.11: - resolution: {integrity: sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==} + vitest@4.1.12: + resolution: {integrity: sha512-5eWdz2yb3ajxWF3C8C4TjkXl7FGm3n2E/VlazALrQHzK7nYMSGFnCZto0bbPNl77AwkOZVLk0YnBAejUu0NiWA==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@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: 7.3.5 + vite: 7.3.7 peerDependenciesMeta: '@edge-runtime/vm': optional: true @@ -4181,7 +4181,7 @@ snapshots: d3-selection: 3.0.0 d3-transition: 3.0.1(d3-selection@3.0.0) - '@vitejs/plugin-react@4.7.0(vite@7.3.5(@types/node@22.18.12)(jiti@2.6.1)(yaml@2.8.3))': + '@vitejs/plugin-react@4.7.0(vite@7.3.7(@types/node@22.18.12)(jiti@2.6.1)(yaml@2.8.3))': dependencies: '@babel/core': 7.29.7 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.7) @@ -4189,56 +4189,56 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.27 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: 7.3.5(@types/node@22.18.12)(jiti@2.6.1)(yaml@2.8.3) + vite: 7.3.7(@types/node@22.18.12)(jiti@2.6.1)(yaml@2.8.3) transitivePeerDependencies: - supports-color - '@vitest/expect@4.1.11': + '@vitest/expect@4.1.12': 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 - '@vitest/mocker@4.1.11(vite@7.3.5(@types/node@22.18.12)(jiti@2.6.1)(yaml@2.8.3))': + '@vitest/mocker@4.1.12(vite@7.3.7(@types/node@22.18.12)(jiti@2.6.1)(yaml@2.8.3))': dependencies: - '@vitest/spy': 4.1.11 + '@vitest/spy': 4.1.12 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.5(@types/node@22.18.12)(jiti@2.6.1)(yaml@2.8.3) + vite: 7.3.7(@types/node@22.18.12)(jiti@2.6.1)(yaml@2.8.3) - '@vitest/mocker@4.1.11(vite@7.3.5(@types/node@24.3.1)(jiti@2.6.1)(yaml@2.8.3))': + '@vitest/mocker@4.1.12(vite@7.3.7(@types/node@24.3.1)(jiti@2.6.1)(yaml@2.8.3))': dependencies: - '@vitest/spy': 4.1.11 + '@vitest/spy': 4.1.12 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.5(@types/node@24.3.1)(jiti@2.6.1)(yaml@2.8.3) + vite: 7.3.7(@types/node@24.3.1)(jiti@2.6.1)(yaml@2.8.3) - '@vitest/pretty-format@4.1.11': + '@vitest/pretty-format@4.1.12': dependencies: tinyrainbow: 3.1.0 - '@vitest/runner@4.1.11': + '@vitest/runner@4.1.12': dependencies: - '@vitest/utils': 4.1.11 + '@vitest/utils': 4.1.12 pathe: 2.0.3 - '@vitest/snapshot@4.1.11': + '@vitest/snapshot@4.1.12': 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 - '@vitest/spy@4.1.11': {} + '@vitest/spy@4.1.12': {} - '@vitest/utils@4.1.11': + '@vitest/utils@4.1.12': dependencies: - '@vitest/pretty-format': 4.1.11 + '@vitest/pretty-format': 4.1.12 convert-source-map: 2.0.0 tinyrainbow: 3.1.0 @@ -4251,7 +4251,7 @@ snapshots: class-variance-authority: 0.7.1 clsx: 2.1.1 color-string: 2.1.0 - dompurify: 3.4.16 + dompurify: 3.4.17 highlight.js: 10.7.3 html-to-image: 1.11.13 immer: 10.1.3 @@ -4289,7 +4289,7 @@ snapshots: ajv@8.20.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.1.8 + fast-uri: 3.1.9 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 @@ -4804,7 +4804,7 @@ snapshots: dlv@1.1.3: {} - dompurify@3.4.16: + dompurify@3.4.17: optionalDependencies: '@types/trusted-types': 2.0.7 @@ -4942,7 +4942,7 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 - fast-uri@3.1.8: {} + fast-uri@3.1.9: {} fastparse@1.1.2: {} @@ -5114,7 +5114,7 @@ snapshots: internmap@2.0.3: {} - ip-address@10.7.2: {} + ip-address@10.7.4: {} is-alphabetical@2.0.1: {} @@ -5266,7 +5266,7 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - markdown-it@14.3.2: + markdown-it@14.3.3: dependencies: argparse: 2.0.1 entities: 4.5.0 @@ -5283,7 +5283,7 @@ snapshots: js-yaml: 4.3.2 jsonc-parser: 3.3.1 jsonpointer: 5.0.1 - markdown-it: 14.3.2 + markdown-it: 14.3.3 markdownlint: 0.40.0 minimatch: 10.2.3 run-con: 1.3.2 @@ -5339,7 +5339,7 @@ snapshots: d3-sankey: 0.12.3 dagre-d3-es: 7.0.14 dayjs: 1.11.20 - dompurify: 3.4.16 + dompurify: 3.4.17 es-toolkit: 1.46.1 katex: 0.18.2 khroma: 2.1.0 @@ -6047,7 +6047,7 @@ snapshots: socks@2.8.7: dependencies: - ip-address: 10.7.2 + ip-address: 10.7.4 smart-buffer: 4.2.0 sonic-boom@4.2.0: @@ -6256,7 +6256,7 @@ snapshots: dependencies: '@gerrit0/mini-shiki': 3.23.0 lunr: 2.3.9 - markdown-it: 14.3.2 + markdown-it: 14.3.3 minimatch: 10.2.3 typescript: 5.9.2 yaml: 2.8.3 @@ -6312,43 +6312,43 @@ snapshots: validator@13.15.23: {} - vite@7.3.5(@types/node@22.18.12)(jiti@2.6.1)(yaml@2.8.3): + vite@7.3.7(@types/node@22.18.12)(jiti@2.6.1)(yaml@2.8.3): dependencies: esbuild: 0.28.1 fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 postcss: 8.5.23 rollup: 4.59.0 - tinyglobby: 0.2.15 + tinyglobby: 0.2.17 optionalDependencies: '@types/node': 22.18.12 fsevents: 2.3.3 jiti: 2.6.1 yaml: 2.8.3 - vite@7.3.5(@types/node@24.3.1)(jiti@2.6.1)(yaml@2.8.3): + vite@7.3.7(@types/node@24.3.1)(jiti@2.6.1)(yaml@2.8.3): dependencies: esbuild: 0.28.1 fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 postcss: 8.5.23 rollup: 4.59.0 - tinyglobby: 0.2.15 + tinyglobby: 0.2.17 optionalDependencies: '@types/node': 24.3.1 fsevents: 2.3.3 jiti: 2.6.1 yaml: 2.8.3 - vitest@4.1.11(@types/node@22.18.12)(vite@7.3.5(@types/node@22.18.12)(jiti@2.6.1)(yaml@2.8.3)): + vitest@4.1.12(@types/node@22.18.12)(vite@7.3.7(@types/node@22.18.12)(jiti@2.6.1)(yaml@2.8.3)): dependencies: - '@vitest/expect': 4.1.11 - '@vitest/mocker': 4.1.11(vite@7.3.5(@types/node@22.18.12)(jiti@2.6.1)(yaml@2.8.3)) - '@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(vite@7.3.7(@types/node@22.18.12)(jiti@2.6.1)(yaml@2.8.3)) + '@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.1.0 expect-type: 1.3.0 magic-string: 0.30.21 @@ -6360,22 +6360,22 @@ snapshots: tinyexec: 1.2.4 tinyglobby: 0.2.17 tinyrainbow: 3.1.0 - vite: 7.3.5(@types/node@22.18.12)(jiti@2.6.1)(yaml@2.8.3) + vite: 7.3.7(@types/node@22.18.12)(jiti@2.6.1)(yaml@2.8.3) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 22.18.12 transitivePeerDependencies: - msw - vitest@4.1.11(@types/node@24.3.1)(vite@7.3.5(@types/node@24.3.1)(jiti@2.6.1)(yaml@2.8.3)): + vitest@4.1.12(@types/node@24.3.1)(vite@7.3.7(@types/node@24.3.1)(jiti@2.6.1)(yaml@2.8.3)): dependencies: - '@vitest/expect': 4.1.11 - '@vitest/mocker': 4.1.11(vite@7.3.5(@types/node@24.3.1)(jiti@2.6.1)(yaml@2.8.3)) - '@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(vite@7.3.7(@types/node@24.3.1)(jiti@2.6.1)(yaml@2.8.3)) + '@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.1.0 expect-type: 1.3.0 magic-string: 0.30.21 @@ -6387,7 +6387,7 @@ snapshots: tinyexec: 1.2.4 tinyglobby: 0.2.17 tinyrainbow: 3.1.0 - vite: 7.3.5(@types/node@24.3.1)(jiti@2.6.1)(yaml@2.8.3) + vite: 7.3.7(@types/node@24.3.1)(jiti@2.6.1)(yaml@2.8.3) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 24.3.1 diff --git a/security/audit-exceptions.json b/security/audit-exceptions.json index 2c414fbe1..dc6d35025 100644 --- a/security/audit-exceptions.json +++ b/security/audit-exceptions.json @@ -41,5 +41,14 @@ "reason": "Development-time tooling only. braces reaches the tree through chokidar@3.6.0 and micromatch@4.0.8, which both pin braces@3.0.3 in pnpm-lock.yaml. The brace patterns come from repository-controlled configuration, not runtime user input, but pull requests can change that configuration and cause their own CI build to fail. The advisory (published 2026-09-18, stack exhaustion on deeply nested patterns) covers every release up to 3.0.3, which is the latest published, so there is no patched release to move to. Tracked in https://github.com/leynos/wildside/issues/527; remove this entry once a patched braces exists or chokidar and micromatch drop braces 3.", "addedAt": "2026-10-04", "expiresAt": "2026-12-04" + }, + { + "id": "ZOD_ARRAY_MEMORY_EXHAUSTION_2026_10", + "package": "frontend-pwa", + "advisory": "GHSA-mrhx-c7xc-m3w5", + "introducedBy": "zod", + "reason": "zod is patched only in 4.6.6 and above, and the workspace is on zod 3.25.76 (`^3` in frontend-pwa and packages/types), so the fix is a major-version migration that needs its own change and verification rather than a pin bump. The advisory is memory exhaustion while validating a very large array against an array schema with no length constraint. The only array schema is UsersSchema in packages/types/src/user.ts, applied by customFetchParsed in frontend-pwa/src/api/fetcher.ts to responses from the project's own backend and running in the user's browser tab, so an attacker would need to control the backend response, and the impact is a client-side memory exhaustion of one tab, not a server compromise. The other path, puppeteer > chromium-bidi > zod, is build-time Mermaid rendering tooling that never parses untrusted input. The entry expires after 14 days. Tracked in https://github.com/leynos/wildside/issues/561.", + "addedAt": "2026-10-24", + "expiresAt": "2026-11-07" } ] diff --git a/tests/workflow_contracts/audit_exception_test.py b/tests/workflow_contracts/audit_exception_test.py index 64c9989dc..df1ff886e 100644 --- a/tests/workflow_contracts/audit_exception_test.py +++ b/tests/workflow_contracts/audit_exception_test.py @@ -145,10 +145,18 @@ class AuditException(typ.TypedDict): #: identifier is registered here so the check below still sees every entry. BRACES_EXCEPTION_IDS = ("BRACES_NESTED_PATTERN_DOS_2026_10",) +#: GHSA-mrhx-c7xc-m3w5, memory exhaustion in zod below 4.6.6. Its removal +#: invariants live in `zod_exception_test.py`; the identifier is registered here +#: so the check below still sees every entry. +ZOD_EXCEPTION_IDS = ("ZOD_ARRAY_MEMORY_EXHAUSTION_2026_10",) + #: Every ledger entry must appear here, so that adding an exception means #: writing the condition that retires it rather than only a date. EXCEPTIONS_WITH_REMOVAL_INVARIANTS = frozenset( - EXTRACT_ZIP_EXCEPTION_IDS + PICOMATCH_EXCEPTION_IDS + BRACES_EXCEPTION_IDS + EXTRACT_ZIP_EXCEPTION_IDS + + PICOMATCH_EXCEPTION_IDS + + BRACES_EXCEPTION_IDS + + ZOD_EXCEPTION_IDS ) #: A removal condition is an issue reference, either bare or as a full URL. An diff --git a/tests/workflow_contracts/zod_exception_test.py b/tests/workflow_contracts/zod_exception_test.py new file mode 100644 index 000000000..51b401b6c --- /dev/null +++ b/tests/workflow_contracts/zod_exception_test.py @@ -0,0 +1,97 @@ +"""Keep the `zod` audit exception honest. + +GHSA-mrhx-c7xc-m3w5 (memory exhaustion validating a large unconstrained array) +is patched only in zod 4.6.6 and above. The workspace is on zod 3, so the +exception in `security/audit-exceptions.json` is a fourteen-day stopgap for a +major-version migration, argued from reach: the only dependent in the Bun +lockfile is `chromium-bidi`, which is Puppeteer's build-time tooling, while the +workspace's own use is declared in `frontend-pwa` and `packages/types`. These +tests fail when the migration lands or when a new package starts depending on +zod, either of which retires the argument. The shared checks over the whole +ledger stay in `audit_exception_test.py`. +""" + +from __future__ import annotations + +import json +import typing as typ +from pathlib import Path + +import bun_lockfile as lock +import pytest + +REPOSITORY_ROOT = Path(__file__).resolve().parents[2] +LEDGER_PATH = REPOSITORY_ROOT / "security" / "audit-exceptions.json" +BUN_LOCK_PATH = REPOSITORY_ROOT / "bun.lock" + +ZOD_EXCEPTION_ID = "ZOD_ARRAY_MEMORY_EXHAUSTION_2026_10" +ZOD_TRACKING_ISSUE = "https://github.com/leynos/wildside/issues/561" + +#: The first release that carries the fix. A resolved version below this is +#: what the exception exists to tolerate. +FIRST_PATCHED_ZOD = (4, 6, 6) + +#: The only resolved package the reasoning permits as a parent: Puppeteer's +#: tooling, which never parses untrusted input. A parent outside this set is a +#: new way to reach the code, which the entry's reason does not cover. +ZOD_PERMITTED_DEPENDENTS = frozenset({"chromium-bidi"}) + + +@pytest.fixture(scope="module") +def ledger_ids() -> frozenset[str]: + """Return the identifiers of every entry in the audit exception ledger.""" + ledger: list[dict[str, typ.Any]] = json.loads( + LEDGER_PATH.read_text(encoding="utf-8") + ) + return frozenset(entry["id"] for entry in ledger) + + +@pytest.fixture(scope="module") +def packages() -> lock.PackageTable: + """Return the package table of the repository's Bun lockfile.""" + return lock.parse_packages(BUN_LOCK_PATH.read_text(encoding="utf-8")) + + +def test_the_zod_exception_is_void_once_only_patched_releases_resolve( + ledger_ids: frozenset[str], packages: lock.PackageTable +) -> None: + """The exception rests on the fix being a major-version migration away. + + The day `bun.lock` resolves only zod 4.6.6 or later, the migration has + landed and the advisory no longer applies, so the entry is unnecessary. + """ + if ZOD_EXCEPTION_ID not in ledger_ids: + pytest.skip("the zod advisory is no longer excepted") + + resolved = lock.resolved_versions(packages, "zod") + assert resolved, "bun.lock must resolve zod for this entry to apply" + + vulnerable = [version for version in resolved if version < FIRST_PATCHED_ZOD] + assert vulnerable, ( + f"{ZOD_EXCEPTION_ID} excepts GHSA-mrhx-c7xc-m3w5 on the grounds that the " + f"fix needs a major-version migration, but bun.lock now resolves only " + f"{sorted(resolved)}. Remove the entry and close {ZOD_TRACKING_ISSUE}." + ) + + +def test_the_zod_exception_is_void_once_anything_else_pulls_it( + ledger_ids: frozenset[str], packages: lock.PackageTable +) -> None: + """The entry is repository-wide, but its reason is about one parent. + + `run-bun-audit.js` turns a ledger entry into a `--ignore` for every caller, + while the reason argues only that Puppeteer's `chromium-bidi` reaches zod + through tooling. A further parent would be covered by an argument written + about neither, and checking that zod is still resolved would not notice. + """ + if ZOD_EXCEPTION_ID not in ledger_ids: + pytest.skip("the zod advisory is no longer excepted") + + dependents = lock.dependents_of(packages, "zod") + unexpected = dependents - ZOD_PERMITTED_DEPENDENTS + assert not unexpected, ( + f"{ZOD_EXCEPTION_ID} excepts zod on the grounds that only " + f"{sorted(ZOD_PERMITTED_DEPENDENTS)} reach it, but " + f"{sorted(unexpected)} now depend on it too. The exception is " + f"repository-wide, so it would cover those callers." + ) From c8b1d651079d457e3459a27e22f664614ea7de7a Mon Sep 17 00:00:00 2001 From: Payton McIntosh Date: Sat, 24 Oct 2026 14:23:27 +0100 Subject: [PATCH 2/6] Pin the workspaces that declare zod in the zod exception contract bun.lock's package table lists registry packages only, so the check that no package other than chromium-bidi depends on zod never saw the workspace's own frontend-pwa and packages/types. Read the workspace manifests as well, and say in the docstring which half reads which. --- .../workflow_contracts/zod_exception_test.py | 70 ++++++++++++++++--- 1 file changed, 62 insertions(+), 8 deletions(-) diff --git a/tests/workflow_contracts/zod_exception_test.py b/tests/workflow_contracts/zod_exception_test.py index 51b401b6c..a194741e5 100644 --- a/tests/workflow_contracts/zod_exception_test.py +++ b/tests/workflow_contracts/zod_exception_test.py @@ -3,11 +3,14 @@ GHSA-mrhx-c7xc-m3w5 (memory exhaustion validating a large unconstrained array) is patched only in zod 4.6.6 and above. The workspace is on zod 3, so the exception in `security/audit-exceptions.json` is a fourteen-day stopgap for a -major-version migration, argued from reach: the only dependent in the Bun -lockfile is `chromium-bidi`, which is Puppeteer's build-time tooling, while the -workspace's own use is declared in `frontend-pwa` and `packages/types`. These -tests fail when the migration lands or when a new package starts depending on -zod, either of which retires the argument. The shared checks over the whole +major-version migration, argued from reach, and reach has two halves that are +read from different places. The registry packages that depend on zod are read +from the Bun lockfile's package table, which lists resolved registry packages +only: it holds `chromium-bidi` (Puppeteer's build-time tooling) and never the +workspace's own packages. The workspace's own use is therefore read separately, +from the workspace manifests: `frontend-pwa` and `packages/types` declare zod +directly, for `UsersSchema` and the fetcher. These tests fail when the +migration lands or when either half gains a parent the reason does not cover. The shared checks over the whole ledger stay in `audit_exception_test.py`. """ @@ -31,11 +34,19 @@ #: what the exception exists to tolerate. FIRST_PATCHED_ZOD = (4, 6, 6) -#: The only resolved package the reasoning permits as a parent: Puppeteer's -#: tooling, which never parses untrusted input. A parent outside this set is a -#: new way to reach the code, which the entry's reason does not cover. +#: The only resolved registry package the reasoning permits as a parent: +#: Puppeteer's tooling, which never parses untrusted input. A parent outside +#: this set is a new way to reach the code, which the entry's reason does not +#: cover. Workspace packages are not in the lockfile's package table, so they +#: are checked against the next constant instead. ZOD_PERMITTED_DEPENDENTS = frozenset({"chromium-bidi"}) +#: The workspace manifests, relative to the repository root, that the reason +#: names as declaring zod: `frontend-pwa` parses API responses through it and +#: `packages/types` defines the schemas. A third manifest declaring zod is a new +#: consumer the argument about `UsersSchema` was not written for. +ZOD_PERMITTED_WORKSPACES = frozenset({"frontend-pwa", "packages/types"}) + @pytest.fixture(scope="module") def ledger_ids() -> frozenset[str]: @@ -52,6 +63,28 @@ def packages() -> lock.PackageTable: return lock.parse_packages(BUN_LOCK_PATH.read_text(encoding="utf-8")) +def _workspaces_declaring_zod() -> frozenset[str]: + """Return the workspace directories whose manifest declares zod. + + Examples + -------- + >>> isinstance(_workspaces_declaring_zod(), frozenset) + True + """ + manifests = [ + REPOSITORY_ROOT / "package.json", + *sorted(REPOSITORY_ROOT.glob("frontend-pwa/package.json")), + *sorted(REPOSITORY_ROOT.glob("packages/*/package.json")), + ] + declaring = set() + for manifest in manifests: + data: dict[str, typ.Any] = json.loads(manifest.read_text(encoding="utf-8")) + fields = ("dependencies", "devDependencies", "peerDependencies") + if any("zod" in data.get(field, {}) for field in fields): + declaring.add(manifest.parent.relative_to(REPOSITORY_ROOT).as_posix()) + return frozenset(declaring) + + def test_the_zod_exception_is_void_once_only_patched_releases_resolve( ledger_ids: frozenset[str], packages: lock.PackageTable ) -> None: @@ -95,3 +128,24 @@ def test_the_zod_exception_is_void_once_anything_else_pulls_it( f"{sorted(unexpected)} now depend on it too. The exception is " f"repository-wide, so it would cover those callers." ) + + +def test_the_zod_exception_is_void_once_another_workspace_declares_zod( + ledger_ids: frozenset[str], +) -> None: + """The workspace's own use of zod is argued about, so it is pinned too. + + `bun.lock`'s package table never lists workspace packages, so the check + above cannot see a new workspace that declares zod. This one reads the + manifests, and also fails if a named workspace stops declaring zod, which + is the migration's first sign and means the argument needs rewriting. + """ + if ZOD_EXCEPTION_ID not in ledger_ids: + pytest.skip("the zod advisory is no longer excepted") + + declaring = _workspaces_declaring_zod() + assert declaring == ZOD_PERMITTED_WORKSPACES, ( + f"{ZOD_EXCEPTION_ID} argues from {sorted(ZOD_PERMITTED_WORKSPACES)} " + f"being the workspaces that declare zod, but {sorted(declaring)} do. " + f"Rewrite the entry's reason or remove it, and see {ZOD_TRACKING_ISSUE}." + ) From 888a017c8f97f38cd76db6b71550b31132cbdd3c Mon Sep 17 00:00:00 2001 From: Payton McIntosh Date: Sat, 24 Oct 2026 14:24:57 +0100 Subject: [PATCH 3/6] Wrap the zod exception contract docstring to the line limit --- tests/workflow_contracts/zod_exception_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/workflow_contracts/zod_exception_test.py b/tests/workflow_contracts/zod_exception_test.py index a194741e5..ef785bf13 100644 --- a/tests/workflow_contracts/zod_exception_test.py +++ b/tests/workflow_contracts/zod_exception_test.py @@ -10,8 +10,8 @@ workspace's own packages. The workspace's own use is therefore read separately, from the workspace manifests: `frontend-pwa` and `packages/types` declare zod directly, for `UsersSchema` and the fetcher. These tests fail when the -migration lands or when either half gains a parent the reason does not cover. The shared checks over the whole -ledger stay in `audit_exception_test.py`. +migration lands or when either half gains a parent the reason does not cover. +The shared checks over the whole ledger stay in `audit_exception_test.py`. """ from __future__ import annotations From 120108ff4cf656ba0043c54254876829faea6695 Mon Sep 17 00:00:00 2001 From: Payton McIntosh Date: Sat, 24 Oct 2026 15:46:48 +0100 Subject: [PATCH 4/6] Update ctutils, rustls-webpki and webpki-roots for their advisories cargo audit failed on RUSTSEC-2026-0385 (ctutils 0.4.2, fixed in 0.4.4), RUSTSEC-2026-0382 (rustls-webpki 0.103.15, fixed in 0.103.16) and RUSTSEC-2026-0381 (webpki-roots 1.0.6, fixed in 1.0.10). Lock-only: cargo update -p ctutils -p rustls-webpki -p webpki-roots@1.0.6 moves them to 0.4.5, 0.103.16 and 1.0.11. --- Cargo.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 60ac458ff..2a7e12964 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1433,9 +1433,9 @@ dependencies = [ [[package]] name = "ctutils" -version = "0.4.2" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +checksum = "38d28fb6aa7dd680b3ffd1a3e64e115c177bdba63c0ae7f9cd48129ff05c4fa0" dependencies = [ "cmov", ] @@ -2708,7 +2708,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots 1.0.6", + "webpki-roots 1.0.11", ] [[package]] @@ -4860,7 +4860,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams 0.4.2", "web-sys", - "webpki-roots 1.0.6", + "webpki-roots 1.0.11", ] [[package]] @@ -5291,9 +5291,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.15" +version = "0.103.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" +checksum = "4443b1b6d39d7c663139942cc4558262562d2c84123bb6282ef177009ca8b281" dependencies = [ "ring", "rustls-pki-types", @@ -7180,14 +7180,14 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "webpki-roots 1.0.6", + "webpki-roots 1.0.11", ] [[package]] name = "webpki-roots" -version = "1.0.6" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +checksum = "f5f280bbb2b287bc8e7de7e3852945a0ff50d1bbcb5ed42b6c7a568c546df171" dependencies = [ "rustls-pki-types", ] From bfa83d714e54a838600a76958d04e4238b9e2449 Mon Sep 17 00:00:00 2001 From: Payton McIntosh Date: Sat, 24 Oct 2026 17:19:39 +0100 Subject: [PATCH 5/6] Read the zod exception's files through the repository boundary zod_exception_test now opens every file in a fixture through repository_reading (new parse_json), so a missing or malformed file fails with its path, and the workspace query takes parsed manifests and opens nothing. It also reads optionalDependencies, replaces typ.Any with a TypedDict and narrowed tables, and the docs name the current Vite and Vitest ranges. --- docs/developers-guide.md | 4 +- docs/v2a-front-end-stack.md | 8 +- .../workflow_contracts/repository_reading.py | 34 +++ .../repository_reading_test.py | 13 ++ .../workflow_contracts/zod_exception_test.py | 218 +++++++++++++++--- 5 files changed, 243 insertions(+), 34 deletions(-) diff --git a/docs/developers-guide.md b/docs/developers-guide.md index 8c4cf8aab..e30ed6026 100644 --- a/docs/developers-guide.md +++ b/docs/developers-guide.md @@ -581,9 +581,9 @@ make test enabled so `pnpm` is available locally and in CI, and it requires `cargo-audit` for the Rust dependency check. -The front-end package uses Bun-compatible workspace scripts, Vite `^7.3.5`, +The front-end package uses Bun-compatible workspace scripts, Vite `^7.3.7`, React 19, React DOM 18, TanStack Query 5, Tailwind CSS `^3`, DaisyUI `^4`, Zod -3, TypeScript 5, Vitest `^4.1.8`, and Orval 8. TanStack Router, Radix UI, +3, TypeScript 5, Vitest `^4.1.12`, and Orval 8. TanStack Router, Radix UI, i18next, Fluent, MapLibre GL JS, Dexie, Tailwind CSS v4, and DaisyUI v5 are target-stack items until a roadmap task adds them to `frontend-pwa/package.json` and the lockfile. diff --git a/docs/v2a-front-end-stack.md b/docs/v2a-front-end-stack.md index fb22bb179..657bea94e 100644 --- a/docs/v2a-front-end-stack.md +++ b/docs/v2a-front-end-stack.md @@ -35,7 +35,7 @@ family would need them. The current `frontend-pwa/package.json` declares: - Bun, -- Vite `^7.3.5`, +- Vite `^7.3.7`, - React 19, - React DOM 18, - TanStack Query 5, @@ -44,7 +44,7 @@ The current `frontend-pwa/package.json` declares: - Zod 3, - clsx 2, - TypeScript 5, -- Vitest 3, +- Vitest `^4.1.12`, - Orval 8, and - the current build, linting, token generation, and type checking toolchain described below. @@ -69,7 +69,7 @@ state architecture. - **Package manager and runner:** Bun drives local scripts, tests, and token generation via `package.json` and `bunfig.toml`. -- **Bundler and dev server:** Vite `^7.3.5` is the application bundler and +- **Bundler and dev server:** Vite `^7.3.7` is the application bundler and development server. - **React integration:** `@vitejs/plugin-react` handles JSX and React Fast Refresh. @@ -323,7 +323,7 @@ For the shortest accurate summary of the checked-in mockup, the current front-end stack is: - Bun for scripts and package-manager-adjacent tooling, -- Vite `^7.3.5` for bundling and development, +- Vite `^7.3.7` for bundling and development, - React 19 for the SPA runtime, - React DOM 18 for rendering, - TanStack Query 5 for server-state caching, diff --git a/tests/workflow_contracts/repository_reading.py b/tests/workflow_contracts/repository_reading.py index 3aa18c495..0e23e8961 100644 --- a/tests/workflow_contracts/repository_reading.py +++ b/tests/workflow_contracts/repository_reading.py @@ -19,6 +19,7 @@ from __future__ import annotations +import json import typing as typ from fnmatch import fnmatch @@ -95,6 +96,39 @@ def read_text(path: Path) -> str: raise RepositoryReadError(message, path=path) from error +def parse_json(text: str, path: Path) -> object: + """Return one JSON document's value. + + Parameters + ---------- + text : str + The file's text. + path : Path + The file it came from, for the message. + + Returns + ------- + object + The parsed value; the caller narrows it. + + Raises + ------ + RepositoryReadError + If the text is not valid JSON. + + Examples + -------- + >>> from pathlib import Path + >>> parse_json('{"zod": "^3"}', Path("package.json")) + {'zod': '^3'} + """ + try: + return json.loads(text) + except json.JSONDecodeError as error: + message = f"{path}: is not valid JSON: {error}" + raise RepositoryReadError(message, path=path) from error + + def parse_workflow(text: str, path: Path) -> Node | None: """Return one workflow document, or None when it declares nothing. diff --git a/tests/workflow_contracts/repository_reading_test.py b/tests/workflow_contracts/repository_reading_test.py index 377bac898..df4500eaa 100644 --- a/tests/workflow_contracts/repository_reading_test.py +++ b/tests/workflow_contracts/repository_reading_test.py @@ -21,6 +21,7 @@ import pytest from repository_reading import ( RepositoryReadError, + parse_json, parse_workflow, read_text, workflow_documents, @@ -286,3 +287,15 @@ def test_a_workflow_directory_that_cannot_be_listed_is_refused( assert caught.value.path == locked, ( "the failure names the directory whose listing was refused" ) + + +def test_a_json_file_that_is_not_json_names_the_file(tmp_path: Path) -> None: + """Scenario: a manifest is truncated mid-edit. + + Invariant: the failure carries the manifest's path, not only the line and + column `json` reports for a document it does not name. + """ + path = tmp_path / "package.json" + with pytest.raises(RepositoryReadError, match=r"package\.json") as caught: + parse_json('{"dependencies": ', path) + assert caught.value.path == path diff --git a/tests/workflow_contracts/zod_exception_test.py b/tests/workflow_contracts/zod_exception_test.py index ef785bf13..29ab197a3 100644 --- a/tests/workflow_contracts/zod_exception_test.py +++ b/tests/workflow_contracts/zod_exception_test.py @@ -12,16 +12,20 @@ directly, for `UsersSchema` and the fetcher. These tests fail when the migration lands or when either half gains a parent the reason does not cover. The shared checks over the whole ledger stay in `audit_exception_test.py`. + +Every file is opened by a fixture through `repository_reading`, so a missing or +malformed file fails there with its path. The queries below take what the +fixtures parsed and open nothing. """ from __future__ import annotations -import json import typing as typ from pathlib import Path import bun_lockfile as lock import pytest +from repository_reading import RepositoryReadError, parse_json, read_text REPOSITORY_ROOT = Path(__file__).resolve().parents[2] LEDGER_PATH = REPOSITORY_ROOT / "security" / "audit-exceptions.json" @@ -47,42 +51,173 @@ #: consumer the argument about `UsersSchema` was not written for. ZOD_PERMITTED_WORKSPACES = frozenset({"frontend-pwa", "packages/types"}) +#: Every manifest field through which a workspace can declare a dependency, +#: optional ones included: a consumer that lists zod only as an optional +#: dependency still parses with it when it is installed. +DEPENDENCY_FIELDS = ( + "dependencies", + "devDependencies", + "peerDependencies", + "optionalDependencies", +) + + +class LedgerEntry(typ.TypedDict): + """The part of an audit exception entry this contract reads.""" + + id: str + + +#: A manifest narrowed to its dependency tables: field name to package name to +#: version range. Fields a manifest does not declare are absent. +Manifest = dict[str, dict[str, str]] + + +def _read_json_object(path: Path) -> dict[str, object]: + """Return the JSON object a repository file holds. + + Parameters + ---------- + path : Path + The file to read. + + Returns + ------- + dict[str, object] + The file's top-level object. + + Raises + ------ + RepositoryReadError + If the file cannot be read, is not JSON, or is not an object. + """ + value = parse_json(read_text(path), path) + if not isinstance(value, dict): + message = f"{path}: must hold a JSON object" + raise RepositoryReadError(message, path=path) + return value + + +def _manifest_of(path: Path) -> Manifest: + """Return one package manifest narrowed to its dependency tables. + + Parameters + ---------- + path : Path + The `package.json` to read. + + Returns + ------- + Manifest + Only the dependency fields the manifest declares, each a table of + package name to range. + + Raises + ------ + RepositoryReadError + If the manifest cannot be read or a dependency field is not a table. + """ + data = _read_json_object(path) + manifest: Manifest = {} + for field in DEPENDENCY_FIELDS: + table = data.get(field) + if table is None: + continue + if not isinstance(table, dict): + message = f"{path}: {field} must be an object" + raise RepositoryReadError(message, path=path) + manifest[field] = {str(name): str(rng) for name, rng in table.items()} + return manifest + + +def _workspace_directories(root: Path) -> list[Path]: + """Return the repository root and every workspace directory below it. + + The directory under `packages` is listed with `iterdir` rather than + matched with `glob`, because `glob` reports a missing directory as an empty + result, and an empty result would read as a workspace set that declares + nothing. + + Parameters + ---------- + root : Path + The repository root. + + Returns + ------- + list[Path] + The root, `frontend-pwa` and each directory under `packages`, by name. + + Raises + ------ + RepositoryReadError + If `packages` cannot be listed. + """ + packages = root / "packages" + try: + children = sorted(entry for entry in packages.iterdir() if entry.is_dir()) + except OSError as error: + message = f"{packages}: cannot be listed: {error}" + raise RepositoryReadError(message, path=packages) from error + return [root, root / "frontend-pwa", *children] + + +def workspaces_declaring_zod(manifests: dict[str, Manifest]) -> frozenset[str]: + """Return the workspaces whose manifest declares zod in any field. + + Parameters + ---------- + manifests : dict[str, Manifest] + Workspace directory, relative to the root, to its parsed manifest. + + Returns + ------- + frozenset[str] + The workspace directories that declare zod. + + Examples + -------- + >>> workspaces_declaring_zod({"a": {"dependencies": {"zod": "^3"}}, "b": {}}) + frozenset({'a'}) + + An optional dependency counts: + + >>> workspaces_declaring_zod({"a": {"optionalDependencies": {"zod": "^3"}}}) + frozenset({'a'}) + """ + return frozenset( + workspace + for workspace, manifest in manifests.items() + if any("zod" in manifest.get(field, {}) for field in DEPENDENCY_FIELDS) + ) + @pytest.fixture(scope="module") def ledger_ids() -> frozenset[str]: """Return the identifiers of every entry in the audit exception ledger.""" - ledger: list[dict[str, typ.Any]] = json.loads( - LEDGER_PATH.read_text(encoding="utf-8") - ) - return frozenset(entry["id"] for entry in ledger) + value = parse_json(read_text(LEDGER_PATH), LEDGER_PATH) + if not isinstance(value, list): + message = f"{LEDGER_PATH}: must hold a JSON array" + raise RepositoryReadError(message, path=LEDGER_PATH) + entries: list[LedgerEntry] = typ.cast("list[LedgerEntry]", value) + return frozenset(entry["id"] for entry in entries) @pytest.fixture(scope="module") def packages() -> lock.PackageTable: """Return the package table of the repository's Bun lockfile.""" - return lock.parse_packages(BUN_LOCK_PATH.read_text(encoding="utf-8")) - + return lock.parse_packages(read_text(BUN_LOCK_PATH)) -def _workspaces_declaring_zod() -> frozenset[str]: - """Return the workspace directories whose manifest declares zod. - Examples - -------- - >>> isinstance(_workspaces_declaring_zod(), frozenset) - True - """ - manifests = [ - REPOSITORY_ROOT / "package.json", - *sorted(REPOSITORY_ROOT.glob("frontend-pwa/package.json")), - *sorted(REPOSITORY_ROOT.glob("packages/*/package.json")), - ] - declaring = set() - for manifest in manifests: - data: dict[str, typ.Any] = json.loads(manifest.read_text(encoding="utf-8")) - fields = ("dependencies", "devDependencies", "peerDependencies") - if any("zod" in data.get(field, {}) for field in fields): - declaring.add(manifest.parent.relative_to(REPOSITORY_ROOT).as_posix()) - return frozenset(declaring) +@pytest.fixture(scope="module") +def workspace_manifests() -> dict[str, Manifest]: + """Return every workspace manifest, keyed by directory from the root.""" + return { + directory.relative_to(REPOSITORY_ROOT).as_posix() or ".": _manifest_of( + directory / "package.json" + ) + for directory in _workspace_directories(REPOSITORY_ROOT) + } def test_the_zod_exception_is_void_once_only_patched_releases_resolve( @@ -131,7 +266,7 @@ def test_the_zod_exception_is_void_once_anything_else_pulls_it( def test_the_zod_exception_is_void_once_another_workspace_declares_zod( - ledger_ids: frozenset[str], + ledger_ids: frozenset[str], workspace_manifests: dict[str, Manifest] ) -> None: """The workspace's own use of zod is argued about, so it is pinned too. @@ -143,9 +278,36 @@ def test_the_zod_exception_is_void_once_another_workspace_declares_zod( if ZOD_EXCEPTION_ID not in ledger_ids: pytest.skip("the zod advisory is no longer excepted") - declaring = _workspaces_declaring_zod() + declaring = workspaces_declaring_zod(workspace_manifests) assert declaring == ZOD_PERMITTED_WORKSPACES, ( f"{ZOD_EXCEPTION_ID} argues from {sorted(ZOD_PERMITTED_WORKSPACES)} " f"being the workspaces that declare zod, but {sorted(declaring)} do. " f"Rewrite the entry's reason or remove it, and see {ZOD_TRACKING_ISSUE}." ) + + +def test_an_optional_dependency_on_zod_is_a_declaration( + tmp_path: Path, +) -> None: + """Scenario: a new workspace lists zod only under `optionalDependencies`. + + Invariant: the manifest reader keeps that field, and the query reports the + workspace, so the removal test above cannot be bypassed by choosing it. + """ + manifest_path = tmp_path / "package.json" + manifest_path.write_text( + '{"optionalDependencies": {"zod": "^3"}}', encoding="utf-8" + ) + manifests = {"packages/extra": _manifest_of(manifest_path)} + assert workspaces_declaring_zod(manifests) == frozenset({"packages/extra"}) + + +def test_a_manifest_that_is_not_json_names_the_file(tmp_path: Path) -> None: + """Scenario: a workspace manifest is truncated. + + Invariant: the failure names that manifest, not a key several frames later. + """ + manifest_path = tmp_path / "package.json" + manifest_path.write_text('{"dependencies": ', encoding="utf-8") + with pytest.raises(RepositoryReadError, match=r"package\.json"): + _manifest_of(manifest_path) From eab384504ad07045eb175291fbb88fc1979bfaa6 Mon Sep 17 00:00:00 2001 From: Payton McIntosh Date: Sat, 24 Oct 2026 23:27:03 +0100 Subject: [PATCH 6/6] Cast the narrowed manifest object for the type checker --- tests/workflow_contracts/zod_exception_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/workflow_contracts/zod_exception_test.py b/tests/workflow_contracts/zod_exception_test.py index 29ab197a3..7368e6c43 100644 --- a/tests/workflow_contracts/zod_exception_test.py +++ b/tests/workflow_contracts/zod_exception_test.py @@ -95,7 +95,7 @@ def _read_json_object(path: Path) -> dict[str, object]: if not isinstance(value, dict): message = f"{path}: must hold a JSON object" raise RepositoryReadError(message, path=path) - return value + return typ.cast("dict[str, object]", value) def _manifest_of(path: Path) -> Manifest: