diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..8640359 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,17 @@ +# API config / startup surfaces +/src/config.ts @metanallok +/src/main.ts @metanallok +/src/app.ts @metanallok + +# Solana / Futarchy / external data surfaces +/src/services/solanaService.ts @metanallok +/src/services/futarchyService.ts @metanallok +/src/services/launchpadService.ts @metanallok +/src/services/meteoraService.ts @metanallok +/src/services/databaseService.ts @metanallok +/src/services/externalDatabaseService.ts @metanallok + +# Operational scripts +/scripts/safe-update.ts @metanallok +/scripts/backfill.ts @metanallok +/scripts/backfillV06.ts @metanallok diff --git a/.github/branch-protection.md b/.github/branch-protection.md new file mode 100644 index 0000000..be48888 --- /dev/null +++ b/.github/branch-protection.md @@ -0,0 +1,28 @@ +# GitHub protection for `master` + +## What the package-protection check does + +**Merge-blocking:** +- **Lockfile drift** — `bun.lock` must match `package.json`. +- **Socket package scan** — runs through Bun's security scanner on every install. +- **Exact dependency pinning** — package manifests must use exact versions or `workspace:*`. +- **Minimum dependency age** — newly introduced external npm packages must be at least 14 days old. +- **Phantom deps** — imported packages must be declared in `package.json`. + +**Review hint:** +- **Sensitive wallet / program diff** — warns on Solana address literals, program IDs, wallet-routing identifiers, signing paths, and `new PublicKey(` changes. CODEOWNERS is the merge gate for sensitive paths. + +## Recommended branch protection for `master` + +1. Require a pull request before merging. +2. Require at least 1 approval. +3. Dismiss stale approvals when new commits are pushed. +4. Require conversation resolution before merging. +5. Require status checks to pass before merging. +6. Require the `package-protection` job from the `Package Protection` workflow. +7. Require code owner review so `.github/CODEOWNERS` gates sensitive API, Solana, and operational-script paths. + +## Emergency bypass + +- Label: `emergency-override` downgrades repo-guard and phantom-dep failures to warnings. +- Lockfile drift / install failures are never bypassed. diff --git a/.github/workflows/package-protection.yml b/.github/workflows/package-protection.yml new file mode 100644 index 0000000..a43cc70 --- /dev/null +++ b/.github/workflows/package-protection.yml @@ -0,0 +1,179 @@ +name: Package Protection + +on: + pull_request: + +permissions: + contents: read + pull-requests: write + +jobs: + package-protection: + runs-on: ubuntu-latest + env: + EMERGENCY_BYPASS: ${{ contains(github.event.pull_request.labels.*.name, 'emergency-override') }} + + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + fetch-depth: 0 + + - name: Fetch PR base branch + run: git fetch origin "${{ github.base_ref }}" --depth=1 + + - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 + with: + bun-version: 1.3.14 + + - name: Verify lockfile and scan packages + id: lockfile + continue-on-error: true + run: bun install --frozen-lockfile + + - name: Run repository guard checks + id: guard + continue-on-error: true + env: + GITHUB_BASE_REF: ${{ github.base_ref }} + PACKAGE_MIN_AGE_DAYS: 14 + REPO_GUARD_SUMMARY_PATH: repo-guard-summary.md + run: bun run repo:guard + + - name: Check for undeclared dependencies (phantom deps) + id: knip + if: steps.lockfile.outcome == 'success' + continue-on-error: true + run: | + set +e + bun run deps:check > knip-raw.txt 2>&1 + knip_exit=$? + grep -E '^[^:[:space:]]+\.(ts|tsx|js|mjs|cjs|jsx): [^[:space:]]' knip-raw.txt > knip-output.txt || true + echo "--- knip-raw.txt (full) ---" + cat knip-raw.txt + echo "--- knip-output.txt (filtered findings) ---" + cat knip-output.txt + if [ "$knip_exit" -ne 0 ] && [ -s knip-output.txt ]; then + while IFS= read -r line; do + file="${line%%:*}" + pkgs="${line#*: }" + echo "::error file=${file}::Undeclared import(s): ${pkgs}. Declare them in package.json — hoisting works locally but breaks in production Docker." + done < knip-output.txt + fi + exit $knip_exit + + - name: Build PR comment + id: comment + env: + LOCKFILE_OUTCOME: ${{ steps.lockfile.outcome }} + GUARD_OUTCOME: ${{ steps.guard.outcome }} + KNIP_OUTCOME: ${{ steps.knip.outcome }} + run: | + { + echo "" + echo "**Package Protection**" + echo + + if [ "$LOCKFILE_OUTCOME" = "success" ]; then + echo "- Lockfile + Socket scan: pass" + else + echo "- Lockfile + Socket scan: fail" + echo " - Run \\`bun install\\` and commit the updated lockfile." + fi + + if [ "$GUARD_OUTCOME" = "success" ]; then + echo "- Repo guard: pass" + else + if [ "$EMERGENCY_BYPASS" = "true" ]; then + echo "- Repo guard: bypassed with \\`emergency-override\\`" + else + echo "- Repo guard: fail" + fi + fi + + if [ "$KNIP_OUTCOME" = "success" ]; then + echo "- Phantom deps: pass" + elif [ "$KNIP_OUTCOME" = "skipped" ]; then + echo "- Phantom deps: skipped (lockfile check failed — fix that first)" + elif [ -s knip-output.txt ]; then + count=$(wc -l < knip-output.txt | tr -d ' ') + echo "- Phantom deps: fail — ${count} file(s) import packages not declared in package.json." + echo + echo "
Undeclared imports (${count})" + echo + echo '```' + cat knip-output.txt + echo '```' + echo + echo "
" + else + echo "- Phantom deps: fail — check logs for details." + fi + + echo + if [ -f repo-guard-summary.md ]; then + cat repo-guard-summary.md + else + echo "_Repository guard summary was not generated._" + fi + } > body.md + + delimiter="PACKAGE_PROTECTION_BODY_$(date +%s%N)" + echo "body<<${delimiter}" >> "$GITHUB_OUTPUT" + cat body.md >> "$GITHUB_OUTPUT" + echo "${delimiter}" >> "$GITHUB_OUTPUT" + + - name: Find existing comment + uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3 + id: find + with: + issue-number: ${{ github.event.pull_request.number }} + body-includes: "" + + - name: Create or update PR comment + uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4 + with: + issue-number: ${{ github.event.pull_request.number }} + comment-id: ${{ steps.find.outputs.comment-id }} + body: ${{ steps.comment.outputs.body }} + edit-mode: replace + + - name: Fail if any check failed + env: + LOCKFILE_OUTCOME: ${{ steps.lockfile.outcome }} + GUARD_OUTCOME: ${{ steps.guard.outcome }} + KNIP_OUTCOME: ${{ steps.knip.outcome }} + run: | + failed=() + bypassed=() + + if [ "$LOCKFILE_OUTCOME" != "success" ]; then + failed+=("lockfile / Socket scan") + fi + + if [ "$GUARD_OUTCOME" != "success" ]; then + if [ "$EMERGENCY_BYPASS" = "true" ]; then + bypassed+=("repo guard") + else + failed+=("repo guard") + fi + fi + + if [ "$KNIP_OUTCOME" = "failure" ]; then + if [ "$EMERGENCY_BYPASS" = "true" ]; then + bypassed+=("phantom deps") + else + failed+=("phantom deps") + fi + fi + + if [ ${#bypassed[@]} -gt 0 ]; then + echo "::warning::Bypassed via emergency-override label: ${bypassed[*]}" + fi + + if [ ${#failed[@]} -gt 0 ]; then + echo "::error::Package Protection failed:" + for item in "${failed[@]}"; do + echo "::error:: - ${item}" + done + exit 1 + fi diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..cffe8cd --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +save-exact=true diff --git a/bun.lock b/bun.lock index 0cfad57..368b3f0 100644 --- a/bun.lock +++ b/bun.lock @@ -22,12 +22,12 @@ "ts-node": "10.9.2", }, "devDependencies": { - "@types/bun": "latest", + "@socketsecurity/bun-security-scanner": "1.1.2", + "@types/bun": "1.3.3", "@types/pg": "8.20.0", "@types/supertest": "7.2.0", - }, - "peerDependencies": { - "typescript": "^5.9.3", + "knip": "6.3.0", + "typescript": "5.9.3", }, }, }, @@ -46,6 +46,12 @@ "@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="], + "@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], + + "@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], + + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], + "@jest/diff-sequences": ["@jest/diff-sequences@30.0.1", "", {}, "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw=="], "@jest/expect-utils": ["@jest/expect-utils@30.2.0", "", { "dependencies": { "@jest/get-type": "30.1.0" } }, "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA=="], @@ -72,16 +78,108 @@ "@metaplex-foundation/cusper": ["@metaplex-foundation/cusper@0.0.2", "", {}, "sha512-S9RulC2fFCFOQraz61bij+5YCHhSO9llJegK8c8Y6731fSi6snUSQJdCUqYS8AIgR0TKbQvdvgSyIIdbDFZbBA=="], + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="], + "@noble/curves": ["@noble/curves@1.9.7", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw=="], "@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], + + "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], + + "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], + "@oxc-parser/binding-android-arm-eabi": ["@oxc-parser/binding-android-arm-eabi@0.121.0", "", { "os": "android", "cpu": "arm" }, "sha512-n07FQcySwOlzap424/PLMtOkbS7xOu8nsJduKL8P3COGHKgKoDYXwoAHCbChfgFpHnviehrLWIPX0lKGtbEk/A=="], + + "@oxc-parser/binding-android-arm64": ["@oxc-parser/binding-android-arm64@0.121.0", "", { "os": "android", "cpu": "arm64" }, "sha512-/Dd1xIXboYAicw+twT2utxPD7bL8qh7d3ej0qvaYIMj3/EgIrGR+tSnjCUkiCT6g6uTC0neSS4JY8LxhdSU/sA=="], + + "@oxc-parser/binding-darwin-arm64": ["@oxc-parser/binding-darwin-arm64@0.121.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-A0jNEvv7QMtCO1yk205t3DWU9sWUjQ2KNF0hSVO5W9R9r/R1BIvzG01UQAfmtC0dQm7sCrs5puixurKSfr2bRQ=="], + + "@oxc-parser/binding-darwin-x64": ["@oxc-parser/binding-darwin-x64@0.121.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-SsHzipdxTKUs3I9EOAPmnIimEeJOemqRlRDOp9LIj+96wtxZejF51gNibmoGq8KoqbT1ssAI5po/E3J+vEtXGA=="], + + "@oxc-parser/binding-freebsd-x64": ["@oxc-parser/binding-freebsd-x64@0.121.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-v1APOTkCp+RWOIDAHRoaeW/UoaHF15a60E8eUL6kUQXh+i4K7PBwq2Wi7jm8p0ymID5/m/oC1w3W31Z/+r7HQw=="], + + "@oxc-parser/binding-linux-arm-gnueabihf": ["@oxc-parser/binding-linux-arm-gnueabihf@0.121.0", "", { "os": "linux", "cpu": "arm" }, "sha512-PmqPQuqHZyFVWA4ycr0eu4VnTMmq9laOHZd+8R359w6kzuNZPvmmunmNJ8ybkm769A0nCoVp3TJ6dUz7B3FYIQ=="], + + "@oxc-parser/binding-linux-arm-musleabihf": ["@oxc-parser/binding-linux-arm-musleabihf@0.121.0", "", { "os": "linux", "cpu": "arm" }, "sha512-vF24htj+MOH+Q7y9A8NuC6pUZu8t/C2Fr/kDOi2OcNf28oogr2xadBPXAbml802E8wRAVfbta6YLDQTearz+jw=="], + + "@oxc-parser/binding-linux-arm64-gnu": ["@oxc-parser/binding-linux-arm64-gnu@0.121.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-wjH8cIG2Lu/3d64iZpbYr73hREMgKAfu7fqpXjgM2S16y2zhTfDIp8EQjxO8vlDtKP5Rc7waZW72lh8nZtWrpA=="], + + "@oxc-parser/binding-linux-arm64-musl": ["@oxc-parser/binding-linux-arm64-musl@0.121.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-qT663J/W8yQFw3dtscbEi9LKJevr20V7uWs2MPGTnvNZ3rm8anhhE16gXGpxDOHeg9raySaSHKhd4IGa3YZvuw=="], + + "@oxc-parser/binding-linux-ppc64-gnu": ["@oxc-parser/binding-linux-ppc64-gnu@0.121.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-mYNe4NhVvDBbPkAP8JaVS8lC1dsoJZWH5WCjpw5E+sjhk1R08wt3NnXYUzum7tIiWPfgQxbCMcoxgeemFASbRw=="], + + "@oxc-parser/binding-linux-riscv64-gnu": ["@oxc-parser/binding-linux-riscv64-gnu@0.121.0", "", { "os": "linux", "cpu": "none" }, "sha512-+QiFoGxhAbaI/amqX567784cDyyuZIpinBrJNxUzb+/L2aBRX67mN6Jv40pqduHf15yYByI+K5gUEygCuv0z9w=="], + + "@oxc-parser/binding-linux-riscv64-musl": ["@oxc-parser/binding-linux-riscv64-musl@0.121.0", "", { "os": "linux", "cpu": "none" }, "sha512-9ykEgyTa5JD/Uhv2sttbKnCfl2PieUfOjyxJC/oDL2UO0qtXOtjPLl7H8Kaj5G7p3hIvFgu3YWvAxvE0sqY+hQ=="], + + "@oxc-parser/binding-linux-s390x-gnu": ["@oxc-parser/binding-linux-s390x-gnu@0.121.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-DB1EW5VHZdc1lIRjOI3bW/wV6R6y0xlfvdVrqj6kKi7Ayu2U3UqUBdq9KviVkcUGd5Oq+dROqvUEEFRXGAM7EQ=="], + + "@oxc-parser/binding-linux-x64-gnu": ["@oxc-parser/binding-linux-x64-gnu@0.121.0", "", { "os": "linux", "cpu": "x64" }, "sha512-s4lfobX9p4kPTclvMiH3gcQUd88VlnkMTF6n2MTMDAyX5FPNRhhRSFZK05Ykhf8Zy5NibV4PbGR6DnK7FGNN6A=="], + + "@oxc-parser/binding-linux-x64-musl": ["@oxc-parser/binding-linux-x64-musl@0.121.0", "", { "os": "linux", "cpu": "x64" }, "sha512-P9KlyTpuBuMi3NRGpJO8MicuGZfOoqZVRP1WjOecwx8yk4L/+mrCRNc5egSi0byhuReblBF2oVoDSMgV9Bj4Hw=="], + + "@oxc-parser/binding-openharmony-arm64": ["@oxc-parser/binding-openharmony-arm64@0.121.0", "", { "os": "none", "cpu": "arm64" }, "sha512-R+4jrWOfF2OAPPhj3Eb3U5CaKNAH9/btMveMULIrcNW/hjfysFQlF8wE0GaVBr81dWz8JLgQlsxwctoL78JwXw=="], + + "@oxc-parser/binding-wasm32-wasi": ["@oxc-parser/binding-wasm32-wasi@0.121.0", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.1.1" }, "cpu": "none" }, "sha512-5TFISkPTymKvsmIlKasPVTPuWxzCcrT8pM+p77+mtQbIZDd1UC8zww4CJcRI46kolmgrEX6QpKO8AvWMVZ+ifw=="], + + "@oxc-parser/binding-win32-arm64-msvc": ["@oxc-parser/binding-win32-arm64-msvc@0.121.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-V0pxh4mql4XTt3aiEtRNUeBAUFOw5jzZNxPABLaOKAWrVzSr9+XUaB095lY7jqMf5t8vkfh8NManGB28zanYKw=="], + + "@oxc-parser/binding-win32-ia32-msvc": ["@oxc-parser/binding-win32-ia32-msvc@0.121.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-4Ob1qvYMPnlF2N9rdmKdkQFdrq16QVcQwBsO8yiPZXof0fHKFF+LmQV501XFbi7lHyrKm8rlJRfQ/M8bZZPVLw=="], + + "@oxc-parser/binding-win32-x64-msvc": ["@oxc-parser/binding-win32-x64-msvc@0.121.0", "", { "os": "win32", "cpu": "x64" }, "sha512-BOp1KCzdboB1tPqoCPXgntgFs0jjeSyOXHzgxVFR7B/qfr3F8r4YDacHkTOUNXtDgM8YwKnkf3rE5gwALYX7NA=="], + + "@oxc-project/types": ["@oxc-project/types@0.121.0", "", {}, "sha512-CGtOARQb9tyv7ECgdAlFxi0Fv7lmzvmlm2rpD/RdijOO9rfk/JvB1CjT8EnoD+tjna/IYgKKw3IV7objRb+aYw=="], + + "@oxc-resolver/binding-android-arm-eabi": ["@oxc-resolver/binding-android-arm-eabi@11.19.1", "", { "os": "android", "cpu": "arm" }, "sha512-aUs47y+xyXHUKlbhqHUjBABjvycq6YSD7bpxSW7vplUmdzAlJ93yXY6ZR0c1o1x5A/QKbENCvs3+NlY8IpIVzg=="], + + "@oxc-resolver/binding-android-arm64": ["@oxc-resolver/binding-android-arm64@11.19.1", "", { "os": "android", "cpu": "arm64" }, "sha512-oolbkRX+m7Pq2LNjr/kKgYeC7bRDMVTWPgxBGMjSpZi/+UskVo4jsMU3MLheZV55jL6c3rNelPl4oD60ggYmqA=="], + + "@oxc-resolver/binding-darwin-arm64": ["@oxc-resolver/binding-darwin-arm64@11.19.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-nUC6d2i3R5B12sUW4O646qD5cnMXf2oBGPLIIeaRfU9doJRORAbE2SGv4eW6rMqhD+G7nf2Y8TTJTLiiO3Q/dQ=="], + + "@oxc-resolver/binding-darwin-x64": ["@oxc-resolver/binding-darwin-x64@11.19.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-cV50vE5+uAgNcFa3QY1JOeKDSkM/9ReIcc/9wn4TavhW/itkDGrXhw9jaKnkQnGbjJ198Yh5nbX/Gr2mr4Z5jQ=="], + + "@oxc-resolver/binding-freebsd-x64": ["@oxc-resolver/binding-freebsd-x64@11.19.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-xZOQiYGFxtk48PBKff+Zwoym7ScPAIVp4c14lfLxizO2LTTTJe5sx9vQNGrBymrf/vatSPNMD4FgsaaRigPkqw=="], + + "@oxc-resolver/binding-linux-arm-gnueabihf": ["@oxc-resolver/binding-linux-arm-gnueabihf@11.19.1", "", { "os": "linux", "cpu": "arm" }, "sha512-lXZYWAC6kaGe/ky2su94e9jN9t6M0/6c+GrSlCqL//XO1cxi5lpAhnJYdyrKfm0ZEr/c7RNyAx3P7FSBcBd5+A=="], + + "@oxc-resolver/binding-linux-arm-musleabihf": ["@oxc-resolver/binding-linux-arm-musleabihf@11.19.1", "", { "os": "linux", "cpu": "arm" }, "sha512-veG1kKsuK5+t2IsO9q0DErYVSw2azvCVvWHnfTOS73WE0STdLLB7Q1bB9WR+yHPQM76ASkFyRbogWo1GR1+WbQ=="], + + "@oxc-resolver/binding-linux-arm64-gnu": ["@oxc-resolver/binding-linux-arm64-gnu@11.19.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-heV2+jmXyYnUrpUXSPugqWDRpnsQcDm2AX4wzTuvgdlZfoNYO0O3W2AVpJYaDn9AG4JdM6Kxom8+foE7/BcSig=="], + + "@oxc-resolver/binding-linux-arm64-musl": ["@oxc-resolver/binding-linux-arm64-musl@11.19.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-jvo2Pjs1c9KPxMuMPIeQsgu0mOJF9rEb3y3TdpsrqwxRM+AN6/nDDwv45n5ZrUnQMsdBy5gIabioMKnQfWo9ew=="], + + "@oxc-resolver/binding-linux-ppc64-gnu": ["@oxc-resolver/binding-linux-ppc64-gnu@11.19.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-vLmdNxWCdN7Uo5suays6A/+ywBby2PWBBPXctWPg5V0+eVuzsJxgAn6MMB4mPlshskYbppjpN2Zg83ArHze9gQ=="], + + "@oxc-resolver/binding-linux-riscv64-gnu": ["@oxc-resolver/binding-linux-riscv64-gnu@11.19.1", "", { "os": "linux", "cpu": "none" }, "sha512-/b+WgR+VTSBxzgOhDO7TlMXC1ufPIMR6Vj1zN+/x+MnyXGW7prTLzU9eW85Aj7Th7CCEG9ArCbTeqxCzFWdg2w=="], + + "@oxc-resolver/binding-linux-riscv64-musl": ["@oxc-resolver/binding-linux-riscv64-musl@11.19.1", "", { "os": "linux", "cpu": "none" }, "sha512-YlRdeWb9j42p29ROh+h4eg/OQ3dTJlpHSa+84pUM9+p6i3djtPz1q55yLJhgW9XfDch7FN1pQ/Vd6YP+xfRIuw=="], + + "@oxc-resolver/binding-linux-s390x-gnu": ["@oxc-resolver/binding-linux-s390x-gnu@11.19.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-EDpafVOQWF8/MJynsjOGFThcqhRHy417sRyLfQmeiamJ8qVhSKAn2Dn2VVKUGCjVB9C46VGjhNo7nOPUi1x6uA=="], + + "@oxc-resolver/binding-linux-x64-gnu": ["@oxc-resolver/binding-linux-x64-gnu@11.19.1", "", { "os": "linux", "cpu": "x64" }, "sha512-NxjZe+rqWhr+RT8/Ik+5ptA3oz7tUw361Wa5RWQXKnfqwSSHdHyrw6IdcTfYuml9dM856AlKWZIUXDmA9kkiBQ=="], + + "@oxc-resolver/binding-linux-x64-musl": ["@oxc-resolver/binding-linux-x64-musl@11.19.1", "", { "os": "linux", "cpu": "x64" }, "sha512-cM/hQwsO3ReJg5kR+SpI69DMfvNCp+A/eVR4b4YClE5bVZwz8rh2Nh05InhwI5HR/9cArbEkzMjcKgTHS6UaNw=="], + + "@oxc-resolver/binding-openharmony-arm64": ["@oxc-resolver/binding-openharmony-arm64@11.19.1", "", { "os": "none", "cpu": "arm64" }, "sha512-QF080IowFB0+9Rh6RcD19bdgh49BpQHUW5TajG1qvWHvmrQznTZZjYlgE2ltLXyKY+qs4F/v5xuX1XS7Is+3qA=="], + + "@oxc-resolver/binding-wasm32-wasi": ["@oxc-resolver/binding-wasm32-wasi@11.19.1", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.1.1" }, "cpu": "none" }, "sha512-w8UCKhX826cP/ZLokXDS6+milN8y4X7zidsAttEdWlVoamTNf6lhBJldaWr3ukTDiye7s4HRcuPEPOXNC432Vg=="], + + "@oxc-resolver/binding-win32-arm64-msvc": ["@oxc-resolver/binding-win32-arm64-msvc@11.19.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-nJ4AsUVZrVKwnU/QRdzPCCrO0TrabBqgJ8pJhXITdZGYOV28TIYystV1VFLbQ7DtAcaBHpocT5/ZJnF78YJPtQ=="], + + "@oxc-resolver/binding-win32-ia32-msvc": ["@oxc-resolver/binding-win32-ia32-msvc@11.19.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-EW+ND5q2Tl+a3pH81l1QbfgbF3HmqgwLfDfVithRFheac8OTcnbXt/JxqD2GbDkb7xYEqy1zNaVFRr3oeG8npA=="], + + "@oxc-resolver/binding-win32-x64-msvc": ["@oxc-resolver/binding-win32-x64-msvc@11.19.1", "", { "os": "win32", "cpu": "x64" }, "sha512-6hIU3RQu45B+VNTY4Ru8ppFwjVS/S5qwYyGhBotmjxfEKk41I2DlGtRfGJndZ5+6lneE2pwloqunlOyZuX/XAw=="], + "@paralleldrive/cuid2": ["@paralleldrive/cuid2@2.3.1", "", { "dependencies": { "@noble/hashes": "^1.1.5" } }, "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw=="], "@sinclair/typebox": ["@sinclair/typebox@0.34.41", "", {}, "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g=="], + "@socketsecurity/bun-security-scanner": ["@socketsecurity/bun-security-scanner@1.1.2", "", {}, "sha512-TdsAg6SMolubyZ6HfIjLWlANfHvhV6i7pdWof4OQ33zPEwXJm2ilA755levHMR618MKq22+06Ag8efiVKowxqA=="], + "@solana/buffer-layout": ["@solana/buffer-layout@4.0.1", "", { "dependencies": { "buffer": "~6.0.3" } }, "sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA=="], "@solana/buffer-layout-utils": ["@solana/buffer-layout-utils@0.2.0", "", { "dependencies": { "@solana/buffer-layout": "^4.0.0", "@solana/web3.js": "^1.32.0", "bigint-buffer": "^1.1.5", "bignumber.js": "^9.0.1" } }, "sha512-szG4sxgJGktbuZYDg2FfNmkMi0DYQoVjN2h7ta1W1hPrwzarcFLBq9UpX1UjNXsNpT9dn+chgprtWGioUAr4/g=="], @@ -120,6 +218,8 @@ "@tsconfig/node16": ["@tsconfig/node16@1.0.4", "", {}, "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA=="], + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg=="], + "@types/bn.js": ["@types/bn.js@5.2.0", "", { "dependencies": { "@types/node": "*" } }, "sha512-DLbJ1BPqxvQhIGbeu8VbUC1DiAiahHtAYvA0ZEAa4P31F7IaArc8z3C3BRQdWX4mtLQuABG4yzp76ZrS02Ui1Q=="], "@types/body-parser": ["@types/body-parser@1.19.6", "", { "dependencies": { "@types/connect": "*", "@types/node": "*" } }, "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g=="], @@ -326,12 +426,18 @@ "eyes": ["eyes@0.1.8", "", {}, "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ=="], + "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], + "fast-safe-stringify": ["fast-safe-stringify@2.1.1", "", {}, "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA=="], "fast-stable-stringify": ["fast-stable-stringify@1.0.0", "", {}, "sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag=="], "fastestsmallesttextencoderdecoder": ["fastestsmallesttextencoderdecoder@1.0.22", "", {}, "sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw=="], + "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], + + "fd-package-json": ["fd-package-json@2.0.0", "", { "dependencies": { "walk-up-path": "^4.0.0" } }, "sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ=="], + "file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="], "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], @@ -342,6 +448,8 @@ "form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="], + "formatly": ["formatly@0.3.0", "", { "dependencies": { "fd-package-json": "^2.0.0" }, "bin": { "formatly": "bin/index.mjs" } }, "sha512-9XNj/o4wrRFyhSMJOvsuyMwy8aUfBaZ1VrqHVfohyXf0Sw0e+yfKG+xZaY3arGCOMdwFsqObtzVOc1gU9KiT9w=="], + "formidable": ["formidable@3.5.4", "", { "dependencies": { "@paralleldrive/cuid2": "^2.2.2", "dezalgo": "^1.0.4", "once": "^1.4.0" } }, "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug=="], "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], @@ -358,6 +466,8 @@ "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + "get-tsconfig": ["get-tsconfig@4.13.7", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q=="], + "glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], @@ -428,10 +538,14 @@ "jest-util": ["jest-util@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", "graceful-fs": "^4.2.11", "picomatch": "^4.0.2" } }, "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA=="], + "jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="], + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], "json-stringify-safe": ["json-stringify-safe@5.0.1", "", {}, "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="], + "knip": ["knip@6.3.0", "", { "dependencies": { "@nodelib/fs.walk": "^1.2.3", "fast-glob": "^3.3.3", "formatly": "^0.3.0", "get-tsconfig": "4.13.7", "jiti": "^2.6.0", "minimist": "^1.2.8", "oxc-parser": "^0.121.0", "oxc-resolver": "^11.19.1", "picocolors": "^1.1.1", "picomatch": "^4.0.1", "smol-toml": "^1.6.1", "strip-json-comments": "5.0.3", "unbash": "^2.2.0", "yaml": "^2.8.2", "zod": "^4.1.11" }, "bin": { "knip": "bin/knip.js", "knip-bun": "bin/knip-bun.js" } }, "sha512-g6dVPoTw6iNm3cubC5IWxVkVsd0r5hXhTBTbAGIEQN53GdA2ZM/slMTPJ7n5l8pBebNQPHpxjmKxuR4xVQ2/hQ=="], + "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], "lower-case": ["lower-case@2.0.2", "", { "dependencies": { "tslib": "^2.0.3" } }, "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg=="], @@ -444,6 +558,8 @@ "merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="], + "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], + "methods": ["methods@1.1.2", "", {}, "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w=="], "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], @@ -456,6 +572,8 @@ "minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], + "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], @@ -482,6 +600,10 @@ "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + "oxc-parser": ["oxc-parser@0.121.0", "", { "dependencies": { "@oxc-project/types": "^0.121.0" }, "optionalDependencies": { "@oxc-parser/binding-android-arm-eabi": "0.121.0", "@oxc-parser/binding-android-arm64": "0.121.0", "@oxc-parser/binding-darwin-arm64": "0.121.0", "@oxc-parser/binding-darwin-x64": "0.121.0", "@oxc-parser/binding-freebsd-x64": "0.121.0", "@oxc-parser/binding-linux-arm-gnueabihf": "0.121.0", "@oxc-parser/binding-linux-arm-musleabihf": "0.121.0", "@oxc-parser/binding-linux-arm64-gnu": "0.121.0", "@oxc-parser/binding-linux-arm64-musl": "0.121.0", "@oxc-parser/binding-linux-ppc64-gnu": "0.121.0", "@oxc-parser/binding-linux-riscv64-gnu": "0.121.0", "@oxc-parser/binding-linux-riscv64-musl": "0.121.0", "@oxc-parser/binding-linux-s390x-gnu": "0.121.0", "@oxc-parser/binding-linux-x64-gnu": "0.121.0", "@oxc-parser/binding-linux-x64-musl": "0.121.0", "@oxc-parser/binding-openharmony-arm64": "0.121.0", "@oxc-parser/binding-wasm32-wasi": "0.121.0", "@oxc-parser/binding-win32-arm64-msvc": "0.121.0", "@oxc-parser/binding-win32-ia32-msvc": "0.121.0", "@oxc-parser/binding-win32-x64-msvc": "0.121.0" } }, "sha512-ek9o58+SCv6AV7nchiAcUJy1DNE2CC5WRdBcO0mF+W4oRjNQfPO7b3pLjTHSFECpHkKGOZSQxx3hk8viIL5YCg=="], + + "oxc-resolver": ["oxc-resolver@11.19.1", "", { "optionalDependencies": { "@oxc-resolver/binding-android-arm-eabi": "11.19.1", "@oxc-resolver/binding-android-arm64": "11.19.1", "@oxc-resolver/binding-darwin-arm64": "11.19.1", "@oxc-resolver/binding-darwin-x64": "11.19.1", "@oxc-resolver/binding-freebsd-x64": "11.19.1", "@oxc-resolver/binding-linux-arm-gnueabihf": "11.19.1", "@oxc-resolver/binding-linux-arm-musleabihf": "11.19.1", "@oxc-resolver/binding-linux-arm64-gnu": "11.19.1", "@oxc-resolver/binding-linux-arm64-musl": "11.19.1", "@oxc-resolver/binding-linux-ppc64-gnu": "11.19.1", "@oxc-resolver/binding-linux-riscv64-gnu": "11.19.1", "@oxc-resolver/binding-linux-riscv64-musl": "11.19.1", "@oxc-resolver/binding-linux-s390x-gnu": "11.19.1", "@oxc-resolver/binding-linux-x64-gnu": "11.19.1", "@oxc-resolver/binding-linux-x64-musl": "11.19.1", "@oxc-resolver/binding-openharmony-arm64": "11.19.1", "@oxc-resolver/binding-wasm32-wasi": "11.19.1", "@oxc-resolver/binding-win32-arm64-msvc": "11.19.1", "@oxc-resolver/binding-win32-ia32-msvc": "11.19.1", "@oxc-resolver/binding-win32-x64-msvc": "11.19.1" } }, "sha512-qE/CIg/spwrTBFt5aKmwe3ifeDdLfA2NESN30E42X/lII5ClF8V7Wt6WIJhcGZjp0/Q+nQ+9vgxGk//xZNX2hg=="], + "pako": ["pako@2.1.0", "", {}, "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug=="], "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], @@ -528,6 +650,8 @@ "qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="], + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="], "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=="], @@ -536,10 +660,16 @@ "readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], + + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + "router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="], "rpc-websockets": ["rpc-websockets@9.3.1", "", { "dependencies": { "@swc/helpers": "^0.5.11", "@types/uuid": "^8.3.4", "@types/ws": "^8.2.2", "buffer": "^6.0.3", "eventemitter3": "^5.0.1", "uuid": "^8.3.2", "ws": "^8.5.0" }, "optionalDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" } }, "sha512-bY6a+i/lEtBJ/mUxwsCTgevoV1P0foXTVA7UoThzaIWbM+3NDqorf8NBWs5DmqKTFeA1IoNzgvkWjFCPgnzUiQ=="], + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], "safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="], @@ -568,6 +698,8 @@ "slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], + "smol-toml": ["smol-toml@1.6.1", "", {}, "sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg=="], + "snake-case": ["snake-case@3.0.4", "", { "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg=="], "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], @@ -580,6 +712,8 @@ "stream-json": ["stream-json@1.9.1", "", { "dependencies": { "stream-chain": "^2.2.5" } }, "sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw=="], + "strip-json-comments": ["strip-json-comments@5.0.3", "", {}, "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw=="], + "superagent": ["superagent@10.3.0", "", { "dependencies": { "component-emitter": "^1.3.1", "cookiejar": "^2.1.4", "debug": "^4.3.7", "fast-safe-stringify": "^2.1.1", "form-data": "^4.0.5", "formidable": "^3.5.4", "methods": "^1.1.2", "mime": "2.6.0", "qs": "^6.14.1" } }, "sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ=="], "superstruct": ["superstruct@0.15.5", "", {}, "sha512-4AOeU+P5UuE/4nOUkmcQdW5y7i9ndt1cQd/3iUe+LTz3RxESf/W/5lg4B74HbDMMv8PHnPnGCQFH45kBcrQYoQ=="], @@ -610,6 +744,8 @@ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + "unbash": ["unbash@2.2.0", "", {}, "sha512-X2wH19RAPZE3+ldGicOkoj/SIA83OIxcJ6Cuaw23hf8Xc6fQpvZXY0SftE2JgS0QhYLUG4uwodSI3R53keyh7w=="], + "undefsafe": ["undefsafe@2.0.5", "", {}, "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA=="], "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], @@ -626,6 +762,8 @@ "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], + "walk-up-path": ["walk-up-path@4.0.0", "", {}, "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A=="], + "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], @@ -638,8 +776,12 @@ "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="], + "yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="], + "yn": ["yn@3.1.1", "", {}, "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q=="], + "zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], + "@jest/pattern/@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="], "@jest/types/@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="], diff --git a/bunfig.toml b/bunfig.toml new file mode 100644 index 0000000..6a9829b --- /dev/null +++ b/bunfig.toml @@ -0,0 +1,10 @@ +[install] +exact = true + +[install.security] +# Socket security scanner hooks into `bun install` via Bun's security plugin +# API. Every install (dev + CI) scans direct and transitive deps against +# Socket's threat database and blocks malicious packages. +# Works in free mode without an API key. Set SOCKET_API_KEY for enhanced +# scanning tied to the Socket org (packages scope). +scanner = "@socketsecurity/bun-security-scanner" diff --git a/package.json b/package.json index 1413a68..579c37e 100644 --- a/package.json +++ b/package.json @@ -4,11 +4,6 @@ "type": "module", "private": true, "version": "2.0.0", - "devDependencies": { - "@types/bun": "latest", - "@types/pg": "8.20.0", - "@types/supertest": "7.2.0" - }, "scripts": { "dev": "bun --watch src/main.ts", "build": "tsc", @@ -23,10 +18,9 @@ "safe-update": "bun run scripts/safe-update.ts", "safe-update:dry": "bun run scripts/safe-update.ts --dry-run", "compare:dune-vs-v06": "bun run scripts/compareDuneVsV06.ts", - "typecheck": "tsc --noEmit" - }, - "peerDependencies": { - "typescript": "^5.9.3" + "typecheck": "tsc --noEmit", + "repo:guard": "bun scripts/repo-guard.ts", + "deps:check": "knip --include unlisted --reporter compact" }, "dependencies": { "@coral-xyz/anchor": "0.32.1", @@ -44,5 +38,17 @@ "prom-client": "15.1.3", "supertest": "7.2.2", "ts-node": "10.9.2" - } + }, + "devDependencies": { + "@types/bun": "1.3.3", + "@types/pg": "8.20.0", + "@types/supertest": "7.2.0", + "@socketsecurity/bun-security-scanner": "1.1.2", + "knip": "6.3.0", + "typescript": "5.9.3" + }, + "installConfig": { + "exact": true + }, + "packageManager": "bun@1.3.14" } diff --git a/scripts/repo-guard.ts b/scripts/repo-guard.ts new file mode 100644 index 0000000..fc271fa --- /dev/null +++ b/scripts/repo-guard.ts @@ -0,0 +1,676 @@ +import { execFileSync } from "node:child_process"; +import { readdirSync, readFileSync, writeFileSync } from "node:fs"; +import { join, relative } from "node:path"; + +type CheckStatus = "pass" | "fail" | "skip" | "warn"; + +type PackageViolation = { + file: string; + section: string; + dependency: string; + version: string; +}; + +type PackageAgeViolation = { + dependency: string; + version: string; + publishedAt: string; + ageDays: number; + usedIn: string[]; +}; + +type PackageAgeResult = + | { status: "pass"; violations: PackageAgeViolation[] } + | { status: "fail"; violations: PackageAgeViolation[] } + | { status: "skip"; violations: PackageAgeViolation[]; reason: string }; + +type SensitiveFinding = { + file: string; + line: number; + kind: string; + text: string; +}; + +const ROOT = process.cwd(); +const SUMMARY_PATH = + process.env.REPO_GUARD_SUMMARY_PATH ?? + join(process.env.TMPDIR ?? "/tmp", "repo-guard-summary.md"); +const BASE_REF = process.env.GITHUB_BASE_REF ?? ""; +const IS_CI = process.env.CI === "true"; +const PACKAGE_MIN_AGE_DAYS = Number.parseInt( + process.env.PACKAGE_MIN_AGE_DAYS ?? "14", + 10 +); + +const dependencySections = [ + "dependencies", + "devDependencies", + "optionalDependencies", + "peerDependencies", + "overrides", +] as const; + +const ignoredDirectories = new Set([ + ".git", + ".next", + "dist", + "node_modules", + "coverage", +]); + +const exactVersionPattern = + /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?(?:\+[0-9A-Za-z-.]+)?$/; + +const sensitiveFiles = [ + "src/config.ts", + "src/main.ts", + "src/app.ts", + "src/services/solanaService.ts", + "src/services/futarchyService.ts", + "src/services/launchpadService.ts", + "src/services/meteoraService.ts", + "src/services/databaseService.ts", + "src/services/externalDatabaseService.ts", + "scripts/safe-update.ts", + "scripts/backfill.ts", + "scripts/backfillV06.ts", +]; + +// Files whose purpose is to define the guard rules themselves, or to document +// them. Their diffs trivially match the heuristics and create noise without +// signal. +const excludedFromSensitiveDiff = new Set([ + "scripts/repo-guard.ts", + ".github/CODEOWNERS", + ".github/CODEOWNERS.example", + ".github/branch-protection.md", + ".github/workflows/package-protection.yml", +]); + +const suspiciousContentRules: Array<{ kind: string; pattern: RegExp }> = [ + { + kind: "Hardcoded Solana address or program literal", + pattern: /["'`](?:[1-9A-HJ-NP-Za-km-z]{32,44})["'`]/, + }, + { + kind: "Program ID constant or variable change", + pattern: /\b[A-Z0-9_]*PROGRAM_ID\b|\bprogramId\b/, + }, + { + kind: "Wallet or destination routing change", + pattern: + /\b(?:PAYMENT_DESTINATION_ADDRESS|WALLET|TREASURY|RECIPIENT|DESTINATION|AUTHORITY)\b/i, + }, + { + kind: "Signing or transaction submission path change", + pattern: + /\b(?:signTransaction|signAllTransactions|sendTransaction|verifyPaymentTransaction)\b/, + }, + { + kind: "Public key construction change", + pattern: /\bnew PublicKey\s*\(/, + }, +]; + +function run(command: string, args: string[]): string { + return execFileSync(command, args, { + cwd: ROOT, + encoding: "utf8", + }).trim(); +} + +function walk(dir: string, acc: string[]): string[] { + for (const entry of readdirSync(dir, { withFileTypes: true })) { + if (ignoredDirectories.has(entry.name)) { + continue; + } + + const fullPath = join(dir, entry.name); + if (entry.isDirectory()) { + walk(fullPath, acc); + continue; + } + + if (entry.isFile() && entry.name === "package.json") { + acc.push(relative(ROOT, fullPath)); + } + } + + return acc; +} + +function readJsonAtRef( + ref: string, + filePath: string +): Record | null { + try { + const content = execFileSync("git", ["show", `${ref}:${filePath}`], { + cwd: ROOT, + encoding: "utf8", + stdio: ["ignore", "pipe", "ignore"], + }); + return JSON.parse(content) as Record; + } catch { + return null; + } +} + +// Scopes the exact-dependency map down to entries whose version differs from +// the PR base. Prevents every PR from tripping the age gate for ~14 days after +// an unrelated dep bump lands on the base branch. +function filterExactDependenciesToChanges( + all: Map>, + diffBase: string +): Map> { + const locationPattern = /^(.+) \((\w+)\)$/; + const changed = new Map>(); + + for (const [key, locations] of all.entries()) { + const atIndex = key.lastIndexOf("@"); + const dependency = key.slice(0, atIndex); + const headVersion = key.slice(atIndex + 1); + + for (const location of locations) { + const match = locationPattern.exec(location); + if (!match) { + continue; + } + const file = match[1]!; + const section = match[2]!; + + const basePkg = readJsonAtRef(diffBase, file); + const baseSection = + basePkg && typeof basePkg[section] === "object" && basePkg[section] !== null + ? (basePkg[section] as Record) + : null; + const baseVersion = baseSection?.[dependency]; + + if (baseVersion === headVersion) { + continue; + } + + const set = changed.get(key) ?? new Set(); + set.add(location); + changed.set(key, set); + } + } + + return changed; +} + +function isAllowedDependencyVersion(version: string): boolean { + if (exactVersionPattern.test(version)) { + return true; + } + + if (version === "workspace:*") { + return true; + } + + return false; +} + +function collectPackageDependencyData(): { + violations: PackageViolation[]; + exactDependencies: Map>; +} { + const packageFiles = walk(ROOT, []).sort(); + const violations: PackageViolation[] = []; + const exactDependencies = new Map>(); + + for (const packageFile of packageFiles) { + const packageJson = JSON.parse(readFileSync(join(ROOT, packageFile), "utf8")); + + for (const section of dependencySections) { + const dependencies = packageJson[section]; + if (!dependencies || typeof dependencies !== "object") { + continue; + } + + for (const [dependency, version] of Object.entries(dependencies)) { + if (typeof version !== "string") { + continue; + } + + if (isAllowedDependencyVersion(version)) { + if (version !== "workspace:*") { + const key = `${dependency}@${version}`; + const locations = exactDependencies.get(key) ?? new Set(); + locations.add(`${packageFile} (${section})`); + exactDependencies.set(key, locations); + } + continue; + } + + violations.push({ + file: packageFile, + section, + dependency, + version, + }); + } + } + } + + return { violations, exactDependencies }; +} + +async function fetchPackagePublishTime( + dependency: string, + version: string +): Promise { + // npm accepts fully encoded scoped names, but keep the canonical scoped + // package shape (`@scope%2fname`) to avoid registry/proxy compatibility + // issues with `%40scope%2Fname`. + const registryName = dependency.startsWith("@") + ? dependency.replace("/", "%2F") + : encodeURIComponent(dependency); + + const response = await fetch( + `https://registry.npmjs.org/${registryName}`, + { + headers: { + Accept: "application/json", + }, + } + ); + + if (!response.ok) { + throw new Error(`npm registry returned ${response.status} for ${dependency}`); + } + + const packageMeta = (await response.json()) as { + time?: Record; + }; + + const publishedAt = packageMeta.time?.[version]; + if (!publishedAt) { + throw new Error(`publish time not found for ${dependency}@${version}`); + } + + return publishedAt; +} + +async function checkPackageMinimumAge( + exactDependencies: Map> +): Promise { + const violations: PackageAgeViolation[] = []; + const now = Date.now(); + + for (const [key, usedIn] of [...exactDependencies.entries()].sort()) { + const atIndex = key.lastIndexOf("@"); + const dependency = key.slice(0, atIndex); + const version = key.slice(atIndex + 1); + const publishedAt = await fetchPackagePublishTime(dependency, version); + const ageDays = Math.floor( + (now - new Date(publishedAt).getTime()) / (1000 * 60 * 60 * 24) + ); + + if (ageDays >= PACKAGE_MIN_AGE_DAYS) { + continue; + } + + violations.push({ + dependency, + version, + publishedAt, + ageDays, + usedIn: [...usedIn].sort(), + }); + } + + return violations; +} + + +function getDiffBase(): string | null { + if (!BASE_REF) { + return null; + } + + return run("git", ["merge-base", "HEAD", `origin/${BASE_REF}`]); +} + +function collectChangedFiles(diffBase: string): Set { + const output = run("git", ["diff", "--name-only", `${diffBase}...HEAD`]); + return new Set(output.split("\n").filter(Boolean)); +} + +function checkSensitiveDiff(): + | { status: "skip"; findings: SensitiveFinding[]; touchedSensitiveFiles: string[] } + | { status: "pass" | "fail" | "warn"; findings: SensitiveFinding[]; touchedSensitiveFiles: string[] } { + const diffBase = getDiffBase(); + if (!diffBase) { + return { + status: "skip", + findings: [], + touchedSensitiveFiles: [], + }; + } + + const changedFiles = collectChangedFiles(diffBase); + const touchedSensitiveFiles = sensitiveFiles.filter((file) => changedFiles.has(file)); + + const diff = run("git", [ + "diff", + "--unified=0", + "--no-color", + `${diffBase}...HEAD`, + ]); + + const findings: SensitiveFinding[] = []; + let currentFile = ""; + let nextLineNumber = 0; + + for (const rawLine of diff.split("\n")) { + if (rawLine.startsWith("+++ b/")) { + currentFile = rawLine.slice(6); + continue; + } + + if (rawLine.startsWith("--- ")) { + continue; + } + + if (rawLine.startsWith("@@")) { + const match = rawLine.match(/\+(\d+)(?:,(\d+))?/); + nextLineNumber = match ? Number.parseInt(match[1]!, 10) : 0; + continue; + } + + if (excludedFromSensitiveDiff.has(currentFile)) { + continue; + } + + // Documentation (Markdown, plain text, READMEs) naturally contains words + // like "wallet", "authority", or example Solana addresses; the heuristics + // are intended to catch changes to *code*, not docs. Skip prose files. + if (/\.(md|mdx|txt|rst)$/i.test(currentFile)) { + continue; + } + + if (!rawLine.startsWith("+") || rawLine.startsWith("+++")) { + if (rawLine.startsWith("-") && !rawLine.startsWith("---")) { + continue; + } + + if (!rawLine.startsWith("-")) { + continue; + } + } + + const sign = rawLine[0]; + const lineText = rawLine.slice(1); + if (!currentFile || !lineText.trim()) { + if (sign === "+") { + nextLineNumber += 1; + } + continue; + } + + const matchedKinds = suspiciousContentRules + .filter((rule) => rule.pattern.test(lineText)) + .map((rule) => rule.kind); + + if (matchedKinds.length > 0) { + findings.push({ + file: currentFile, + line: nextLineNumber, + kind: matchedKinds.join("; "), + text: `${sign}${lineText}`.trim(), + }); + } + + if (sign === "+") { + nextLineNumber += 1; + } + } + + if (findings.length === 0 && touchedSensitiveFiles.length === 0) { + return { + status: "pass", + findings, + touchedSensitiveFiles, + }; + } + + // Sensitive-diff is a review-assist, not a merge gate. Merge-blocking for + // sensitive paths is handled by CODEOWNERS + branch protection (the review + // required by @metanallok on files listed in .github/CODEOWNERS). This + // check surfaces the findings for reviewer visibility — in the PR comment + // and as GitHub annotations — without failing CI. + return { + status: "warn", + findings, + touchedSensitiveFiles, + }; +} + +function renderPackageSection(violations: PackageViolation[]): string[] { + if (violations.length === 0) { + return [ + "### Exact dependency versions", + "", + "- Status: pass", + "- All `dependencies`, `devDependencies`, `optionalDependencies`, and `peerDependencies` use exact versions or `workspace:*`.", + ]; + } + + return [ + "### Exact dependency versions", + "", + "- Status: fail", + "- The following dependency specs are not exact:", + ...violations.map( + (violation) => + `- \`${violation.file}\` -> \`${violation.section}.${violation.dependency}\` uses \`${violation.version}\`` + ), + ]; +} + +function renderPackageAgeSection(violations: PackageAgeViolation[]): string[] { + if (violations.length === 0) { + return [ + "### Package minimum age", + "", + `- Status: pass`, + `- All pinned external packages are at least ${PACKAGE_MIN_AGE_DAYS} days old.`, + ]; + } + + return [ + "### Package minimum age", + "", + "- Status: fail", + `- The following packages are newer than ${PACKAGE_MIN_AGE_DAYS} days:`, + ...violations.map( + (violation) => + `- \`${violation.dependency}@${violation.version}\` is ${violation.ageDays} days old (published ${violation.publishedAt}) and is used in ${violation.usedIn + .map((item) => `\`${item}\``) + .join(", ")}` + ), + ]; +} + +function renderPackageAgeResult(result: PackageAgeResult): string[] { + if (result.status === "skip") { + return [ + "### Package minimum age", + "", + "- Status: skipped", + `- The npm registry age check could not run in this environment: ${result.reason}`, + ]; + } + + return renderPackageAgeSection(result.violations); +} + + +function renderSensitiveSection( + result: ReturnType +): string[] { + if (result.status === "skip") { + return [ + "### Sensitive wallet / program changes", + "", + "- Status: skipped", + "- No PR base ref was available, so diff-based security checks did not run.", + ]; + } + + const heading = [ + "### Sensitive wallet / program changes", + "", + `- Status: ${result.status}`, + ]; + + if (result.status === "pass") { + return [ + ...heading, + "- No suspicious wallet routing, signing-path, or program-ID changes were detected in this PR diff.", + ]; + } + + const lines = [...heading]; + + lines.push( + "- Suspicious wallet routing, signing-path, or program-ID changes detected. This is a review hint, not a merge gate — CODEOWNERS + branch protection enforce the actual review requirement. Please take a closer look at the lines below." + ); + + if (result.touchedSensitiveFiles.length > 0) { + lines.push( + `- High-sensitivity files touched: ${result.touchedSensitiveFiles + .map((file) => `\`${file}\``) + .join(", ")}` + ); + } + + if (result.findings.length > 0) { + lines.push("- Matching diff lines:"); + lines.push( + ...result.findings.slice(0, 20).map( + (finding) => + `- \`${finding.file}:${finding.line}\` ${finding.kind} -> \`${finding.text}\`` + ) + ); + } else if (result.touchedSensitiveFiles.length > 0) { + lines.push("- No single diff line matched the heuristics, but a known high-sensitivity file was modified."); + } + + return lines; +} + +async function main() { + const { violations: packageViolations, exactDependencies } = + collectPackageDependencyData(); + let packageAgeResult: PackageAgeResult; + + const diffBase = getDiffBase(); + + if (!diffBase) { + packageAgeResult = { + status: "skip", + violations: [], + reason: + "no PR base ref (GITHUB_BASE_REF) was available, so the age check could not be scoped to PR-introduced changes.", + }; + } else { + try { + const changedExactDependencies = filterExactDependenciesToChanges( + exactDependencies, + diffBase + ); + const packageAgeViolations = await checkPackageMinimumAge( + changedExactDependencies + ); + packageAgeResult = { + status: packageAgeViolations.length === 0 ? "pass" : "fail", + violations: packageAgeViolations, + }; + } catch (error) { + if (IS_CI) { + throw error; + } + + packageAgeResult = { + status: "skip", + violations: [], + reason: error instanceof Error ? error.message : String(error), + }; + } + } + + const sensitiveDiff = checkSensitiveDiff(); + + const packageStatus: CheckStatus = + packageViolations.length === 0 ? "pass" : "fail"; + const packageAgeStatus: CheckStatus = packageAgeResult.status; + + // sensitive-diff is intentionally NOT part of overallFailed. It's a review + // hint surfaced via PR comment + GitHub annotations. Merge-blocking for + // sensitive paths is CODEOWNERS + branch protection's job. + const overallFailed = + packageStatus === "fail" || packageAgeStatus === "fail"; + + const lines = [ + "## Repository Guard", + "", + ...renderPackageSection(packageViolations), + "", + ...renderPackageAgeResult(packageAgeResult), + "", + ...renderSensitiveSection(sensitiveDiff), + "", + `Overall status: ${overallFailed ? "fail" : "pass"}`, + "", + "_Transitive supply-chain threats are covered by the Socket scanner (via \`@socketsecurity/bun-security-scanner\` in \`bunfig.toml\`), which runs on every \`bun install\`. This guard blocks merge on direct-dep pinning and age policy; the sensitive-diff section is a review hint, not a merge gate (CODEOWNERS handles the actual review requirement)._", + ]; + + const summary = `${lines.join("\n")}\n`; + writeFileSync(SUMMARY_PATH, summary); + + // Sensitive-diff annotations are emitted as `::warning::` so they show up + // in the Files Changed view for reviewers but don't fail the job. This + // runs regardless of overallFailed — a clean lockfile/age check shouldn't + // silence a review hint. + if (sensitiveDiff.status === "warn") { + for (const finding of sensitiveDiff.findings.slice(0, 20)) { + console.log( + `::warning file=${finding.file},line=${finding.line}::${finding.kind}: ${finding.text}` + ); + } + for (const file of sensitiveDiff.touchedSensitiveFiles) { + console.log( + `::warning file=${file}::High-sensitivity file modified — please review carefully.` + ); + } + } + + if (overallFailed) { + console.error(""); + console.error("Repository Guard failed. Details:"); + console.error(""); + console.error(summary); + + for (const violation of packageViolations) { + console.error( + `::error file=${violation.file}::${violation.section}.${violation.dependency} uses \`${violation.version}\` — pin to an exact version.` + ); + } + + if (packageAgeResult.status === "fail") { + for (const violation of packageAgeResult.violations) { + const usedIn = violation.usedIn.join(", "); + console.error( + `::error file=${violation.usedIn[0] ?? "package.json"}::${violation.dependency}@${violation.version} is only ${violation.ageDays} days old (published ${violation.publishedAt}, min age ${PACKAGE_MIN_AGE_DAYS}d). Used in: ${usedIn}` + ); + } + } + + process.exit(1); + } + + console.log(summary); +} + +await main();