Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (6)
✅ Files skipped from review due to trivial changes (1)
📜 Recent review details⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
🧰 Additional context used📓 Path-based instructions (2)pacquet/**/*.rs📄 CodeRabbit inference engine (pacquet/AGENTS.md)
Files:
**/*.{ts,tsx}📄 CodeRabbit inference engine (AGENTS.md)
Files:
🧠 Learnings (4)📚 Learning: 2026-05-20T19:40:55.051ZApplied to files:
📚 Learning: 2026-05-22T00:08:44.646ZApplied to files:
📚 Learning: 2026-05-20T23:07:58.444ZApplied to files:
📚 Learning: 2026-05-14T09:04:00.133ZApplied to files:
🔇 Additional comments (7)
📝 WalkthroughWalkthroughThis PR rejects git resolution commit values that are not 40-character hexadecimal SHAs before invoking git, implementing the check in Node.js and Rust git-fetchers, adding error diagnostics, tests, and a changeset documenting the patch release. ChangesGit Commit Hash Validation Security Fix
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Review Summary by QodoReject non-SHA commit values before invoking git
WalkthroughsDescription• Validate git commit hashes before passing to git commands - Rejects non-40-character hexadecimal SHA values - Prevents command injection via malicious lockfile entries • Implements validation in both pnpm and pacquet git fetchers • Adds comprehensive tests for partial commits and option-shaped values Diagramflowchart LR
A["Malicious lockfile<br/>with commit option"] -->|"resolution.commit"| B["Git Fetcher Entry"]
B -->|"Validate SHA"| C{"Is 40-char<br/>hex SHA?"}
C -->|"No"| D["Throw INVALID_GIT_COMMIT"]
C -->|"Yes"| E["Proceed to git fetch"]
D -->|"Prevents"| F["Command Injection"]
File Changes1. fetching/git-fetcher/src/index.ts
|
Micro-Benchmark ResultsLinux |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.changeset/git-fetcher-reject-non-sha-commits.md:
- Line 2: Update the changeset so the package entry uses the correct package
name: replace the string "`@pnpm/fetching.git-fetcher`" with "`@pnpm/fetching`" in
the changeset header/body so the patch-level bump targets the intended package
(`@pnpm/fetching`) instead of the incorrect `@pnpm/fetching.git-fetcher`.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: b956b20c-8cdd-48b3-ad42-67d5dd8bdea1
📒 Files selected for processing (6)
.changeset/git-fetcher-reject-non-sha-commits.mdfetching/git-fetcher/src/index.tsfetching/git-fetcher/test/index.tspacquet/crates/git-fetcher/src/error.rspacquet/crates/git-fetcher/src/fetcher.rspacquet/crates/git-fetcher/src/fetcher/tests.rs
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
- GitHub Check: Lint and Test (macos-latest)
- GitHub Check: Lint and Test (windows-latest)
- GitHub Check: Run benchmark on ubuntu-latest
- GitHub Check: Lint and Test (ubuntu-latest)
- GitHub Check: Run benchmark on ubuntu-latest
- GitHub Check: Code Coverage
- GitHub Check: Compile & Lint
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Follow Standard Style with trailing commas, preferring functions over classes, and declaring functions after they are used (relying on hoisting)
Use a single options object instead of multiple parameters when a function needs more than two or three arguments
Follow Import Order: Standard libraries first, then external dependencies (alphabetically), then relative imports
Write self-documenting code where function names, parameters, and types explain what a function does without requiring prose comments
Do not write comments that restate what the code already says; refactor via renaming, splitting helpers, or restructuring instead
Do not repeat documentation at call sites that already exists in JSDoc on the callee; update JSDoc once for all call sites to benefit
Use JSDoc only for a function's contract (preconditions, postconditions, edge cases, why the function exists), not for re-narrating the body
Do not record past implementation shape, refactor history, or 'the previous code did X' framing in code; use git log and git blame instead
Write comments only when: the reason for code is non-obvious (hidden invariant, workaround for known bug, deliberate exception), or the right name doesn't fit (temporary technical constraint)
Files:
fetching/git-fetcher/src/index.tsfetching/git-fetcher/test/index.ts
pacquet/**/*.rs
📄 CodeRabbit inference engine (pacquet/AGENTS.md)
pacquet/**/*.rs: When porting a function that firespnpm:<channel>events throughglobalLogger,logger.debug(), orstreamParser.write(), mirror the call site, payload, and ordering so the reporter parses pacquet's NDJSON the same way it parses pnpm's.
Declare a newtype wrapper for branded string types. Do not collapse the brand into a plainStringor&str.
If upstream always validates before construction, validate in pacquet's wrapper too. The wrapper must construct only viaTryFrom<String>and/orFromStr. Do not provide an infallible public constructor.
If upstream never validates, just brand for type-safety. Expose an infallibleFrom<String>(andFrom<&str>when convenient).
If upstream occasionally constructs without validation, exposefrom_str_uncheckedas an escape hatch alongside the validating constructor.
Match upstream serde behavior for branded types that cross JSON, YAML, or INI boundaries. Use#[serde(try_from = "String")]for deserialization and#[serde(into = "String")]for serialization.
Use#[derive(derive_more::From)]and#[derive(derive_more::Into)]for mechanical conversion impls. Fall back to manualimplonly when conversion needs custom logic.
String-literal unions should becomeenums, not newtype wrappers. Model closed sets of valid string values as enums.
Template literal types should be treated as branded strings with validation discipline from rules 2-5.
Choose owned vs. borrowed parameters to minimize copies. Widen to the most encompassing type (&Pathover&PathBuf,&strover&String) when it doesn't force extra copies.
PreferArc::clone(&x)/Rc::clone(&x)overx.clone()for reference-counted types, so the cost is visible at the call site.
Follow Rust API Guidelines for naming conventions.
Do not use star imports inside module bodies. Writeuse super::{Foo, bar}instead ofuse super::*;. Two forms stay allowed: external-crate preludes likeuse rayon::prelude::*;and root-of-module re-...
Files:
pacquet/crates/git-fetcher/src/error.rspacquet/crates/git-fetcher/src/fetcher.rspacquet/crates/git-fetcher/src/fetcher/tests.rs
🧠 Learnings (4)
📚 Learning: 2026-05-14T09:04:00.133Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11622
File: resolving/npm-resolver/test/publishedBy.test.ts:350-354
Timestamp: 2026-05-14T09:04:00.133Z
Learning: In the pnpm/pnpm repository, ESLint is the authoritative style linter. Do not raise review findings for missing trailing commas in multiline function calls (e.g., `fs.writeFileSync(...)`) when this repo’s ESLint configuration does not report them and lint passes. Prefer deferring to the ESLint results for this specific trailing-comma rule rather than enforcing it manually in code review.
Applied to files:
fetching/git-fetcher/src/index.tsfetching/git-fetcher/test/index.ts
📚 Learning: 2026-05-20T19:40:55.051Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11774
File: pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs:0-0
Timestamp: 2026-05-20T19:40:55.051Z
Learning: In the pacquet Rust code, ensure the semver implementation uses the `node-semver` crate (not `nodejs-semver`). `node-semver`’s public API does not include a `satisfies_with_prerelease`-style method; prerelease-tolerant matching should be implemented inline by first calling `Range::satisfies`, and when it rejects a prerelease version, retry matching against a stripped `MAJOR.MINOR.PATCH` base of the prerelease version.
Applied to files:
pacquet/crates/git-fetcher/src/error.rspacquet/crates/git-fetcher/src/fetcher.rspacquet/crates/git-fetcher/src/fetcher/tests.rs
📚 Learning: 2026-05-22T00:08:44.646Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11837
File: pacquet/crates/resolving-npm-resolver/src/pick_package.rs:33-51
Timestamp: 2026-05-22T00:08:44.646Z
Learning: In the pnpm/pnpm repo’s pacquet Rust crates, do not flag Unicode ellipsis characters (U+2026, `…`) in Rust doc comments (`///` / `/** */`) as a lint violation. The pacquet crate’s `dylint.toml` only enables `perfectionist::derive_ordering`, and the Dylint `unicode-ellipsis` rule is not enabled for this project—so `…` in doc comments is an intentional, repo-consistent style.
Applied to files:
pacquet/crates/git-fetcher/src/error.rspacquet/crates/git-fetcher/src/fetcher.rspacquet/crates/git-fetcher/src/fetcher/tests.rs
📚 Learning: 2026-05-20T23:07:58.444Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11784
File: pacquet/crates/resolving-deps-resolver/src/hoist_peers.rs:120-133
Timestamp: 2026-05-20T23:07:58.444Z
Learning: When reviewing code in this pacquet Rust port, follow the upstream pnpm compatibility rule: only match pnpm’s behavior exactly. Do not propose review changes that intentionally deviate from pnpm’s documented/observed behavior, even if pnpm appears buggy. If you identify a real bug in pnpm behavior, the review should prioritize fixing it upstream in pnpm first, and avoid implementing a pnpm-behavior workaround here unless the same fix has already landed upstream.
Applied to files:
pacquet/crates/git-fetcher/src/error.rspacquet/crates/git-fetcher/src/fetcher.rspacquet/crates/git-fetcher/src/fetcher/tests.rs
🔇 Additional comments (5)
fetching/git-fetcher/src/index.ts (1)
29-31: LGTM!Also applies to: 84-86
fetching/git-fetcher/test/index.ts (1)
230-246: LGTM!Also applies to: 248-262
pacquet/crates/git-fetcher/src/error.rs (1)
92-101: LGTM!pacquet/crates/git-fetcher/src/fetcher.rs (1)
109-114: LGTM!Also applies to: 235-240
pacquet/crates/git-fetcher/src/fetcher/tests.rs (1)
1-1: LGTM!Also applies to: 87-134
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #11967 +/- ##
=======================================
Coverage 87.93% 87.94%
=======================================
Files 228 228
Lines 27810 27819 +9
=======================================
+ Hits 24456 24466 +10
+ Misses 3354 3353 -1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Integrated-Benchmark Report (Linux)Scenario: Isolated linker: fresh restore, cold cache + cold store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 1.9934191823600003,
"stddev": 0.15113710489693202,
"median": 1.94583643336,
"user": 2.72395318,
"system": 3.3874479600000003,
"min": 1.8912364698600002,
"max": 2.40090932686,
"times": [
1.96199417286,
1.93912603686,
1.91565222086,
1.95254682986,
1.9157912138600002,
1.8912364698600002,
2.05183057586,
2.40090932686,
1.90427928886,
2.00082568786
]
},
{
"command": "pacquet@main",
"mean": 1.9618813851599999,
"stddev": 0.06358985131235681,
"median": 1.9294572198600002,
"user": 2.7131698799999997,
"system": 3.3822848599999995,
"min": 1.90153662386,
"max": 2.09855010086,
"times": [
1.90153662386,
1.92518842886,
2.09855010086,
1.9183675528600002,
1.93372601086,
1.96193771386,
1.92013155586,
1.92224132686,
2.0308081518599996,
2.00632638586
]
}
]
}Scenario: Isolated linker: fresh restore, hot cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 0.6433479453400001,
"stddev": 0.03441335633007782,
"median": 0.6333702854400001,
"user": 0.37326702,
"system": 1.3371399800000001,
"min": 0.6254698254400001,
"max": 0.73993164044,
"times": [
0.73993164044,
0.6424411374400001,
0.62644076144,
0.6373731634400001,
0.6282762254400001,
0.6280908564400001,
0.6254698254400001,
0.63871527244,
0.63531923044,
0.6314213404400001
]
},
{
"command": "pacquet@main",
"mean": 0.6263564079400001,
"stddev": 0.013898527365190021,
"median": 0.6225422524400002,
"user": 0.34785451999999994,
"system": 1.3413618799999998,
"min": 0.6085907194400001,
"max": 0.6537090874400001,
"times": [
0.6537090874400001,
0.6085907194400001,
0.61829712944,
0.61644874644,
0.6282320264400001,
0.6236219634400001,
0.6214625414400001,
0.62530075644,
0.6473710144400001,
0.6205300944400001
]
}
]
}Scenario: Isolated linker: fresh install, cold cache + cold store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 2.2415608180199995,
"stddev": 0.06217088401457929,
"median": 2.22576064202,
"user": 3.755966779999999,
"system": 3.10370642,
"min": 2.18232304052,
"max": 2.41323145952,
"times": [
2.22497562952,
2.41323145952,
2.18232304052,
2.23008041252,
2.22346582452,
2.23709600052,
2.22196419752,
2.22189959352,
2.23402636752,
2.22654565452
]
},
{
"command": "pacquet@main",
"mean": 2.19200466132,
"stddev": 0.03519879166838471,
"median": 2.18525905852,
"user": 3.748655779999999,
"system": 3.04067892,
"min": 2.15096087952,
"max": 2.28290347752,
"times": [
2.18106685352,
2.28290347752,
2.1723258525199998,
2.19589295752,
2.15096087952,
2.17239639552,
2.17783649152,
2.19972323852,
2.18945126352,
2.19748920352
]
}
]
}Scenario: Isolated linker: fresh install, hot cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 1.39076634758,
"stddev": 0.016823110034591863,
"median": 1.3866068348799998,
"user": 1.7011812,
"system": 1.80626918,
"min": 1.36685342088,
"max": 1.41771772588,
"times": [
1.3898324148799999,
1.36685342088,
1.40117916388,
1.41289037288,
1.41771772588,
1.38006122288,
1.40071042488,
1.3735719258799999,
1.38338125488,
1.38146554888
]
},
{
"command": "pacquet@main",
"mean": 1.38246060998,
"stddev": 0.031907527617307946,
"median": 1.3759349693799998,
"user": 1.6550117999999998,
"system": 1.8133045799999998,
"min": 1.3455251928799998,
"max": 1.4554284228799999,
"times": [
1.34932366688,
1.36444211088,
1.39880496588,
1.3455251928799998,
1.4554284228799999,
1.37365748588,
1.40696648388,
1.37417065588,
1.3776992828799999,
1.37858783188
]
}
]
} |
|
| Branch | pr/11967 |
| Testbed | pacquet |
Click to view all benchmark results
| Benchmark | Latency | Benchmark Result milliseconds (ms) (Result Δ%) | Upper Boundary milliseconds (ms) (Limit %) |
|---|---|---|---|
| isolated-linker.fresh-install.cold-cache.cold-store | 📈 view plot 🚷 view threshold | 2,241.56 ms(-19.34%)Baseline: 2,779.05 ms | 3,334.86 ms (67.22%) |
| isolated-linker.fresh-install.hot-cache.hot-store | 📈 view plot 🚷 view threshold | 1,390.77 ms(-26.67%)Baseline: 1,896.54 ms | 2,275.85 ms (61.11%) |
| isolated-linker.fresh-restore.cold-cache.cold-store | 📈 view plot 🚷 view threshold | 1,993.42 ms(-3.47%)Baseline: 2,065.15 ms | 2,478.18 ms (80.44%) |
| isolated-linker.fresh-restore.hot-cache.hot-store | 📈 view plot 🚷 view threshold | 643.35 ms(-2.27%)Baseline: 658.30 ms | 789.96 ms (81.44%) |
The git fetcher passed the lockfile-controlled `resolution.commit` value
to `git fetch --depth 1 origin <commit>` and `git checkout <commit>`
without a `--` separator and without validating that the value is a
SHA. A malicious lockfile could smuggle a value such as
`--upload-pack=touch /tmp/pwned`, which `git` parses as an option. On
SSH and local-file transports `--upload-pack` runs the supplied
command. HTTPS ignores it, but the SSH/file paths are enough to
execute code as the user running `pnpm install`.
Validate `resolution.commit` against `/^[0-9a-f]{40}$/i` at the entry
of the fetcher and throw `INVALID_GIT_COMMIT` otherwise. The check is
strictly stronger than adding a `--` separator: a validated value
cannot start with `-` or contain shell-significant characters at all.
Ported the same fix to pacquet's `pacquet-git-fetcher` crate, which
shells out to `git` along the same code path. Added a new
`GitFetcherError::InvalidCommit` variant carrying `INVALID_GIT_COMMIT`,
and unit + integration tests for both the helper and the fetcher
entry-point.
---
Written by an agent (Claude Code, claude-opus-4-7).
The validation throws before any host check, so wiring up `gitShallowHosts` and a matching `file://` URL host added nothing to the test. cspell also flagged the placeholder host name; using a real-looking `file:///tmp/repo.git` URL removes both issues at once. --- Written by an agent (Claude Code, claude-opus-4-7).
The git fetcher passed the lockfile-controlled `resolution.commit` value
to `git fetch --depth 1 origin <commit>` and `git checkout <commit>`
without a `--` separator and without validating that the value is a
SHA. A malicious lockfile could smuggle a value such as
`--upload-pack=touch /tmp/pwned`, which `git` parses as an option. On
SSH and local-file transports `--upload-pack` runs the supplied
command. HTTPS ignores it, but the SSH/file paths are enough to
execute code as the user running `pnpm install`.
Validate `resolution.commit` against `/^[0-9a-f]{40}$/i` at the entry
of the fetcher and throw `INVALID_GIT_COMMIT` otherwise. The check is
strictly stronger than adding a `--` separator: a validated value
cannot start with `-` or contain shell-significant characters at all.
Backport of #11967 to release/10.
---
Written by an agent (Claude Code, claude-opus-4-7).
Summary
fetching/git-fetcher/src/index.tspassed the lockfile-controlledresolution.commitvalue straight togit fetch --depth 1 origin <commit>andgit checkout <commit>with no--separator and no format validation. A maliciouspnpm-lock.yamlcould put a value such as--upload-pack=touch /tmp/pwnedinresolution.commit;gitparses anything starting with-as an option, and on SSH or local-file transports--upload-packruns the supplied command as the user runningpnpm install. HTTPS ignores--upload-pack, but the SSH/file paths are enough to reach code execution.The fix validates
resolution.commitagainst/^[0-9a-f]{40}$/iat the entry of the fetcher and throwsINVALID_GIT_COMMITotherwise. This is strictly stronger than adding a--separator — a validated value cannot start with-or contain shell-significant characters at all.Pacquet's
pacquet-git-fetchercrate shells out togitalong the same code path (pacquet/crates/git-fetcher/src/fetcher.rs) and had the identical issue. Ported the same check there, with a newGitFetcherError::InvalidCommitvariant carrying theINVALID_GIT_COMMITdiagnostic code.Reported by AutoFyn.
Test plan
pnpm --filter @pnpm/fetching.git-fetcher test -t reject— newreject a partial commit before invoking gitandreject a commit value that looks like a git optiontests pass; both assertexecais never invoked.cargo nextest run -p pacquet-git-fetcher is_valid_commit_hash— helper unit tests pass.cargo nextest run -p pacquet-git-fetcher fetcher_rejects_option_shaped_commit— integration test passes.cargo clippy --locked -p pacquet-git-fetcher --all-targets -- --deny warnings— clean.Written by an agent (Claude Code, claude-opus-4-7).
Summary by CodeRabbit