Conversation
📝 WalkthroughWalkthroughProject/workspace-controlled registry/proxy destination URLs and registry credential values with ChangesRegistry Environment Variable Placeholder Handling
Sequence Diagram(s)sequenceDiagram
participant WorkspaceYAML as pnpm-workspace.yaml
participant ProjectNpmrc as Project .npmrc
participant UserNpmrc as User/Trusted .npmrc
participant NpmrcAuth as NpmrcAuth Parser
participant Config as Config::current
WorkspaceYAML->>NpmrcAuth: substitute_env_untrusted() (detect ${...})
alt placeholder in workspace registry
NpmrcAuth->>Config: drop registry entry + warn
else no placeholder
NpmrcAuth->>Config: set registry
end
ProjectNpmrc->>NpmrcAuth: from_project_ini(text)
NpmrcAuth->>NpmrcAuth: detect ${...} in auth/registry
NpmrcAuth->>Config: omit placeholder-bearing entries + warn
UserNpmrc->>NpmrcAuth: from_ini(text) (trusted)
NpmrcAuth->>NpmrcAuth: expand env placeholders
NpmrcAuth->>Config: apply expanded auth/registry
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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 |
Micro-Benchmark ResultsLinux |
Integrated-Benchmark Report (Linux)Each scenario reports direct installs and pnpr installs. Bencher consumes pacquet@HEAD and pnpr@HEAD. Scenario: Isolated linker: fresh restore, cold cache + cold store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 9.995139170000002,
"stddev": 0.1625347415029795,
"median": 9.9492269152,
"user": 3.16293682,
"system": 3.2578371400000004,
"min": 9.8438914192,
"max": 10.349606784199999,
"times": [
9.9971918922,
9.9485050102,
10.349606784199999,
9.8813903432,
9.8729264822,
9.9499488202,
10.0168100202,
9.8438914192,
10.2061602862,
9.8849606422
]
},
{
"command": "pacquet@main",
"mean": 9.9157525703,
"stddev": 0.07802117435331599,
"median": 9.908808089199999,
"user": 3.1964032199999997,
"system": 3.29710254,
"min": 9.8142884922,
"max": 10.0831088272,
"times": [
9.9261177452,
10.0831088272,
9.9513106462,
9.896914860199999,
9.8749885332,
9.9859934992,
9.8488735382,
9.920701318199999,
9.8142884922,
9.8552282432
]
},
{
"command": "pnpr@HEAD",
"mean": 5.3783576379,
"stddev": 0.12022960777483067,
"median": 5.3415578612,
"user": 2.5674654199999996,
"system": 2.9169978399999996,
"min": 5.289830193199999,
"max": 5.6966421712,
"times": [
5.6966421712,
5.319927692199999,
5.328508288199999,
5.3546074342,
5.3698977732,
5.289830193199999,
5.4447792912,
5.3077753162,
5.309305890199999,
5.362302329199999
]
},
{
"command": "pnpr@main",
"mean": 5.3814268665,
"stddev": 0.13919532086313352,
"median": 5.314399808199999,
"user": 2.56930712,
"system": 2.8691519399999996,
"min": 5.2925537972,
"max": 5.7038997791999995,
"times": [
5.399067370199999,
5.3002908862,
5.2925537972,
5.5580533742,
5.3230461272,
5.7038997791999995,
5.311199321199999,
5.317600295199999,
5.3106818121999995,
5.2978759021999995
]
}
]
}Scenario: Isolated linker: fresh restore, hot cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 0.6585977243600001,
"stddev": 0.012778841832479524,
"median": 0.6579725685600001,
"user": 0.373954,
"system": 1.31965266,
"min": 0.6370568690600001,
"max": 0.6776199230600001,
"times": [
0.6559752550600001,
0.6453216310600001,
0.6370568690600001,
0.6522266360600001,
0.6776199230600001,
0.6659120690600001,
0.6599698820600001,
0.6620371130600001,
0.6766065540600001,
0.6532513110600001
]
},
{
"command": "pacquet@main",
"mean": 0.6842234302600001,
"stddev": 0.06483568421022756,
"median": 0.6572127120600001,
"user": 0.38078860000000003,
"system": 1.3307805599999998,
"min": 0.64957815106,
"max": 0.8613997150600001,
"times": [
0.6573318850600001,
0.7006268500600001,
0.6604049310600001,
0.6950677260600001,
0.8613997150600001,
0.6570935390600001,
0.6532024540600001,
0.64957815106,
0.6514958800600001,
0.6560331710600001
]
},
{
"command": "pnpr@HEAD",
"mean": 0.7894334877600001,
"stddev": 0.025816696294918687,
"median": 0.7800560520600002,
"user": 0.40014779999999994,
"system": 1.3400728599999998,
"min": 0.7625719590600001,
"max": 0.8343663450600001,
"times": [
0.7636357510600001,
0.7727419060600001,
0.8343663450600001,
0.7625719590600001,
0.7963112820600001,
0.7933979600600001,
0.7783283570600001,
0.77779245906,
0.8334051110600001,
0.7817837470600001
]
},
{
"command": "pnpr@main",
"mean": 0.7951062875600001,
"stddev": 0.10978874440211951,
"median": 0.7573771115600001,
"user": 0.3933595999999999,
"system": 1.34066846,
"min": 0.7373187370600001,
"max": 1.10435163306,
"times": [
0.7976240480600001,
0.7620202870600001,
0.7578147180600001,
0.7373187370600001,
1.10435163306,
0.7729434880600001,
0.7523837780600001,
0.75567621106,
0.7569395050600001,
0.7539904700600001
]
}
]
}Scenario: Isolated linker: fresh install, cold cache + cold store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 9.2671837046,
"stddev": 0.03255126534029447,
"median": 9.2559623808,
"user": 3.6631851999999996,
"system": 3.28446818,
"min": 9.232183128800001,
"max": 9.3224414528,
"times": [
9.302220717800001,
9.3066956498,
9.3224414528,
9.2559101518,
9.2524701228,
9.2723591348,
9.2560146098,
9.2362133868,
9.2353286908,
9.232183128800001
]
},
{
"command": "pacquet@main",
"mean": 9.2625667404,
"stddev": 0.07086006248289144,
"median": 9.2429607023,
"user": 3.6909587999999998,
"system": 3.24469128,
"min": 9.2036909858,
"max": 9.4567648568,
"times": [
9.2036909858,
9.2401684508,
9.2425509418,
9.2268390348,
9.2555689248,
9.2433704628,
9.249032207800001,
9.2770717128,
9.2306098258,
9.4567648568
]
},
{
"command": "pnpr@HEAD",
"mean": 5.080075688999999,
"stddev": 0.04171282891374835,
"median": 5.0640776263,
"user": 2.3946572,
"system": 2.7645821800000006,
"min": 5.0297632858000005,
"max": 5.1582606488,
"times": [
5.1095442788000005,
5.0674938098,
5.0447830538,
5.1582606488,
5.0297632858000005,
5.0426099228000005,
5.1246296488,
5.0594440828,
5.1035667158,
5.0606614428
]
},
{
"command": "pnpr@main",
"mean": 5.1691243926,
"stddev": 0.15183418631370277,
"median": 5.0959060893,
"user": 2.3887691999999996,
"system": 2.7996173800000004,
"min": 5.0064137768,
"max": 5.4409876058,
"times": [
5.2444838558,
5.0955312358,
5.0641746778000005,
5.0064137768,
5.3307614118,
5.0962809428,
5.4409876058,
5.3209528588,
5.0529207698,
5.0387367908
]
}
]
}Scenario: Isolated linker: fresh install, hot cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 1.4754108228,
"stddev": 0.026215752342861196,
"median": 1.4800953683000002,
"user": 1.6125555199999997,
"system": 1.8097438999999997,
"min": 1.4351198098,
"max": 1.5157208388,
"times": [
1.4914993788000002,
1.4916580078000001,
1.4686913578,
1.4924303828,
1.5157208388,
1.4631936448000002,
1.4515955648,
1.4979950318000002,
1.4462042108000002,
1.4351198098
]
},
{
"command": "pacquet@main",
"mean": 1.4824152724000002,
"stddev": 0.028174480310372663,
"median": 1.4737773788000001,
"user": 1.5857301199999998,
"system": 1.8454836999999997,
"min": 1.4562004168,
"max": 1.5496555558,
"times": [
1.4774015128,
1.5009826138,
1.4635118148000001,
1.4562004168,
1.4723657888000001,
1.5496555558,
1.4728618238000002,
1.4567720808000002,
1.4997081828,
1.4746929338
]
},
{
"command": "pnpr@HEAD",
"mean": 0.6904304455,
"stddev": 0.008134609693296392,
"median": 0.6882640763000001,
"user": 0.34432841999999997,
"system": 1.3251674999999998,
"min": 0.6751640648,
"max": 0.7048161668,
"times": [
0.6956974728,
0.6882035398,
0.6874234528000001,
0.6846971518,
0.6962205618,
0.6883246128,
0.7048161668,
0.6962813978,
0.6874760338,
0.6751640648
]
},
{
"command": "pnpr@main",
"mean": 0.6933444239,
"stddev": 0.033636233858117225,
"median": 0.6857998048,
"user": 0.33571792,
"system": 1.2956302,
"min": 0.6652832748,
"max": 0.7837748778,
"times": [
0.6942970598,
0.6857758768000001,
0.6741685308,
0.7837748778,
0.7043726898,
0.6858237328,
0.6865834948,
0.6652832748,
0.6791746098,
0.6741900918
]
}
]
}Scenario: Isolated linker: fresh install, cold cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 5.08720432096,
"stddev": 0.055728499550249125,
"median": 5.08765515516,
"user": 1.8281135199999998,
"system": 1.9661190199999996,
"min": 5.00888750766,
"max": 5.195184311659999,
"times": [
5.00888750766,
5.0548066686599995,
5.02703186666,
5.08654357866,
5.195184311659999,
5.15180770066,
5.09523288866,
5.05749772366,
5.10628423166,
5.08876673166
]
},
{
"command": "pacquet@main",
"mean": 5.155775925859999,
"stddev": 0.06089675088645302,
"median": 5.17839210516,
"user": 1.8496820200000001,
"system": 2.00138122,
"min": 5.075386690659999,
"max": 5.24813356366,
"times": [
5.07761315266,
5.19175804066,
5.13784847466,
5.17231178666,
5.24813356366,
5.18632195166,
5.2061668246599995,
5.18447242366,
5.075386690659999,
5.07774634966
]
},
{
"command": "pnpr@HEAD",
"mean": 0.6994300332600001,
"stddev": 0.014248295879442792,
"median": 0.69555973816,
"user": 0.34028132,
"system": 1.32315082,
"min": 0.6827468836600001,
"max": 0.7280917266600001,
"times": [
0.7280917266600001,
0.6827468836600001,
0.69340970166,
0.7017518396600001,
0.6977097746600001,
0.69249871766,
0.7109915856600001,
0.6837527916600001,
0.7127451186600001,
0.6906021926600001
]
},
{
"command": "pnpr@main",
"mean": 0.72439780836,
"stddev": 0.06927590331628163,
"median": 0.6969156221600001,
"user": 0.35168232,
"system": 1.30513132,
"min": 0.6776775876600001,
"max": 0.9130948666600001,
"times": [
0.73175011166,
0.68909887566,
0.6931238146600001,
0.9130948666600001,
0.6954692706600001,
0.7194444176600001,
0.6872413916600001,
0.7387157736600001,
0.6776775876600001,
0.6983619736600001
]
}
]
} |
|
| Branch | pr/12291 |
| Testbed | pacquet |
🚨 1 Alert
| Benchmark | Measure Units | View | Benchmark Result (Result Δ%) | Upper Boundary (Limit %) |
|---|---|---|---|---|
| isolated-linker.fresh-install.cold-cache.cold-store | Latency seconds (s) | 📈 plot 🚷 threshold 🚨 alert (🔔) | 9.27 s(+29.44%)Baseline: 7.16 s | 8.59 s (107.87%) |
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 🚨 view alert (🔔) | 9,267.18 ms(+29.44%)Baseline: 7,159.34 ms | 8,591.21 ms (107.87%) |
| isolated-linker.fresh-install.cold-cache.hot-store | 📈 view plot 🚷 view threshold | 5,087.20 ms(+1.38%)Baseline: 5,018.19 ms | 6,021.83 ms (84.48%) |
| isolated-linker.fresh-install.hot-cache.hot-store | 📈 view plot 🚷 view threshold | 1,475.41 ms(+4.66%)Baseline: 1,409.71 ms | 1,691.65 ms (87.22%) |
| isolated-linker.fresh-restore.cold-cache.cold-store | 📈 view plot 🚷 view threshold | 9,995.14 ms(+9.29%)Baseline: 9,145.30 ms | 10,974.36 ms (91.08%) |
| isolated-linker.fresh-restore.hot-cache.hot-store | 📈 view plot 🚷 view threshold | 658.60 ms(+1.16%)Baseline: 651.03 ms | 781.23 ms (84.30%) |
Code Review by Qodo
1.
|
PR Summary by QodoBlock env placeholder expansion in project-controlled registry/auth config WalkthroughsDescription• Stop expanding ${VAR} in repo-controlled registry/proxy URLs and registry credentials.
• Keep env expansion for trusted user/global/auth.ini/CLI config to preserve token workflows.
• Add regression tests in pnpm config reader and pacquet for trust-boundary behavior.
Diagramgraph TD
A["Project config (.npmrc, workspace)"] --> B(["pnpm config reader"]) --> E{{"Registry/proxy requests"}}
C["Trusted config (user/auth.ini/CLI)"] --> B(["pnpm config reader"]) --> E{{"Registry/proxy requests"}}
A["Project config (.npmrc, workspace)"] --> D(["pacquet config parser"]) --> E{{"Registry/proxy requests"}}
C["Trusted config (user/auth.ini/CLI)"] --> D(["pacquet config parser"]) --> E{{"Registry/proxy requests"}}
subgraph Legend
direction LR
_file["Config source"] ~~~ _svc(["Parser"]) ~~~ _ext{{"Outbound requests"}}
end
High-Level AssessmentThe following are alternative approaches to this PR: 1. Disable env expansion everywhere
2. Project-level opt-in allowlist for env expansion
3. Expand but sanitize/denylist sensitive variables
Recommendation: Keep the PR’s trust-boundary approach: treat repository-controlled config as untrusted and filter env placeholders for request destinations and credentials, while preserving expansion for user/global/auth.ini/CLI sources. This balances security (prevents env exfiltration via repo config) with compatibility (existing trusted token workflows continue to work). File ChangesBug fix (5)
Tests (5)
Documentation (1)
Other (2)
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #12291 +/- ##
==========================================
- Coverage 87.55% 87.54% -0.02%
==========================================
Files 280 288 +8
Lines 33664 34952 +1288
==========================================
+ Hits 29476 30599 +1123
- Misses 4188 4353 +165 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
config/reader/src/loadNpmrcFiles.ts (1)
188-217: ⚡ Quick winConsider adding a comment explaining the two-pass filtering approach.
The logic correctly handles keys that match patterns before or after env substitution, but the reasoning is non-obvious. A brief comment would help future maintainers understand why checking both
rawKeyand the substitutedkeyis necessary.Example:
// Two-pass filtering: // 1. Check rawKey before substitution (catches patterns with literal ${...}) // 2. Substitute, then check if original rawKey had placeholders and substituted key matches pattern // (catches keys like ${HOST}/:_authToken that become //example.com/:_authToken)🤖 Prompt for 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. In `@config/reader/src/loadNpmrcFiles.ts` around lines 188 - 217, Add a short comment above the two-pass filtering logic in loadNpmrcFiles.ts explaining why we check both rawKey and the substituted key: note that we first check rawKey (to catch keys that literally contain ${...} placeholders) and then substitute via substituteEnv into key and re-check (to catch cases where substitution produces a pattern that matches isRequestDestinationKey / isAuthValueKey / isRequestDestinationValueKey); mention that this prevents expanding env placeholders when expandRequestDestinationEnv or expandAuthValueEnv is false and reference the related symbols rawKey, key, substituteEnv, hasEnvPlaceholder, isRequestDestinationKey, isAuthValueKey, isRequestDestinationValueKey, and the warnIgnored* helpers so future readers understand the intent of both checks.pacquet/crates/config/src/workspace_yaml/tests.rs (1)
191-191: ⚡ Quick winAssert “unchanged default” instead of hardcoding the registry URL.
This test is about ignoring env placeholders, not pinning
Config::new()’s default registry literal. Capturebefore_registryand assert it is unchanged afterapply_toto avoid brittle failures if defaults change.Suggested diff
let mut settings: WorkspaceSettings = serde_saphyr::from_str(yaml).unwrap(); settings.substitute_env::<EnvWithHost>(); let mut config = Config::new(); + let before_registry = config.registry.clone(); settings.apply_to(&mut config, Path::new("/irrelevant")); - assert_eq!(config.registry, "https://registry.npmjs.org/"); + assert_eq!(config.registry, before_registry);🤖 Prompt for 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. In `@pacquet/crates/config/src/workspace_yaml/tests.rs` at line 191, The test currently asserts a hardcoded default registry URL; instead capture the default before applying changes and assert it remains unchanged after apply_to. Update the test to call Config::new() (or access the initial default) into a before_registry variable, call apply_to, then assert_eq!(config.registry, before_registry) so the test checks "unchanged default" rather than a brittle literal; reference Config::new(), apply_to, and config.registry to locate and modify the assertions.
🤖 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 `@pacquet/crates/config/src/lib.rs`:
- Around line 1655-1664: The project `.npmrc` is being read from start_dir
causing workspace-root configs to be missed; update the code that builds
project_source (the read_npmrc call and the NpmrcAuth::from_project_ini::<Sys>
invocation) so it reads from the resolved workspace root (the resolved
workspaceDir/localPrefix used elsewhere) instead of start_dir — e.g., ensure you
call read_npmrc and NpmrcAuth::from_project_ini with the resolved workspace
directory variable (or move this project_source creation until after workspace
resolution) and keep the same rescope_unscoped("<project>/.npmrc") behavior.
In `@pacquet/crates/config/src/npmrc_auth.rs`:
- Around line 214-255: has_env_placeholder is too permissive (it treats any
literal "${" as a placeholder) causing legitimate values with "${" fragments to
be dropped; change its implementation to detect complete pnpm-style placeholders
only (e.g., a pattern matching "${...}" with a non-empty interior) instead of
contains("${"), and update any other uses (the other occurrence around lines
675-677) to rely on the tightened has_env_placeholder behavior; keep existing
callers (env_replace_lossy, the checks using
is_request_destination_key/is_auth_value_key/is_request_destination_value_key
and the auth.warn_ignored_* calls) unchanged so they only trigger when a full
"${...}" placeholder is present.
In `@pacquet/crates/config/src/workspace_yaml.rs`:
- Around line 648-663: substitute_env currently drops `${...}` only for registry
fields but no longer uses Sys so ordinary string settings aren't expanded;
restore upstream behavior by making substitute_env accept the Sys: EnvVar
parameter again and use it to expand environment placeholders for non-registry
workspace/global string fields while still filtering registry and
named_registries (i.e., keep the existing registry check that sets self.registry
= None and the named_registries.retain call, but also walk the other string
fields (storeDir, cacheDir, pnprServer, scriptShell, etc. on self) and replace
`${...}` using the Sys expansion method before calling Self::apply_to so Config
gets expanded values).
---
Nitpick comments:
In `@config/reader/src/loadNpmrcFiles.ts`:
- Around line 188-217: Add a short comment above the two-pass filtering logic in
loadNpmrcFiles.ts explaining why we check both rawKey and the substituted key:
note that we first check rawKey (to catch keys that literally contain ${...}
placeholders) and then substitute via substituteEnv into key and re-check (to
catch cases where substitution produces a pattern that matches
isRequestDestinationKey / isAuthValueKey / isRequestDestinationValueKey);
mention that this prevents expanding env placeholders when
expandRequestDestinationEnv or expandAuthValueEnv is false and reference the
related symbols rawKey, key, substituteEnv, hasEnvPlaceholder,
isRequestDestinationKey, isAuthValueKey, isRequestDestinationValueKey, and the
warnIgnored* helpers so future readers understand the intent of both checks.
In `@pacquet/crates/config/src/workspace_yaml/tests.rs`:
- Line 191: The test currently asserts a hardcoded default registry URL; instead
capture the default before applying changes and assert it remains unchanged
after apply_to. Update the test to call Config::new() (or access the initial
default) into a before_registry variable, call apply_to, then
assert_eq!(config.registry, before_registry) so the test checks "unchanged
default" rather than a brittle literal; reference Config::new(), apply_to, and
config.registry to locate and modify the assertions.
🪄 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: dd0eeb8e-74b2-45c3-a970-7ce21eddf708
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (12)
.changeset/sharp-registry-env-placeholders.mdconfig/reader/src/getOptionsFromRootManifest.tsconfig/reader/src/loadNpmrcFiles.tsconfig/reader/test/getOptionsFromRootManifest.test.tsconfig/reader/test/index.tspacquet/crates/cli/tests/install.rspacquet/crates/config/src/lib.rspacquet/crates/config/src/npmrc_auth.rspacquet/crates/config/src/npmrc_auth/tests.rspacquet/crates/config/src/workspace_yaml.rspacquet/crates/config/src/workspace_yaml/tests.rspnpm-workspace.yaml
📜 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). (4)
- GitHub Check: Lint and Test (macos-latest)
- GitHub Check: Lint and Test (windows-latest)
- GitHub Check: Run benchmark on ubuntu-latest
- GitHub Check: Analyze (javascript)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,js,jsx}: Use Standard Style with trailing commas, prefer functions over classes, declare functions after they are used (relying on hoisting), limit functions to no more than two or three arguments, and use a single options object for functions needing more parameters
Follow import order: standard libraries first, then external dependencies (sorted alphabetically), then relative imports
Do not write comments that restate what the code already says; rename variables, split helpers, or move checks to more obvious places instead
Do not repeat documentation at call sites that already lives on the callee; update the JSDoc once and let every call site benefit
Use JSDoc for the function's contract (preconditions, postconditions, edge cases, why the function exists), not for re-narrating the function body
Do not record past implementation shape, refactor history, or removed code in comments; use git log and git blame for that information instead
Write comments only when the reason for code is non-obvious, a hidden invariant exists, a workaround for a known bug is needed, or an exception to surrounding pattern is deliberate
Files:
config/reader/test/getOptionsFromRootManifest.test.tsconfig/reader/src/getOptionsFromRootManifest.tsconfig/reader/test/index.tsconfig/reader/src/loadNpmrcFiles.ts
**/*.test.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Do not use
instanceof Errorfor checking if a caught error is an Error object in Jest tests; useutil.types.isNativeError()instead to work across realms
Files:
config/reader/test/getOptionsFromRootManifest.test.ts
pacquet/**/*.rs
📄 CodeRabbit inference engine (pacquet/AGENTS.md)
pacquet/**/*.rs: Log emissions are part of matching pnpm — when porting a function that firespnpm:<channel>events throughglobalLogger,logger.debug(...), orstreamParser.write(...), mirror the call site, payload, and ordering so@pnpm/cli.default-reporterparses pacquet's NDJSON the same way
Declare a newtype wrapper for branded string types instead of collapsing the brand into a plainStringor&strin Rust
If upstream TypeScript always validates before construction of a branded string, validate in the Rust wrapper too viaTryFrom<String>and/orFromStrand do not provide an infallible public constructor
If upstream TypeScript never validates a branded string, just brand for type-safety in Rust by exposing an infallibleFrom<String>constructor
If upstream TypeScript occasionally constructs a branded string without validation, exposefrom_str_uncheckedin Rust as an escape hatch alongside the validating constructor
Match upstream serde behavior for branded strings crossing JSON, YAML, or INI boundaries by using#[serde(try_from = "String")]for deserialization and#[serde(into = "String")]for serialization
Derive simple conversions for branded strings using#[derive(derive_more::From)]and#[derive(derive_more::Into)]instead of handwritingimplblocks; use manualimplonly when conversion needs custom logic
Model TypeScript string literal unions (like'auto' | 'always' | 'never') as Rustenums instead of newtype wrappers, since the set of valid values is closed
Treat TypeScript string template literal types (like`${string}@${string}`) the same as branded string types in Rust, using a newtype wrapper with validation
Follow the code style guide inCODE_STYLE_GUIDE.md— imports, modules, naming, ownership and borrowing, parameter type selection, trait bounds, pattern matching,pipe-trait, error handling, test layout, and cloning ofArcandRc
Choose owned vs. borrowed parameters to minimize copies; widen to t...
Files:
pacquet/crates/config/src/lib.rspacquet/crates/config/src/workspace_yaml.rspacquet/crates/config/src/workspace_yaml/tests.rspacquet/crates/config/src/npmrc_auth/tests.rspacquet/crates/config/src/npmrc_auth.rspacquet/crates/cli/tests/install.rs
pacquet/**/tests/**/*.rs
📄 CodeRabbit inference engine (pacquet/AGENTS.md)
pacquet/**/tests/**/*.rs: Prefer real fixtures over dependency-injection seams — usetempfile::TempDir, the mocked registry, or integration tests spawning the actual binary for happy paths and most error paths; use the DI seam only for filesystem error kinds, deterministic time, shared process-global state, or external-service happy paths
Follow test-logging guidance — log before non-assert_eq!assertions,dbg!complex structures, skip logging for simple scalarassert_eq!
Tests must not be tolerant of missing build/runtime environment by silently returning early when a tool isn't found — let the test panic when a required tool is absent
Prefer#[cfg_attr(target_os = "windows", ignore = "...")](or matching#[cfg(unix)]gates) over runtime probe-and-skip helpers for platform-locked tools
Port relevant pnpm tests to Rust tests whenever they translate when porting behavior from pnpm
Use snapshot tests withinstaand carefully review diffs when intentional changes alter snapshots; accept withcargo insta reviewonly after careful review
Tests that need the mocked registry should startpnprthroughpacquet-testing-utils;cargo test/cargo nextest runshould not require a separatejust registry-mock launchstep
Files:
pacquet/crates/cli/tests/install.rs
🧠 Learnings (8)
📚 Learning: 2026-05-26T21:01:06.666Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11966
File: .changeset/require-tarball-integrity.md:6-6
Timestamp: 2026-05-26T21:01:06.666Z
Learning: In pnpm lockfile-related release notes/docs (especially changeset markdown), preserve URL hostnames exactly as they appear in pnpm-lock.yaml tarball resolution entries—keep hosts like `codeload.github.com`, `bitbucket.org`, and `gitlab.com` in lowercase. Do not “correct” them to title-case/preserve brand capitalization (e.g., LanguageTool rules like `GITHUB` capitalization) because these are literal URL fragments, not platform brand names.
Applied to files:
.changeset/sharp-registry-env-placeholders.md
📚 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:
config/reader/test/getOptionsFromRootManifest.test.tsconfig/reader/src/getOptionsFromRootManifest.tsconfig/reader/test/index.tsconfig/reader/src/loadNpmrcFiles.ts
📚 Learning: 2026-06-05T13:47:05.929Z
Learnt from: vsumner
Repo: pnpm/pnpm PR: 12190
File: installing/deps-installer/test/install/frozenStore.ts:2-17
Timestamp: 2026-06-05T13:47:05.929Z
Learning: In the pnpm/pnpm repository, the shared Jest preset keeps `injectGlobals` at its default (`true`), so `test` and `expect` are available as Jest globals. Therefore, reviewers should not flag (or treat as TypeScript/compilation errors) missing `import { test, expect } from 'jest/globals'` when a test file uses `test`/`expect` without importing them. Importing from `jest/globals` may still be used for consistency with sibling files, but it is not required for execution in this repo unless a Jest preset is explicitly configured with `injectGlobals: false`.
Applied to files:
config/reader/test/getOptionsFromRootManifest.test.tsconfig/reader/test/index.ts
📚 Learning: 2026-06-05T13:47:26.046Z
Learnt from: vsumner
Repo: pnpm/pnpm PR: 12190
File: installing/deps-installer/src/install/index.ts:2337-2343
Timestamp: 2026-06-05T13:47:26.046Z
Learning: In the pnpm/pnpm codebase, `PnpmError` automatically prefixes `err.code` with `ERR_PNPM_` when you pass a code that does not already start with `ERR_PNPM_` (it normalizes `this.code` via `code.startsWith('ERR_PNPM_') ? code : `ERR_PNPM_${code}``). Therefore, during code review you should NOT flag `new PnpmError(...)` call sites for passing a bare error code (e.g., `new PnpmError('FROZEN_STORE_INCOMPATIBLE_WITH_PNPR', ...)`); the resulting `err.code` will still be `ERR_PNPM_FROZEN_STORE_INCOMPATIBLE_WITH_PNPR`.
Applied to files:
config/reader/test/getOptionsFromRootManifest.test.tsconfig/reader/src/getOptionsFromRootManifest.tsconfig/reader/test/index.tsconfig/reader/src/loadNpmrcFiles.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/config/src/lib.rspacquet/crates/config/src/workspace_yaml.rspacquet/crates/config/src/workspace_yaml/tests.rspacquet/crates/config/src/npmrc_auth/tests.rspacquet/crates/config/src/npmrc_auth.rspacquet/crates/cli/tests/install.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/config/src/lib.rspacquet/crates/config/src/workspace_yaml.rspacquet/crates/config/src/workspace_yaml/tests.rspacquet/crates/config/src/npmrc_auth/tests.rspacquet/crates/config/src/npmrc_auth.rspacquet/crates/cli/tests/install.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/config/src/lib.rspacquet/crates/config/src/workspace_yaml.rspacquet/crates/config/src/workspace_yaml/tests.rspacquet/crates/config/src/npmrc_auth/tests.rspacquet/crates/config/src/npmrc_auth.rspacquet/crates/cli/tests/install.rs
📚 Learning: 2026-06-06T18:58:37.156Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 12243
File: pacquet/crates/package-manager/src/install_package_by_snapshot.rs:319-322
Timestamp: 2026-06-06T18:58:37.156Z
Learning: When reviewing Rust code, do not assume `matches!(expr, Pattern(_))` will move out of `expr` if `Pattern(_)` contains no by-value bindings. `matches!` desugars to a `match` that auto-borrows the scrutinee for discrimination, so even if `expr` is a non-`Copy` value behind a shared reference (e.g., `&T`), the macro should not move-out of the borrowed data purely due to `matches!`. Treat `matches!(&expr, Pattern(_))` as a readability/clarity improvement, not a correctness requirement. Only flag potential move-out-of-borrow risks when the pattern includes by-value bindings that would require moving the matched value.
Applied to files:
pacquet/crates/config/src/lib.rspacquet/crates/config/src/workspace_yaml.rspacquet/crates/config/src/workspace_yaml/tests.rspacquet/crates/config/src/npmrc_auth/tests.rspacquet/crates/config/src/npmrc_auth.rspacquet/crates/cli/tests/install.rs
🔇 Additional comments (16)
.changeset/sharp-registry-env-placeholders.md (1)
1-6: LGTM!pnpm-workspace.yaml (1)
439-439: LGTM!config/reader/src/getOptionsFromRootManifest.ts (3)
75-77: LGTM!
87-95: LGTM!
97-99: LGTM!config/reader/src/loadNpmrcFiles.ts (5)
47-50: LGTM!
64-69: LGTM!
233-243: LGTM!
252-262: LGTM!
245-250: Consider blocking env-placeholder expansion for inlinecert/keyin project.npmrcWorkspace
.npmrcis loaded withexpandAuthValueEnv: false, but the guard that ignores${...}is based onisAuthValueKey, which only treats_authToken,_auth,_password,username, andtokenHelperas auth-value keys. Inline client certificate settingscert/keyare excluded from this guard, so${...}insidecert=/key=in a repository-controlled.npmrcwill be expanded and then pinned/rescoped like other per-registry settings.Confirm whether
${...}expansion should be blocked forcert/keytoo (e.g., extend the auth-value key set or add a dedicated check), or document the rationale for allowing it given they carry client identity.config/reader/test/getOptionsFromRootManifest.test.ts (1)
20-49: LGTM!config/reader/test/index.ts (3)
610-629: LGTM!
722-740: LGTM!
780-804: LGTM!pacquet/crates/cli/tests/install.rs (2)
257-297: LGTM!
299-340: LGTM!
|
Code review by qodo was updated up to the latest commit 5320d4d |
|
Also addressed the non-inline CodeRabbit note about inline Written by an agent (Codex, GPT-5). |
|
Code review by qodo was updated up to the latest commit b89cea5 |
|
Code review by qodo was updated up to the latest commit 9ec847d |
* fix(package-bins): reject reserved manifest bin names Manifest bin keys "", ".", "..", and scoped forms such as "@scope/.." passed the bin-name guard because encodeURIComponent leaves them unchanged. When joined to the global bin directory during global remove/update/add operations, "." resolves to the bin directory itself and ".." to its parent, which removeBin then recursively deletes. Reject empty, ".", and ".." bin names after scope stripping. Backport of #12289 to v10. * fix: block untrusted request destination env expansion Makes environment expansion trust-aware for registry/auth config and request destinations: - Stops project and workspace .npmrc files from expanding ${...} placeholders in registry/proxy request destinations, URL-scoped keys, and registry credential values. - Stops repository-controlled pnpm-workspace.yaml from expanding ${...} placeholders in the registry setting. - Preserves env expansion for trusted user/global/CLI/env config so existing token and registry setup flows continue to work. Backport of #12291 (CAND-PNPM-122 / GHSA-3qhv-2rgh-x77r) to v10. * fix(security): verify npm registry signature before spawning a package-manager binary The packageManager field (and pnpm self-update) makes pnpm download and run a specific pnpm version. The staged install's bytes were trusted based on lockfile integrity alone, which proves nothing when the inputs are repository-controlled. pnpm now verifies the npm registry signature of the engine it is about to spawn, over the installed integrity, against npm's public signing keys embedded in the pnpm CLI (exactly as corepack does): - verifyPnpmEngineIdentity() checks pnpm/@pnpm/exe and the materialized platform binaries of the staged install before it is linked into the tools directory. - Fails closed: any verification failure, including an unreachable registry, refuses the version switch rather than running an unverified binary. Runs only on a tools-directory cache miss (an actual download). - The embedded keys live in a generated file kept in sync with npm's keys endpoint by scripts/update-npm-signing-keys.mjs; the release workflow runs the check as a gate so a key rotation cannot silently break verification. Backport of #12292 (CAND-PNPM-097) to v10. * fix: harden package-manager bootstrap metadata Resolve package-manager bootstrap traffic through trusted user/CLI registries and trusted network config, defaulting to the public npm registry instead of project/workspace registry settings: - getConfig() now computes packageManagerRegistries and packageManagerNetworkConfig from trusted config sources only (CLI options, env config, user and global .npmrc) — never the repository's project/workspace .npmrc or pnpm-workspace.yaml. - switchCliVersion() applies that bootstrap config when installing and verifying the wanted pnpm version, so repository .npmrc proxy/TLS/registry values cannot steer package-manager bootstrap traffic. Backport of #12296 to v10. The v11 env-lockfile validation parts do not apply: v10 bootstraps the wanted version through a staged child install instead of an env lockfile. * fix(security): verify Node.js runtime SHASUMS OpenPGP signature When a repository requests a Node.js runtime (useNodeVersion or an execution env), pnpm downloads and then executes a Node binary. The download mirror is repository-configurable via node-mirror:<channel> in project .npmrc, and the integrity came from SHASUMS256.txt fetched from that same mirror — a circular check a malicious mirror can satisfy with a tampered binary and matching hashes. pnpm now fetches SHASUMS256.txt.sig and verifies its detached OpenPGP signature against the Node.js release team's public keys, embedded in the pnpm CLI, before trusting the hashes: - @pnpm/crypto.shasums-file: new fetchVerifiedNodeShasums / fetchVerifiedNodeShasumsFile verify the signature via openpgp against the embedded keys (generated src/nodeReleaseKeys.ts, mirrored from the canonical nodejs/release-keys list). - @pnpm/node.fetcher verifies the configurable-mirror SHASUMS for the release channel; pre-release channels (rc, nightly, ...) are unsigned by Node and remain unverified. - scripts/update-node-release-keys.mjs keeps the keys current (pnpm run check:node-release-keys / update:node-release-keys), and the release workflow runs the check as a gate. Backport of #12295 to v10 (without the pacquet Rust port, which does not exist on this branch). * test(env): sign the SHASUMS fixture for Node.js download tests The Node.js download tests exercise the release channel, whose SHASUMS256.txt is now signature-verified. Sign the fixture with a generated OpenPGP key and trust it through the new trustedNodeReleaseKeys test seam (threaded from plugin-commands-env via @pnpm/node.fetcher to fetchVerifiedNodeShasums), so the tests keep exercising the verification path instead of bypassing it. * fix(self-updater): redact registry credentials from engine identity errors Registry URLs may legally embed basic-auth credentials (https://user:pass@host/). verifyPnpmEngineIdentity() interpolated the packument URL and registry URL into PnpmError messages, and the unreachable-registry path surfaced fetch-layer error messages that embed the request URL — all of which land in terminal output and CI logs. Strip URL credentials from every error message and truncate the non-200 response body. * fix: update vulnerable transitive dependencies Override shell-quote to >=1.8.4 (GHSA-w7jw-789q-3m8p, critical, pulled in via concurrently) so the audit workflow passes again. The advisory was published after the last release/10 audit run; it is unrelated to the security backports on this branch.
|
🚢 v11.5.3 |
Summary
Fixes CAND-PNPM-122 / GHSA-3qhv-2rgh-x77r by making environment expansion trust-aware for registry/auth config and request destinations.
.npmrcfrom expanding${...}placeholders in registry/proxy request destinations, URL-scoped keys, and registry credential values.pnpm-workspace.yamlfrom expanding${...}placeholders in request destinations:registry,registries,namedRegistries, andpnprServer.Verification
./node_modules/.bin/tsgo --build config/reader/tsconfig.jsonNODE_OPTIONS="--experimental-vm-modules --disable-warning=ExperimentalWarning --disable-warning=DEP0169" ../../node_modules/.bin/jest test/getOptionsFromRootManifest.test.ts --runInBandconfig/reader/test/index.tsJest cases for project.npmrc, trusted user/global config, and workspace request-destination env placeholders./node_modules/.bin/eslint config/reader/src/loadNpmrcFiles.ts config/reader/src/getOptionsFromRootManifest.ts config/reader/src/index.ts config/reader/test/index.ts config/reader/test/getOptionsFromRootManifest.test.tscargo fmt --all -- --checkcargo test --manifest-path pacquet/crates/config/Cargo.toml ... --libcases for project/trusted.npmrc, trusted global/env config, and workspace YAML request-destination env placeholdersgit diff --checkvuln-x77rat9ec847d1b5Written by an agent (Codex, GPT-5).