diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 436d5f81..5a235f01 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,9 @@ jobs: name: Rust — fmt + clippy + deny + tests runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false - name: Install Linux dependencies run: | @@ -29,11 +31,11 @@ jobs: librsvg2-dev \ patchelf - - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@4be7066ada62dd38de10e7b70166bc74ed198c30 # stable with: components: rustfmt, clippy, llvm-tools-preview - - uses: Swatinem/rust-cache@v2 + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 with: workspaces: src-tauri @@ -44,7 +46,7 @@ jobs: run: cargo clippy --manifest-path src-tauri/Cargo.toml --workspace -- -D warnings - name: Install cargo tooling - uses: taiki-e/install-action@v2 + uses: taiki-e/install-action@43aecc8d72668fbcfe75c31400bc4f890f1c5853 # v2 with: tool: cargo-llvm-cov,cargo-deny @@ -59,7 +61,7 @@ jobs: --lcov \ --output-path lcov.info - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: rust-coverage path: lcov.info @@ -68,9 +70,11 @@ jobs: name: Frontend — oxlint + oxfmt + tsc + knip + vitest runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false - - uses: actions/setup-node@v4 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: 22 cache: npm @@ -78,6 +82,9 @@ jobs: - name: npm ci run: npm ci + - name: npm audit + run: npm audit + - name: oxlint run: npx oxlint . --max-warnings=0 @@ -96,7 +103,7 @@ jobs: - name: vitest + coverage run: npx vitest run --coverage --passWithNoTests - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: frontend-coverage path: coverage/ @@ -105,42 +112,23 @@ jobs: name: Secrets scan runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false - - name: Check for secret files in repo - run: | - # `(^|/)\.npmrc$|(^|/)\.pypirc$` catches nested forms (e.g. - # `tools/.npmrc`) on top of the root-level files. - SECRET_FILES=$(git ls-files | grep -iE '\.(env|env\..+|pem|key|p12|pfx|secret|creds|aws|netrc)$|(^|/)(\.env|\.secrets|secrets)/|(^|/)\.npmrc$|(^|/)\.pypirc$' || true) - if [ -n "$SECRET_FILES" ]; then - echo "::error::Secret files in repo:" - echo "$SECRET_FILES" - exit 1 - fi + - name: Test secrets scanner + run: ./scripts/test-no-secrets.sh - - name: Check for API key patterns in tracked files - run: | - # Patterns aligned with scripts/no-secrets.sh — defence in depth. - # All distinctive prefixes (AKIA, sk-ant, sk-, ghp_, github_pat_, - # glpat-, AIza), so manifests like package.json / Cargo.toml are - # safe to scan and are no longer excluded. - # `ASIA…` covers temporary STS credentials (vs long-lived `AKIA…`). - PATTERNS='AKIA[0-9A-Z]{16}|ASIA[0-9A-Z]{16}|sk-ant-[A-Za-z0-9_-]{20,}|sk-[A-Za-z0-9]{32,}|ghp_[A-Za-z0-9]{36}|github_pat_[A-Za-z0-9_]{82}|glpat-[A-Za-z0-9_-]{20}|AIza[0-9A-Za-z_-]{35}' - MATCHES=$(git grep -nE "$PATTERNS" -- ':(exclude)*.lock' || true) - if [ -n "$MATCHES" ]; then - echo "::error::API key pattern detected:" - # Strip the matched secret value from the output — `git grep -n` - # returns `file:line:matched_text`; emit only `file:line` so CI - # logs never reveal the raw credential. - echo "$MATCHES" | head -10 | awk -F: '{print $1 ":" $2}' - exit 1 - fi + - name: Scan tracked files for secrets + run: ./scripts/no-secrets.sh --all forbidden-tools: name: Forbidden tools runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false - name: Reject pnpm-lock.yaml / yarn.lock run: | @@ -213,15 +201,17 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false - - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@4be7066ada62dd38de10e7b70166bc74ed198c30 # stable - - uses: Swatinem/rust-cache@v2 + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 with: workspaces: src-tauri - - uses: actions/setup-node@v4 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: 22 cache: npm @@ -240,15 +230,27 @@ jobs: run: npm ci - name: Tauri build - run: npx tauri build --config .github/tauri.ci.conf.json + id: tauri-build + shell: bash + run: npx tauri build --config .github/tauri.ci.conf.json 2>&1 | tee tauri-build.log + + - name: Upload Tauri build diagnostics + if: failure() && steps.tauri-build.outcome == 'failure' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: tauri-build-diagnostics-${{ matrix.os }} + path: tauri-build.log + if-no-files-found: warn + retention-days: 7 changelog-check: name: CHANGELOG updated runs-on: ubuntu-latest if: github.event_name == 'pull_request' steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: + persist-credentials: false fetch-depth: 0 - name: Verify CHANGELOG.md updated when code changes diff --git a/CHANGELOG.md b/CHANGELOG.md index 9975d9f0..a6fc14d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- **Lot 0 maintenance baseline (MAT-126–MAT-128)**: restored CI by allowing non-secret `.npmrc` configuration through the shared redacted scanner, updated compatible npm and Rust dependencies to clear security audits, pinned CI actions on their Node 24 releases, kept the codebase clean under the current Rust stable Clippy, and added multi-OS Tauri build diagnostics. - **Task 40 follow-up — PR #153 review fixes round 6** (scope `download`, sprint task 40, PR #153 review): `chatgpt-codex-connector[bot]` flagged a cancellation race in the failover loop. After `AttemptOutcome::Failed`, the loop bumped `mirror_idx`, ran the file/meta cleanup, and unconditionally published `MirrorSwitched` (which `progress_bridge` persists to `current_mirror_index`). A user-cancel that landed in the window between the failed attempt returning and the next attempt starting — including while `tokio::task::spawn_blocking` was running the cleanup — would still bump the persisted cursor through the bridge before the next iteration's `run_mirror_attempt` observed `cancel_token.is_cancelled()` and reported `AttemptOutcome::Cancelled`. The retry would then resume from a slot the user never asked for. Two `cancel_token.is_cancelled()` guards added inside the failover branch: one before bumping `mirror_idx` (catches a cancel that arrived between the attempt finishing and the loop re-entering), one after the cleanup completes (catches a cancel that arrived during the spawn_blocking). Both paths emit `DomainEvent::DownloadCancelled` and break out, so no `MirrorSwitched` ever fires for an aborted switch. - **Task 40 follow-up — PR #153 review fixes round 5** (scope `download`, sprint task 40, PR #153 review): `coderabbitai[bot]` flagged that the round-2 cursor-reset path mapped every `DomainEvent::DownloadFailed` to `MirrorCursorReset`, but `extract_archive::extract_archive_handler` (and the domain `Download::fail()` max-retries path) also publish `DownloadFailed` for post-download errors. A successful download from mirror N followed by an extract / verify failure would zero the cursor and lose the last-known-good slot, so a manual re-download would walk the full mirror list from the top instead of going straight back to the mirror that already produced the bytes. New domain event `DomainEvent::AllMirrorsExhausted { id }` published by the engine's failover loop right before the existing `DownloadFailed`; `progress_bridge` now keys the cursor reset on `AllMirrorsExhausted` and ignores generic `DownloadFailed`. Wired through `tauri_bridge` (`mirrors-exhausted` event name, `{ "id": }` payload) and the download log bridge (silent — no log line). Three tests: `test_event_to_message_maps_all_mirrors_exhausted_to_cursor_reset`, `test_event_to_message_does_not_reset_cursor_on_generic_download_failed` (asserts `event_to_message(&DownloadFailed)` returns `None`), `test_all_mirrors_exhausted_resets_persisted_mirror_cursor_to_zero` (write-through). The previous round-2 tests were renamed in place. - **Task 40 follow-up — PR #153 review fixes round 4** (scope `download`, sprint task 40, PR #153 review): `chatgpt-codex-connector[bot]` flagged that the round-3 `MirrorSwitched` handler clears the cursor + segment rows but leaves `downloads.downloaded_bytes` / `total_bytes` pinned at the failed mirror's last value. Because `update_download_progress` enforces `downloaded_bytes = MAX(downloaded_bytes, ?)` so concurrent state-transition saves cannot regress the counter, the new mirror's progress events would be silently swallowed until the new attempt crossed the failed mirror's high-water mark — the UI would show a frozen / inflated progress bar (and ETA) until then. `progress_bridge` now adds a `reset_download_bytes` step inside the `MirrorSwitched` handler that runs `UPDATE downloads SET downloaded_bytes = 0, total_bytes = NULL WHERE id = ?` between the cursor write and the segments purge. The next attempt's `DownloadProgress` event repopulates `total_bytes` through the existing `COALESCE(NULLIF(...))` guard, and `downloaded_bytes` rises monotonically from 0 under the same `MAX` guard. Two regression tests: `test_reset_download_bytes_zeroes_downloaded_and_clears_total` (write-through) and `test_reset_download_bytes_then_progress_repopulates_total` (the round-trip with a follow-up `update_download_progress` call). diff --git a/deny.toml b/deny.toml index 98385e2e..eba42c07 100644 --- a/deny.toml +++ b/deny.toml @@ -43,6 +43,18 @@ ignore = [ "RUSTSEC-2025-0081", "RUSTSEC-2025-0098", "RUSTSEC-2025-0100", + # proc-macro-error2 2.0.1 is unmaintained through sea-bae → + # sea-orm-macros 1.1.20. It runs only while compiling, so it has no runtime + # input surface. Mitigation: follow SeaORM 1.x updates and remove this entry + # when its macros migrate. Review by 2026-10-15. + "RUSTSEC-2026-0173", + # quick-xml <0.41 is pulled by tauri-winrt-notification 0.7.2 only for XML + # escaping, and by wayland-scanner 0.31.10 for trusted build-time protocol + # XML. Neither vulnerable parser path processes remote XML in Vortex. + # Mitigation: keep parent crates current and remove when they adopt 0.41+. + # Review by 2026-10-15. + "RUSTSEC-2026-0194", + "RUSTSEC-2026-0195", ] # === License compliance (GPL-3.0 compatible) === diff --git a/package-lock.json b/package-lock.json index 7b144a45..84177ca8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "react": "^19.0.0", "react-dom": "^19.0.0", "react-i18next": "^17.0.2", - "react-router": "^7.14.0", + "react-router": "^7.18.1", "recharts": "^3.8.1", "sonner": "^2.0.7", "tailwind-merge": "^3.5.0", @@ -50,7 +50,7 @@ "oxlint": "^1.59.0", "tailwindcss": "^4.0.0", "typescript": "~5.7.0", - "vite": "^6.0.0", + "vite": "^6.4.3", "vitest": "^4.1.3" } }, @@ -101,13 +101,13 @@ "license": "MIT" }, "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", + "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -116,9 +116,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", "dev": true, "license": "MIT", "engines": { @@ -126,21 +126,21 @@ } }, "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -157,14 +157,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -174,14 +174,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -191,9 +191,9 @@ } }, "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", "dev": true, "license": "MIT", "engines": { @@ -201,29 +201,29 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -243,9 +243,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "dev": true, "license": "MIT", "engines": { @@ -253,9 +253,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "dev": true, "license": "MIT", "engines": { @@ -263,9 +263,9 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", "dev": true, "license": "MIT", "engines": { @@ -273,27 +273,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", - "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0" + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", - "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.29.0" + "@babel/types": "^7.29.7" }, "bin": { "parser": "bin/babel-parser.js" @@ -344,33 +344,33 @@ } }, "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", "debug": "^4.3.1" }, "engines": { @@ -378,14 +378,14 @@ } }, "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -5650,9 +5650,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.16", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.16.tgz", - "integrity": "sha512-Lyf3aK28zpsD1yQMiiHD4RvVb6UdMoo8xzG2XzFIfR9luPzOpcBlAsT/qfB1XWS1bxWT+UtE4WmQgsp297FYOA==", + "version": "2.10.43", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.43.tgz", + "integrity": "sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -5673,9 +5673,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", - "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "version": "4.28.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.6.tgz", + "integrity": "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==", "dev": true, "funding": [ { @@ -5693,10 +5693,10 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.10.12", - "caniuse-lite": "^1.0.30001782", - "electron-to-chromium": "^1.5.328", - "node-releases": "^2.0.36", + "baseline-browser-mapping": "^2.10.42", + "caniuse-lite": "^1.0.30001803", + "electron-to-chromium": "^1.5.389", + "node-releases": "^2.0.51", "update-browserslist-db": "^1.2.3" }, "bin": { @@ -5707,9 +5707,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001786", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001786.tgz", - "integrity": "sha512-4oxTZEvqmLLrERwxO76yfKM7acZo310U+v4kqexI2TL1DkkUEMT8UijrxxcnVdxR3qkVf5awGRX+4Z6aPHVKrA==", + "version": "1.0.30001805", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001805.tgz", + "integrity": "sha512-52noaS3DubycKSXaU30TwPGIp+POyQSUVa5jBEq3vkRkY0kjyb3LQgvhU6WGyCcyXqVLWO0Cw0Q6BSdD0kUfVA==", "dev": true, "funding": [ { @@ -6007,9 +6007,9 @@ "peer": true }, "node_modules/electron-to-chromium": { - "version": "1.5.332", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.332.tgz", - "integrity": "sha512-7OOtytmh/rINMLwaFTbcMVvYXO3AUm029X0LcyfYk0B557RlPkdpTpnH9+htMlfu5dKwOmT0+Zs2Aw+lnn6TeQ==", + "version": "1.5.392", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.392.tgz", + "integrity": "sha512-1yQq3VQCZRwsnYc67Oc+1fge6Lwtn0hzi6zmEVkB61Zx21kTbwJAW4dFLadl5Rc1tKhG/kSpYXnfiAhu0f0a1g==", "dev": true, "license": "ISC" }, @@ -7061,9 +7061,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", "dev": true, "funding": [ { @@ -7080,11 +7080,14 @@ } }, "node_modules/node-releases": { - "version": "2.0.37", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", - "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==", + "version": "2.0.51", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", + "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/obug": { "version": "2.1.1", @@ -7293,9 +7296,9 @@ } }, "node_modules/postcss": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", - "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "version": "8.5.19", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.19.tgz", + "integrity": "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==", "dev": true, "funding": [ { @@ -7313,7 +7316,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -7578,9 +7581,9 @@ } }, "node_modules/react-router": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.14.0.tgz", - "integrity": "sha512-m/xR9N4LQLmAS0ZhkY2nkPA1N7gQ5TUVa5n8TgANuDTARbn1gt+zLPXEm7W0XDTbrQ2AJSJKhoa6yx1D8BcpxQ==", + "version": "7.18.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.1.tgz", + "integrity": "sha512-GDLgg3i3uM0aeJO3Fm+TCS+sDQ7gu12T6x0qdTEzcwqEfleci7JwugVNIF3U//0FWKnJT7ptG+20B2jfDqnZAg==", "license": "MIT", "dependencies": { "cookie": "^1.0.1", @@ -8054,9 +8057,9 @@ } }, "node_modules/undici": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.7.tgz", - "integrity": "sha512-H/nlJ/h0ggGC+uRL3ovD+G0i4bqhvsDOpbDv7At5eFLlj2b41L8QliGbnl2H7SnDiYhENphh1tQFJZf+MyfLsQ==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", + "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==", "dev": true, "license": "MIT", "engines": { @@ -8176,9 +8179,9 @@ } }, "node_modules/vite": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", - "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 2d376a1d..0000b353 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "react": "^19.0.0", "react-dom": "^19.0.0", "react-i18next": "^17.0.2", - "react-router": "^7.14.0", + "react-router": "^7.18.1", "recharts": "^3.8.1", "sonner": "^2.0.7", "tailwind-merge": "^3.5.0", @@ -59,7 +59,7 @@ "oxlint": "^1.59.0", "tailwindcss": "^4.0.0", "typescript": "~5.7.0", - "vite": "^6.0.0", + "vite": "^6.4.3", "vitest": "^4.1.3" } } diff --git a/scripts/no-secrets.sh b/scripts/no-secrets.sh index 45a2f25e..77598f88 100755 --- a/scripts/no-secrets.sh +++ b/scripts/no-secrets.sh @@ -1,60 +1,119 @@ #!/usr/bin/env bash -# Reject staged files likely to contain secrets. -# Invoked at pre-commit via lefthook.yml. +# Reject staged or tracked files likely to contain secrets. +# Invoked at pre-commit via lefthook.yml and with --all in CI. set -euo pipefail # File patterns that typically contain secrets SECRET_PATTERNS='\.(env|env\..+|pem|key|p12|pfx|secret|creds|aws|netrc)$|(^|/)(\.env|\.secrets|secrets)/|(^|/)\.pypirc$' +NPMRC_PATH_PATTERN='(^|/)\.npmrc$' NPMRC_AUTH_PATTERNS='(^|[[:space:]])(_authToken|_password|_auth)[[:space:]]*=|//.+:(_authToken|_password|_auth)[[:space:]]*=' +API_KEY_PATTERNS='AKIA[0-9A-Z]{16}|ASIA[0-9A-Z]{16}|sk-ant-[A-Za-z0-9_-]{20,}|sk-[A-Za-z0-9]{32,}|ghp_[A-Za-z0-9]{36}|github_pat_[A-Za-z0-9_]{82}|glpat-[A-Za-z0-9_-]{20}|AIza[0-9A-Za-z_-]{35}' -# List staged files (excluding deletions) -STAGED=$(git diff --cached --diff-filter=d --name-only) +print_redacted_path() { + local file=$1 + local suffix=${2:-} -if [ -z "$STAGED" ]; then + shopt -s nocasematch + if [[ $file =~ $API_KEY_PATTERNS ]]; then + printf ' - [redacted-path]%s\n' "$suffix" + else + printf ' - %q%s\n' "$file" "$suffix" + fi + shopt -u nocasematch +} + +FILES=() +case "${1:-}" in + "") + SCOPE=staged + while IFS= read -r -d '' file; do + FILES+=("$file") + done < <(git diff --cached --diff-filter=d --name-only -z) + ;; + --all) + SCOPE=tracked + while IFS= read -r -d '' file; do + FILES+=("$file") + done < <(git ls-files -z) + ;; + *) echo "Usage: $0 [--all]" >&2; exit 2 ;; +esac + +if [ "${#FILES[@]}" -eq 0 ]; then exit 0 fi -# Check filenames -SECRET_FILES=$(echo "$STAGED" | grep -iE "$SECRET_PATTERNS" || true) +# Classify paths without line-delimited parsing: Git permits newlines in names. +SECRET_FILES=() +NPMRC_FILES=() +shopt -s nocasematch +for file in "${FILES[@]}"; do + if [[ $file =~ $SECRET_PATTERNS || $file =~ $API_KEY_PATTERNS ]]; then + SECRET_FILES+=("$file") + fi +done +shopt -u nocasematch +for file in "${FILES[@]}"; do + if [[ $file =~ $NPMRC_PATH_PATTERN ]]; then + NPMRC_FILES+=("$file") + fi +done -if [ -n "$SECRET_FILES" ]; then - echo "BLOCKED: files possibly containing secrets staged:" - echo "$SECRET_FILES" | sed 's/^/ - /' +if [ "${#SECRET_FILES[@]}" -gt 0 ]; then + echo "BLOCKED: files possibly containing secrets are $SCOPE:" + for file in "${SECRET_FILES[@]}"; do + print_redacted_path "$file" + done echo "" echo "If intentional, add the file to .gitignore and use a .example variant instead." exit 1 fi # .npmrc is allowed for non-secret config (registries, loglevel, hoisting). -# Reject only when the staged diff introduces auth tokens. -NPMRC_STAGED=$(echo "$STAGED" | grep -E '(^|/)\.npmrc$' || true) -if [ -n "$NPMRC_STAGED" ]; then - NPMRC_LEAK=$(git diff --cached -U0 -- $NPMRC_STAGED | grep -E '^\+' | grep -vE '^\+\+\+ [ab]/' | grep -iE "$NPMRC_AUTH_PATTERNS" || true) - if [ -n "$NPMRC_LEAK" ]; then - echo "BLOCKED: auth token detected in staged .npmrc:" - echo "$NPMRC_LEAK" | head -5 - echo "" - echo "Move credentials to ~/.npmrc and reference them via env vars (\${NPM_TOKEN})." - exit 1 +# Scan the complete indexed file so findings are identical locally and in CI. +NPMRC_LEAKS=() +for file in "${NPMRC_FILES[@]}"; do + # Do not use grep -q here: under pipefail its early exit can SIGPIPE + # git-show and turn a real match in a large file into a false negative. + if git show ":$file" | grep -iE "$NPMRC_AUTH_PATTERNS" >/dev/null; then + NPMRC_LEAKS+=("$file") fi +done + +if [ "${#NPMRC_LEAKS[@]}" -gt 0 ]; then + echo "BLOCKED: npm authentication config detected in tracked .npmrc:" + for file in "${NPMRC_LEAKS[@]}"; do + print_redacted_path "$file" + done + echo "Store npm credentials in the user-level ~/.npmrc or CI secrets instead." + exit 1 fi -# Check content: grep known API key patterns in the diff -CONTENT_LEAK=$(git diff --cached -U0 | grep -E '^\+' | grep -vE '^\+\+\+ [ab]/' | grep -iE \ - -e 'AKIA[0-9A-Z]{16}' \ - -e 'ASIA[0-9A-Z]{16}' \ - -e 'sk-ant-[A-Za-z0-9_-]{20,}' \ - -e 'sk-[A-Za-z0-9]{32,}' \ - -e 'ghp_[A-Za-z0-9]{36}' \ - -e 'github_pat_[A-Za-z0-9_]{82}' \ - -e 'glpat-[A-Za-z0-9_-]{20}' \ - -e 'AIza[0-9A-Za-z_-]{35}' \ - || true) - -if [ -n "$CONTENT_LEAK" ]; then - echo "BLOCKED: API key pattern detected in diff:" - echo "$CONTENT_LEAK" | head -5 - echo "" +# Check complete indexed content so local and CI findings are identical. +CONTENT_FOUND=false +if git grep --cached -qEia "$API_KEY_PATTERNS"; then + CONTENT_FOUND=true +else + GREP_STATUS=$? + if [ "$GREP_STATUS" -ne 1 ]; then + echo "Secret scan failed while reading the Git index." >&2 + exit "$GREP_STATUS" + fi +fi + +if [ "$CONTENT_FOUND" = true ]; then + echo "BLOCKED: API key pattern detected; matched values were redacted." + MATCH_COUNT=0 + # With -z -n, Git NUL-terminates the path and line number; content remains + # newline-terminated. Treating binary files as text keeps them in scope. + while IFS= read -r -d '' file \ + && IFS= read -r -d '' line \ + && IFS= read -r _content; do + if [ "$MATCH_COUNT" -lt 5 ]; then + print_redacted_path "$file" ":$line" + fi + MATCH_COUNT=$((MATCH_COUNT + 1)) + done < <(git grep --cached -z -nEia "$API_KEY_PATTERNS") echo "Remove the key and revoke it immediately if it has been committed even locally." exit 1 fi diff --git a/scripts/test-no-secrets.sh b/scripts/test-no-secrets.sh new file mode 100755 index 00000000..df30f050 --- /dev/null +++ b/scripts/test-no-secrets.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT=$(git rev-parse --show-toplevel) +SCANNER="$ROOT/scripts/no-secrets.sh" +REPO=$(mktemp -d) + +assert_rejected() { + local repo=$1 + local label=$2 + local secret=$3 + local diagnostic=$4 + local output + shift 4 + + if output=$(cd "$repo" && "$SCANNER" "$@" 2>&1); then + echo "$label was accepted" + exit 1 + fi + if [[ "$output" == *"$secret"* ]]; then + echo "$label leaked in scanner output" + exit 1 + fi + if [[ -n "$diagnostic" && "$output" != *"$diagnostic"* ]]; then + echo "$label diagnostic omitted $diagnostic" + exit 1 + fi +} + +git -C "$REPO" init -q +git -C "$REPO" config user.email test@example.com +git -C "$REPO" config user.name "Vortex Test" + +printf 'loglevel=error\n' > "$REPO/.npmrc" +git -C "$REPO" add .npmrc +(cd "$REPO" && "$SCANNER" --all) \ + || { echo "safe .npmrc was rejected"; exit 1; } + +NPM_TOKEN='npm-secret-value' +printf '//registry.npmjs.org/:_authToken=%s\n' "$NPM_TOKEN" > "$REPO/.npmrc" +git -C "$REPO" add .npmrc +assert_rejected "$REPO" "npm auth token" "$NPM_TOKEN" ".npmrc" --all + +printf 'loglevel=error\n' > "$REPO/.npmrc" +API_TOKEN="ghp_$(printf 'a%.0s' {1..36})" +printf '%s\n' "$API_TOKEN" > "$REPO/credential.txt" +git -C "$REPO" add .npmrc credential.txt +assert_rejected "$REPO" "API token" "$API_TOKEN" "credential.txt:1" --all +assert_rejected "$REPO" "staged API token" "$API_TOKEN" "credential.txt:1" + +BINARY_REPO=$(mktemp -d) +git -C "$BINARY_REPO" init -q +printf '\0prefix%s\0suffix\n' "$API_TOKEN" > "$BINARY_REPO/payload.bin" +git -C "$BINARY_REPO" add payload.bin +assert_rejected "$BINARY_REPO" "binary API token" "$API_TOKEN" "payload.bin:1" --all +assert_rejected "$BINARY_REPO" "staged binary API token" "$API_TOKEN" "payload.bin:1" + +LOCK_REPO=$(mktemp -d) +git -C "$LOCK_REPO" init -q +printf '%s\n' "$API_TOKEN" > "$LOCK_REPO/dependencies.lock" +git -C "$LOCK_REPO" add dependencies.lock +assert_rejected "$LOCK_REPO" "lockfile API token" "$API_TOKEN" "dependencies.lock:1" --all +assert_rejected "$LOCK_REPO" "staged lockfile API token" "$API_TOKEN" "dependencies.lock:1" + +PATH_REPO=$(mktemp -d) +git -C "$PATH_REPO" init -q +printf 'safe content\n' > "$PATH_REPO/$API_TOKEN.txt" +git -C "$PATH_REPO" add -- "$API_TOKEN.txt" +assert_rejected "$PATH_REPO" "filename API token" "$API_TOKEN" "[redacted-path]" --all +assert_rejected "$PATH_REPO" "staged filename API token" "$API_TOKEN" "[redacted-path]" + +LARGE_REPO=$(mktemp -d) +git -C "$LARGE_REPO" init -q +printf '//registry.npmjs.org/:_authToken=%s\n' "$NPM_TOKEN" > "$LARGE_REPO/.npmrc" +head -c 1048576 /dev/zero | tr '\0' x >> "$LARGE_REPO/.npmrc" +git -C "$LARGE_REPO" add .npmrc +assert_rejected "$LARGE_REPO" "large npm auth token" "$NPM_TOKEN" ".npmrc" --all +assert_rejected "$LARGE_REPO" "large staged npm auth token" "$NPM_TOKEN" ".npmrc" + +WEIRD_REPO=$(mktemp -d) +WEIRD_DIR=$'odd\nname' +git -C "$WEIRD_REPO" init -q +mkdir -p "$WEIRD_REPO/$WEIRD_DIR" +printf '//registry.npmjs.org/:_authToken=%s\n' "$NPM_TOKEN" > "$WEIRD_REPO/$WEIRD_DIR/.npmrc" +git -C "$WEIRD_REPO" add -- "$WEIRD_DIR/.npmrc" +assert_rejected "$WEIRD_REPO" "newline-path npm auth token" "$NPM_TOKEN" ".npmrc" --all +assert_rejected "$WEIRD_REPO" "newline-path staged npm auth token" "$NPM_TOKEN" ".npmrc" + +COLON_REPO=$(mktemp -d) +git -C "$COLON_REPO" init -q +mkdir -p "$COLON_REPO/dir:part" +printf '%s\n' "$API_TOKEN" > "$COLON_REPO/dir:part/credential.txt" +git -C "$COLON_REPO" add -- "dir:part/credential.txt" +assert_rejected "$COLON_REPO" "colon-path API token" "$API_TOKEN" "dir:part/credential.txt:1" --all + +echo "no-secrets checks passed" diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 13d3d1d1..a176f142 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -172,9 +172,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.102" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" [[package]] name = "approx" @@ -1051,9 +1051,9 @@ dependencies = [ [[package]] name = "cmov" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f88a43d011fc4a6876cb7344703e297c71dda42494fee094d5f7c76bf13f746" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" [[package]] name = "cobs" @@ -1465,9 +1465,9 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.18" +version = "0.9.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" dependencies = [ "crossbeam-utils", ] @@ -2064,7 +2064,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3435,7 +3435,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -4455,15 +4455,14 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl" -version = "0.10.76" +version = "0.10.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" +checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967" dependencies = [ "bitflags 2.11.0", "cfg-if", "foreign-types 0.3.2", "libc", - "once_cell", "openssl-macros", "openssl-sys", ] @@ -4487,9 +4486,9 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-sys" -version = "0.9.112" +version = "0.9.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" +checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695" dependencies = [ "cc", "libc", @@ -4928,13 +4927,13 @@ checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" [[package]] name = "plist" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" +checksum = "7da1d65da6dd5d1e44199ac0f58712d241c0f439f80adea8924d832384087f85" dependencies = [ "base64 0.22.1", "indexmap 2.13.1", - "quick-xml 0.38.4", + "quick-xml 0.41.0", "serde", "time", ] @@ -5280,18 +5279,18 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.38.4" +version = "0.39.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" +checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d" dependencies = [ "memchr", ] [[package]] name = "quick-xml" -version = "0.39.2" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d" +checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" dependencies = [ "memchr", ] @@ -5864,7 +5863,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -5933,7 +5932,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -6669,9 +6668,9 @@ dependencies = [ [[package]] name = "spin" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" dependencies = [ "lock_api", ] @@ -7154,9 +7153,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tar" -version = "0.4.45" +version = "0.4.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22692a6476a21fa75fdfc11d452fda482af402c008cdbaf3476414e122040973" +checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840" dependencies = [ "filetime", "libc", @@ -7555,7 +7554,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix 1.1.4", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/src-tauri/gen/schemas/capabilities.json b/src-tauri/gen/schemas/capabilities.json index 0e80e748..f4c20fca 100644 --- a/src-tauri/gen/schemas/capabilities.json +++ b/src-tauri/gen/schemas/capabilities.json @@ -1 +1 @@ -{"default":{"identifier":"default","description":"Default capabilities for Vortex","local":true,"windows":["main"],"permissions":["core:default","core:tray:default","notification:allow-notify","notification:allow-request-permission","notification:allow-is-permission-granted","dialog:allow-save"]},"dev-pilot":{"identifier":"dev-pilot","description":"tauri-pilot testing plugin (Unix debug builds only)","local":true,"windows":["main"],"permissions":["pilot:default"],"platforms":["linux","macOS"]}} \ No newline at end of file +{"default":{"identifier":"default","description":"Default capabilities for Vortex","local":true,"windows":["main"],"permissions":["core:default","core:tray:default","notification:allow-notify","notification:allow-request-permission","notification:allow-is-permission-granted","dialog:allow-save","dialog:allow-open"]},"dev-pilot":{"identifier":"dev-pilot","description":"tauri-pilot testing plugin (Unix debug builds only)","local":true,"windows":["main"],"permissions":["pilot:default"],"platforms":["linux","macOS"]}} \ No newline at end of file diff --git a/src-tauri/src/application/commands/resolve_links.rs b/src-tauri/src/application/commands/resolve_links.rs index 7a8de198..42a16cd5 100644 --- a/src-tauri/src/application/commands/resolve_links.rs +++ b/src-tauri/src/application/commands/resolve_links.rs @@ -176,10 +176,8 @@ fn extract_filename_from_url(url: &str) -> Option { // Extract the path component after the scheme + authority (e.g. after "https://host") let path_only = if let Some(after_scheme) = path.find("://") { let after = &path[after_scheme + 3..]; - match after.find('/') { - Some(slash) => &after[slash + 1..], - None => return None, - } + let slash = after.find('/')?; + &after[slash + 1..] } else { path }; diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index ea327e68..81d515b4 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -93,15 +93,13 @@ pub use adapters::driving::tauri_ipc::{ #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { - let mut builder = tauri::Builder::default() + let builder = tauri::Builder::default() .plugin(tauri_plugin_clipboard_manager::init()) .plugin(tauri_plugin_notification::init()) .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_updater::Builder::new().build()); #[cfg(all(debug_assertions, unix))] - { - builder = builder.plugin(tauri_plugin_pilot::init()); - } + let builder = builder.plugin(tauri_plugin_pilot::init()); builder .setup(|app| { let app_handle = app.handle().clone();