From dd0b5577946d0605e303fff9e329565780657ed1 Mon Sep 17 00:00:00 2001 From: Daniel Han Date: Tue, 19 May 2026 05:56:56 -0700 Subject: [PATCH] ci: advisory lockfile supply-chain audit (no install-script changes) (#5604) * ci: add advisory lockfile supply-chain audit Adds a fast, focused workflow that scans every checked-in npm and cargo lockfile on PRs touching one. Default behaviour is advisory: only public indicator-of-compromise strings, versions on the public known-malicious list, and structurally broken lockfiles fail the build. Structural anomalies (missing integrity hashes, non-default registry, etc.) surface as ::warning:: annotations without gating merges, so reviewers see the audit result inline on every PR without changing the existing install behaviour. Also commits the two missing npm lockfiles the audit needs: studio/package-lock.json (Tauri CLI holder for desktop release) and studio/backend/core/data_recipe/oxc-validator/package-lock.json (oxc-parser runtime for the data-recipe validator). studio/setup.sh, studio/setup.ps1, build.sh, and pyproject.toml are intentionally left alone so the existing install path keeps working unchanged. Audit script behaviour: default mode -> exits 1 only on blocked-known-malicious, known-ioc-string, malformed-lockfile, missing-lockfile, unreadable-lockfile, or missing-toml-parser --strict -> promotes every finding to blocking (opt-in) Adds a try/except around lockfile reads so a permissions error prints a finding instead of crashing CI with a raw traceback. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * test(security): update cargo regression test for advisory mode `scripts/lockfile_supply_chain_audit.py` now classifies `non-registry-cargo-source` as an advisory finding by default (returns exit 0 with a `::warning::` annotation) rather than unconditionally blocking with exit 1. Update the existing `test_malicious_cargo_lockfile_refused` to pass --strict so it keeps verifying the "refuse to install" behavior it is named for, and add a second test that pins the default-mode behavior: advisory finding emitted, exit code 0. * audit: escape Finding for GH Actions annotations `::warning::` and `::error::` workflow commands truncate the annotation message at the first newline unless the message is %-encoded per the workflow-commands spec. Since `Finding.__str__` returns three lines (kind+path, package, detail), the package and detail fields were being dropped from the GitHub Actions UI. Add a `_gha_escape()` helper that applies the spec'd escapes (`%` -> `%25`, then `\r` -> `%0D`, then `\n` -> `%0A`; the `%` replacement must happen first so the subsequent escapes are not double-encoded), wrap every Finding rendered into a workflow command with it, and pin both the helper and the end-to-end single-line emission with two new regression tests. Caught by gemini-code-assist on PR #5604. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .github/workflows/lockfile-audit.yml | 79 ++ .gitignore | 4 + scripts/lockfile_supply_chain_audit.py | 170 +++- .../oxc-validator/package-lock.json | 799 ++++++++++++++++++ studio/package-lock.json | 233 +++++ studio/package.json | 10 + .../test_lockfile_supply_chain_audit.py | 105 ++- 7 files changed, 1386 insertions(+), 14 deletions(-) create mode 100644 .github/workflows/lockfile-audit.yml create mode 100644 studio/backend/core/data_recipe/oxc-validator/package-lock.json create mode 100644 studio/package-lock.json create mode 100644 studio/package.json diff --git a/.github/workflows/lockfile-audit.yml b/.github/workflows/lockfile-audit.yml new file mode 100644 index 0000000000..9c28e21672 --- /dev/null +++ b/.github/workflows/lockfile-audit.yml @@ -0,0 +1,79 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. +# +# Fast, focused supply-chain audit of every checked-in lockfile. +# +# Runs scripts/lockfile_supply_chain_audit.py on PRs that touch any +# npm or cargo lockfile, on push to main, and on a daily schedule so +# newly-published IOCs surface even when no PR opens. +# +# Default behavior is "advisory": only public indicator-of-compromise +# strings, known-malicious pinned versions, and structurally broken +# lockfiles fail the build. Structural anomalies (missing integrity, +# non-default registry, etc.) are emitted as GitHub Actions warnings +# but do not block merges. This deliberately keeps the noise floor +# low while still failing the moment a checked-in lockfile starts +# pointing at known-bad bytes. +# +# This workflow is intentionally separate from security-audit.yml: +# - security-audit.yml is the umbrella job (pip-audit + npm audit + +# cargo audit + OSV + Semgrep + secret scanning + SBOM + ...); +# it takes ~25 minutes and runs only when dep manifests change. +# - lockfile-audit.yml is a ~30 second pure-Python parse + grep on +# the lockfiles themselves; it runs on every PR that even nudges +# a lockfile so reviewers always see the audit result inline. + +name: Lockfile supply-chain audit + +on: + pull_request: + paths: + - 'studio/frontend/package-lock.json' + - 'studio/backend/core/data_recipe/oxc-validator/package-lock.json' + - 'studio/package-lock.json' + - 'studio/src-tauri/Cargo.lock' + - 'scripts/lockfile_supply_chain_audit.py' + - '.github/workflows/lockfile-audit.yml' + push: + branches: [main] + paths: + - 'studio/frontend/package-lock.json' + - 'studio/backend/core/data_recipe/oxc-validator/package-lock.json' + - 'studio/package-lock.json' + - 'studio/src-tauri/Cargo.lock' + - 'scripts/lockfile_supply_chain_audit.py' + - '.github/workflows/lockfile-audit.yml' + schedule: + - cron: '37 5 * * *' + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + audit: + name: lockfile supply-chain audit + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Verify audit script parses + run: python3 -c "import ast; ast.parse(open('scripts/lockfile_supply_chain_audit.py').read())" + + - name: Run lockfile supply-chain audit + # Default mode: only known-malicious pinned versions, known IOC + # strings, and structurally broken lockfiles fail the build. + # Missing-integrity and other structural anomalies are emitted + # as ::warning:: annotations and do not gate merges. + run: python3 scripts/lockfile_supply_chain_audit.py diff --git a/.gitignore b/.gitignore index bc7d59316d..a839633790 100644 --- a/.gitignore +++ b/.gitignore @@ -229,5 +229,9 @@ log.txt setup_leo.sh server.pid *.log +# Ignore stray lockfiles; real npm projects opt back in below (npm ci needs them). package-lock.json +!studio/frontend/package-lock.json +!studio/backend/core/data_recipe/oxc-validator/package-lock.json +!studio/package-lock.json llama.cpp/ diff --git a/scripts/lockfile_supply_chain_audit.py b/scripts/lockfile_supply_chain_audit.py index ae215bf344..ffeea51c23 100644 --- a/scripts/lockfile_supply_chain_audit.py +++ b/scripts/lockfile_supply_chain_audit.py @@ -389,6 +389,18 @@ class Finding: ) +def _gha_escape(text: str) -> str: + """Escape a string for use in a GitHub Actions `::warning::` / + `::error::` workflow command message. GH Actions truncates + annotation messages at the first newline unless `\\n` is + escaped as `%0A`; carriage returns and the percent sign need + matching escapes per the workflow-commands spec. Order matters: + `%` must be replaced first so the subsequent `%0A` / `%0D` + sequences are not double-encoded. + """ + return text.replace("%", "%25").replace("\r", "%0D").replace("\n", "%0A") + + # ───────────────────────────────────────────────────────────────────── # package-lock.json audit. # ───────────────────────────────────────────────────────────────────── @@ -397,9 +409,35 @@ class Finding: def audit_npm_lockfile(path: Path) -> list[Finding]: findings: list[Finding] = [] if not path.exists(): + # A missing requested lockfile is a config error, not a clean + # audit; surface it so a deleted default cannot pass silently. + findings.append( + Finding( + path = str(path), + package = "", + kind = "missing-lockfile", + detail = ( + "expected lockfile not found; refusing to silently " + "report a clean audit for a path that was not scanned" + ), + ) + ) return findings - raw = path.read_text(encoding = "utf-8") + try: + raw = path.read_text(encoding = "utf-8") + except OSError as exc: + # Permission denied, is-a-directory, broken-pipe etc. -- surface + # as a finding instead of crashing CI with a raw traceback. + findings.append( + Finding( + path = str(path), + package = "", + kind = "unreadable-lockfile", + detail = f"could not read file: {exc}", + ) + ) + return findings try: lock = json.loads(raw) except json.JSONDecodeError as exc: @@ -553,9 +591,32 @@ _PACKAGE_HEADER = re.compile(r"^\[\[package\]\]\s*$") def audit_cargo_lockfile(path: Path) -> list[Finding]: findings: list[Finding] = [] if not path.exists(): + # See audit_npm_lockfile: missing lockfile is a finding. + findings.append( + Finding( + path = str(path), + package = "", + kind = "missing-lockfile", + detail = ( + "expected lockfile not found; refusing to silently " + "report a clean audit for a path that was not scanned" + ), + ) + ) return findings - raw = path.read_text(encoding = "utf-8") + try: + raw = path.read_text(encoding = "utf-8") + except OSError as exc: + findings.append( + Finding( + path = str(path), + package = "", + kind = "unreadable-lockfile", + detail = f"could not read file: {exc}", + ) + ) + return findings try: import tomllib # type: ignore[import-not-found] except ImportError: @@ -652,7 +713,31 @@ def audit_cargo_lockfile(path: Path) -> list[Finding]: # ───────────────────────────────────────────────────────────────────── -DEFAULT_NPM_LOCKFILES = ("studio/frontend/package-lock.json",) +# Finding kinds split into BLOCKING vs ADVISORY for the default run mode. +# Blocking findings come from public supply-chain attack indicators (a +# version we know is malicious, a string an attacker would have to embed +# for an attack to work). Advisory findings are structural lockfile +# anomalies (missing integrity, non-default registry, etc.) -- they +# WARN the maintainer but do not block merges. Pass --strict to make +# every finding blocking (PR-5479-style behavior for opt-in adopters). +BLOCKING_KINDS: frozenset[str] = frozenset( + { + "blocked-known-malicious", + "known-ioc-string", + # Internal-failure kinds: a structurally broken lockfile MIGHT + # be hiding a real attack, so we keep these blocking too. + "malformed-lockfile", + "missing-lockfile", + "unreadable-lockfile", + "missing-toml-parser", + } +) + +DEFAULT_NPM_LOCKFILES = ( + "studio/frontend/package-lock.json", + "studio/backend/core/data_recipe/oxc-validator/package-lock.json", + "studio/package-lock.json", +) DEFAULT_CARGO_LOCKFILES = ("studio/src-tauri/Cargo.lock",) @@ -671,7 +756,9 @@ def main(argv: list[str] | None = None) -> int: default = None, help = ( "Path to a package-lock.json (repeatable). " - "Default: studio/frontend/package-lock.json." + "Default: studio/frontend/package-lock.json, " + "studio/backend/core/data_recipe/oxc-validator/package-lock.json, " + "and studio/package-lock.json (Tauri CLI for desktop release)." ), ) parser.add_argument( @@ -683,6 +770,18 @@ def main(argv: list[str] | None = None) -> int: "Default: studio/src-tauri/Cargo.lock." ), ) + parser.add_argument( + "--strict", + action = "store_true", + help = ( + "Treat every finding as blocking (exit 1). " + "Default mode only blocks on known-malicious versions, " + "indicator-of-compromise strings, or structurally broken " + "lockfiles; everything else is printed as an advisory " + "warning with exit 0. CI should use the default; local " + "audits aiming for zero noise can opt in via --strict." + ), + ) args = parser.parse_args(argv) # SF4: require a real justification (e.g. JIRA ticket id) for the @@ -715,8 +814,15 @@ def main(argv: list[str] | None = None) -> int: return 0 root = Path(args.root).resolve() - npm_paths = [root / p for p in (args.npm_lockfile or DEFAULT_NPM_LOCKFILES)] - cargo_paths = [root / p for p in (args.cargo_lockfile or DEFAULT_CARGO_LOCKFILES)] + # Explicit --npm-lockfile/--cargo-lockfile scopes the scan to those + # paths; defaults apply only to the no-args CI invocation. + _user_explicit = args.npm_lockfile is not None or args.cargo_lockfile is not None + if _user_explicit: + npm_paths = [root / p for p in (args.npm_lockfile or ())] + cargo_paths = [root / p for p in (args.cargo_lockfile or ())] + else: + npm_paths = [root / p for p in DEFAULT_NPM_LOCKFILES] + cargo_paths = [root / p for p in DEFAULT_CARGO_LOCKFILES] all_findings: list[Finding] = [] for p in npm_paths: @@ -734,17 +840,57 @@ def main(argv: list[str] | None = None) -> int: ) return 0 + # Split findings into blocking (known-malicious / IOC / structurally + # broken) and advisory (everything else, e.g. missing integrity on a + # registry-published tarball). In default mode advisory findings are + # printed but do not change the exit code; --strict treats every + # finding as blocking. + blocking = [f for f in all_findings if f.kind in BLOCKING_KINDS] + advisory = [f for f in all_findings if f.kind not in BLOCKING_KINDS] + + if args.strict: + blocking = list(all_findings) + advisory = [] + + if advisory: + print( + f"\n[lockfile-audit] {len(advisory)} advisory finding(s) " + "(non-blocking; pass --strict to fail the build on these):\n", + file = sys.stderr, + ) + for f in advisory: + # Surface in GitHub Actions UI as a warning annotation when run + # under Actions; harmless prefix elsewhere. GH Actions + # truncates annotation messages at the first newline unless + # newlines are escaped as `%0A`, so the full multi-line + # Finding (kind + path + package + detail) only renders in + # the UI after _gha_escape collapses it onto one line. + print(f"::warning::{_gha_escape(str(f))}", file = sys.stderr) + print(file = sys.stderr) + + if not blocking: + print( + f"[lockfile-audit] OK: {len(advisory)} advisory finding(s), " + "0 blocking. Run with --strict to escalate advisory findings.", + flush = True, + ) + return 0 + print( - f"\n[lockfile-audit] FAIL: {len(all_findings)} finding(s):\n", + f"\n[lockfile-audit] FAIL: {len(blocking)} blocking finding(s):\n", file = sys.stderr, ) - for f in all_findings: - print(str(f), file = sys.stderr) + for f in blocking: + # Same %-encoding rationale as the advisory branch above: the + # GH Actions annotation is truncated at the first newline + # unless the message is escaped. + print(f"::error::{_gha_escape(str(f))}", file = sys.stderr) print(file = sys.stderr) print( - "[lockfile-audit] Refusing to proceed. Each finding above is " - "either a structural lockfile anomaly or a public indicator-of-" - "compromise. Investigate before running `npm ci` or `cargo fetch`.", + "[lockfile-audit] Refusing to proceed. Each blocking finding " + "above is either a public indicator-of-compromise, a known-" + "malicious pinned version, or a structurally broken lockfile. " + "Investigate before running `npm ci` or `cargo fetch`.", file = sys.stderr, ) return 1 diff --git a/studio/backend/core/data_recipe/oxc-validator/package-lock.json b/studio/backend/core/data_recipe/oxc-validator/package-lock.json new file mode 100644 index 0000000000..bb2ae29b23 --- /dev/null +++ b/studio/backend/core/data_recipe/oxc-validator/package-lock.json @@ -0,0 +1,799 @@ +{ + "name": "unsloth-oxc-validator-runtime", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "unsloth-oxc-validator-runtime", + "version": "0.0.1", + "dependencies": { + "oxc-parser": "^0.123.0", + "oxlint": "^1.51.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-parser/binding-android-arm-eabi": { + "version": "0.123.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm-eabi/-/binding-android-arm-eabi-0.123.0.tgz", + "integrity": "sha512-EHQ58z+6DbZWokMOKg5AB1KuwrXVgfbBLuuLFfzdc7bI5A4igvdvjKMhUv1VBV+0FABiUCOjNKUmMF7ugprwbQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-android-arm64": { + "version": "0.123.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.123.0.tgz", + "integrity": "sha512-BK1E0zqNoHf38nTHjnGZ+olKHSKNHh65pChjY06yhaWYP8X7yNDqhQDA4neMPRqnPBgpN4/OW1oSMrdJgDi2aw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-darwin-arm64": { + "version": "0.123.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.123.0.tgz", + "integrity": "sha512-dkMPbtTbqU+cm+k4YGOBs4zAuq3Xu+wqjbGQvLAuVO7qHhNY4p5LBNudOmOoi0jxS8h1W6Jmlzv8MAKGpK+iDg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-darwin-x64": { + "version": "0.123.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.123.0.tgz", + "integrity": "sha512-85pic0rCd59DGdM69jI9xE/Snb2KtrfiU48QigjJXjzxUOenGvH4SAFIjFpO/2ZnI3Kz50D8pht4jKN3t2022Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-freebsd-x64": { + "version": "0.123.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.123.0.tgz", + "integrity": "sha512-mjEiW6z7JtaiHMK/8aJic1lfjkKpzFwK2XFNmm187BFbtDamjGVuKNr2TEyrFEYJyZc217wokR1wrYeZGBQo4Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm-gnueabihf": { + "version": "0.123.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.123.0.tgz", + "integrity": "sha512-mYxigPtGt6SZfhNZBIJfuDM92cLo8XUW08WuKxzHvcmWu6xndLqwLp99Vg4uHke1AXicQEHU3Wri2X9bHF0Vlw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm-musleabihf": { + "version": "0.123.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.123.0.tgz", + "integrity": "sha512-ttWirDC9eUBn0R4Tzz3aeDaLrx9drPdNiLJ8MXeDBFxd6cwLfTIC27qjsdfGpn942tkVIZY3sjWAnvbwDDjX7g==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm64-gnu": { + "version": "0.123.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.123.0.tgz", + "integrity": "sha512-apAHyoMNRYT+2G98Y14caZmsr5LD9PsWpGI7nXmSwK26LGiQneCU6HvHQ+d+AX+RJ5TTWZtEb2RD7OLqAC0cYQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm64-musl": { + "version": "0.123.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.123.0.tgz", + "integrity": "sha512-3r99Qa4egjO/iXUBxTlN6Ddt1YkLifG6olzvj8gkoKEK2U/MOW7mQfXRyBmuoMgmZ7O4vk41gO3d21c6VcN3yQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-ppc64-gnu": { + "version": "0.123.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.123.0.tgz", + "integrity": "sha512-Hr/Z24kUE4pjJs346g80WDwjyJGrxiw6hExJuOiME/76ZFz68y5L11UzprRkW9FN4HxBB7tLZ/fytczV2fEsiA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-riscv64-gnu": { + "version": "0.123.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.123.0.tgz", + "integrity": "sha512-sxjbhs+8WXeuoLnZ2rBmQ96gPdq3SCmz24reIltsKLUt1EDMgdaQsr7RqwBphw3QAImkMtlPQfAWDWwZyo0xDg==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-riscv64-musl": { + "version": "0.123.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.123.0.tgz", + "integrity": "sha512-d6xHHhqldA/W+VC7v8uHs24zM69Ad3HnHQ45h+uuBhCsbZx3d0E0wL2K3uJ5mYKTR6UPMFk9VMXcHWwvg1PRZQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-s390x-gnu": { + "version": "0.123.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.123.0.tgz", + "integrity": "sha512-+di9A5wJQlv0VodyhADjJ2rC4geyHY+uhJDl3TFjMgYhhlgLZchi9uHD5mfiUEDWHt1x7/eU2u1ge3LLazZmFw==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-x64-gnu": { + "version": "0.123.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.123.0.tgz", + "integrity": "sha512-sh7pw2g/u6LE1TaRRQsV9Kv9+1y+CywaaNwWWP+3bnEPk/L692oTG0hmEviUlawI8v3OGC+AhbjtAD+HXWQAkg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-x64-musl": { + "version": "0.123.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.123.0.tgz", + "integrity": "sha512-S+LoD8PiJ639JwIqK1knIeqAyYkeCbLHtAgfapszKX0yVCaYP+aer8dJxL25de9qcDjvYWVrYCkuDZzHmOl2Xw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-openharmony-arm64": { + "version": "0.123.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-openharmony-arm64/-/binding-openharmony-arm64-0.123.0.tgz", + "integrity": "sha512-/65vryK11q1I+k+7ukDlwZOxUFCLYsoZBZPGZHyet5bIP5e3D8mV3uCuvpWZ9Hoe6vUZFw/nAfCrX59MeuJPgw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-wasm32-wasi": { + "version": "0.123.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.123.0.tgz", + "integrity": "sha512-y4OsMGQiAbZzj2Rq0LEfvhR48rQDvbvqsl/dPdn4tdf+z3H79nZuR+lQ/+KUGjD30vpVGem138sBWHFj9UR+Vg==", + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.1.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-parser/binding-win32-arm64-msvc": { + "version": "0.123.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.123.0.tgz", + "integrity": "sha512-9lBqI6AXAkjYavkdpizNU3Q51uoVYfp9FJPx19hnCEdPku1jSgzSnvgmCvhCue0GziIvIvIdWgZ41wXQ3EOoBw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-win32-ia32-msvc": { + "version": "0.123.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.123.0.tgz", + "integrity": "sha512-zJbqBHwSUB7CyvAONy9ewGtQwcQj+ylOhYGETvUPp3KIYx7lolj4Gayof7iA22SU5eMSjO5COL0c8wYhmn9agA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-win32-x64-msvc": { + "version": "0.123.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.123.0.tgz", + "integrity": "sha512-q7RZvglQvGo3RX5ljtcGSabu2B2c0oDU/6xC3sBMhsV5KRo0PvyxLdordbEN31NTfuZu4Sgl86C76cAURZIHWA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.123.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.123.0.tgz", + "integrity": "sha512-YtECP/y8Mj1lSHiUWGSRzy/C6teUKlS87dEfuVKT09LgQbUsBW1rNg+MiJ4buGu3yuADV60gbIvo9/HplA56Ew==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@oxlint/binding-android-arm-eabi": { + "version": "1.64.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.64.0.tgz", + "integrity": "sha512-2r6Nq3XXGLHEXKkSj8JtmJ6N4gDw431DPFOg0ZoJHlNjnG6HVMm/ksQ10m0HJ8WBvwgMe1L50UHPaYZutCRPCw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-android-arm64": { + "version": "1.64.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.64.0.tgz", + "integrity": "sha512-ePJMpePgg7fBv+L/hVx1xXRU5/5gd5m0obLA6hPEfLXF3GjpR8idIDbY1dhQYhyz1ms2wdTccSboo6KEd2Oxtg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-darwin-arm64": { + "version": "1.64.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.64.0.tgz", + "integrity": "sha512-U4DMLQd10gJLuoSTLSGbfv3bGjTlUNsScm9Dgb8wwBqmCzidf1pE1pXV4doGNxqwH3KtVng1AGTINA0NvkGLvQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-darwin-x64": { + "version": "1.64.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.64.0.tgz", + "integrity": "sha512-GoRIL48QWm4/TAvjN8pB1nAG+1/uqc9EdnWT9zqHeb6wsmjZtywj8VRe5aGW47Fdb64YtLOsdLqVxOvQuz98Wg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-freebsd-x64": { + "version": "1.64.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.64.0.tgz", + "integrity": "sha512-5dFkv4tkg7PxJJGS9/OjrJwjhuHczrd3OQOkRE0wHcLM+ncUnULtzEPWjqGOxTXxZnLWcB91bGiIznx89TVXyQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm-gnueabihf": { + "version": "1.64.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.64.0.tgz", + "integrity": "sha512-jsBqMLl/uOL5+Kq/+BtK9FrmiNGUbx8SiyZXv+WlUxA45KuwcLu9BfiSIL3I3DBDgWM3yZizDITnTK9BcqNBQg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm-musleabihf": { + "version": "1.64.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.64.0.tgz", + "integrity": "sha512-1lrj8At/Uuc9GhjrVFBQo0NEjfBrTkzpmtHIGAhNnIXqn1CAyGL+qrztUsXb2GIluJrpl9Q7qRLJOb/NqydacQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm64-gnu": { + "version": "1.64.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.64.0.tgz", + "integrity": "sha512-HpSQbubwh03mMhAdy2BYtad/fsY8vDFHDAb6bUwuCYg2VD3xCQgn6ArKcO0oZyLCheacKTv4PrF3Mfu5hgoE2g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm64-musl": { + "version": "1.64.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.64.0.tgz", + "integrity": "sha512-00QQ0h0Y7u0G69BgiH3+ky2aaq/QvkDL6DYok8htIuJHxybiux5aQ8jwmg8qIk9wha6UagUP2BAwAzbemcJbpg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-ppc64-gnu": { + "version": "1.64.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.64.0.tgz", + "integrity": "sha512-2GaimTV6EMW+s5HS0An3oGbQme3BgHswvfVdGk3EB57Xe9+/gyT+Qd7lNVzb3rtir52vbIPzXfaYArzs5b5zcw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-riscv64-gnu": { + "version": "1.64.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.64.0.tgz", + "integrity": "sha512-H46AtFb9wypjoVwGdlxrm0DsD809NGmtiK9HiyPKTxkSte2YjhC4S+00rOIrwCaxcyPiGid3Y3OMXp5KMAkGZw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-riscv64-musl": { + "version": "1.64.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.64.0.tgz", + "integrity": "sha512-HEgsidjjvvyzdg82icYkuFCf7REDV7B9JFwbIMbVwrKLBY0MrXX+bku3POn/hduZ2yW91IyVDUMq0Bf02KwXQw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-s390x-gnu": { + "version": "1.64.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.64.0.tgz", + "integrity": "sha512-Axvm8qryotmKN00P5w4JapaSjvP2LOSbdbBJiX+2SuHd3QzhW7TUc8skqgw+ahQZ5DmzEYeHCqauvW8f32Ns6Q==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-x64-gnu": { + "version": "1.64.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.64.0.tgz", + "integrity": "sha512-cR60vSd7+m+KRZ3GQGfDxWwahW5RMXg0qlGvAluZr0fTUYvw0H9N9AXAF/M/PMqgytyqvVNmBAkJG9l7U30Y1g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-x64-musl": { + "version": "1.64.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.64.0.tgz", + "integrity": "sha512-2u/aPZ9pEg7HnvZPDsHxUGNnrpr4qaHi+mCgLgpt+LYRzPrS4Px4wPfkIdRdr2GvKnaYyt+XSlto0Vm5sbStTg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-openharmony-arm64": { + "version": "1.64.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.64.0.tgz", + "integrity": "sha512-kfhkGfCdoXLSxEkrhDlJrvBYajGmq+ma4EMc53dsOWTq+rIBOlI0vTBmpZNnM5oH2LY/K/w1HAK+UQEgjgpVUg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-arm64-msvc": { + "version": "1.64.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.64.0.tgz", + "integrity": "sha512-r/cNKBFieONoVu2bb1KkVouq9W+edDUgHumXJGphCRRj+U0xaD4nanrw8ZOqo0IsutPkEM4vCcGBpak6x5aXMg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-ia32-msvc": { + "version": "1.64.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.64.0.tgz", + "integrity": "sha512-tUw0xUUwEFVZbpJoeCblkv8SJA4Xz3CdXCJbAnBsiNLyxDrk2tLcxEAS6M73Q7hHHDg3OtwI8vZVK3t5RJt4Gw==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-x64-msvc": { + "version": "1.64.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.64.0.tgz", + "integrity": "sha512-9CBR+LO0JVST87fNTzzNxS5I29jIUO5gxT9i9+M3SDHHALElj9sY1Prf12tad3vIRC6OD7Ehtvvh+sn13vSwHw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/oxc-parser": { + "version": "0.123.0", + "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.123.0.tgz", + "integrity": "sha512-F6ak0tFc01ZGbl5KxvLDQ2K005Z086mp3ByCQBDhUjqXLkapGUkMuJSsYixncdEpkLlcRDcruHR71LD339ADUA==", + "license": "MIT", + "dependencies": { + "@oxc-project/types": "^0.123.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxc-parser/binding-android-arm-eabi": "0.123.0", + "@oxc-parser/binding-android-arm64": "0.123.0", + "@oxc-parser/binding-darwin-arm64": "0.123.0", + "@oxc-parser/binding-darwin-x64": "0.123.0", + "@oxc-parser/binding-freebsd-x64": "0.123.0", + "@oxc-parser/binding-linux-arm-gnueabihf": "0.123.0", + "@oxc-parser/binding-linux-arm-musleabihf": "0.123.0", + "@oxc-parser/binding-linux-arm64-gnu": "0.123.0", + "@oxc-parser/binding-linux-arm64-musl": "0.123.0", + "@oxc-parser/binding-linux-ppc64-gnu": "0.123.0", + "@oxc-parser/binding-linux-riscv64-gnu": "0.123.0", + "@oxc-parser/binding-linux-riscv64-musl": "0.123.0", + "@oxc-parser/binding-linux-s390x-gnu": "0.123.0", + "@oxc-parser/binding-linux-x64-gnu": "0.123.0", + "@oxc-parser/binding-linux-x64-musl": "0.123.0", + "@oxc-parser/binding-openharmony-arm64": "0.123.0", + "@oxc-parser/binding-wasm32-wasi": "0.123.0", + "@oxc-parser/binding-win32-arm64-msvc": "0.123.0", + "@oxc-parser/binding-win32-ia32-msvc": "0.123.0", + "@oxc-parser/binding-win32-x64-msvc": "0.123.0" + } + }, + "node_modules/oxlint": { + "version": "1.64.0", + "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.64.0.tgz", + "integrity": "sha512-Star3SNpWPeWFPw7kRXIhXUSn6fdiAl25q15CQzH/9WaOtG6e9CWTc25vNZOCr4PE1yEP1GtKJKIKglhj3OmEQ==", + "license": "MIT", + "bin": { + "oxlint": "bin/oxlint" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxlint/binding-android-arm-eabi": "1.64.0", + "@oxlint/binding-android-arm64": "1.64.0", + "@oxlint/binding-darwin-arm64": "1.64.0", + "@oxlint/binding-darwin-x64": "1.64.0", + "@oxlint/binding-freebsd-x64": "1.64.0", + "@oxlint/binding-linux-arm-gnueabihf": "1.64.0", + "@oxlint/binding-linux-arm-musleabihf": "1.64.0", + "@oxlint/binding-linux-arm64-gnu": "1.64.0", + "@oxlint/binding-linux-arm64-musl": "1.64.0", + "@oxlint/binding-linux-ppc64-gnu": "1.64.0", + "@oxlint/binding-linux-riscv64-gnu": "1.64.0", + "@oxlint/binding-linux-riscv64-musl": "1.64.0", + "@oxlint/binding-linux-s390x-gnu": "1.64.0", + "@oxlint/binding-linux-x64-gnu": "1.64.0", + "@oxlint/binding-linux-x64-musl": "1.64.0", + "@oxlint/binding-openharmony-arm64": "1.64.0", + "@oxlint/binding-win32-arm64-msvc": "1.64.0", + "@oxlint/binding-win32-ia32-msvc": "1.64.0", + "@oxlint/binding-win32-x64-msvc": "1.64.0" + }, + "peerDependencies": { + "oxlint-tsgolint": ">=0.22.1" + }, + "peerDependenciesMeta": { + "oxlint-tsgolint": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "optional": true + } + } +} diff --git a/studio/package-lock.json b/studio/package-lock.json new file mode 100644 index 0000000000..b5ed4ea2f3 --- /dev/null +++ b/studio/package-lock.json @@ -0,0 +1,233 @@ +{ + "name": "unsloth-studio-tauri-cli", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "unsloth-studio-tauri-cli", + "version": "0.0.0", + "license": "AGPL-3.0-only", + "devDependencies": { + "@tauri-apps/cli": "2.10.1" + } + }, + "node_modules/@tauri-apps/cli": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.10.1.tgz", + "integrity": "sha512-jQNGF/5quwORdZSSLtTluyKQ+o6SMa/AUICfhf4egCGFdMHqWssApVgYSbg+jmrZoc8e1DscNvjTnXtlHLS11g==", + "dev": true, + "license": "Apache-2.0 OR MIT", + "bin": { + "tauri": "tauri.js" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + }, + "optionalDependencies": { + "@tauri-apps/cli-darwin-arm64": "2.10.1", + "@tauri-apps/cli-darwin-x64": "2.10.1", + "@tauri-apps/cli-linux-arm-gnueabihf": "2.10.1", + "@tauri-apps/cli-linux-arm64-gnu": "2.10.1", + "@tauri-apps/cli-linux-arm64-musl": "2.10.1", + "@tauri-apps/cli-linux-riscv64-gnu": "2.10.1", + "@tauri-apps/cli-linux-x64-gnu": "2.10.1", + "@tauri-apps/cli-linux-x64-musl": "2.10.1", + "@tauri-apps/cli-win32-arm64-msvc": "2.10.1", + "@tauri-apps/cli-win32-ia32-msvc": "2.10.1", + "@tauri-apps/cli-win32-x64-msvc": "2.10.1" + } + }, + "node_modules/@tauri-apps/cli-darwin-arm64": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.10.1.tgz", + "integrity": "sha512-Z2OjCXiZ+fbYZy7PmP3WRnOpM9+Fy+oonKDEmUE6MwN4IGaYqgceTjwHucc/kEEYZos5GICve35f7ZiizgqEnQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-darwin-x64": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.10.1.tgz", + "integrity": "sha512-V/irQVvjPMGOTQqNj55PnQPVuH4VJP8vZCN7ajnj+ZS8Kom1tEM2hR3qbbIRoS3dBKs5mbG8yg1WC+97dq17Pw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.10.1.tgz", + "integrity": "sha512-Hyzwsb4VnCWKGfTw+wSt15Z2pLw2f0JdFBfq2vHBOBhvg7oi6uhKiF87hmbXOBXUZaGkyRDkCHsdzJcIfoJC2w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-gnu": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.10.1.tgz", + "integrity": "sha512-OyOYs2t5GkBIvyWjA1+h4CZxTcdz1OZPCWAPz5DYEfB0cnWHERTnQ/SLayQzncrT0kwRoSfSz9KxenkyJoTelA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-musl": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.10.1.tgz", + "integrity": "sha512-MIj78PDDGjkg3NqGptDOGgfXks7SYJwhiMh8SBoZS+vfdz7yP5jN18bNaLnDhsVIPARcAhE1TlsZe/8Yxo2zqg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-riscv64-gnu": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.10.1.tgz", + "integrity": "sha512-X0lvOVUg8PCVaoEtEAnpxmnkwlE1gcMDTqfhbefICKDnOTJ5Est3qL0SrWxizDackIOKBcvtpejrSiVpuJI1kw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-gnu": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.10.1.tgz", + "integrity": "sha512-2/12bEzsJS9fAKybxgicCDFxYD1WEI9kO+tlDwX5znWG2GwMBaiWcmhGlZ8fi+DMe9CXlcVarMTYc0L3REIRxw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-musl": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.10.1.tgz", + "integrity": "sha512-Y8J0ZzswPz50UcGOFuXGEMrxbjwKSPgXftx5qnkuMs2rmwQB5ssvLb6tn54wDSYxe7S6vlLob9vt0VKuNOaCIQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-arm64-msvc": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.10.1.tgz", + "integrity": "sha512-iSt5B86jHYAPJa/IlYw++SXtFPGnWtFJriHn7X0NFBVunF6zu9+/zOn8OgqIWSl8RgzhLGXQEEtGBdR4wzpVgg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-ia32-msvc": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.10.1.tgz", + "integrity": "sha512-gXyxgEzsFegmnWywYU5pEBURkcFN/Oo45EAwvZrHMh+zUSEAvO5E8TXsgPADYm31d1u7OQU3O3HsYfVBf2moHw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-x64-msvc": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.10.1.tgz", + "integrity": "sha512-6Cn7YpPFwzChy0ERz6djKEmUehWrYlM+xTaNzGPgZocw3BD7OfwfWHKVWxXzdjEW2KfKkHddfdxK1XXTYqBRLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + } + } +} diff --git a/studio/package.json b/studio/package.json new file mode 100644 index 0000000000..ac4789e874 --- /dev/null +++ b/studio/package.json @@ -0,0 +1,10 @@ +{ + "name": "unsloth-studio-tauri-cli", + "version": "0.0.0", + "private": true, + "description": "Lockfile holder for @tauri-apps/cli used by the desktop release workflow. Not a real npm package; `npm ci --prefix studio` resolves the pinned Tauri CLI from this directory's package-lock.json.", + "license": "AGPL-3.0-only", + "devDependencies": { + "@tauri-apps/cli": "2.10.1" + } +} diff --git a/tests/security/test_lockfile_supply_chain_audit.py b/tests/security/test_lockfile_supply_chain_audit.py index 275794da9c..483bb9e763 100644 --- a/tests/security/test_lockfile_supply_chain_audit.py +++ b/tests/security/test_lockfile_supply_chain_audit.py @@ -27,9 +27,12 @@ def _run_auditor( root: Path, npm_lockfiles: list[Path] | None = None, cargo_lockfiles: list[Path] | None = None, + strict: bool = False, timeout: int = 30, ) -> subprocess.CompletedProcess: cmd = [sys.executable, str(SCRIPT), "--root", str(root)] + if strict: + cmd.append("--strict") for p in npm_lockfiles or []: cmd.extend(["--npm-lockfile", str(p)]) for p in cargo_lockfiles or []: @@ -170,6 +173,32 @@ checksum = "0000000000000000000000000000000000000000000000000000000000000000" def test_malicious_cargo_lockfile_refused(tmp_path): """Inline Cargo.lock with `source = "git+https://example.com/..."` must trip the `non-registry-cargo-source` check. + + `non-registry-cargo-source` is an advisory finding kind in the + auditor's default mode (per the audit script's BLOCKING_KINDS + set). To exercise the historical "refuse to install" behavior we + pass --strict here; that promotes every finding to blocking and + keeps the test honest about its intent (detection + refusal). + """ + lockfile = tmp_path / "Cargo.lock" + lockfile.write_text(_MALICIOUS_CARGO_LOCK) + proc = _run_auditor( + root = tmp_path, + npm_lockfiles = [FIXTURES / "clean_lockfile.json"], + cargo_lockfiles = [lockfile], + strict = True, + ) + assert proc.returncode == 1 + combined = proc.stdout + proc.stderr + assert "non-registry-cargo-source" in combined + assert "git+https://example.com" in combined + + +def test_malicious_cargo_lockfile_default_mode_advisory(tmp_path): + """Default (non-strict) mode classifies `non-registry-cargo-source` + as advisory: the finding is still emitted as a `::warning::` + annotation but the process exits 0 so the build is not gated. + Regression test for the advisory/strict split. """ lockfile = tmp_path / "Cargo.lock" lockfile.write_text(_MALICIOUS_CARGO_LOCK) @@ -178,10 +207,13 @@ def test_malicious_cargo_lockfile_refused(tmp_path): npm_lockfiles = [FIXTURES / "clean_lockfile.json"], cargo_lockfiles = [lockfile], ) - assert proc.returncode == 1 + assert proc.returncode == 0, ( + f"expected exit 0 (advisory), got {proc.returncode}\n" + f"--- stdout ---\n{proc.stdout}\n--- stderr ---\n{proc.stderr}" + ) combined = proc.stdout + proc.stderr assert "non-registry-cargo-source" in combined - assert "git+https://example.com" in combined + assert "advisory finding" in combined def test_audit_cargo_lockfile_direct_call(tmp_path): @@ -192,6 +224,75 @@ def test_audit_cargo_lockfile_direct_call(tmp_path): assert "non-registry-cargo-source" in kinds +# --------------------------------------------------------------------------- +# GitHub Actions annotation escape: ::warning:: / ::error:: messages +# are truncated at the first newline unless escaped, so the multi-line +# Finding must be collapsed via the spec'd %0A / %0D / %25 encoding. +# --------------------------------------------------------------------------- + + +def test_gha_escape_collapses_finding_to_one_line(): + """`_gha_escape()` must collapse newlines (`%0A`), carriage + returns (`%0D`), and percent signs (`%25`) so that + `::warning::` / `::error::` render the full finding + in the GitHub Actions UI annotation instead of being truncated + at the first newline. The `%` replacement must happen first or + the subsequent `%0A` / `%0D` escapes get double-encoded. + """ + assert lsa._gha_escape("a\nb\nc") == "a%0Ab%0Ac" + assert lsa._gha_escape("a\rb") == "a%0Db" + assert lsa._gha_escape("100%") == "100%25" + # Order regression: `%` must escape before `\n` so the literal + # text `a%b\nc` becomes `a%25b%0Ac`, not `a%250Ab%0Ac`. + assert lsa._gha_escape("a%b\nc") == "a%25b%0Ac" + + f = lsa.Finding( + path = "/x/lock.json", + package = "node_modules/foo", + kind = "missing-integrity-hash", + detail = "bad stuff", + ) + escaped = lsa._gha_escape(str(f)) + assert "\n" not in escaped + assert "%0A" in escaped + assert "missing-integrity-hash" in escaped + assert "node_modules/foo" in escaped + assert "bad stuff" in escaped + + +def test_advisory_finding_emitted_as_single_line_annotation(tmp_path): + """End-to-end check: the `::warning::` line emitted for an + advisory finding must be a SINGLE physical line (the rest of + the Finding is `%0A`-escaped inside the message). Regression + test for the gemini-code-assist review on PR #5604: without + `_gha_escape`, GitHub Actions truncates the annotation after + `[kind] path` and the package + detail fields never render. + """ + lockfile = tmp_path / "Cargo.lock" + lockfile.write_text(_MALICIOUS_CARGO_LOCK) + proc = _run_auditor( + root = tmp_path, + npm_lockfiles = [FIXTURES / "clean_lockfile.json"], + cargo_lockfiles = [lockfile], + ) + warning_lines = [ + line for line in proc.stderr.splitlines() if line.startswith("::warning::") + ] + assert warning_lines, ( + "expected at least one ::warning:: annotation; " f"stderr was:\n{proc.stderr}" + ) + for line in warning_lines: + # Single physical line: kind, package, detail all present + # via %0A escape, not split across stderr lines. + assert "%0A" in line, ( + f"::warning:: line has no %0A escape; multi-line text " + f"would be truncated by GH Actions:\n{line}" + ) + assert "non-registry-cargo-source" in line + assert "package:" in line + assert "detail:" in line + + # --------------------------------------------------------------------------- # SF4: skip env var requires a justification value. # ---------------------------------------------------------------------------