From 3423091f83799eda72e9c9ae79136ef344d9ce43 Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 2 Nov 2026 08:06:45 -0800 Subject: [PATCH 1/3] fix: harden action inputs and decode Git-quoted paths --- action.yml | 37 ++- package-lock.json | 515 +++++++++++++++++++--------------- src/diff-parser.ts | 43 ++- tests/action-security.test.ts | 59 ++++ tests/diff-parser.test.ts | 42 +++ 5 files changed, 464 insertions(+), 232 deletions(-) create mode 100644 tests/action-security.test.ts diff --git a/action.yml b/action.yml index f67b5f9..1666510 100644 --- a/action.yml +++ b/action.yml @@ -43,22 +43,45 @@ runs: steps: - name: Install PatchBrake shell: bash - run: npm install -g "patchbrake@${{ inputs.version }}" --ignore-scripts + env: + PATCHBRAKE_VERSION: ${{ inputs.version }} + run: | + set -euo pipefail + if [[ ! "$PATCHBRAKE_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$ ]]; then + echo 'PatchBrake version must be a pinned npm semver version.' >&2 + exit 2 + fi + npm install -g "patchbrake@$PATCHBRAKE_VERSION" --ignore-scripts - name: Run PatchBrake shell: bash + env: + PATCHBRAKE_BASE: ${{ inputs.base }} + PATCHBRAKE_HEAD: ${{ inputs.head }} + PATCHBRAKE_FORMAT: ${{ inputs.format }} + PATCHBRAKE_FAIL_ON: ${{ inputs.fail-on }} + PATCHBRAKE_OUTPUT: ${{ inputs.output }} + PATCHBRAKE_ALLOW_CUSTOM_RULES: ${{ inputs.allow-custom-rules }} + PATCHBRAKE_ALLOW_INLINE_IGNORE: ${{ inputs.allow-inline-ignore }} + PATCHBRAKE_FAIL_ON_NEW_IGNORE: ${{ inputs.fail-on-new-ignore }} run: | set -euo pipefail - args=(scan --base "${{ inputs.base }}" --head "${{ inputs.head }}" --format "${{ inputs.format }}" --fail-on "${{ inputs.fail-on }}") - if [ -n "${{ inputs.output }}" ]; then - args+=(--output "${{ inputs.output }}") + for flag in "$PATCHBRAKE_ALLOW_CUSTOM_RULES" "$PATCHBRAKE_ALLOW_INLINE_IGNORE" "$PATCHBRAKE_FAIL_ON_NEW_IGNORE"; do + if [[ "$flag" != true && "$flag" != false ]]; then + echo 'PatchBrake boolean inputs must be true or false.' >&2 + exit 2 + fi + done + args=(scan --base "$PATCHBRAKE_BASE" --head "$PATCHBRAKE_HEAD" --format "$PATCHBRAKE_FORMAT" --fail-on "$PATCHBRAKE_FAIL_ON") + if [ -n "$PATCHBRAKE_OUTPUT" ]; then + args+=(--output "$PATCHBRAKE_OUTPUT") fi - if [ "${{ inputs.allow-custom-rules }}" != "true" ]; then + if [ "$PATCHBRAKE_ALLOW_CUSTOM_RULES" != "true" ]; then args+=(--no-custom-rules) fi - if [ "${{ inputs.allow-inline-ignore }}" != "true" ]; then + if [ "$PATCHBRAKE_ALLOW_INLINE_IGNORE" != "true" ]; then args+=(--disallow-inline-ignore) fi - if [ "${{ inputs.fail-on-new-ignore }}" = "true" ]; then + if [ "$PATCHBRAKE_FAIL_ON_NEW_IGNORE" = "true" ]; then args+=(--fail-on-new-ignore) fi patchbrake "${args[@]}" diff --git a/package-lock.json b/package-lock.json index d586411..d425d37 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,7 @@ }, "node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", "cpu": [ "ppc64" @@ -42,7 +42,7 @@ }, "node_modules/@esbuild/android-arm": { "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", "cpu": [ "arm" @@ -59,7 +59,7 @@ }, "node_modules/@esbuild/android-arm64": { "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", "cpu": [ "arm64" @@ -76,7 +76,7 @@ }, "node_modules/@esbuild/android-x64": { "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", "cpu": [ "x64" @@ -93,7 +93,7 @@ }, "node_modules/@esbuild/darwin-arm64": { "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", "cpu": [ "arm64" @@ -110,7 +110,7 @@ }, "node_modules/@esbuild/darwin-x64": { "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", "cpu": [ "x64" @@ -127,7 +127,7 @@ }, "node_modules/@esbuild/freebsd-arm64": { "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", "cpu": [ "arm64" @@ -144,7 +144,7 @@ }, "node_modules/@esbuild/freebsd-x64": { "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", "cpu": [ "x64" @@ -161,7 +161,7 @@ }, "node_modules/@esbuild/linux-arm": { "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", "cpu": [ "arm" @@ -178,7 +178,7 @@ }, "node_modules/@esbuild/linux-arm64": { "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", "cpu": [ "arm64" @@ -195,7 +195,7 @@ }, "node_modules/@esbuild/linux-ia32": { "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", "cpu": [ "ia32" @@ -212,7 +212,7 @@ }, "node_modules/@esbuild/linux-loong64": { "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", "cpu": [ "loong64" @@ -229,7 +229,7 @@ }, "node_modules/@esbuild/linux-mips64el": { "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", "cpu": [ "mips64el" @@ -246,7 +246,7 @@ }, "node_modules/@esbuild/linux-ppc64": { "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", "cpu": [ "ppc64" @@ -263,7 +263,7 @@ }, "node_modules/@esbuild/linux-riscv64": { "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", "cpu": [ "riscv64" @@ -280,7 +280,7 @@ }, "node_modules/@esbuild/linux-s390x": { "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", "cpu": [ "s390x" @@ -297,7 +297,7 @@ }, "node_modules/@esbuild/linux-x64": { "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", "cpu": [ "x64" @@ -314,7 +314,7 @@ }, "node_modules/@esbuild/netbsd-x64": { "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", "cpu": [ "x64" @@ -331,7 +331,7 @@ }, "node_modules/@esbuild/openbsd-x64": { "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", "cpu": [ "x64" @@ -348,7 +348,7 @@ }, "node_modules/@esbuild/sunos-x64": { "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", "cpu": [ "x64" @@ -365,7 +365,7 @@ }, "node_modules/@esbuild/win32-arm64": { "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", "cpu": [ "arm64" @@ -382,7 +382,7 @@ }, "node_modules/@esbuild/win32-ia32": { "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", "cpu": [ "ia32" @@ -399,7 +399,7 @@ }, "node_modules/@esbuild/win32-x64": { "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", "cpu": [ "x64" @@ -416,7 +416,7 @@ }, "node_modules/@jest/schemas": { "version": "29.6.3", - "resolved": "https://registry.npmmirror.com/@jest/schemas/-/schemas-29.6.3.tgz", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dev": true, "license": "MIT", @@ -428,16 +428,36 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", "dev": true, "license": "MIT" }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.62.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.0.tgz", - "integrity": "sha512-IPIQ55ythEHkfEd9jMEi32OQ7SxURsGA43JI22lj01OLZNt2NUbJX8YUHxkVWyQ6daHPNn0truF5nSj3DQp6YQ==", + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.67.4.tgz", + "integrity": "sha512-/v/pUAPOcwp+r1Hjjdg0cBV1TE89CzGKrKh2FgkIQw2QBcIMmVPOCSfuOZStCn4Xh/98HXfo/ht6ztcssctPNg==", "cpu": [ "arm" ], @@ -449,9 +469,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.62.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.0.tgz", - "integrity": "sha512-M6s9cr10MibETyo8JsOkq+Lo1+lU6hcvb1MApnUql5qte/5hMEgzlN8/ReIKNfRV8rrqX50W1BX9zoUhC192RA==", + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.67.4.tgz", + "integrity": "sha512-6tmmB/P52Vt167FXXEw5auiEUgTtZz7aqUgWHqYXToIUE0HFDY6fTdAyeekjjsNk6O/8dtpSKtClxSj1mMneKQ==", "cpu": [ "arm64" ], @@ -462,10 +482,24 @@ "android" ] }, + "node_modules/@rollup/rollup-android-x64": { + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-x64/-/rollup-android-x64-4.67.4.tgz", + "integrity": "sha512-8Z9Q4RmotVmuL4llOvqX814JNiOK1oqoBv+glG5KbpRfUiypqzz2/fxIzK4Pq129l4dT5a8Gszmpc0/XPYoFuQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.62.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.0.tgz", - "integrity": "sha512-BqCoMoIbn0keKys+dEAdBa70EtOwV1bEsQCUgU9FdiZmmMge/Zk7LlkYGqbrdHR+Frnt0E1FOanly+rlwvvQzw==", + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.67.4.tgz", + "integrity": "sha512-pnq6hssxTtwuRAl6Kdi/+IDY+b1IhkUetjhUkdLAAJCssjfqtL2Oqy/FXJVJFtAWOu1oRlArIvf0Yk1O92x2HQ==", "cpu": [ "arm64" ], @@ -477,9 +511,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.62.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.0.tgz", - "integrity": "sha512-SIMzST3VFNXDAbeIWDWiFCNM5qncUBDWaEV7NfE7oZbDt2mgfW4MvbKdbYiGOLoM32gbTv608UMd0XktEYSD7w==", + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.67.4.tgz", + "integrity": "sha512-BeIrGZvHMVdJg/N+ohZfFywG/gF41684YhDj2wBvouj7m9ziP1tZDUNwzXRiDY+3WIKngaC4hiUBqVtBPGP5og==", "cpu": [ "x64" ], @@ -491,9 +525,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.62.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.0.tgz", - "integrity": "sha512-ezjfSQMP7ArdUsbBwbQIfwAlhE84I2iVnzQNCFSveqV42q+BmKlzVpf7mxv5EchLcoWU4y6/heFzVg1F+hodUQ==", + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.67.4.tgz", + "integrity": "sha512-fRFX/oqNg/g780DKoS8RIfYcZD59Ru5O/bSeK1N7gQljBFrTxJY/q5EX4eMs5SrJf32VgVBVVYCdfPpzAwPPnQ==", "cpu": [ "arm64" ], @@ -505,9 +539,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.62.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.0.tgz", - "integrity": "sha512-9+qTWGW9AZRhnUgwtTwzNwcPlL87ngkeN0LA+q1bADvmY9aNvWaF2TFW8BZgnQPYxpDI7+rMVLivcd4V737TAQ==", + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.67.4.tgz", + "integrity": "sha512-bSrBRopG1q+0RuOsW0NtYTtIODlvNA3T3ebx5WnQVdSxWIoXFmj+rZdHUHdSLgFdZ5M6VGmekYE7r4RMGQXjfg==", "cpu": [ "x64" ], @@ -519,13 +553,16 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.62.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.0.tgz", - "integrity": "sha512-T1dMEQhXA/jkJ/jyMIw9IovK8bSUq7A8kLIlvZTb/6YIVsp2zLavr4F3oyllHWo7eIVJRyE5n3tUjQJEbE1IuQ==", + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.67.4.tgz", + "integrity": "sha512-L8IvdrJVtl65g1ozwDSyjGPG+FmibhCdFUwmqTItJLw0S3+Oj4QzFs7rfgTUiK7rsPjkCLdG1/GApFxoynXFJQ==", "cpu": [ "arm" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -533,13 +570,16 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.62.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.0.tgz", - "integrity": "sha512-2as0LgT7qQpyceQq6VUJYnumUMUrgGQCWIiDIN9DE0/tglsk6o66uCB4f3djRawAltvfCNLyZZrsqbPA6inCsA==", + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.67.4.tgz", + "integrity": "sha512-h7PcoQOzC31jshNpQ48J0akB9tsghjkSjSl5AZ6XsVGTxVsln7t7C4SQyw62tNVSaz2SeHgk4VT2ELbL17BSHQ==", "cpu": [ "arm" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -547,13 +587,16 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.62.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.0.tgz", - "integrity": "sha512-bVURMg+6eNN9C/yc0aVjooZcwTTtYF4YW3xta5pP0//r3o1V8gXEHXWCndj47w/HhwsFroZrFhR+6uQP5T0n0g==", + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.67.4.tgz", + "integrity": "sha512-KSPuOBNHoOl7AsVjeY1KZci8+0up0U4TFAHU5IzBYN8P+Z6cB9DZq28+JQycZW7f6WLLa/tSzhV7Rdy95bVi+g==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -561,13 +604,16 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.62.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.0.tgz", - "integrity": "sha512-Ful8pM/2yYI83PViWdFdpZhdI8HJ5qsXANe5atypbHDf+KIBBDsZsbyy8hbXnULVvW9NsTh5DHwbcBftyLTfiw==", + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.67.4.tgz", + "integrity": "sha512-0E/mKmVk1PlQeQYygIwQm0jlwjANTzGSiNBMPrFx+rdFGFj/2Axh4p6XwcrystQIqXZVudTaGsdbDpzYgJ4wSA==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -575,13 +621,16 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.62.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.0.tgz", - "integrity": "sha512-9Gp/DgrkzfUBmNPVTyPTvay+4xEP7M/clXpj3efXBcm6uTIVIgDg4rqUpqKXvLEuFRVuEpSAOkhgNeecvaZ4Cg==", + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.67.4.tgz", + "integrity": "sha512-Fvw7dOSc2Sa9UOipRZVI0zOgdLCzsjVyD/6JuHXzUHTXFu7mMgmeX/0/bCVBIut1dz+nWz3pV4WpS/Qg0XVnxw==", "cpu": [ "loong64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -589,13 +638,16 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.62.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.0.tgz", - "integrity": "sha512-m9tsJz54LUXkSYM8+8PG81B9IKK5r+2T0clMq4QrS16xFosufU7firBDAZEsDheDs7wTlP7h3++S7lMsU955HA==", + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.67.4.tgz", + "integrity": "sha512-jzxbVHaE7V3F+P0wEK7UydQtvv7Twfdtx58jI5/3VRx1jJxyrn+qFwfXmqjVFxPhBl+zjaRs+ZJMFJw7vdHaQA==", "cpu": [ "loong64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -603,13 +655,16 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.62.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.0.tgz", - "integrity": "sha512-3UvJ5PNVU16aJf6M3tFI24pWzAl2/ynfbyRN3ICyQajK1lSkrnVYNnLz3v04J32qKa0FczJc22zeToc0lr2A3w==", + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.67.4.tgz", + "integrity": "sha512-fhtrzp9p4GjEJVaigaap9g6X9nP6xgJtKiLLXSDgpE18HXufA5aqfltGn+KAqMtVZReJZfOrXm//y5YSA9BWbA==", "cpu": [ "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -617,13 +672,16 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.62.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.0.tgz", - "integrity": "sha512-vRWUAbYLGHBZS6Q8Msb2sfnf1fvJf+47t8l/TwOerM2qArzy+IeNMTHrYLHXh95h8MoatPHI5hhSZNs+mGXKPg==", + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.67.4.tgz", + "integrity": "sha512-uynaF7IUHwAckV0S+yC1f5esy9gB+aqCBsPH5BOck32TYdCuAn2lVG5tvUcFnEsrviGEmRZxE1NHSOieUPYJEg==", "cpu": [ "ppc64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -631,13 +689,16 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.62.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.0.tgz", - "integrity": "sha512-c00T5SYENHAt86cfW47URaP3Us5vLC/4QO7GYud1G5VNRffCwwCuBspwqYrriuJB+5m0WFzClCn9wed0FBjKvg==", + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.67.4.tgz", + "integrity": "sha512-uaqRFdfb8eIa2bVHN/FE3ndCvR0zV2xv0AwjCfxrqSu6l6fp/54VhjfhslKlf2RIik9L1jwlt4E56PeVX6tQtQ==", "cpu": [ "riscv64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -645,13 +706,16 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.62.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.0.tgz", - "integrity": "sha512-krrCDilhXOwFkSkO3Wm9I/f9H0L92XHHwy2fwxjukxIbh0dem8gZqOW5Y8BsHrpJv5qwlRBV+Wl4ZFyRWhUpwg==", + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.67.4.tgz", + "integrity": "sha512-JZRow7kmxUDEASnxHuVG5itTm+nTXi9BQUABcVUnUEngC+OPwMrpx1uJ9hIA/v+IcAEBL52ktCSPXk5OJn9Jqg==", "cpu": [ "riscv64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -659,13 +723,16 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.62.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.0.tgz", - "integrity": "sha512-7pfYFSTc4/rUC/FtAI0Qp6QthDBCIi6/AuP1xYqFk5vanI6KnL5dWKP60OM/05LOsbwTmIcvr6eXC4CJuJ75IA==", + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.67.4.tgz", + "integrity": "sha512-rdrh4e+Lq9fCNKoDPaCqeHXbQCe7yl77Ukl45tlSClwYdZ1gpFGZz1XLJdYtY++FRlfL5aHvb6/0n3GDW/Nxsw==", "cpu": [ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -673,13 +740,16 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.62.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.0.tgz", - "integrity": "sha512-7SDIalKeIpG0Ifogbbdn58HmSotYMlf23K3dCJEmiVd9Fg36Vmni82iPQec27N3wY4Bvbxftkxz6vSx9OcouTg==", + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.67.4.tgz", + "integrity": "sha512-wL7MS4r0fYcxluF3ne2plofHZiB/LsqNCnyaUlyiKpAJLb6agPSFz7DEFCfkonWAZXMrLwCmC08b2ZEnEdJkog==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -687,13 +757,16 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.62.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.0.tgz", - "integrity": "sha512-eRZevouTH2i1HeAVLqJuLnt256krQkGY0TN6WsTmsIhuzbh457HuWDMakKwmi0Cjadux983CoSr8Lim2QhUIFw==", + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.67.4.tgz", + "integrity": "sha512-4H0XHfd2VufGjX7NjUyeue78sD0bRIoKSGjGqO+F5nSvL6jN48NAU7esdmtFEYxZXwObLtt3MJ/kHS0jASMW3g==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -701,9 +774,9 @@ ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.62.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.0.tgz", - "integrity": "sha512-3oVS7FLGa4U1qcvao9ylGxrjXZyUQqR8UwxEcnUEyPX53O/C/mKDZegNXTdHCP+h3e6ta/f1EN38Yif1mmZHYg==", + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.67.4.tgz", + "integrity": "sha512-osRHQbE55pZpCLetpMsFmhkapyZnAUH73qRKMh+npi3boUom6u6hdaTTPSxlInXxwJHsn9r2uDhdz0V7Yoxx2A==", "cpu": [ "x64" ], @@ -715,9 +788,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.62.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.0.tgz", - "integrity": "sha512-yTB9TgfWj5wHe5QgktAgXTLLot1gvEjl1NiPPAUiCs4oPrIWFl5V4nC3GrkNdj9LaAU4s94nVrGbGOCqUpyWsg==", + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.67.4.tgz", + "integrity": "sha512-95+exYEyOwrZePsE0czi4q7X5lxKPa21+lmyAZlwbYENITQ0Unk4HAQAFp/p+KhXBHBx9mUABXRYjOC5A1wY3Q==", "cpu": [ "arm64" ], @@ -729,9 +802,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.62.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.0.tgz", - "integrity": "sha512-5LOhoaesY3doG1c+ac/2JtgREpKoJr5bUHH8tKY0V8di7+uSV6BwLs2PlR0/yzefGOkR+wE7ZolZphHCsyG5Rw==", + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.67.4.tgz", + "integrity": "sha512-FOXOcKhNQpO4Po+Ynan6IbtM67SrXZbgpRJWVYwz53CT5veI7sVzDwVBmWS65N5Imv+wK3SVhIPAl+C5DXXNCg==", "cpu": [ "arm64" ], @@ -743,9 +816,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.62.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.0.tgz", - "integrity": "sha512-yYkWHhmbhRTWTnWos5HC4GcPQfjlzzCNbM9e/+GXrLuaBXYA3qSDR9f0Vgufd5S8yX81U8jPKp7ZnAjZFMtRnw==", + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.67.4.tgz", + "integrity": "sha512-cklKh0gJOkAZQcWziQa3hrjxZKN+PCHZ7SPTyGJDAiBVr05ZXlEjGFCG7zFJMaVibxsmzAe1FimN4u/HBns9+Q==", "cpu": [ "ia32" ], @@ -757,9 +830,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.62.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.0.tgz", - "integrity": "sha512-SoTb6lPg25xZlA2ibwQ++ahCCnH+FP0qmEuafMJ4gznZKOlXioKEAeJLgCrqjM98ACziXM9V1amFjICVL4IFoA==", + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.67.4.tgz", + "integrity": "sha512-AegwxJPc9bvUp8fQtlsUHkFMOMkGylWU7glyL+YD4vwG+QaYoGXYLSsQoeTD7kvoLZrVlMWNVhTg1dedY5C1VQ==", "cpu": [ "x64" ], @@ -771,9 +844,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.62.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.0.tgz", - "integrity": "sha512-5L+T1fMX4RIEBoZzT0+sQ0PhTS36NULFmMXtl1TZo44TMAROIMHbZufSOjVWt/Y622BtxgxtaNOokbTDvfsrZA==", + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.67.4.tgz", + "integrity": "sha512-RJZnrwTWj7Iq2ooBwoGbDdC65tDwYawneGcAfFMJqVUrRZMcskka6nTkBpXR/47G7kzn3EW2qupExnaBy11vwg==", "cpu": [ "x64" ], @@ -785,22 +858,22 @@ ] }, "node_modules/@sinclair/typebox": { - "version": "0.27.10", - "resolved": "https://registry.npmmirror.com/@sinclair/typebox/-/typebox-0.27.10.tgz", - "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", + "version": "0.27.12", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.12.tgz", + "integrity": "sha512-hhyNJ+nbR6ZR7pToHvllEFun9TL0sbL+tk/ON75lo+Xas054uez98qRbsuNt7MBCyZKK4+8Yli/OAGZhmfBZ/g==", "dev": true, "license": "MIT" }, "node_modules/@types/estree": { - "version": "1.0.9", - "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.9.tgz", - "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.10.tgz", + "integrity": "sha512-lTyOMnOcmRBzdCv6/4zUZCrEhV0xrqQiUZBZtVcbmLHBXKLnN8q89v/wKRF/Q0HbwVsqbIG3o5HDoFl9sduM+g==", "dev": true, "license": "MIT" }, "node_modules/@types/node": { "version": "20.19.43", - "resolved": "https://registry.npmmirror.com/@types/node/-/node-20.19.43.tgz", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", "dev": true, "license": "MIT", @@ -810,7 +883,7 @@ }, "node_modules/@vitest/expect": { "version": "1.6.1", - "resolved": "https://registry.npmmirror.com/@vitest/expect/-/expect-1.6.1.tgz", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.1.tgz", "integrity": "sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==", "dev": true, "license": "MIT", @@ -825,7 +898,7 @@ }, "node_modules/@vitest/runner": { "version": "1.6.1", - "resolved": "https://registry.npmmirror.com/@vitest/runner/-/runner-1.6.1.tgz", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.1.tgz", "integrity": "sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==", "dev": true, "license": "MIT", @@ -840,7 +913,7 @@ }, "node_modules/@vitest/snapshot": { "version": "1.6.1", - "resolved": "https://registry.npmmirror.com/@vitest/snapshot/-/snapshot-1.6.1.tgz", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.1.tgz", "integrity": "sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==", "dev": true, "license": "MIT", @@ -855,7 +928,7 @@ }, "node_modules/@vitest/spy": { "version": "1.6.1", - "resolved": "https://registry.npmmirror.com/@vitest/spy/-/spy-1.6.1.tgz", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.1.tgz", "integrity": "sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==", "dev": true, "license": "MIT", @@ -868,7 +941,7 @@ }, "node_modules/@vitest/utils": { "version": "1.6.1", - "resolved": "https://registry.npmmirror.com/@vitest/utils/-/utils-1.6.1.tgz", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.1.tgz", "integrity": "sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==", "dev": true, "license": "MIT", @@ -883,9 +956,9 @@ } }, "node_modules/acorn": { - "version": "8.17.0", - "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.17.0.tgz", - "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.19.0.tgz", + "integrity": "sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw==", "dev": true, "license": "MIT", "bin": { @@ -897,7 +970,7 @@ }, "node_modules/acorn-walk": { "version": "8.3.5", - "resolved": "https://registry.npmmirror.com/acorn-walk/-/acorn-walk-8.3.5.tgz", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", "dev": true, "license": "MIT", @@ -910,7 +983,7 @@ }, "node_modules/ansi-styles": { "version": "5.2.0", - "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-5.2.0.tgz", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", @@ -923,7 +996,7 @@ }, "node_modules/assertion-error": { "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/assertion-error/-/assertion-error-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true, "license": "MIT", @@ -933,7 +1006,7 @@ }, "node_modules/cac": { "version": "6.7.14", - "resolved": "https://registry.npmmirror.com/cac/-/cac-6.7.14.tgz", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", "dev": true, "license": "MIT", @@ -943,7 +1016,7 @@ }, "node_modules/chai": { "version": "4.5.0", - "resolved": "https://registry.npmmirror.com/chai/-/chai-4.5.0.tgz", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", "dev": true, "license": "MIT", @@ -962,7 +1035,7 @@ }, "node_modules/check-error": { "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/check-error/-/check-error-1.0.3.tgz", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", "dev": true, "license": "MIT", @@ -975,7 +1048,7 @@ }, "node_modules/commander": { "version": "12.1.0", - "resolved": "https://registry.npmmirror.com/commander/-/commander-12.1.0.tgz", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", "license": "MIT", "engines": { @@ -984,14 +1057,14 @@ }, "node_modules/confbox": { "version": "0.1.8", - "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.1.8.tgz", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", "dev": true, "license": "MIT" }, "node_modules/cross-spawn": { "version": "7.0.6", - "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", @@ -1006,7 +1079,7 @@ }, "node_modules/debug": { "version": "4.4.3", - "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", @@ -1024,7 +1097,7 @@ }, "node_modules/deep-eql": { "version": "4.1.4", - "resolved": "https://registry.npmmirror.com/deep-eql/-/deep-eql-4.1.4.tgz", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", "dev": true, "license": "MIT", @@ -1037,7 +1110,7 @@ }, "node_modules/diff-sequences": { "version": "29.6.3", - "resolved": "https://registry.npmmirror.com/diff-sequences/-/diff-sequences-29.6.3.tgz", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", "dev": true, "license": "MIT", @@ -1047,7 +1120,7 @@ }, "node_modules/esbuild": { "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "dev": true, "hasInstallScript": true, @@ -1086,7 +1159,7 @@ }, "node_modules/estree-walker": { "version": "3.0.3", - "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-3.0.3.tgz", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, "license": "MIT", @@ -1096,7 +1169,7 @@ }, "node_modules/execa": { "version": "8.0.1", - "resolved": "https://registry.npmmirror.com/execa/-/execa-8.0.1.tgz", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", "dev": true, "license": "MIT", @@ -1120,7 +1193,7 @@ }, "node_modules/fsevents": { "version": "2.3.3", - "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, @@ -1135,7 +1208,7 @@ }, "node_modules/get-func-name": { "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/get-func-name/-/get-func-name-2.0.2.tgz", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", "dev": true, "license": "MIT", @@ -1145,7 +1218,7 @@ }, "node_modules/get-stream": { "version": "8.0.1", - "resolved": "https://registry.npmmirror.com/get-stream/-/get-stream-8.0.1.tgz", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", "dev": true, "license": "MIT", @@ -1158,7 +1231,7 @@ }, "node_modules/human-signals": { "version": "5.0.0", - "resolved": "https://registry.npmmirror.com/human-signals/-/human-signals-5.0.0.tgz", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", "dev": true, "license": "Apache-2.0", @@ -1168,7 +1241,7 @@ }, "node_modules/is-stream": { "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/is-stream/-/is-stream-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", "dev": true, "license": "MIT", @@ -1181,21 +1254,21 @@ }, "node_modules/isexe": { "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, "license": "ISC" }, "node_modules/js-tokens": { "version": "9.0.1", - "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-9.0.1.tgz", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", "dev": true, "license": "MIT" }, "node_modules/local-pkg": { "version": "0.5.1", - "resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-0.5.1.tgz", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz", "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==", "dev": true, "license": "MIT", @@ -1212,7 +1285,7 @@ }, "node_modules/loupe": { "version": "2.3.7", - "resolved": "https://registry.npmmirror.com/loupe/-/loupe-2.3.7.tgz", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", "dev": true, "license": "MIT", @@ -1222,7 +1295,7 @@ }, "node_modules/magic-string": { "version": "0.30.21", - "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, "license": "MIT", @@ -1232,14 +1305,14 @@ }, "node_modules/merge-stream": { "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/merge-stream/-/merge-stream-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "dev": true, "license": "MIT" }, "node_modules/mimic-fn": { "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/mimic-fn/-/mimic-fn-4.0.0.tgz", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", "dev": true, "license": "MIT", @@ -1252,7 +1325,7 @@ }, "node_modules/mlly": { "version": "1.8.2", - "resolved": "https://registry.npmmirror.com/mlly/-/mlly-1.8.2.tgz", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", "dev": true, "license": "MIT", @@ -1265,22 +1338,22 @@ }, "node_modules/mlly/node_modules/pathe": { "version": "2.0.3", - "resolved": "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "dev": true, "license": "MIT" }, "node_modules/ms": { "version": "2.1.3", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.12", - "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", "dev": true, "funding": [ { @@ -1298,7 +1371,7 @@ }, "node_modules/npm-run-path": { "version": "5.3.0", - "resolved": "https://registry.npmmirror.com/npm-run-path/-/npm-run-path-5.3.0.tgz", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", "dev": true, "license": "MIT", @@ -1314,7 +1387,7 @@ }, "node_modules/npm-run-path/node_modules/path-key": { "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/path-key/-/path-key-4.0.0.tgz", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", "dev": true, "license": "MIT", @@ -1327,7 +1400,7 @@ }, "node_modules/onetime": { "version": "6.0.0", - "resolved": "https://registry.npmmirror.com/onetime/-/onetime-6.0.0.tgz", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", "dev": true, "license": "MIT", @@ -1343,7 +1416,7 @@ }, "node_modules/p-limit": { "version": "5.0.0", - "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-5.0.0.tgz", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", "dev": true, "license": "MIT", @@ -1359,7 +1432,7 @@ }, "node_modules/path-key": { "version": "3.1.1", - "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, "license": "MIT", @@ -1369,14 +1442,14 @@ }, "node_modules/pathe": { "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/pathe/-/pathe-1.1.2.tgz", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", "dev": true, "license": "MIT" }, "node_modules/pathval": { "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/pathval/-/pathval-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", "dev": true, "license": "MIT", @@ -1386,14 +1459,14 @@ }, "node_modules/picocolors": { "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, "license": "ISC" }, "node_modules/pkg-types": { "version": "1.3.1", - "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.3.1.tgz", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", "dev": true, "license": "MIT", @@ -1405,15 +1478,15 @@ }, "node_modules/pkg-types/node_modules/pathe": { "version": "2.0.3", - "resolved": "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "dev": true, "license": "MIT" }, "node_modules/postcss": { - "version": "8.5.15", - "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.15.tgz", - "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "version": "8.5.32", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.32.tgz", + "integrity": "sha512-LJlKCCAUFKOuZxipUdYw1NzIITEsPcgyOG9LkvRJvuKj/bRzbuRY0ARXvL0pWGpsZvn8jku+LMep2eU0seHp8g==", "dev": true, "funding": [ { @@ -1431,9 +1504,9 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.12", + "nanoid": "^3.3.20", "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" + "source-map-js": "^1.2.2" }, "engines": { "node": "^10 || ^12 || >=14" @@ -1441,7 +1514,7 @@ }, "node_modules/pretty-format": { "version": "29.7.0", - "resolved": "https://registry.npmmirror.com/pretty-format/-/pretty-format-29.7.0.tgz", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "license": "MIT", @@ -1456,19 +1529,19 @@ }, "node_modules/react-is": { "version": "18.3.1", - "resolved": "https://registry.npmmirror.com/react-is/-/react-is-18.3.1.tgz", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, "license": "MIT" }, "node_modules/rollup": { - "version": "4.62.0", - "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.62.0.tgz", - "integrity": "sha512-nc72Wgq62I7rtDV4izT5/aaS0zxy3kttkinf9586ApknY3jZO9NYsmtc24fUckA0X7Q2v+ML4a15pdUlV5V/jA==", + "version": "4.67.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.67.4.tgz", + "integrity": "sha512-Lsv3oKcz5jgOQJej/p7nfwbVEdAbDT+z6OPf/SCdI+nF+q2ENp0pTKT35ZwXAmH8g45mfrIiJeEyAqKYSYoL9Q==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.9" + "@types/estree": "^1.0.10" }, "bin": { "rollup": "dist/bin/rollup" @@ -1478,37 +1551,39 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.62.0", - "@rollup/rollup-android-arm64": "4.62.0", - "@rollup/rollup-darwin-arm64": "4.62.0", - "@rollup/rollup-darwin-x64": "4.62.0", - "@rollup/rollup-freebsd-arm64": "4.62.0", - "@rollup/rollup-freebsd-x64": "4.62.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.62.0", - "@rollup/rollup-linux-arm-musleabihf": "4.62.0", - "@rollup/rollup-linux-arm64-gnu": "4.62.0", - "@rollup/rollup-linux-arm64-musl": "4.62.0", - "@rollup/rollup-linux-loong64-gnu": "4.62.0", - "@rollup/rollup-linux-loong64-musl": "4.62.0", - "@rollup/rollup-linux-ppc64-gnu": "4.62.0", - "@rollup/rollup-linux-ppc64-musl": "4.62.0", - "@rollup/rollup-linux-riscv64-gnu": "4.62.0", - "@rollup/rollup-linux-riscv64-musl": "4.62.0", - "@rollup/rollup-linux-s390x-gnu": "4.62.0", - "@rollup/rollup-linux-x64-gnu": "4.62.0", - "@rollup/rollup-linux-x64-musl": "4.62.0", - "@rollup/rollup-openbsd-x64": "4.62.0", - "@rollup/rollup-openharmony-arm64": "4.62.0", - "@rollup/rollup-win32-arm64-msvc": "4.62.0", - "@rollup/rollup-win32-ia32-msvc": "4.62.0", - "@rollup/rollup-win32-x64-gnu": "4.62.0", - "@rollup/rollup-win32-x64-msvc": "4.62.0", + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.67.4", + "@rollup/rollup-android-arm64": "4.67.4", + "@rollup/rollup-android-x64": "4.67.4", + "@rollup/rollup-darwin-arm64": "4.67.4", + "@rollup/rollup-darwin-x64": "4.67.4", + "@rollup/rollup-freebsd-arm64": "4.67.4", + "@rollup/rollup-freebsd-x64": "4.67.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.67.4", + "@rollup/rollup-linux-arm-musleabihf": "4.67.4", + "@rollup/rollup-linux-arm64-gnu": "4.67.4", + "@rollup/rollup-linux-arm64-musl": "4.67.4", + "@rollup/rollup-linux-loong64-gnu": "4.67.4", + "@rollup/rollup-linux-loong64-musl": "4.67.4", + "@rollup/rollup-linux-ppc64-gnu": "4.67.4", + "@rollup/rollup-linux-ppc64-musl": "4.67.4", + "@rollup/rollup-linux-riscv64-gnu": "4.67.4", + "@rollup/rollup-linux-riscv64-musl": "4.67.4", + "@rollup/rollup-linux-s390x-gnu": "4.67.4", + "@rollup/rollup-linux-x64-gnu": "4.67.4", + "@rollup/rollup-linux-x64-musl": "4.67.4", + "@rollup/rollup-openbsd-x64": "4.67.4", + "@rollup/rollup-openharmony-arm64": "4.67.4", + "@rollup/rollup-win32-arm64-msvc": "4.67.4", + "@rollup/rollup-win32-ia32-msvc": "4.67.4", + "@rollup/rollup-win32-x64-gnu": "4.67.4", + "@rollup/rollup-win32-x64-msvc": "4.67.4", "fsevents": "~2.3.2" } }, "node_modules/shebang-command": { "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "license": "MIT", @@ -1521,7 +1596,7 @@ }, "node_modules/shebang-regex": { "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, "license": "MIT", @@ -1531,14 +1606,14 @@ }, "node_modules/siginfo": { "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/siginfo/-/siginfo-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", "dev": true, "license": "ISC" }, "node_modules/signal-exit": { "version": "4.1.0", - "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-4.1.0.tgz", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, "license": "ISC", @@ -1550,9 +1625,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -1561,21 +1636,21 @@ }, "node_modules/stackback": { "version": "0.0.2", - "resolved": "https://registry.npmmirror.com/stackback/-/stackback-0.0.2.tgz", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", "dev": true, "license": "MIT" }, "node_modules/std-env": { "version": "3.10.0", - "resolved": "https://registry.npmmirror.com/std-env/-/std-env-3.10.0.tgz", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", "dev": true, "license": "MIT" }, "node_modules/strip-final-newline": { "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", "dev": true, "license": "MIT", @@ -1588,7 +1663,7 @@ }, "node_modules/strip-literal": { "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/strip-literal/-/strip-literal-2.1.1.tgz", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.1.tgz", "integrity": "sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==", "dev": true, "license": "MIT", @@ -1601,14 +1676,14 @@ }, "node_modules/tinybench": { "version": "2.9.0", - "resolved": "https://registry.npmmirror.com/tinybench/-/tinybench-2.9.0.tgz", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", "dev": true, "license": "MIT" }, "node_modules/tinypool": { "version": "0.8.4", - "resolved": "https://registry.npmmirror.com/tinypool/-/tinypool-0.8.4.tgz", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz", "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==", "dev": true, "license": "MIT", @@ -1618,7 +1693,7 @@ }, "node_modules/tinyspy": { "version": "2.2.1", - "resolved": "https://registry.npmmirror.com/tinyspy/-/tinyspy-2.2.1.tgz", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", "dev": true, "license": "MIT", @@ -1628,7 +1703,7 @@ }, "node_modules/type-detect": { "version": "4.1.0", - "resolved": "https://registry.npmmirror.com/type-detect/-/type-detect-4.1.0.tgz", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", "dev": true, "license": "MIT", @@ -1638,7 +1713,7 @@ }, "node_modules/typescript": { "version": "5.9.3", - "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", @@ -1652,21 +1727,21 @@ }, "node_modules/ufo": { "version": "1.6.4", - "resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.6.4.tgz", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", "dev": true, "license": "MIT" }, "node_modules/undici-types": { "version": "6.21.0", - "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-6.21.0.tgz", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "dev": true, "license": "MIT" }, "node_modules/vite": { "version": "5.4.21", - "resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.21.tgz", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", "dev": true, "license": "MIT", @@ -1726,7 +1801,7 @@ }, "node_modules/vite-node": { "version": "1.6.1", - "resolved": "https://registry.npmmirror.com/vite-node/-/vite-node-1.6.1.tgz", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.6.1.tgz", "integrity": "sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==", "dev": true, "license": "MIT", @@ -1749,7 +1824,7 @@ }, "node_modules/vitest": { "version": "1.6.1", - "resolved": "https://registry.npmmirror.com/vitest/-/vitest-1.6.1.tgz", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.1.tgz", "integrity": "sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==", "dev": true, "license": "MIT", @@ -1815,7 +1890,7 @@ }, "node_modules/which": { "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "license": "ISC", @@ -1831,7 +1906,7 @@ }, "node_modules/why-is-node-running": { "version": "2.3.0", - "resolved": "https://registry.npmmirror.com/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", "dev": true, "license": "MIT", @@ -1848,7 +1923,7 @@ }, "node_modules/yocto-queue": { "version": "1.2.2", - "resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-1.2.2.tgz", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", "dev": true, "license": "MIT", diff --git a/src/diff-parser.ts b/src/diff-parser.ts index 51ed9c8..00cc615 100644 --- a/src/diff-parser.ts +++ b/src/diff-parser.ts @@ -1,5 +1,4 @@ import type { DiffFile, DiffFileStatus, DiffHunk, DiffLine } from "./types"; -import { normalizePath } from "./path-utils"; const HUNK_RE = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@\s?(.*)$/; @@ -87,9 +86,9 @@ export function parseDiff(diff: string): DiffFile[] { } function parseDiffGitLine(raw: string): DiffFile { - const match = /^diff --git a\/(.+) b\/(.+)$/.exec(raw); - const oldPath = match ? normalizePath(match[1]) : "unknown"; - const newPath = match ? normalizePath(match[2]) : oldPath; + const match = /^diff --git ("(?:[^"\\]|\\.)*"|a\/.+) ("(?:[^"\\]|\\.)*"|b\/.+)$/.exec(raw); + const oldPath = match ? parsePathHeader(match[1], "unknown") : "unknown"; + const newPath = match ? parsePathHeader(match[2], oldPath) : oldPath; return { oldPath, @@ -105,7 +104,41 @@ function parsePathHeader(rawPath: string, fallback: string): string { } const pathOnly = rawPath.split("\t")[0] ?? rawPath; - return normalizePath(pathOnly.replace(/^[ab]\//, "")) || fallback; + // Git paths always use slash separators. Backslashes inside a C-quoted path + // are filename data, so decode before stripping the single diff prefix. + return decodeGitPath(pathOnly).replace(/^[ab]\//, "") || fallback; +} + +function decodeGitPath(value: string): string { + if (!value.startsWith('"')) return value; + if (!value.endsWith('"')) throw new Error("Invalid quoted path in git diff."); + + const bytes: number[] = []; + const escapes: Record = { a: 7, b: 8, t: 9, n: 10, v: 11, f: 12, r: 13, '"': 34, "\\": 92 }; + for (let i = 1; i < value.length - 1; i += 1) { + if (value[i] !== "\\") { + const codePoint = value.codePointAt(i)!; + bytes.push(...Buffer.from(String.fromCodePoint(codePoint), "utf8")); + if (codePoint > 0xffff) i += 1; + continue; + } + + const escaped = value[++i]; + if (escaped in escapes) { + bytes.push(escapes[escaped]); + continue; + } + if (/[0-7]/.test(escaped ?? "")) { + const octal = value.slice(i).match(/^[0-7]{1,3}/)![0]; + const byte = parseInt(octal, 8); + if (byte > 255) throw new Error("Invalid octal path escape in git diff."); + bytes.push(byte); + i += octal.length - 1; + continue; + } + throw new Error("Invalid path escape in git diff."); + } + return Buffer.from(bytes).toString("utf8"); } function parseHunkLine( diff --git a/tests/action-security.test.ts b/tests/action-security.test.ts new file mode 100644 index 0000000..f0383f1 --- /dev/null +++ b/tests/action-security.test.ts @@ -0,0 +1,59 @@ +import fs from "node:fs"; +import path from "node:path"; +import { spawnSync } from "node:child_process"; +import { describe, expect, it } from "vitest"; + +const action = fs.readFileSync(path.join(process.cwd(), "action.yml"), "utf8"); +const scripts = Array.from(action.matchAll(/ run: \|\r?\n((?: .*\r?\n?)*)/g), match => + match[1].split(/\r?\n/).map(line => line.replace(/^ /, "")).join("\n") +); +const gitBash = path.join(process.env.ProgramFiles || "C:/Program Files", "Git/bin/bash.exe"); +const bash = process.platform === "win32" && fs.existsSync(gitBash) ? gitBash : "bash"; +const defaults = { + PATCHBRAKE_VERSION: "0.2.0", + PATCHBRAKE_BASE: "origin/main", + PATCHBRAKE_HEAD: "HEAD", + PATCHBRAKE_FORMAT: "text", + PATCHBRAKE_FAIL_ON: "error", + PATCHBRAKE_OUTPUT: "", + PATCHBRAKE_ALLOW_CUSTOM_RULES: "false", + PATCHBRAKE_ALLOW_INLINE_IGNORE: "false", + PATCHBRAKE_FAIL_ON_NEW_IGNORE: "true" +}; +function run(script: string, env: Record = {}) { + return spawnSync(bash, ["-c", 'npm() { printf "%s\\n" "$@"; }; patchbrake() { printf "%s\\n" "$@"; };\n' + script], { + env: { ...process.env, ...defaults, ...env }, encoding: "utf8", windowsHide: true + }); +} + +describe("composite Action input boundaries", () => { + it("passes every input through env instead of interpolating shell source", () => { + expect(scripts).toHaveLength(2); + for (const script of scripts) expect(script).not.toContain("${{"); + for (const name of ["version", "base", "head", "format", "fail-on", "output", "allow-custom-rules", "allow-inline-ignore", "fail-on-new-ignore"]) { + expect(action).toContain(`: \${{ inputs.${name} }}`); + } + }); + it("keeps command substitutions, quotes and newlines literal in argument values", () => { + const malicious = 'HEAD$(printf${IFS}INJECTED)";printf BAD;#\nnext'; + const result = run(scripts[1], { PATCHBRAKE_HEAD: malicious, PATCHBRAKE_OUTPUT: malicious }); + expect(result.status, result.stderr).toBe(0); + expect(result.stdout).toContain(`--head\n${malicious}\n`); + expect(result.stdout).toContain(`--output\n${malicious}\n`); + }); + it("validates pinned versions before invoking npm", () => { + for (const version of ["0.2.0", "1.2.3-beta.1+build.2"]) { + expect(run(scripts[0], { PATCHBRAKE_VERSION: version }).status).toBe(0); + } + for (const version of ["latest", "0.2.0$(printf${IFS}BAD)", "0.2.0\nBAD", "../local"]) { + const result = run(scripts[0], { PATCHBRAKE_VERSION: version }); + expect(result.status).toBe(2); + expect(result.stdout).toBe(""); + } + }); + it("rejects invalid boolean input without invoking the scanner", () => { + const result = run(scripts[1], { PATCHBRAKE_ALLOW_CUSTOM_RULES: 'false$(printf${IFS}BAD)' }); + expect(result.status).toBe(2); + expect(result.stdout).toBe(""); + }); +}); diff --git a/tests/diff-parser.test.ts b/tests/diff-parser.test.ts index b4efc33..098e777 100644 --- a/tests/diff-parser.test.ts +++ b/tests/diff-parser.test.ts @@ -1,7 +1,12 @@ import fs from "node:fs"; import path from "node:path"; +import os from "node:os"; +import { spawnSync } from "node:child_process"; import { describe, expect, it } from "vitest"; import { parseDiff } from "../src/diff-parser"; +import { loadConfig } from "../src/config"; +import { runRules } from "../src/rule-engine"; +import { builtinRules } from "../src/rules"; describe("parseDiff", () => { it("parses files, hunks, and added lines", () => { @@ -13,4 +18,41 @@ describe("parseDiff", () => { expect(files[0]?.hunks).toHaveLength(1); expect(files[0]?.hunks[0]?.lines.some((line) => line.type === "add" && line.newLineNumber === 3)).toBe(true); }); + + it("decodes real Git UTF-8 octal-quoted workflow paths before applying rules", () => { + const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "patchbrake-quoted-")); + try { + spawnSync("git", ["init"], { cwd, encoding: "utf8" }); + fs.mkdirSync(path.join(cwd, ".github/workflows"), { recursive: true }); + const filename = ".github/workflows/安全 workflow.yml"; + fs.writeFileSync(path.join(cwd, filename), "on:\n pull_request_target:\n"); + spawnSync("git", ["add", "--", filename], { cwd, encoding: "utf8" }); + const diff = spawnSync("git", ["-c", "core.quotePath=true", "diff", "--cached"], { cwd, encoding: "utf8" }); + expect(diff.status).toBe(0); + expect(diff.stdout).toContain("\\345"); + const files = parseDiff(diff.stdout); + expect(files[0].newPath).toBe(filename); + const result = runRules(files, builtinRules, loadConfig(cwd), { cwd, staged: true, format: "json", failOn: "error" }); + expect(result.findings.map(finding => finding.ruleId)).toContain("workflow-permissions"); + } finally { + fs.rmSync(cwd, { recursive: true, force: true }); + } + }); + + it("roundtrips C-quoted paths including UTF-8, control characters and literal backslashes", () => { + const filenames = ["src/安全.ts", "src/tab\tname.ts", 'src/quote"name.ts', "src/back\\slash.ts", "src/emoji😀.ts", "a/real-directory.ts"]; + function quote(value: string) { + return '"' + [...Buffer.from(value, "utf8")].map(byte => + byte < 32 || byte >= 127 || byte === 34 || byte === 92 ? "\\" + byte.toString(8).padStart(3, "0") : String.fromCharCode(byte) + ).join("") + '"'; + } + for (const filename of filenames) { + const a = quote("a/" + filename), b = quote("b/" + filename); + const diff = `diff --git ${a} ${b}\n--- ${a}\n+++ ${b}\n@@ -1 +1 @@\n-old\n+new\n`; + expect(parseDiff(diff)[0].oldPath).toBe(filename); + expect(parseDiff(diff)[0].newPath).toBe(filename); + // Binary/rename-only headers have no ---/+++ path to repair a bad header. + expect(parseDiff(`diff --git ${a} ${b}\nBinary files ${a} and ${b} differ\n`)[0].newPath).toBe(filename); + } + }); }); From ce20564567e47d8a5991da60fcbd505b54ede6bb Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 4 Nov 2026 07:29:41 -0800 Subject: [PATCH 2/3] fix: close diff scanning bypasses and redact all findings [skip ci] --- docs/deep-audit-20261104.md | 9 +++++ src/path-utils.ts | 14 +++++--- src/redaction.ts | 15 ++++++++ src/rule-engine.ts | 9 ++++- src/rules/secret-leak.ts | 44 +++++++++++------------- tests/deep-audit-security.test.ts | 57 +++++++++++++++++++++++++++++++ 6 files changed, 119 insertions(+), 29 deletions(-) create mode 100644 docs/deep-audit-20261104.md create mode 100644 src/redaction.ts create mode 100644 tests/deep-audit-security.test.ts diff --git a/docs/deep-audit-20261104.md b/docs/deep-audit-20261104.md new file mode 100644 index 0000000..5235f3b --- /dev/null +++ b/docs/deep-audit-20261104.md @@ -0,0 +1,9 @@ +# Deep audit repairs (2026-11-04) + +- `**/` includes zero or more directories and `?` matches one non-separator character. Parsed repository paths retain actual `a/` and `b/` directories; diff prefixes are removed once by the parser. +- Placeholder suppression applies to the matched value, not unrelated comments or words elsewhere on the line. Quoted JSON assignments and high-confidence tokens in comments are scanned. +- All rule findings, including inline-ignore findings, redact recognized token formats and complete secret assignments before report output. One line triggering another rule cannot reintroduce the same recognized secret in its excerpt. + +Regression tests use synthetic values only. This remains a heuristic diff scanner, not a complete secret detector. Changed redacted excerpts can change secret-rule fingerprints: review regenerated baselines rather than copying exceptions blindly. Reports do not include raw diff files, though the programmatic scan result still includes the requested input diff for callers. + +Verification: TypeScript build, full Vitest suite, deterministic benchmark and Git whitespace check. No major development dependency migration is included. diff --git a/src/path-utils.ts b/src/path-utils.ts index 39713cc..0790f64 100644 --- a/src/path-utils.ts +++ b/src/path-utils.ts @@ -1,5 +1,6 @@ export function normalizePath(path: string): string { - return path.replace(/\\/g, "/").replace(/^a\//, "").replace(/^b\//, ""); + // Diff prefixes are removed once by the parser; a/ and b/ can be real directories. + return path.replace(/\\/g, "/").replace(/^\.\//, ""); } export function getDisplayPath(path: string): string { @@ -63,12 +64,17 @@ function globToRegExp(pattern: string): RegExp { const next = normalized[i + 1]; if (char === "*" && next === "*") { - source += ".*"; - i += 1; + if (normalized[i + 2] === "/") { + source += "(?:[^/]+/)*"; + i += 2; + } else { + source += ".*"; + i += 1; + } } else if (char === "*") { source += "[^/]*"; } else if (char === "?") { - source += "."; + source += "[^/]"; } else { source += escapeRegExp(char); } diff --git a/src/redaction.ts b/src/redaction.ts new file mode 100644 index 0000000..91a1513 --- /dev/null +++ b/src/redaction.ts @@ -0,0 +1,15 @@ +// Shared by all findings, since one source line can trigger several rules. +export const SECRET_ASSIGNMENT = /\b(?:api[_-]?key|token|secret|password|passwd|client[_-]?secret|private[_-]?key)\b["']?\s*[:=]\s*(?:"((?:\\.|[^"\\\r\n])*)"|'((?:\\.|[^'\\\r\n])*)'|([^\s"'#,;]+))/gi; + +export function redactSecrets(content: string): string { + return content + .replace(/\bsk-[A-Za-z0-9_-]{8,}/g, "sk-...redacted") + .replace(/\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{8,}/g, "gh...redacted") + .replace(/\bgithub_pat_[A-Za-z0-9_]{8,}/g, "github_pat_...redacted") + .replace(/\bAKIA[0-9A-Z]{16}\b/g, "AKIA...redacted") + .replace(/-----BEGIN (?:RSA |EC |OPENSSH |DSA |)?PRIVATE KEY-----/gi, "-----BEGIN ... REDACTED PRIVATE KEY-----") + .replace(new RegExp(SECRET_ASSIGNMENT.source, "gi"), match => { + const separator = match.search(/[:=]/); + return match.slice(0, separator + 1) + "[REDACTED]"; + }); +} diff --git a/src/rule-engine.ts b/src/rule-engine.ts index 0c51f3e..318c3ce 100644 --- a/src/rule-engine.ts +++ b/src/rule-engine.ts @@ -3,6 +3,7 @@ import type { DiffFile, Finding, FindingDraft, IgnoreEntry, ResolvedConfig, Rule import { getConfiguredSeverity, hasRuleSeverityOverride, resolveConfigForPath } from "./config"; import { getFilePath } from "./diff-parser"; import { matchesAnyGlob } from "./path-utils"; +import { redactSecrets } from "./redaction"; const SEVERITY_RANK: Record, number> = { info: 1, @@ -60,7 +61,13 @@ export function runRules(files: RuleContext["files"], rules: Rule[], config: Res return { files: scannedFiles, - findings, + findings: findings.map(finding => ({ + ...finding, + message: redactSecrets(finding.message), + excerpt: finding.excerpt === undefined ? undefined : redactSecrets(finding.excerpt), + remediation: finding.remediation === undefined ? undefined : redactSecrets(finding.remediation), + suppressed: finding.suppressed ? { ...finding.suppressed, reason: finding.suppressed.reason === undefined ? undefined : redactSecrets(finding.suppressed.reason) } : undefined + })), rulesRun, skippedFiles, ruleTimings, diff --git a/src/rules/secret-leak.ts b/src/rules/secret-leak.ts index 284f0d6..ffd496c 100644 --- a/src/rules/secret-leak.ts +++ b/src/rules/secret-leak.ts @@ -1,18 +1,14 @@ import type { Rule } from "../types"; import { getAddedLines, getFilePath } from "../diff-parser"; import { normalizePath } from "../path-utils"; +import { redactSecrets, SECRET_ASSIGNMENT } from "../redaction"; const SECRET_PATTERNS: Array<{ name: string; pattern: RegExp }> = [ { name: "private key", pattern: /-----BEGIN (?:RSA |EC |OPENSSH |DSA |)?PRIVATE KEY-----/i }, { name: "GitHub token", pattern: /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{20,}\b/ }, { name: "GitHub fine-grained token", pattern: /\bgithub_pat_[A-Za-z0-9_]{20,}\b/ }, { name: "OpenAI API key", pattern: /\bsk-[A-Za-z0-9_-]{20,}\b/ }, - { name: "AWS access key", pattern: /\bAKIA[0-9A-Z]{16}\b/ }, - { - name: "secret assignment", - pattern: - /\b(?:api[_-]?key|token|secret|password|passwd|client[_-]?secret|private[_-]?key)\b\s*[:=]\s*["']?[^"'\s#]{10,}/i - } + { name: "AWS access key", pattern: /\bAKIA[0-9A-Z]{16}\b/ } ]; export const secretLeakRule: Rule = { @@ -33,12 +29,22 @@ export const secretLeakRule: Rule = { for (const line of getAddedLines(file)) { const content = line.content.trim(); - if (!content || content.startsWith("#") || isLikelyPlaceholder(content)) { + if (!content) { continue; } - const matched = SECRET_PATTERNS.find((entry) => entry.pattern.test(content)); - if (!matched && !(isEnvFile && /^[A-Z0-9_]{3,}\s*=/.test(content))) { + const matched = SECRET_PATTERNS.find(entry => { + const value = content.match(entry.pattern)?.[0]; + return value !== undefined && !isLikelyPlaceholder(value); + }); + const assignment = [...content.matchAll(new RegExp(SECRET_ASSIGNMENT.source, "gi"))] + .some(match => { + const value = match[1] ?? match[2] ?? match[3] ?? ""; + return value.length >= 10 && !isLikelyPlaceholder(value); + }); + const envValue = isEnvFile ? content.match(/^[A-Z0-9_]{3,}\s*=\s*(.*)$/)?.[1] : undefined; + const envSecret = envValue !== undefined && envValue.length > 0 && !isLikelyPlaceholder(envValue.replace(/^["']|["']$/g, "")); + if (!matched && !assignment && !envSecret) { continue; } @@ -46,10 +52,10 @@ export const secretLeakRule: Rule = { ruleId: "secret-leak", category: "secrets" as const, severity: "error" as const, - message: matched ? `Possible ${matched.name} added in this diff.` : "Possible secret added to an env file.", + message: matched ? `Possible ${matched.name} added in this diff.` : assignment ? "Possible secret assignment added in this diff." : "Possible secret added to an env file.", filePath, line: line.newLineNumber, - excerpt: redact(content), + excerpt: redactSecrets(content), remediation: "Remove the value from git history, rotate it if real, and load it from environment or CI secrets." }); } @@ -61,18 +67,8 @@ export const secretLeakRule: Rule = { function isLikelyPlaceholder(content: string): boolean { return ( - /\b(example|sample|placeholder|redacted|changeme|replace_me|your[_-]?key|dummy|test[_-]?only|xxxxx)\b/i.test(content) || - /\bsk-(?:test|example|placeholder|your[_-]?key)/i.test(content) || - /["']?\.\.\.["']?$/.test(content.trim()) + /^(?:example|sample|placeholder|redacted|changeme|replace_me|your[_-]?key(?:[_-]here)?|dummy|test[_-]?only|xxxxx)$/i.test(content) || + /^sk-(?:test|example|placeholder|your[_-]?key)(?:\.\.\.)?$/i.test(content) || + /^\.\.\.$/.test(content.trim()) ); } - -function redact(content: string): string { - return content - .replace(/\bsk-[A-Za-z0-9_-]{8,}/g, "sk-...redacted") - .replace(/\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{8,}/g, "gh...redacted") - .replace(/\bgithub_pat_[A-Za-z0-9_]{8,}/g, "github_pat_...redacted") - .replace(/\bAKIA[0-9A-Z]{16}\b/g, "AKIA...redacted") - .replace(/-----BEGIN (?:RSA |EC |OPENSSH |DSA |)?PRIVATE KEY-----/gi, "-----BEGIN ... REDACTED PRIVATE KEY-----") - .replace(/([:=]\s*["']?)(?![^"'\s#]*redacted)([^"'\s#]{6})[^"'\s#]+/g, "$1$2..."); -} diff --git a/tests/deep-audit-security.test.ts b/tests/deep-audit-security.test.ts new file mode 100644 index 0000000..d1bac23 --- /dev/null +++ b/tests/deep-audit-security.test.ts @@ -0,0 +1,57 @@ +import { describe, expect, it } from "vitest"; +import { loadConfig } from "../src/config"; +import { parseDiff, getFilePath } from "../src/diff-parser"; +import { runRules } from "../src/rule-engine"; +import { builtinRules } from "../src/rules"; +import { matchesAnyGlob } from "../src/path-utils"; +import { formatJsonReport } from "../src/reporters/json"; +import { formatTextReport } from "../src/reporters/text"; +import { formatSarifReport } from "../src/reporters/sarif"; + +const syntheticToken = "ghp_" + "a".repeat(36); +function scan(filePath: string, line: string, include: string[] = []) { + const files = parseDiff(`diff --git a/${filePath} b/${filePath}\n--- a/${filePath}\n+++ b/${filePath}\n@@ -0,0 +1 @@\n+${line}\n`); + return runRules(files, builtinRules, { ...loadConfig(process.cwd()), include }, { + cwd: process.cwd(), staged: true, format: "json", failOn: "error", disallowInlineIgnore: true + }); +} + +describe("deep audit security regressions", () => { + it("scans root files selected with a globstar include", () => { + expect(scan("index.ts", `const token = "${syntheticToken}"`, ["**/*.ts"]).findings.some(f => f.ruleId === "secret-leak")).toBe(true); + expect(matchesAnyGlob("src/index.ts", ["src/**/*.ts"])).toBe(true); + }); + it("does not let question marks match path separators", () => { + expect(matchesAnyGlob("src/a/index.ts", ["src?a/index.ts"])).toBe(false); + }); + it("preserves real a and b root directory names after diff parsing", () => { + const result = scan("a/b/config.ts", `const token = "${syntheticToken}"`, ["a/b/**"]); + expect(result.files.map(getFilePath)).toEqual(["a/b/config.ts"]); + expect(result.findings.some(f => f.ruleId === "secret-leak")).toBe(true); + expect(matchesAnyGlob("a/src/config.ts", ["src/**"])).toBe(false); + }); + it.each(["example", "sample", "dummy", "..."])("detects tokens beside unrelated placeholder text %s", comment => { + const result = scan("src/config.ts", `const token = "${syntheticToken}"; // ${comment}`); + expect(result.findings.some(f => f.ruleId === "secret-leak")).toBe(true); + }); + it("detects quoted JSON secret assignments", () => { + expect(scan("config.json", '"password": "synthetic-private-value"').findings.some(f => f.ruleId === "secret-leak")).toBe(true); + }); + it("detects high-confidence tokens in comments", () => { + expect(scan("config.py", `# token = "${syntheticToken}"`).findings.some(f => f.ruleId === "secret-leak")).toBe(true); + }); + it("redacts complete escaped quoted assignment values", () => { + const result = scan("migration.sql", 'DROP TABLE users; -- password="synthetic\\"private-tail-value"'); + expect(formatJsonReport(result)).not.toContain("private-tail-value"); + }); + it.each([formatJsonReport, formatTextReport, formatSarifReport])("redacts secrets in every rule's report excerpt", format => { + const result = scan("migration.sql", `DROP TABLE users; -- token="${syntheticToken}"`); + expect(result.findings.some(f => f.ruleId === "migration-risk")).toBe(true); + expect(format(result)).not.toContain(syntheticToken); + }); + it("redacts secret excerpts from inline-ignore findings", () => { + const result = scan("src/config.ts", `const token = "${syntheticToken}"; // patchbrake-ignore`); + expect(result.findings.some(f => f.ruleId === "inline-ignore")).toBe(true); + expect(formatJsonReport(result)).not.toContain(syntheticToken); + }); +}); From 7489d8279b6dc0bc7a29d5e3b9800aee27d68442 Mon Sep 17 00:00:00 2001 From: Ryan Date: Sat, 7 Nov 2026 08:03:52 -0800 Subject: [PATCH 3/3] Fix diff parsing and inline suppression boundaries [skip ci] --- docs/reaudit-20261106.md | 11 +++++ src/diff-parser.ts | 4 +- src/git.ts | 12 +++++- src/rule-engine.ts | 27 ++++++------ tests/reaudit-boundaries.test.ts | 72 ++++++++++++++++++++++++++++++++ 5 files changed, 108 insertions(+), 18 deletions(-) create mode 100644 docs/reaudit-20261106.md create mode 100644 tests/reaudit-boundaries.test.ts diff --git a/docs/reaudit-20261106.md b/docs/reaudit-20261106.md new file mode 100644 index 0000000..d5fea2b --- /dev/null +++ b/docs/reaudit-20261106.md @@ -0,0 +1,11 @@ +# Second audit: diff and suppression boundaries + +Verified against PR #28 head `ce20564567e47d8a5991da60fcbd505b54ede6bb`. + +- Hunk lines beginning with `--- ` or `+++ ` were interpreted as file headers. This could discard a risky added line and change the path used by every rule. Headers are now recognized only before the first hunk of each file. +- Deleted inline-ignore comments still suppressed new findings, and a named ignore could suppress unrelated rules. Only surviving lines apply; a named rule must match exactly. Bare directives keep their existing behavior. The Action's stricter inline-ignore defaults remain in effect. +- Human-oriented Git color/prefix settings could produce missing or mislocated findings. Staged and range scans now request explicit uncolored patch prefixes/indicators and disable text conversion/external diff helpers. + +New regression suite: 17 cases, including actual staged Git repositories. Initial 14 cases had 8 failures before the fix. Final full suite: 75 passed; TypeScript build passed; benchmark 30/30. This remains a heuristic patch scanner, not a complete semantic security audit. + +Git option behavior was checked against the [official git-diff documentation](https://git-scm.com/docs/git-diff). diff --git a/src/diff-parser.ts b/src/diff-parser.ts index 00cc615..83d5dcc 100644 --- a/src/diff-parser.ts +++ b/src/diff-parser.ts @@ -42,12 +42,12 @@ export function parseDiff(diff: string): DiffFile[] { continue; } - if (raw.startsWith("--- ")) { + if (!currentHunk && raw.startsWith("--- ")) { currentFile.oldPath = parsePathHeader(raw.slice(4), currentFile.oldPath); continue; } - if (raw.startsWith("+++ ")) { + if (!currentHunk && raw.startsWith("+++ ")) { currentFile.newPath = parsePathHeader(raw.slice(4), currentFile.newPath); continue; } diff --git a/src/git.ts b/src/git.ts index a8ab04e..9976d62 100644 --- a/src/git.ts +++ b/src/git.ts @@ -1,17 +1,25 @@ import { spawnSync } from "node:child_process"; +// The parser consumes a machine-readable patch, independent of the user's +// color, prefix, textconv and output-indicator preferences. +const PATCH_OPTIONS = [ + "--no-ext-diff", "--no-textconv", "--no-color", "--no-relative", "--unified=3", + "--src-prefix=a/", "--dst-prefix=b/", "--line-prefix=", + "--output-indicator-new=+", "--output-indicator-old=-", "--output-indicator-context= " +]; + export function isInsideGitRepo(cwd: string): boolean { const result = runGit(["rev-parse", "--is-inside-work-tree"], cwd, false); return result.status === 0 && result.stdout.trim() === "true"; } export function getStagedDiff(cwd: string): string { - const result = runGit(["diff", "--staged", "--no-ext-diff", "--unified=3"], cwd, true); + const result = runGit(["diff", "--staged", ...PATCH_OPTIONS], cwd, true); return result.stdout; } export function getRangeDiff(cwd: string, base: string, head: string): string { - const result = runGit(["diff", "--no-ext-diff", "--unified=3", `${base}...${head}`], cwd, true); + const result = runGit(["diff", ...PATCH_OPTIONS, `${base}...${head}`, "--"], cwd, true); return result.stdout; } diff --git a/src/rule-engine.ts b/src/rule-engine.ts index 318c3ce..3994dde 100644 --- a/src/rule-engine.ts +++ b/src/rule-engine.ts @@ -244,24 +244,23 @@ function hasInlineIgnore(file: DiffFile, finding: Finding): boolean { const inlineLines = file.hunks.flatMap((hunk) => hunk.lines); return inlineLines.some((line) => { - if (!line.content.includes("patchbrake-ignore")) { + if (line.type === "remove" || !line.content.includes("patchbrake-ignore")) { return false; } - const appliesToRule = line.content.includes(finding.ruleId) || /patchbrake-ignore(?:-next-line|-file)?(?:\s|$)/.test(line.content); - if (!appliesToRule) { - return false; - } - - if (line.content.includes("patchbrake-ignore-file")) { - return true; - } - - if (line.content.includes("patchbrake-ignore-next-line")) { - return line.newLineNumber !== undefined && finding.line !== undefined && line.newLineNumber + 1 === finding.line; + const directives = line.content.matchAll(/\bpatchbrake-ignore(-next-line|-file)?(?=$|[\s*])(?:[ \t]+([^\s*]+))?/g); + for (const directive of directives) { + // A named exception belongs only to that exact rule. A bare directive + // retains the existing all-rules behavior. + if (directive[2] && directive[2] !== finding.ruleId) continue; + if (directive[1] === "-file") return true; + if (directive[1] === "-next-line") { + if (line.newLineNumber !== undefined && finding.line !== undefined && line.newLineNumber + 1 === finding.line) return true; + } else if (line.newLineNumber === finding.line) { + return true; + } } - - return line.newLineNumber === finding.line || line.oldLineNumber === finding.line; + return false; }); } diff --git a/tests/reaudit-boundaries.test.ts b/tests/reaudit-boundaries.test.ts new file mode 100644 index 0000000..513e831 --- /dev/null +++ b/tests/reaudit-boundaries.test.ts @@ -0,0 +1,72 @@ +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { spawnSync } from "node:child_process"; +import { describe, expect, it } from "vitest"; +import { parseDiff } from "../src/diff-parser"; +import { loadConfig } from "../src/config"; +import { runRules } from "../src/rule-engine"; +import { builtinRules } from "../src/rules"; +import { runScan } from "../src/commands/scan"; + +const token = "ghp_" + "z".repeat(36); +function scan(lines: string[]) { + const oldCount = lines.filter(line => !line.startsWith("+")).length; + const newCount = lines.filter(line => !line.startsWith("-")).length; + const files = parseDiff([ + "diff --git a/src/config.ts b/src/config.ts", + "--- a/src/config.ts", "+++ b/src/config.ts", `@@ -1,${oldCount} +1,${newCount} @@`, ...lines, "" + ].join("\n")); + return runRules(files, builtinRules, loadConfig(process.cwd()), { + cwd: process.cwd(), staged: true, format: "json", failOn: "error" + }); +} + +describe("second audit diff and suppression boundaries", () => { + it("treats header-shaped added and removed text as hunk data", () => { + const result = scan(["--- old text", `+++ token = \"${token}\"`, "+const ok = true;"]); + expect(result.files[0].oldPath).toBe("src/config.ts"); + expect(result.files[0].newPath).toBe("src/config.ts"); + expect(result.files[0].hunks[0].lines[0].type).toBe("remove"); + expect(result.findings.some(f => f.ruleId === "secret-leak" && f.line === 1)).toBe(true); + }); + it("does not retain removed file-wide ignore directives", () => { + const result = scan(["-// patchbrake-ignore-file secret-leak", `+const token = \"${token}\";`]); + expect(result.findings.find(f => f.ruleId === "secret-leak")?.suppressed).toBeUndefined(); + }); + it.each(["", "-next-line", "-file"])("does not widen a rule-specific %s ignore to all rules", suffix => { + const lines = suffix ? [`+// patchbrake-ignore${suffix} migration-risk`, `+const token = \"${token}\";`] + : [`+const token = \"${token}\"; // patchbrake-ignore migration-risk`]; + expect(scan(lines).findings.find(f => f.ruleId === "secret-leak")?.suppressed).toBeUndefined(); + }); + it.each(["", "-next-line", "-file"])("still honors the exact matching %s ignore", suffix => { + const lines = suffix ? [`+// patchbrake-ignore${suffix} secret-leak`, `+const token = \"${token}\";`] + : [`+const token = \"${token}\"; // patchbrake-ignore secret-leak`]; + expect(scan(lines).findings.find(f => f.ruleId === "secret-leak")?.suppressed?.kind).toBe("inline"); + }); + it.each(["custom.rule", "custom:rule", "secret-leak-extra"])("does not treat unknown rule %s as a bare all-rules directive", rule => { + expect(scan([`+const token = \"${token}\"; // patchbrake-ignore ${rule}`]) + .findings.find(f => f.ruleId === "secret-leak")?.suppressed).toBeUndefined(); + }); + it.each([ + ["color.ui", "always"], ["diff.noprefix", "true"], ["diff.mnemonicPrefix", "true"], + ["diff.srcPrefix", "before/"], ["diff.dstPrefix", "after/"], + ["diff.outputIndicatorNew", ">"] + ])("scans staged workflow changes regardless of %s", (key, value) => { + const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "patchbrake-format-")); + const git = (...args: string[]) => { + const r = spawnSync("git", args, { cwd, encoding: "utf8", windowsHide: true }); + expect(r.status, r.stderr).toBe(0); + return r.stdout; + }; + git("init"); + git("config", key, value); + fs.mkdirSync(path.join(cwd, ".github/workflows"), { recursive: true }); + fs.writeFileSync(path.join(cwd, ".github/workflows/check.yml"), "permissions: write-all\n"); + git("add", "--", ".github/workflows/check.yml"); + const result = runScan({ cwd, staged: true, failOn: "warn" }); + expect(result.result.files[0]?.newPath).toBe(".github/workflows/check.yml"); + expect(result.result.findings.some(f => f.ruleId === "workflow-permissions")).toBe(true); + expect(result.exitCode).toBe(1); + }); +});