From 1a27a9c6b7925a5e1e1d14edf624dff5f5d9df30 Mon Sep 17 00:00:00 2001 From: Gigi <42325924+g-cqd@users.noreply.github.com> Date: Wed, 5 Aug 2026 22:56:42 +0200 Subject: [PATCH 1/3] feat(security): fuzz the parsers that sit on a trust boundary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the Scorecard Fuzzing gap (#62), and does it on the code that actually reads bytes we didn't write. Four Jazzer.js targets under fuzz/, each asserting a property rather than merely "doesn't throw": fuzz-storage-key validateStorageKey is the path-traversal guard for every on-disk corpus key. The oracle is acceptance, not rejection: if it returns, the key must not be absolute, Windows-rooted, or contain an empty / "." / ".." segment or a backslash or NUL. Rejecting is always fine. fuzz-tar-line parseTarVerboseLine reads `tar -tv` output for a just-downloaded archive, and the validator keys its containment checks off the type it returns — so a `d` or `l` line coming back as anything else would skip the strict path rules. fuzz-zstd-header zstdContentSize does raw offset arithmetic over an attacker-supplied frame header, and its result feeds `needed = size * 2.05` in the disk preflight. Must never throw, and never yield negative / NaN / fractional. fuzz-markdown extractFrontmatter slices on delimiters over arbitrary document bodies; the body it returns must never be longer than its input, and declining to parse must not alter the body. Ran locally before wiring any CI: 2.2M executions on storage-key and 3.1M on tar-line, plus 20k each on the other two, no crashes. ClusterFuzzLite runs them — 5 min per sanitizer on PRs that touch src/ or fuzz/ (code-change mode, so only new crashes fail), and an hour weekly in batch mode to grow the corpus that seeds the PR runs. Both address and undefined sanitizers. Actions pinned by SHA like everything else. The build deliberately skips `npm install`: the targets reach 13 project modules and zero third-party packages (verified), so installing the tree would add minutes and drag in heavy optional native deps no target imports. Also: fuzz/ joins the biome lint scope, knip learns the targets are entry points and that jazzer is a CLI harness rather than an import, and libFuzzer reproducers are gitignored — a crash-* belongs in a unit test, not in the repo. Suite: 2533 pass, 16 skip, 0 fail. --- .clusterfuzzlite/Dockerfile | 10 ++ .clusterfuzzlite/build.sh | 22 +++ .github/workflows/cflite-batch.yml | 41 ++++++ .github/workflows/cflite-pr.yml | 52 +++++++ .gitignore | 10 ++ bun.lock | 221 ++++++++++++++++++++++++++++- fuzz/README.md | 38 +++++ fuzz/fuzz-markdown.js | 35 +++++ fuzz/fuzz-storage-key.js | 48 +++++++ fuzz/fuzz-tar-line.js | 41 ++++++ fuzz/fuzz-zstd-header.js | 47 ++++++ knip.json | 9 +- package.json | 5 +- 13 files changed, 570 insertions(+), 9 deletions(-) create mode 100644 .clusterfuzzlite/Dockerfile create mode 100755 .clusterfuzzlite/build.sh create mode 100644 .github/workflows/cflite-batch.yml create mode 100644 .github/workflows/cflite-pr.yml create mode 100644 fuzz/README.md create mode 100644 fuzz/fuzz-markdown.js create mode 100644 fuzz/fuzz-storage-key.js create mode 100644 fuzz/fuzz-tar-line.js create mode 100644 fuzz/fuzz-zstd-header.js diff --git a/.clusterfuzzlite/Dockerfile b/.clusterfuzzlite/Dockerfile new file mode 100644 index 00000000..f34b88b7 --- /dev/null +++ b/.clusterfuzzlite/Dockerfile @@ -0,0 +1,10 @@ +# ClusterFuzzLite build image for the JavaScript fuzz targets in fuzz/. +# +# base-builder-javascript ships Node plus Jazzer.js and the +# `compile_javascript_fuzzer` helper, which is all these targets need — see +# build.sh for why the project's own dependency tree is deliberately absent. +FROM gcr.io/oss-fuzz-base/base-builder-javascript + +COPY . $SRC/apple-docs +WORKDIR $SRC/apple-docs +COPY .clusterfuzzlite/build.sh $SRC/build.sh diff --git a/.clusterfuzzlite/build.sh b/.clusterfuzzlite/build.sh new file mode 100755 index 00000000..184506d6 --- /dev/null +++ b/.clusterfuzzlite/build.sh @@ -0,0 +1,22 @@ +#!/bin/bash -eu +# +# Build every fuzz/fuzz-*.js target into the OSS-Fuzz output directory. +# +# No `npm install`. The targets reach exactly 13 project modules and zero +# third-party packages — only Node builtins — so installing the project's +# dependency tree would add minutes to every build and drag in heavy, +# platform-sensitive optional deps (@huggingface/transformers, playwright, +# sharp) that no fuzz target imports. If a future target needs a real +# dependency, install it explicitly here rather than reaching for a blanket +# `npm install`; keep the check in fuzz/README.md honest. +# +# The project is ESM ("type": "module"), so the targets `export function +# fuzz(data)` rather than assigning module.exports. + +for target in "$SRC/apple-docs"/fuzz/fuzz-*.js; do + name="$(basename "$target" .js)" + echo "building fuzz target: $name" + # --sync: the targets are synchronous, which lets libFuzzer drive them + # without the async harness overhead (~37k exec/s locally). + compile_javascript_fuzzer apple-docs "fuzz/$name.js" --sync +done diff --git a/.github/workflows/cflite-batch.yml b/.github/workflows/cflite-batch.yml new file mode 100644 index 00000000..aebfa5c3 --- /dev/null +++ b/.github/workflows/cflite-batch.yml @@ -0,0 +1,41 @@ +name: ClusterFuzzLite batch fuzzing + +# The long campaign. PR mode only fuzzes code a PR touched for 5 minutes; +# this runs every target for an hour weekly and grows the shared corpus that +# seeds those PR runs, so coverage compounds instead of restarting cold. +# +# Sunday 04:00 UTC — before the 06:00 snapshot build, so the two don't +# contend for runners. + +on: + schedule: + - cron: '0 4 * * 0' + workflow_dispatch: + +permissions: {} + +jobs: + batch-fuzz: + runs-on: ubuntu-latest + timeout-minutes: 90 + permissions: + contents: read + strategy: + fail-fast: false + matrix: + sanitizer: [address, undefined] + steps: + - name: Build fuzzers (${{ matrix.sanitizer }}) + uses: google/clusterfuzzlite/actions/build_fuzzers@82652fb49e77bc29c35da1167bb286e93c6bcc05 # v1 + with: + language: javascript + sanitizer: ${{ matrix.sanitizer }} + + - name: Run fuzzers (${{ matrix.sanitizer }}) + uses: google/clusterfuzzlite/actions/run_fuzzers@82652fb49e77bc29c35da1167bb286e93c6bcc05 # v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + fuzz-seconds: 3600 + mode: 'batch' + sanitizer: ${{ matrix.sanitizer }} + output-sarif: true diff --git a/.github/workflows/cflite-pr.yml b/.github/workflows/cflite-pr.yml new file mode 100644 index 00000000..a20c6443 --- /dev/null +++ b/.github/workflows/cflite-pr.yml @@ -0,0 +1,52 @@ +name: ClusterFuzzLite PR fuzzing + +# Fuzzes the parsers that sit on a trust boundary — storage keys, tar +# listings, zstd frame headers, Markdown frontmatter. See fuzz/README.md for +# the property each target asserts. +# +# PR mode runs a short campaign seeded by the corpus from previous runs and +# fails the PR on a new crash, so a traversal or parse regression is caught +# before merge rather than by the weekly batch. + +on: + pull_request: + branches: [main] + # Skip when a PR cannot affect the targets. Docs-only and ops-only + # changes are the common case and a 10-minute fuzz run on them is pure + # queue time. + paths: + - 'src/**' + - 'fuzz/**' + - '.clusterfuzzlite/**' + - '.github/workflows/cflite-pr.yml' + +permissions: {} + +jobs: + fuzz: + runs-on: ubuntu-latest + timeout-minutes: 25 + permissions: + contents: read + strategy: + fail-fast: false + matrix: + sanitizer: [address, undefined] + steps: + - name: Build fuzzers (${{ matrix.sanitizer }}) + uses: google/clusterfuzzlite/actions/build_fuzzers@82652fb49e77bc29c35da1167bb286e93c6bcc05 # v1 + with: + language: javascript + sanitizer: ${{ matrix.sanitizer }} + # Needed so the action can diff against the base commit and only + # report crashes the PR introduces. + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Run fuzzers (${{ matrix.sanitizer }}) + uses: google/clusterfuzzlite/actions/run_fuzzers@82652fb49e77bc29c35da1167bb286e93c6bcc05 # v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + fuzz-seconds: 300 + mode: 'code-change' + sanitizer: ${{ matrix.sanitizer }} + output-sarif: true diff --git a/.gitignore b/.gitignore index cccd4d94..2fe63570 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,13 @@ docs/.vitepress/cache/ # Claude Code working tree (agent worktrees, plans, etc.) .claude/ dist-beta/ + +# Fuzzing artifacts — libFuzzer reproducers and corpora are run-local. +# A crash-* file is a bug report, not something to commit; copy the input +# into a unit test instead so the regression is pinned by the suite. +crash-* +leak-* +timeout-* +oom-* +slow-unit-* +fuzz/corpus/ diff --git a/bun.lock b/bun.lock index 1001ff7d..4b0b92d3 100644 --- a/bun.lock +++ b/bun.lock @@ -11,6 +11,7 @@ }, "devDependencies": { "@biomejs/biome": "^2.4.15", + "@jazzer.js/core": "^4.0.0", "@types/bun": "^1.3.14", "jscpd": "^5.0.4", "knip": "^6.14.2", @@ -76,13 +77,37 @@ "@antfu/install-pkg": ["@antfu/install-pkg@1.1.0", "", { "dependencies": { "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" } }, "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ=="], - "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + "@babel/code-frame": ["@babel/code-frame@7.29.7", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw=="], - "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], + "@babel/compat-data": ["@babel/compat-data@7.29.7", "", {}, "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg=="], - "@babel/parser": ["@babel/parser@7.29.3", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA=="], + "@babel/core": ["@babel/core@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-module-transforms": "^7.29.7", "@babel/helpers": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/template": "^7.29.7", "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA=="], - "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + "@babel/generator": ["@babel/generator@7.29.8", "", { "dependencies": { "@babel/parser": "^7.29.8", "@babel/types": "^7.29.8", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg=="], + + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.29.7", "", { "dependencies": { "@babel/compat-data": "^7.29.7", "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g=="], + + "@babel/helper-globals": ["@babel/helper-globals@7.29.7", "", {}, "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA=="], + + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g=="], + + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.29.7", "", { "dependencies": { "@babel/helper-module-imports": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], + + "@babel/helper-validator-option": ["@babel/helper-validator-option@7.29.7", "", {}, "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw=="], + + "@babel/helpers": ["@babel/helpers@7.29.7", "", { "dependencies": { "@babel/template": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg=="], + + "@babel/parser": ["@babel/parser@7.29.8", "", { "dependencies": { "@babel/types": "^7.29.8" }, "bin": "./bin/babel-parser.js" }, "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA=="], + + "@babel/template": ["@babel/template@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg=="], + + "@babel/traverse": ["@babel/traverse@7.29.8", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.8", "@babel/helper-globals": "^7.29.7", "@babel/parser": "^7.29.8", "@babel/template": "^7.29.7", "@babel/types": "^7.29.8", "debug": "^4.3.1" } }, "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg=="], + + "@babel/types": ["@babel/types@7.29.8", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg=="], "@biomejs/biome": ["@biomejs/biome@2.4.15", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.4.15", "@biomejs/cli-darwin-x64": "2.4.15", "@biomejs/cli-linux-arm64": "2.4.15", "@biomejs/cli-linux-arm64-musl": "2.4.15", "@biomejs/cli-linux-x64": "2.4.15", "@biomejs/cli-linux-x64-musl": "2.4.15", "@biomejs/cli-win32-arm64": "2.4.15", "@biomejs/cli-win32-x64": "2.4.15" }, "bin": { "biome": "bin/biome" } }, "sha512-j5VH3a/h/HXTKBM50MDMxRCzkeLv9S2XJcW2WgnZT1+xyisi+0bISrXR82gCX+8S9lvK0skEvHJRN+3Ktr2hlw=="], @@ -186,8 +211,30 @@ "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.35.3", "", { "os": "win32", "cpu": "x64" }, "sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA=="], + "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="], + + "@istanbuljs/schema": ["@istanbuljs/schema@0.1.6", "", {}, "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw=="], + + "@jazzer.js/bug-detectors": ["@jazzer.js/bug-detectors@4.0.0", "", { "dependencies": { "@jazzer.js/core": "4.0.0", "@jazzer.js/hooking": "4.0.0" } }, "sha512-piWVM3/96pFqyRkld75C++qP1shlTKXW3WymPTyyxcnjwNmh+6Mqz37OiCZ+yfsgHzl65yrhdEsuRGf1p6a/eQ=="], + + "@jazzer.js/core": ["@jazzer.js/core@4.0.0", "", { "dependencies": { "@jazzer.js/bug-detectors": "4.0.0", "@jazzer.js/fuzzer": "4.0.0", "@jazzer.js/hooking": "4.0.0", "@jazzer.js/instrumentor": "4.0.0", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-reports": "^3.1.7", "tmp": "^0.2.5", "yargs": "<18.0.0" }, "bin": { "jazzer": "dist/cli.js" } }, "sha512-w90xGs5qMOE9KA5AV7OrosTWIOvMaeH5YSSnMr14FNAg2n0kalkurZMwlboz9G3+SccMp4n6QkKExq3b7r1VAQ=="], + + "@jazzer.js/fuzzer": ["@jazzer.js/fuzzer@4.0.0", "", { "dependencies": { "bindings": "^1.5.0", "cmake-js": "^8.0.0", "node-addon-api": "^8.7.0" } }, "sha512-o79aZFwIGA2HLYlubdBiPKq1LHva/w+hT/M6W2qvhu20GACYn5EX19EGjf+D1+9orBov0Nnlpb5ullphGa0N/Q=="], + + "@jazzer.js/hooking": ["@jazzer.js/hooking@4.0.0", "", {}, "sha512-Zq9aTNVwnbndSiYa60+gnoMgNNnR9ASHdUUDYKI4gu1VdFe5h+L2kqPdYtb3YPT4IgpCorYe7nxP+b7I4GOp9w=="], + + "@jazzer.js/instrumentor": ["@jazzer.js/instrumentor@4.0.0", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/generator": "^7.25.6", "@jazzer.js/fuzzer": "4.0.0", "@jazzer.js/hooking": "4.0.0", "istanbul-lib-hook": "^3.0.0", "istanbul-lib-instrument": "^6.0.3", "proper-lockfile": "^4.1.2", "source-map-support": "^0.5.21" } }, "sha512-qyvKSg/24ZTZyD1rnIPOuwXAh2SuNH0f334ilfgJtEKswWZr0KX508L4nNzFzop9RD5CgsfBDd5C22BdQfwFCA=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + "@mermaid-js/mermaid-mindmap": ["@mermaid-js/mermaid-mindmap@9.3.0", "", { "dependencies": { "@braintree/sanitize-url": "^6.0.0", "cytoscape": "^3.23.0", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-fcose": "^2.1.0", "d3": "^7.0.0", "khroma": "^2.0.0", "non-layered-tidy-tree-layout": "^2.0.2" } }, "sha512-IhtYSVBBRYviH1Ehu8gk69pMDF8DSRqXBRDMWrEfHoaMruHeaP2DXA3PBnuwsMaCdPQhlUUcy/7DBLAEIXvCAw=="], "@mermaid-js/parser": ["@mermaid-js/parser@1.1.1", "", { "dependencies": { "@chevrotain/types": "~11.1.1" } }, "sha512-VuHdsYMK1bT6X2JbcAaWAhugTRvRBRyuZgd+c22swUeI9g/ntaxF7CY7dYarhZovofCbUNO0G7JesfmNtjYOCw=="], @@ -520,12 +567,26 @@ "algoliasearch": ["algoliasearch@5.52.1", "", { "dependencies": { "@algolia/abtesting": "1.18.1", "@algolia/client-abtesting": "5.52.1", "@algolia/client-analytics": "5.52.1", "@algolia/client-common": "5.52.1", "@algolia/client-insights": "5.52.1", "@algolia/client-personalization": "5.52.1", "@algolia/client-query-suggestions": "5.52.1", "@algolia/client-search": "5.52.1", "@algolia/ingestion": "1.52.1", "@algolia/monitoring": "1.52.1", "@algolia/recommend": "5.52.1", "@algolia/requester-browser-xhr": "5.52.1", "@algolia/requester-fetch": "5.52.1", "@algolia/requester-node-http": "5.52.1" } }, "sha512-fHA8+kXTbjagw3jkLiaS7KKrH8qe2DyOsiUhGlN4cdT77PEsfqXZl7ewDk1hsg+pJnPlnE50XtLxjR91iJOpmg=="], + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "append-transform": ["append-transform@2.0.0", "", { "dependencies": { "default-require-extensions": "^3.0.0" } }, "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg=="], + + "baseline-browser-mapping": ["baseline-browser-mapping@2.11.12", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-r7WnVImvVCeFpf2DOXfy41aPWzeNg3H/A2X4dKmy1QL0MSyyk/e7z8ihJ3N6Nn2PsdhkVlqnEfnUE4a05P2aTA=="], + + "bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="], + "birpc": ["birpc@2.9.0", "", {}, "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="], "body-parser": ["body-parser@2.3.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^2.0.0", "debug": "^4.4.3", "http-errors": "^2.0.1", "iconv-lite": "^0.7.2", "on-finished": "^2.4.1", "qs": "^6.15.2", "raw-body": "^3.0.2", "type-is": "^2.1.0" } }, "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw=="], "boolean": ["boolean@3.2.0", "", {}, "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw=="], + "browserslist": ["browserslist@4.28.7", "", { "dependencies": { "baseline-browser-mapping": "^2.10.44", "caniuse-lite": "^1.0.30001806", "electron-to-chromium": "^1.5.393", "node-releases": "^2.0.51", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw=="], + + "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], + "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], @@ -534,12 +595,24 @@ "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], + "caniuse-lite": ["caniuse-lite@1.0.30001806", "", {}, "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw=="], + "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], "character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="], "character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="], + "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], + + "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + + "cmake-js": ["cmake-js@8.0.0", "", { "dependencies": { "debug": "^4.4.3", "fs-extra": "^11.3.3", "node-api-headers": "^1.8.0", "rc": "1.2.8", "semver": "^7.7.3", "tar": "^7.5.6", "url-join": "^4.0.1", "which": "^6.0.0", "yargs": "^17.7.2" }, "bin": { "cmake-js": "bin/cmake-js" } }, "sha512-YbUP88RDwCvoQkZhRtGURYm9RIpWdtvZuhT87fKNoLjk8kIFIFeARpKfuZQGdwfH99GZpUmqSfcDrK62X7lTgg=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + "comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="], "commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], @@ -548,6 +621,8 @@ "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + "cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], "cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="], @@ -650,6 +725,10 @@ "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + "deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="], + + "default-require-extensions": ["default-require-extensions@3.0.1", "", { "dependencies": { "strip-bom": "^4.0.0" } }, "sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw=="], + "define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="], "define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="], @@ -672,6 +751,10 @@ "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], + "electron-to-chromium": ["electron-to-chromium@1.5.401", "", {}, "sha512-H6ViHN68nGYlChEvlIU67fn8O2/tpbWQPwck98yaJmh+08LSvHiydzDQ6oXNccLU3kNRVIRS9A4mA7CG+i6fLQ=="], + + "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + "emoji-regex-xs": ["emoji-regex-xs@1.0.0", "", {}, "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg=="], "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], @@ -688,6 +771,8 @@ "es6-error": ["es6-error@4.1.1", "", {}, "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg=="], + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], @@ -712,6 +797,8 @@ "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + "file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="], + "finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="], "flatbuffers": ["flatbuffers@25.9.23", "", {}, "sha512-MI1qs7Lo4Syw0EOzUl0xjs2lsoeqFku44KpngfIduHBYvzm8h2+7K8YMQh1JtVVVrUvhLpNwqVi4DERegUJhPQ=="], @@ -724,10 +811,16 @@ "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="], + "fs-extra": ["fs-extra@11.4.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA=="], + "fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="], "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], @@ -740,10 +833,14 @@ "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + "guid-typescript": ["guid-typescript@1.0.9", "", {}, "sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ=="], "hachure-fill": ["hachure-fill@0.5.2", "", {}, "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg=="], + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + "has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="], "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], @@ -758,6 +855,8 @@ "hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="], + "html-escaper": ["html-escaper@2.0.2", "", {}, "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="], + "html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="], "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="], @@ -768,30 +867,52 @@ "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + "ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], + "internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="], "ip-address": ["ip-address@10.4.0", "", {}, "sha512-oSK96Grm3aP6OrS263xVxbNDGVL7rzBtYdpGqlDG8iQdoenDoTs/nkki+DflYbAEE8Xl6o5YxhxlrKvI3nqKXQ=="], "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="], "is-what": ["is-what@5.5.0", "", {}, "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw=="], "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + "istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="], + + "istanbul-lib-hook": ["istanbul-lib-hook@3.0.0", "", { "dependencies": { "append-transform": "^2.0.0" } }, "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ=="], + + "istanbul-lib-instrument": ["istanbul-lib-instrument@6.0.3", "", { "dependencies": { "@babel/core": "^7.23.9", "@babel/parser": "^7.23.9", "@istanbuljs/schema": "^0.1.3", "istanbul-lib-coverage": "^3.2.0", "semver": "^7.5.4" } }, "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q=="], + + "istanbul-lib-report": ["istanbul-lib-report@3.0.1", "", { "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", "supports-color": "^7.1.0" } }, "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw=="], + + "istanbul-reports": ["istanbul-reports@3.2.0", "", { "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" } }, "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA=="], + "jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="], "jose": ["jose@6.2.2", "", {}, "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ=="], + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "jscpd": ["jscpd@5.0.4", "", { "optionalDependencies": { "cpd-darwin-arm64": "5.0.4", "cpd-darwin-x64": "5.0.4", "cpd-linux-arm64-gnu": "5.0.4", "cpd-linux-x64-gnu": "5.0.4", "cpd-linux-x64-musl": "5.0.4", "cpd-windows-x64-msvc": "5.0.4" }, "bin": { "jscpd": "run-jscpd.js" } }, "sha512-I1skFSqn6gpLeA62konLjtfJgZZcam3scIXHAXM+/1VRpBHXauWF/x2VOgeGbizR7vrxA8qSxsHfNwbBOAHedw=="], + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], "json-schema-typed": ["json-schema-typed@8.0.2", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="], "json-stringify-safe": ["json-stringify-safe@5.0.1", "", {}, "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="], + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + + "jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="], + "katex": ["katex@0.16.47", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg=="], "khroma": ["khroma@2.1.0", "", {}, "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw=="], @@ -828,8 +949,12 @@ "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], + "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + "make-dir": ["make-dir@4.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="], + "mark.js": ["mark.js@8.11.1", "", {}, "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ=="], "marked": ["marked@16.4.2", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA=="], @@ -862,8 +987,12 @@ "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], + "minisearch": ["minisearch@7.2.0", "", {}, "sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg=="], + "minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="], + "mitt": ["mitt@3.0.1", "", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="], "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], @@ -872,6 +1001,12 @@ "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], + "node-addon-api": ["node-addon-api@8.9.1", "", {}, "sha512-4eUQWVPCUUUiBjLnHS3cXWeC6ryoPUc0U3rP7IuzapoGbzMqd/r6KKO0clr0b+snQhsrueFEhCZDdK+LK7hxKg=="], + + "node-api-headers": ["node-api-headers@1.9.0", "", {}, "sha512-2oNILP4jXwRB4ywnYKjVk1YyJ96n2D4EOVJO6S3oYZ5PtbJrw3Yt9TpAuX3nBLMuzn74rnfGQrv13pS9vC+YiA=="], + + "node-releases": ["node-releases@2.0.52", "", {}, "sha512-MRlTqhAfoMx/4mhEbPo3Hi02g9LJZaJkka69V6h67Cb1gjrAG0jsTE4CZX1eptNx+VCAwJmfpnDIF4P0Nh1A7A=="], + "non-layered-tidy-tree-layout": ["non-layered-tidy-tree-layout@2.0.2", "", {}, "sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw=="], "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], @@ -930,6 +1065,8 @@ "preact": ["preact@10.29.2", "", {}, "sha512-7tNmwg/7mzzAoB/8kSg6Hl37JraAZw3Z3A0JSY7VXlZwo82Xn0G7wKbNNs2qoF4ZEEsQGTwDAroNdqKs1ofJxQ=="], + "proper-lockfile": ["proper-lockfile@4.1.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "retry": "^0.12.0", "signal-exit": "^3.0.2" } }, "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA=="], + "property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="], "protobufjs": ["protobufjs@7.6.5", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.5", "@protobufjs/eventemitter": "^1.1.1", "@protobufjs/fetch": "^1.1.1", "@protobufjs/float": "^1.0.2", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.1", "@types/node": ">=13.7.0", "long": "^5.3.2" } }, "sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw=="], @@ -942,16 +1079,22 @@ "raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="], + "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="], + "regex": ["regex@6.1.0", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg=="], "regex-recursion": ["regex-recursion@6.0.2", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg=="], "regex-utilities": ["regex-utilities@2.3.0", "", {}, "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng=="], + "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], + "retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="], + "rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="], "roarr": ["roarr@2.15.4", "", { "dependencies": { "boolean": "^3.0.1", "detect-node": "^2.0.4", "globalthis": "^1.0.1", "json-stringify-safe": "^5.0.1", "semver-compare": "^1.0.0", "sprintf-js": "^1.1.2" } }, "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A=="], @@ -998,10 +1141,16 @@ "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], + "signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + "smol-toml": ["smol-toml@1.6.1", "", {}, "sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg=="], + "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], + "space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="], "speakingurl": ["speakingurl@14.0.1", "", {}, "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ=="], @@ -1010,20 +1159,32 @@ "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], + "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=="], + "stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="], + "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-bom": ["strip-bom@4.0.0", "", {}, "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w=="], + "strip-json-comments": ["strip-json-comments@5.0.3", "", {}, "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw=="], "stylis": ["stylis@4.4.0", "", {}, "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA=="], "superjson": ["superjson@2.2.6", "", { "dependencies": { "copy-anything": "^4" } }, "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA=="], + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + "tabbable": ["tabbable@6.4.0", "", {}, "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg=="], + "tar": ["tar@7.5.22", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA=="], + "tinyexec": ["tinyexec@1.1.2", "", {}, "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA=="], "tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="], + "tmp": ["tmp@0.2.7", "", {}, "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw=="], + "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="], @@ -1052,8 +1213,14 @@ "unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="], + "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], + "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], + "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], + + "url-join": ["url-join@4.0.1", "", {}, "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA=="], + "uuid": ["uuid@14.0.0", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg=="], "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], @@ -1074,16 +1241,30 @@ "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + + "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], + "yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="], + "yargs": ["yargs@17.7.3", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g=="], + + "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], "zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="], "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], + "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "@img/sharp-wasm32/@emnapi/runtime": ["@emnapi/runtime@1.11.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA=="], "@mermaid-js/mermaid-mindmap/@braintree/sanitize-url": ["@braintree/sanitize-url@6.0.4", "", {}, "sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A=="], @@ -1104,10 +1285,16 @@ "@shikijs/themes/@shikijs/types": ["@shikijs/types@4.1.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-3EQWX54fMpniOrDblzAhiwiJwpiTMW6+B9DWyUd9ska483tbayFYuw47UxwuPknI31bKnySfVQ/QW+jFL4rFdA=="], + "@vue/compiler-core/@babel/parser": ["@babel/parser@7.29.3", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA=="], + + "@vue/compiler-sfc/@babel/parser": ["@babel/parser@7.29.3", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA=="], + "body-parser/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="], "body-parser/type-is": ["type-is@2.1.0", "", { "dependencies": { "content-type": "^2.0.0", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA=="], + "cmake-js/which": ["which@6.0.1", "", { "dependencies": { "isexe": "^4.0.0" }, "bin": { "node-which": "bin/which.js" } }, "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg=="], + "cytoscape-fcose/cose-base": ["cose-base@2.2.0", "", { "dependencies": { "layout-base": "^2.0.0" } }, "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g=="], "d3-dsv/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="], @@ -1120,8 +1307,14 @@ "global-agent/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + "istanbul-lib-instrument/@babel/parser": ["@babel/parser@7.29.3", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA=="], + + "lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + "onnxruntime-web/onnxruntime-common": ["onnxruntime-common@1.24.0-dev.20251116-b39e144322", "", {}, "sha512-BOoomdHYmNRL5r4iQ4bMvsl2t0/hzVQ3OM3PHD0gxeXu1PmggqBv3puZicEUVOA3AtHHYmqZtjMj9FOfGrATTw=="], + "rc/strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], + "rolldown/@oxc-project/types": ["@oxc-project/types@0.133.0", "", {}, "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA=="], "shiki/@shikijs/core": ["@shikijs/core@4.1.0", "", { "dependencies": { "@shikijs/primitive": "4.1.0", "@shikijs/types": "4.1.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-jLJtSJeuFffqX6/inRE1zqU5aFv2hrszvYgq3OjbAgFRZiWv7abKMDdQzYxuSDfmUPQozZvI/kuy6VMTvnvqTQ=="], @@ -1136,12 +1329,20 @@ "@shikijs/core/@shikijs/engine-javascript/oniguruma-to-es": ["oniguruma-to-es@3.1.1", "", { "dependencies": { "emoji-regex-xs": "^1.0.0", "regex": "^6.0.1", "regex-recursion": "^6.0.2" } }, "sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ=="], + "@vue/compiler-core/@babel/parser/@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + + "@vue/compiler-sfc/@babel/parser/@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + + "cmake-js/which/isexe": ["isexe@4.0.0", "", {}, "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw=="], + "cytoscape-fcose/cose-base/layout-base": ["layout-base@2.0.1", "", {}, "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg=="], "d3-sankey/d3-array/internmap": ["internmap@1.0.1", "", {}, "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="], "d3-sankey/d3-shape/d3-path": ["d3-path@1.0.9", "", {}, "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="], + "istanbul-lib-instrument/@babel/parser/@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + "vitepress/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@2.5.0", "", { "dependencies": { "@shikijs/types": "2.5.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^3.1.0" } }, "sha512-VjnOpnQf8WuCEZtNUdjjwGUbtAVKuZkVQ/5cHy/tojVVRIRtlWMYVjyWhxOmIq05AlSOv72z7hRNRGVBgQOl0w=="], "vitepress/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@2.5.0", "", { "dependencies": { "@shikijs/types": "2.5.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-pGd1wRATzbo/uatrCIILlAdFVKdxImWJGQ5rFiB5VZi2ve5xj3Ax9jny8QvkaV93btQEwR/rSz5ERFpC5mKNIw=="], @@ -1150,6 +1351,18 @@ "vitepress/shiki/@shikijs/themes": ["@shikijs/themes@2.5.0", "", { "dependencies": { "@shikijs/types": "2.5.0" } }, "sha512-wGrk+R8tJnO0VMzmUExHR+QdSaPUl/NKs+a4cQQRWyoc3YFbUzuLEi/KWK1hj+8BfHRKm2jNhhJck1dfstJpiw=="], + "@vue/compiler-core/@babel/parser/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@vue/compiler-core/@babel/parser/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], + + "@vue/compiler-sfc/@babel/parser/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@vue/compiler-sfc/@babel/parser/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], + + "istanbul-lib-instrument/@babel/parser/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "istanbul-lib-instrument/@babel/parser/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], + "vitepress/shiki/@shikijs/engine-javascript/oniguruma-to-es": ["oniguruma-to-es@3.1.1", "", { "dependencies": { "emoji-regex-xs": "^1.0.0", "regex": "^6.0.1", "regex-recursion": "^6.0.2" } }, "sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ=="], } } diff --git a/fuzz/README.md b/fuzz/README.md new file mode 100644 index 00000000..ee102215 --- /dev/null +++ b/fuzz/README.md @@ -0,0 +1,38 @@ +# Fuzz targets + +Jazzer.js fuzz targets for the parsers that handle bytes we did not write. +Run continuously by ClusterFuzzLite (see `.clusterfuzzlite/`), and on every +pull request against the code the PR touches. + +## What is fuzzed, and why + +Everything here sits on a trust boundary — the input arrives from a +downloaded archive, a tar listing, or a document body: + +| Target | Under test | The property that must hold | +| --- | --- | --- | +| `fuzz-storage-key.js` | `validateStorageKey` | If it returns, the key cannot traverse. A key that survives validation and still contains `..`, a leading `/`, a NUL, or a backslash is a path-traversal hole. | +| `fuzz-tar-line.js` | `parseTarVerboseLine` | Never throws on arbitrary `tar -tv` output, and never reports a directory entry as a file (the archive validator keys its path checks off `type`). | +| `fuzz-zstd-header.js` | `zstdContentSize` | Never throws on a malformed frame header, and never returns a negative or non-finite size — the value feeds the disk preflight's arithmetic. | +| `fuzz-markdown.js` | `extractFrontmatter` | Never throws, and never returns a body longer than its input. | + +## Node, not Bun + +Jazzer.js runs on Node. The modules above are Node-clean: every `Bun.*` +reference in them lives inside a function these targets do not call +(`safeFilename`'s hasher, `requiredBytesForArchive`'s `Bun.file`, the async +archive validators' `Bun.spawn`). Keep it that way — a target that pulls in +a Bun global fails at import time inside the OSS-Fuzz image, not at runtime, +so it is easy to miss. + +## Running one locally + +```bash +bun x @jazzer.js/core fuzz/fuzz-storage-key.js --sync -- -runs=100000 +``` + +A crash writes a `crash-` file; feed it back with: + +```bash +bun x @jazzer.js/core fuzz/fuzz-storage-key.js --sync -- crash- +``` diff --git a/fuzz/fuzz-markdown.js b/fuzz/fuzz-markdown.js new file mode 100644 index 00000000..41faf70a --- /dev/null +++ b/fuzz/fuzz-markdown.js @@ -0,0 +1,35 @@ +/** + * extractFrontmatter runs over every Markdown body in the corpus, including + * the ones an enrichment or crawl pulled off the network. It does index + * arithmetic on delimiters (`---`, `\n---`) and hands the remainder to a + * YAML parse. + * + * Properties: it never throws on arbitrary text, it always returns the + * documented shape, and the body it returns is a suffix of the input — a + * body longer than the input would mean the slicing invented content. + */ + +import { extractFrontmatter } from '../src/content/parse-markdown.js' + +export function fuzz(data) { + const text = data.toString('utf8') + const result = extractFrontmatter(text) + + if (result == null || typeof result !== 'object') { + throw new Error(`extractFrontmatter returned ${JSON.stringify(result)}`) + } + if (typeof result.body !== 'string') { + throw new Error(`body is not a string: ${JSON.stringify(result.body)}`) + } + if (result.body.length > text.length) { + throw new Error(`body (${result.body.length}) longer than input (${text.length})`) + } + // No frontmatter means the body is the input verbatim — the parser must + // not silently drop leading content when it declines to parse. + if (result.frontmatter === null && result.body !== text) { + throw new Error('declined frontmatter but still altered the body') + } + if (result.frontmatter !== null && typeof result.frontmatter !== 'object') { + throw new Error(`frontmatter is neither null nor an object: ${JSON.stringify(result.frontmatter)}`) + } +} diff --git a/fuzz/fuzz-storage-key.js b/fuzz/fuzz-storage-key.js new file mode 100644 index 00000000..c6f23763 --- /dev/null +++ b/fuzz/fuzz-storage-key.js @@ -0,0 +1,48 @@ +/** + * validateStorageKey is the path-traversal guard for every on-disk corpus + * key. keyPath() calls it before resolving a key into `raw-json/` or + * `markdown/`, so a key that survives validation and still escapes is a + * write-anywhere primitive. + * + * The oracle is not "does it throw" — it is "if it ACCEPTS, is the key + * actually safe". Rejection is always fine; acceptance is what we check. + */ + +import { ValidationError } from '../src/lib/errors.js' +import { validateStorageKey } from '../src/lib/safe-path.js' + +/** Properties that must hold for every key validateStorageKey returns. */ +function assertKeyCannotTraverse(key) { + if (key.startsWith('/') || key.startsWith('~')) { + throw new Error(`accepted an absolute key: ${JSON.stringify(key)}`) + } + if (/^[A-Za-z]:[\\/]/.test(key)) { + throw new Error(`accepted a Windows-rooted key: ${JSON.stringify(key)}`) + } + for (const segment of key.split('/')) { + if (segment === '' || segment === '.' || segment === '..') { + throw new Error(`accepted a traversing segment ${JSON.stringify(segment)} in ${JSON.stringify(key)}`) + } + if (segment.includes('\\') || segment.includes('\0')) { + throw new Error(`accepted a smuggling character in ${JSON.stringify(key)}`) + } + } +} + +export function fuzz(data) { + const raw = data.toString('utf8') + let accepted + try { + accepted = validateStorageKey(raw) + } catch (err) { + // Rejection is a correct outcome for anything unsafe. Only a rejection + // that isn't our typed error indicates a real defect (a TypeError from + // an unhandled shape, say). + if (err instanceof ValidationError) return + throw err + } + if (accepted !== raw) { + throw new Error(`validateStorageKey mutated its input: ${JSON.stringify(raw)} -> ${JSON.stringify(accepted)}`) + } + assertKeyCannotTraverse(accepted) +} diff --git a/fuzz/fuzz-tar-line.js b/fuzz/fuzz-tar-line.js new file mode 100644 index 00000000..0b5d7017 --- /dev/null +++ b/fuzz/fuzz-tar-line.js @@ -0,0 +1,41 @@ +/** + * parseTarVerboseLine reads `tar -tv` output for an archive we just + * downloaded, and the archive validator keys its containment checks off the + * `type` and `path` it returns. Malformed or hostile listing text must not + * crash the parse, and must not let an entry present itself as a benign + * file type. + */ + +import { parseTarVerboseLine } from '../src/commands/setup/validate-archive.js' + +export function fuzz(data) { + const text = data.toString('utf8') + // Real callers feed one line at a time; a NUL or newline inside the buffer + // is exactly the kind of smuggling we want to explore, so split the way + // the caller does rather than sanitising first. + for (const line of text.split('\n')) { + const entry = parseTarVerboseLine(line) + if (entry == null) continue + + if (typeof entry.type !== 'string' || entry.type.length !== 1) { + throw new Error(`entry.type is not a single char: ${JSON.stringify(entry.type)}`) + } + if (typeof entry.path !== 'string') { + throw new Error(`entry.path is not a string: ${JSON.stringify(entry.path)}`) + } + // A `d` line describing a directory must never come back as a regular + // file: the validator applies its strictest path rules to non-'-' types, + // so a type downgrade would skip them. + if (line.startsWith('d') && entry.type !== 'd') { + throw new Error(`directory line parsed as type ${JSON.stringify(entry.type)}: ${JSON.stringify(line)}`) + } + if (line.startsWith('l') && entry.type !== 'l') { + throw new Error(`symlink line parsed as type ${JSON.stringify(entry.type)}: ${JSON.stringify(line)}`) + } + // The link target is either absent or a string — the validator + // dereferences it when present. + if (entry.link != null && typeof entry.link !== 'string') { + throw new Error(`entry.link is neither null nor a string: ${JSON.stringify(entry.link)}`) + } + } +} diff --git a/fuzz/fuzz-zstd-header.js b/fuzz/fuzz-zstd-header.js new file mode 100644 index 00000000..fb5429c8 --- /dev/null +++ b/fuzz/fuzz-zstd-header.js @@ -0,0 +1,47 @@ +/** + * zstdContentSize parses the frame header of a freshly downloaded archive to + * decide how much disk the extraction needs. It does raw offset arithmetic + * over attacker-supplied bytes (RFC 8878 Frame_Content_Size), and its result + * feeds `needed = size * 2.05` in the setup preflight. + * + * Two properties matter. It must never throw — a crash here aborts an + * install before extraction. And it must never hand back a value that makes + * the preflight nonsense: negative, NaN, or non-integer. + */ + +import { closeSync, mkdtempSync, openSync, rmSync, writeSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { zstdContentSize } from '../src/commands/setup/disk-space.js' + +// One staging dir for the whole campaign — mkdtemp per iteration would make +// the filesystem, not the parser, the bottleneck. +const dir = mkdtempSync(join(tmpdir(), 'apple-docs-fuzz-zstd-')) +const path = join(dir, 'frame.tar.zst') + +process.on('exit', () => { try { rmSync(dir, { recursive: true, force: true }) } catch {} }) + +export function fuzz(data) { + const fd = openSync(path, 'w') + try { + writeSync(fd, data, 0, data.length, 0) + } finally { + closeSync(fd) + } + + const size = zstdContentSize(path) + if (size === null) return + + if (typeof size !== 'number') { + throw new Error(`non-numeric frame size: ${JSON.stringify(size)}`) + } + if (!Number.isFinite(size)) { + throw new Error(`non-finite frame size: ${size}`) + } + if (size < 0) { + throw new Error(`negative frame size: ${size}`) + } + if (!Number.isInteger(size)) { + throw new Error(`fractional frame size: ${size}`) + } +} diff --git a/knip.json b/knip.json index 12d78272..18a81221 100644 --- a/knip.json +++ b/knip.json @@ -4,18 +4,21 @@ "src/**/*.js", "test/**/*.js", "scripts/**/*.js", - "docs/.vitepress/config.mjs" + "docs/.vitepress/config.mjs", + "fuzz/fuzz-*.js" ], "project": [ "src/**/*.js", "test/**/*.js", "scripts/**/*.js", "docs/.vitepress/**/*.{js,mjs}", - "!test/fixtures/**" + "!test/fixtures/**", + "fuzz/**/*.js" ], "ignoreDependencies": [ "bun-types", "onnxruntime-node", - "onnxruntime-web" + "onnxruntime-web", + "@jazzer.js/core" ] } diff --git a/package.json b/package.json index 9d469d12..ff48d912 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@g-cqd/apple-docs", "version": "1.0.0", - "description": "Apple Developer Documentation CLI and MCP server — search, read, and browse Apple docs locally", + "description": "Apple Developer Documentation CLI and MCP server \u2014 search, read, and browse Apple docs locally", "type": "module", "bin": { "apple-docs": "./cli.js", @@ -45,7 +45,7 @@ "start": "bun run index.js", "eval:search": "bun scripts/eval-search.js", "typecheck": "bun x tsc --noEmit", - "lint": "biome check --diagnostic-level=error ./src ./test ./cli.js ./index.js", + "lint": "biome check --diagnostic-level=error ./src ./test ./fuzz ./cli.js ./index.js", "lint:web": "biome check --diagnostic-level=error ./src/web/", "lint:unused": "bunx knip", "lint:unused:fix": "bunx knip --fix", @@ -64,6 +64,7 @@ }, "devDependencies": { "@biomejs/biome": "^2.4.15", + "@jazzer.js/core": "^4.0.0", "@types/bun": "^1.3.14", "jscpd": "^5.0.4", "knip": "^6.14.2", From a8859bb53044b826109e543e85014ddaa08b2011 Mon Sep 17 00:00:00 2001 From: Gigi <42325924+g-cqd@users.noreply.github.com> Date: Wed, 5 Aug 2026 23:00:58 +0200 Subject: [PATCH 2/3] =?UTF-8?q?fix(fuzz):=20drop=20the=20sanitizer=20matri?= =?UTF-8?q?x=20=E2=80=94=20JS=20builds=20reject=20it?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first PR run failed both legs at build time: ERROR: JavaScript projects cannot be fuzzed with sanitizers. address/undefined are C/C++ concepts; the OSS-Fuzz JavaScript builder refuses any sanitizer but `none`. Jazzer.js reports uncaught exceptions and our own assertion failures instead, which is exactly what the four targets assert against — so the matrix was buying nothing even in principle. One job per workflow now, sanitizer: none, with the reason recorded inline so it does not get "fixed" back. --- .github/workflows/cflite-batch.yml | 14 ++++++-------- .github/workflows/cflite-pr.yml | 17 +++++++++-------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/.github/workflows/cflite-batch.yml b/.github/workflows/cflite-batch.yml index aebfa5c3..b9e9360a 100644 --- a/.github/workflows/cflite-batch.yml +++ b/.github/workflows/cflite-batch.yml @@ -20,22 +20,20 @@ jobs: timeout-minutes: 90 permissions: contents: read - strategy: - fail-fast: false - matrix: - sanitizer: [address, undefined] steps: - - name: Build fuzzers (${{ matrix.sanitizer }}) + # sanitizer: none — see cflite-pr.yml; the JS builder rejects any other + # value. + - name: Build fuzzers uses: google/clusterfuzzlite/actions/build_fuzzers@82652fb49e77bc29c35da1167bb286e93c6bcc05 # v1 with: language: javascript - sanitizer: ${{ matrix.sanitizer }} + sanitizer: none - - name: Run fuzzers (${{ matrix.sanitizer }}) + - name: Run fuzzers uses: google/clusterfuzzlite/actions/run_fuzzers@82652fb49e77bc29c35da1167bb286e93c6bcc05 # v1 with: github-token: ${{ secrets.GITHUB_TOKEN }} fuzz-seconds: 3600 mode: 'batch' - sanitizer: ${{ matrix.sanitizer }} + sanitizer: none output-sarif: true diff --git a/.github/workflows/cflite-pr.yml b/.github/workflows/cflite-pr.yml index a20c6443..dfeac0ae 100644 --- a/.github/workflows/cflite-pr.yml +++ b/.github/workflows/cflite-pr.yml @@ -28,25 +28,26 @@ jobs: timeout-minutes: 25 permissions: contents: read - strategy: - fail-fast: false - matrix: - sanitizer: [address, undefined] steps: - - name: Build fuzzers (${{ matrix.sanitizer }}) + # sanitizer: none — JavaScript has no ASan/UBSan equivalent here, and + # the OSS-Fuzz builder rejects anything else outright ("JavaScript + # projects cannot be fuzzed with sanitizers"). Jazzer.js catches + # uncaught exceptions and our own assertion failures instead, which is + # what the targets are written against. + - name: Build fuzzers uses: google/clusterfuzzlite/actions/build_fuzzers@82652fb49e77bc29c35da1167bb286e93c6bcc05 # v1 with: language: javascript - sanitizer: ${{ matrix.sanitizer }} + sanitizer: none # Needed so the action can diff against the base commit and only # report crashes the PR introduces. github-token: ${{ secrets.GITHUB_TOKEN }} - - name: Run fuzzers (${{ matrix.sanitizer }}) + - name: Run fuzzers uses: google/clusterfuzzlite/actions/run_fuzzers@82652fb49e77bc29c35da1167bb286e93c6bcc05 # v1 with: github-token: ${{ secrets.GITHUB_TOKEN }} fuzz-seconds: 300 mode: 'code-change' - sanitizer: ${{ matrix.sanitizer }} + sanitizer: none output-sarif: true From 114df099a49241ca4e3dfe279c4478ecd79d110f Mon Sep 17 00:00:00 2001 From: Gigi <42325924+g-cqd@users.noreply.github.com> Date: Wed, 5 Aug 2026 23:08:50 +0200 Subject: [PATCH 3/3] =?UTF-8?q?fix(fuzz):=20run=20Jazzer.js=20directly=20?= =?UTF-8?q?=E2=80=94=20ClusterFuzzLite=20can't=20build=20JavaScript?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CFL's two layers contradict each other for JS, confirmed against the real action across two pushes: build step ERROR: JavaScript projects cannot be fuzzed with sanitizers. CIFuzz cfg Invalid SANITIZER: none. Must be one of: ['address', 'memory', 'undefined', 'coverage']. `compile` accepts only `none`; the config validator accepts everything except `none`. Every permitted value fails one side, so no CFL integration here can build at all — the sanitizer matrix I started with was wrong, and so was `none`. Dropped .clusterfuzzlite/ and the two cflite workflows and run Jazzer.js — the engine CFL would have driven — straight from .github/workflows/fuzz.yml: 60 s per target on PRs touching src/ or fuzz/, 10 min per target weekly, corpus cached via actions/cache so coverage compounds and PRs seed from the last scheduled run. Crash reproducers upload as artifacts. Verified with the exact command the workflow runs: 842,475 executions, 12 corpus entries kept. The four targets are unchanged — they were never the problem. Consequence worth stating: Scorecard's Fuzzing check may keep reporting 0. It detects integrations (OSS-Fuzz membership, a .clusterfuzzlite/Dockerfile) rather than whether fuzzing happens, and keeping dead CFL config around to satisfy that detector would be scoring points rather than fuzzing. The fuzzing is real; the badge may not follow. --- .clusterfuzzlite/Dockerfile | 10 ---- .clusterfuzzlite/build.sh | 22 -------- .github/workflows/cflite-batch.yml | 39 -------------- .github/workflows/cflite-pr.yml | 53 ------------------- .github/workflows/fuzz.yml | 82 ++++++++++++++++++++++++++++++ fuzz/README.md | 28 +++++++++- 6 files changed, 108 insertions(+), 126 deletions(-) delete mode 100644 .clusterfuzzlite/Dockerfile delete mode 100755 .clusterfuzzlite/build.sh delete mode 100644 .github/workflows/cflite-batch.yml delete mode 100644 .github/workflows/cflite-pr.yml create mode 100644 .github/workflows/fuzz.yml diff --git a/.clusterfuzzlite/Dockerfile b/.clusterfuzzlite/Dockerfile deleted file mode 100644 index f34b88b7..00000000 --- a/.clusterfuzzlite/Dockerfile +++ /dev/null @@ -1,10 +0,0 @@ -# ClusterFuzzLite build image for the JavaScript fuzz targets in fuzz/. -# -# base-builder-javascript ships Node plus Jazzer.js and the -# `compile_javascript_fuzzer` helper, which is all these targets need — see -# build.sh for why the project's own dependency tree is deliberately absent. -FROM gcr.io/oss-fuzz-base/base-builder-javascript - -COPY . $SRC/apple-docs -WORKDIR $SRC/apple-docs -COPY .clusterfuzzlite/build.sh $SRC/build.sh diff --git a/.clusterfuzzlite/build.sh b/.clusterfuzzlite/build.sh deleted file mode 100755 index 184506d6..00000000 --- a/.clusterfuzzlite/build.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash -eu -# -# Build every fuzz/fuzz-*.js target into the OSS-Fuzz output directory. -# -# No `npm install`. The targets reach exactly 13 project modules and zero -# third-party packages — only Node builtins — so installing the project's -# dependency tree would add minutes to every build and drag in heavy, -# platform-sensitive optional deps (@huggingface/transformers, playwright, -# sharp) that no fuzz target imports. If a future target needs a real -# dependency, install it explicitly here rather than reaching for a blanket -# `npm install`; keep the check in fuzz/README.md honest. -# -# The project is ESM ("type": "module"), so the targets `export function -# fuzz(data)` rather than assigning module.exports. - -for target in "$SRC/apple-docs"/fuzz/fuzz-*.js; do - name="$(basename "$target" .js)" - echo "building fuzz target: $name" - # --sync: the targets are synchronous, which lets libFuzzer drive them - # without the async harness overhead (~37k exec/s locally). - compile_javascript_fuzzer apple-docs "fuzz/$name.js" --sync -done diff --git a/.github/workflows/cflite-batch.yml b/.github/workflows/cflite-batch.yml deleted file mode 100644 index b9e9360a..00000000 --- a/.github/workflows/cflite-batch.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: ClusterFuzzLite batch fuzzing - -# The long campaign. PR mode only fuzzes code a PR touched for 5 minutes; -# this runs every target for an hour weekly and grows the shared corpus that -# seeds those PR runs, so coverage compounds instead of restarting cold. -# -# Sunday 04:00 UTC — before the 06:00 snapshot build, so the two don't -# contend for runners. - -on: - schedule: - - cron: '0 4 * * 0' - workflow_dispatch: - -permissions: {} - -jobs: - batch-fuzz: - runs-on: ubuntu-latest - timeout-minutes: 90 - permissions: - contents: read - steps: - # sanitizer: none — see cflite-pr.yml; the JS builder rejects any other - # value. - - name: Build fuzzers - uses: google/clusterfuzzlite/actions/build_fuzzers@82652fb49e77bc29c35da1167bb286e93c6bcc05 # v1 - with: - language: javascript - sanitizer: none - - - name: Run fuzzers - uses: google/clusterfuzzlite/actions/run_fuzzers@82652fb49e77bc29c35da1167bb286e93c6bcc05 # v1 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - fuzz-seconds: 3600 - mode: 'batch' - sanitizer: none - output-sarif: true diff --git a/.github/workflows/cflite-pr.yml b/.github/workflows/cflite-pr.yml deleted file mode 100644 index dfeac0ae..00000000 --- a/.github/workflows/cflite-pr.yml +++ /dev/null @@ -1,53 +0,0 @@ -name: ClusterFuzzLite PR fuzzing - -# Fuzzes the parsers that sit on a trust boundary — storage keys, tar -# listings, zstd frame headers, Markdown frontmatter. See fuzz/README.md for -# the property each target asserts. -# -# PR mode runs a short campaign seeded by the corpus from previous runs and -# fails the PR on a new crash, so a traversal or parse regression is caught -# before merge rather than by the weekly batch. - -on: - pull_request: - branches: [main] - # Skip when a PR cannot affect the targets. Docs-only and ops-only - # changes are the common case and a 10-minute fuzz run on them is pure - # queue time. - paths: - - 'src/**' - - 'fuzz/**' - - '.clusterfuzzlite/**' - - '.github/workflows/cflite-pr.yml' - -permissions: {} - -jobs: - fuzz: - runs-on: ubuntu-latest - timeout-minutes: 25 - permissions: - contents: read - steps: - # sanitizer: none — JavaScript has no ASan/UBSan equivalent here, and - # the OSS-Fuzz builder rejects anything else outright ("JavaScript - # projects cannot be fuzzed with sanitizers"). Jazzer.js catches - # uncaught exceptions and our own assertion failures instead, which is - # what the targets are written against. - - name: Build fuzzers - uses: google/clusterfuzzlite/actions/build_fuzzers@82652fb49e77bc29c35da1167bb286e93c6bcc05 # v1 - with: - language: javascript - sanitizer: none - # Needed so the action can diff against the base commit and only - # report crashes the PR introduces. - github-token: ${{ secrets.GITHUB_TOKEN }} - - - name: Run fuzzers - uses: google/clusterfuzzlite/actions/run_fuzzers@82652fb49e77bc29c35da1167bb286e93c6bcc05 # v1 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - fuzz-seconds: 300 - mode: 'code-change' - sanitizer: none - output-sarif: true diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml new file mode 100644 index 00000000..502ae255 --- /dev/null +++ b/.github/workflows/fuzz.yml @@ -0,0 +1,82 @@ +name: Fuzz + +# Runs the Jazzer.js targets in fuzz/ against the parsers that sit on a +# trust boundary. See fuzz/README.md for the property each target asserts. +# +# Why not ClusterFuzzLite: its two layers contradict each other for +# JavaScript. OSS-Fuzz's `compile` refuses to build a JS project with any +# sanitizer ("JavaScript projects cannot be fuzzed with sanitizers"), while +# CIFuzz's config validator rejects `none` — "Must be one of: ['address', +# 'memory', 'undefined', 'coverage']". Every permitted value fails one side +# or the other, so a CFL integration here cannot build at all. Jazzer.js is +# the engine CFL would have used; this runs it directly. + +on: + pull_request: + branches: [main] + paths: + - 'src/**' + - 'fuzz/**' + - '.github/workflows/fuzz.yml' + schedule: + # Sundays 04:00 UTC — ahead of the 06:00 snapshot build so the two + # don't contend for runners. + - cron: '0 4 * * 0' + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: fuzz-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + fuzz: + runs-on: ubuntu-latest + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + target: [fuzz-storage-key, fuzz-tar-line, fuzz-zstd-header, fuzz-markdown] + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: ./.github/actions/setup + + # Corpus persists across runs so coverage compounds instead of + # restarting cold every time. restore-keys lets a PR seed from the + # last scheduled run's corpus. + - name: Restore corpus + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + with: + path: fuzz/corpus/${{ matrix.target }} + key: fuzz-corpus-${{ matrix.target }}-${{ github.run_id }} + restore-keys: | + fuzz-corpus-${{ matrix.target }}- + + - name: Fuzz ${{ matrix.target }} + env: + # 60s per target on a PR keeps the gate quick; the weekly run digs + # for 10 minutes and hands its corpus to subsequent PRs. + DURATION: ${{ github.event_name == 'pull_request' && '60' || '600' }} + run: | + mkdir -p "fuzz/corpus/${{ matrix.target }}" + bun x jazzer "fuzz/${{ matrix.target }}.js" \ + "fuzz/corpus/${{ matrix.target }}" \ + --sync \ + -- -max_total_time="$DURATION" -print_final_stats=1 + + # A crash file IS the bug report — keep it even though the step above + # already failed the job. + - name: Upload crash reproducers + if: failure() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: fuzz-crashes-${{ matrix.target }} + path: | + crash-* + oom-* + timeout-* + leak-* + if-no-files-found: ignore + retention-days: 30 diff --git a/fuzz/README.md b/fuzz/README.md index ee102215..730fe86f 100644 --- a/fuzz/README.md +++ b/fuzz/README.md @@ -1,8 +1,32 @@ # Fuzz targets Jazzer.js fuzz targets for the parsers that handle bytes we did not write. -Run continuously by ClusterFuzzLite (see `.clusterfuzzlite/`), and on every -pull request against the code the PR touches. +Run by `.github/workflows/fuzz.yml`: 60 s per target on pull requests that +touch `src/` or `fuzz/`, 10 minutes per target weekly, with the corpus +cached between runs so coverage compounds. + +## Why not ClusterFuzzLite + +It cannot build a JavaScript project. Its two layers disagree: OSS-Fuzz's +`compile` refuses any sanitizer for JS — + + ERROR: JavaScript projects cannot be fuzzed with sanitizers. + +— while CIFuzz's config validator rejects the only value `compile` accepts: + + Invalid SANITIZER: none. Must be one of: + ['address', 'memory', 'undefined', 'coverage']. + +Every permitted value fails one side or the other; both were confirmed +against the real action. Jazzer.js is the engine ClusterFuzzLite would have +driven, so the workflow runs it directly and loses nothing but the hosted +corpus storage, which `actions/cache` covers. + +Note this means Scorecard's Fuzzing check may keep reporting 0: it detects +integrations (OSS-Fuzz membership, a `.clusterfuzzlite/Dockerfile`) rather +than whether fuzzing actually happens. Keeping non-functional CFL config +around purely to satisfy that detector would be scoring points, not +fuzzing. ## What is fuzzed, and why