fix(pnpr): screen registry responses with osv#12566
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughOSV vulnerability screening is extended from the resolver surface to all registry-serving endpoints (packument, tarball, dist-tags, version manifest). A new ChangesOSV screening extended to registry serving path
Sequence Diagram(s)sequenceDiagram
participant Client
participant serve_packument
participant filter_osv_vulnerable_versions
participant serve_tarball
participant ensure_osv_allowed
participant Upstream
rect rgba(100, 149, 237, 0.5)
note over Client,filter_osv_vulnerable_versions: Packument path
Client->>serve_packument: GET /:package
serve_packument->>Upstream: fetch packument
Upstream-->>serve_packument: full packument JSON
serve_packument->>filter_osv_vulnerable_versions: packument + osv_index
filter_osv_vulnerable_versions-->>serve_packument: vulnerable versions/dist-tags/time removed
serve_packument-->>Client: filtered packument (200)
end
rect rgba(220, 100, 100, 0.5)
note over Client,ensure_osv_allowed: Tarball path
Client->>serve_tarball: GET /:package/-/:filename
serve_tarball->>ensure_osv_allowed: name + parsed version
ensure_osv_allowed-->>serve_tarball: OsvVulnerability error
serve_tarball-->>Client: 403 Forbidden (no upstream hit)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Assessment against linked issues
Possibly related PRs
✨ Finishing Touches🧪 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 |
PR Summary by Qodopnpr registry: filter vulnerable versions and tarballs using local OSV index Description
Diagram
High-Level Assessment
Files changed (6)
|
Code Review by Qodo
1. No OSV-package fast-path
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
pnpr/crates/pnpr/tests/server.rs (1)
154-159: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick winStrengthen cached-packument assertions for full OSV parity.
The cached response currently asserts only
versionsfiltering. Addtimeanddist-tagschecks here too, so the cache path is explicitly validated for the same filtered fields as the proxy path.Suggested assertion additions
let cached = app.clone().oneshot(Request::get("/foo").body(Body::empty()).unwrap()).await.unwrap(); assert_eq!(cached.status(), StatusCode::OK); let cached_body = body_json(cached.into_body()).await; assert!(cached_body["versions"].get("1.1.0").is_none()); + assert!(cached_body["time"].get("1.1.0").is_none()); + assert!(cached_body["dist-tags"].get("latest").is_none()); + assert_eq!(cached_body["dist-tags"]["stable"], "1.0.0");As per coding guidelines, REVIEW_GUIDE.md asks for meaningful cross-surface regression assertions that prove filtering gates aren’t bypassed.
🤖 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 `@pnpr/crates/pnpr/tests/server.rs` around lines 154 - 159, The cached packument test currently validates only that filtered versions are absent from the cached_body response but lacks assertions for the time and dist-tags fields, which should also be filtered consistently with the proxy path. After the existing assertion checking cached_body["versions"].get("1.1.0").is_none(), add additional assertions to verify that cached_body["time"] and cached_body["dist-tags"] are also properly filtered, ensuring the cache path enforces the same filtering gates as the proxy path for complete OSV parity validation.Source: Coding guidelines
🤖 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 `@pnpr/crates/pnpr/src/server.rs`:
- Around line 2118-2132: The OSV vulnerability bypass occurs in two places:
first, in the filter_map closure around line 2121, only the embedded version
field is checked against vulnerabilities while the key itself is not validated;
an attacker can keep a vulnerable version visible by setting its embedded
version to a safe value. Second, in the dist-tags retention logic around line
2131, dist-tags pointing to versions that were blocked and removed from the
versions map are not being filtered out. Fix this by modifying the filter_map to
check both the key and the embedded version against
osv_index.vulnerability_ids(), and ensure the dist-tags retain condition also
checks that the version still exists in the versions map after blocking,
removing any tags that point to blocked or missing versions.
---
Nitpick comments:
In `@pnpr/crates/pnpr/tests/server.rs`:
- Around line 154-159: The cached packument test currently validates only that
filtered versions are absent from the cached_body response but lacks assertions
for the time and dist-tags fields, which should also be filtered consistently
with the proxy path. After the existing assertion checking
cached_body["versions"].get("1.1.0").is_none(), add additional assertions to
verify that cached_body["time"] and cached_body["dist-tags"] are also properly
filtered, ensuring the cache path enforces the same filtering gates as the proxy
path for complete OSV parity validation.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 82f64d83-c0e6-4239-86e5-571b818accb7
📒 Files selected for processing (6)
pnpr/crates/pnpr/src/config.rspnpr/crates/pnpr/src/error.rspnpr/crates/pnpr/src/package_name.rspnpr/crates/pnpr/src/package_name/tests.rspnpr/crates/pnpr/src/server.rspnpr/crates/pnpr/tests/server.rs
💤 Files with no reviewable changes (1)
- pnpr/crates/pnpr/src/package_name.rs
|
Code review by qodo was updated up to the latest commit 41c88b5 |
|
Addressed the review feedback in follow-up commits:
Verification run locally:
Written by an agent (Codex, gpt-5). |
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 `@pnpr/crates/pnpr/tests/server.rs`:
- Around line 236-240: The assertions for JSON object keys in this test are
using order-dependent vector comparison (collecting keys as Vec and comparing
equality), which can fail due to map ordering differences. Replace these
assertions by converting the keys to a set and comparing against an expected set
of keys, or use order-independent checks like verifying each expected key exists
and the total length matches. Update all three assertions in the block (for
body["versions"], body["dist-tags"], and body["time"]) to use set comparisons
instead of direct vector equality to make the test order-independent.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 563ce02d-8057-4f3b-b6d1-68559a16c4ab
📒 Files selected for processing (4)
pnpr/crates/pnpr/src/resolver/osv.rspnpr/crates/pnpr/src/resolver/osv/tests.rspnpr/crates/pnpr/src/server.rspnpr/crates/pnpr/tests/server.rs
🚧 Files skipped from review as they are similar to previous changes (1)
- pnpr/crates/pnpr/src/server.rs
1 similar comment
|
Code review by qodo was updated up to the latest commit ef0c62f |
|
Code review by qodo was updated up to the latest commit ef0c62f |
|
Addressed Qodo’s tarball canonicalization finding in Added Verification run locally:
Written by an agent (Codex, gpt-5). |
|
Code review by qodo was updated up to the latest commit 299f935 |
|
Addressed Qodo's duplicate OSV manifest check finding in Verification run locally:
Written by an agent (Codex, gpt-5). |
|
Code review by qodo was updated up to the latest commit f1b3bf6 |
|
Addressed Qodo's manifest/dist-tags hot-path finding in The mismatch regression now also covers direct version-manifest requests and direct dist-tags responses so the faster path keeps the previous OSV bypass protections. Verification run locally:
Written by an agent (Codex, gpt-5). |
|
Code review by qodo was updated up to the latest commit 7aef872 |
1 similar comment
|
Code review by qodo was updated up to the latest commit 7aef872 |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #12566 +/- ##
==========================================
- Coverage 88.07% 88.06% -0.02%
==========================================
Files 321 323 +2
Lines 45078 45368 +290
==========================================
+ Hits 39704 39952 +248
- Misses 5374 5416 +42 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
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": 3.7886291563800008,
"stddev": 0.26803275808066984,
"median": 3.64752333918,
"user": 3.47899776,
"system": 3.3836806599999996,
"min": 3.54485305618,
"max": 4.37372923618,
"times": [
4.37372923618,
3.57613539818,
3.66112884818,
3.61105099418,
3.9397874341800003,
3.63391783018,
3.60132158518,
4.00829597318,
3.54485305618,
3.9360712081800004
]
},
{
"command": "pacquet@main",
"mean": 3.7117354375800007,
"stddev": 0.15307595567183074,
"median": 3.63785655718,
"user": 3.50111756,
"system": 3.3511476599999996,
"min": 3.57147937018,
"max": 3.9789396641800003,
"times": [
3.6405359491800002,
3.57147937018,
3.68958224018,
3.6307614531800003,
3.5966677771800004,
3.63517716518,
3.9789396641800003,
3.58880348418,
3.82949574618,
3.9559115261800004
]
},
{
"command": "pnpr@HEAD",
"mean": 2.14638516708,
"stddev": 0.11109694095616475,
"median": 2.1491749346800004,
"user": 2.5932186600000007,
"system": 2.9877140599999996,
"min": 2.01126625718,
"max": 2.34031723618,
"times": [
2.34031723618,
2.20111137718,
2.1057322751800003,
2.2593306871800003,
2.03009739018,
2.0205505981800003,
2.20738954718,
2.09543870818,
2.19261759418,
2.01126625718
]
},
{
"command": "pnpr@main",
"mean": 2.0582144545800003,
"stddev": 0.10124445205557782,
"median": 2.0496057491800004,
"user": 2.61692926,
"system": 2.95400286,
"min": 1.9269663631799998,
"max": 2.24952621518,
"times": [
1.9965485721799998,
2.06299852418,
2.09449399818,
1.94414526318,
2.04979979918,
1.9269663631799998,
2.0149364801800003,
2.04941169918,
2.1933176311800002,
2.24952621518
]
}
]
}Scenario: Isolated linker: fresh restore, hot cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 0.6317573008,
"stddev": 0.015004909824401419,
"median": 0.6321784907,
"user": 0.36856229999999995,
"system": 1.32746642,
"min": 0.6112559797,
"max": 0.6556978537,
"times": [
0.6112559797,
0.6488127377,
0.6132716087,
0.6294683257,
0.6556978537,
0.6232600937,
0.6381127857,
0.6348886557,
0.6197326167,
0.6430723507
]
},
{
"command": "pacquet@main",
"mean": 0.6448947506,
"stddev": 0.017374438475559074,
"median": 0.6489139452,
"user": 0.3756411,
"system": 1.33085912,
"min": 0.6126619307,
"max": 0.6664037877,
"times": [
0.6466351977,
0.6547282747000001,
0.6258958987,
0.6597090307,
0.6511926927,
0.6664037877,
0.6612393807,
0.6306643067,
0.6398170057,
0.6126619307
]
},
{
"command": "pnpr@HEAD",
"mean": 0.7229202953,
"stddev": 0.0787537190027842,
"median": 0.7044403817,
"user": 0.38594560000000006,
"system": 1.3705735199999998,
"min": 0.6628824507,
"max": 0.9424922147,
"times": [
0.7088738347,
0.6878039137,
0.6887798877,
0.6628824507,
0.7208949817,
0.7066690687,
0.7071489527,
0.7022116947,
0.7014459537,
0.9424922147
]
},
{
"command": "pnpr@main",
"mean": 0.7144119255000001,
"stddev": 0.08239918421428115,
"median": 0.6920626747,
"user": 0.39042620000000006,
"system": 1.35234162,
"min": 0.6526185987,
"max": 0.9391371667,
"times": [
0.6688180877,
0.6652904847,
0.7121542597,
0.7034230077,
0.6959937257,
0.6859676037,
0.6526185987,
0.6881316237,
0.9391371667,
0.7325846967
]
}
]
}Scenario: Isolated linker: fresh install, cold cache + cold store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 4.16106503176,
"stddev": 0.03717480963377625,
"median": 4.15563516456,
"user": 3.6669685199999997,
"system": 3.3267433999999994,
"min": 4.11202522456,
"max": 4.24211138856,
"times": [
4.14907781456,
4.12384395756,
4.16495949556,
4.24211138856,
4.16271662656,
4.20064982956,
4.15564883256,
4.11202522456,
4.15562149656,
4.14399565156
]
},
{
"command": "pacquet@main",
"mean": 4.15851549936,
"stddev": 0.04105107487145694,
"median": 4.14910995306,
"user": 3.6620164200000005,
"system": 3.337551,
"min": 4.10403871156,
"max": 4.21928095756,
"times": [
4.16476690956,
4.10403871156,
4.1875963045599995,
4.19266826156,
4.20719647356,
4.11505550556,
4.21928095756,
4.12906078056,
4.13203809256,
4.13345299656
]
},
{
"command": "pnpr@HEAD",
"mean": 2.14129089896,
"stddev": 0.10083950249471413,
"median": 2.09984774456,
"user": 2.468240919999999,
"system": 2.8767052,
"min": 2.02949210556,
"max": 2.33907625856,
"times": [
2.08777797156,
2.05636808356,
2.08986072856,
2.20129514456,
2.33907625856,
2.02949210556,
2.10983476056,
2.08814785656,
2.28139584156,
2.12966023856
]
},
{
"command": "pnpr@main",
"mean": 2.2175148355600003,
"stddev": 0.12785991679844677,
"median": 2.20478807906,
"user": 2.4271606199999995,
"system": 2.9025352,
"min": 2.05115365556,
"max": 2.39163377156,
"times": [
2.21151178156,
2.19806437656,
2.30994972256,
2.09157629656,
2.39163377156,
2.05115365556,
2.36293293756,
2.05559987656,
2.33699773756,
2.16572819956
]
}
]
}Scenario: Isolated linker: fresh install, hot cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 1.30784410404,
"stddev": 0.012071869640314443,
"median": 1.30586282124,
"user": 1.2890770999999996,
"system": 1.7111762,
"min": 1.2922784682400001,
"max": 1.32619003224,
"times": [
1.31552237524,
1.2922784682400001,
1.32619003224,
1.29850580224,
1.29707580024,
1.3110682042400001,
1.31982673924,
1.32013938924,
1.30065743824,
1.29717679124
]
},
{
"command": "pacquet@main",
"mean": 1.35473015584,
"stddev": 0.044289711001699816,
"median": 1.3386479282400001,
"user": 1.3137156999999997,
"system": 1.7682381999999996,
"min": 1.32184445424,
"max": 1.47258058324,
"times": [
1.34641684524,
1.37732504424,
1.32184445424,
1.47258058324,
1.35437449524,
1.3373333122400002,
1.3396237262400001,
1.33579804724,
1.33767213024,
1.32433292024
]
},
{
"command": "pnpr@HEAD",
"mean": 0.67357434224,
"stddev": 0.08473474792312322,
"median": 0.64430041474,
"user": 0.3377559,
"system": 1.2844017,
"min": 0.6345491602400001,
"max": 0.9136293322400001,
"times": [
0.6583716552400001,
0.64393179824,
0.6345491602400001,
0.9136293322400001,
0.65901258224,
0.64447224024,
0.6403944582400001,
0.64195826824,
0.64412858924,
0.6552953382400001
]
},
{
"command": "pnpr@main",
"mean": 0.6429654462400002,
"stddev": 0.00865202688761687,
"median": 0.6396053702400001,
"user": 0.3318463,
"system": 1.2864617,
"min": 0.6320775592400001,
"max": 0.6620332962400001,
"times": [
0.6620332962400001,
0.63773151524,
0.64580103424,
0.6399665402400001,
0.6320775592400001,
0.65048980724,
0.63680455024,
0.6471490292400001,
0.6392442002400001,
0.6383569302400001
]
}
]
}Scenario: Isolated linker: fresh install, cold cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 2.9662301827000004,
"stddev": 0.03743214109296507,
"median": 2.9679507013000004,
"user": 1.71812228,
"system": 1.94530292,
"min": 2.9119107838000002,
"max": 3.0471645498,
"times": [
2.9922226378000003,
2.9666734798000003,
2.9239126738000003,
2.9708258818,
2.9781377148000003,
2.9119107838000002,
2.9537581708000005,
3.0471645498,
2.9692279228,
2.9484680118
]
},
{
"command": "pacquet@main",
"mean": 3.0034692179,
"stddev": 0.04259503134105539,
"median": 2.9909114788,
"user": 1.7196206799999998,
"system": 2.0053145199999998,
"min": 2.9684333358000003,
"max": 3.1175952438000003,
"times": [
3.0150527128,
2.9868168128000003,
3.0007070748,
2.9684333358000003,
2.9827364088,
2.9950061448,
2.9742817828000003,
2.9865102588,
3.1175952438000003,
3.0075524038
]
},
{
"command": "pnpr@HEAD",
"mean": 0.662541383,
"stddev": 0.0163980017412811,
"median": 0.6662758833000001,
"user": 0.33595548,
"system": 1.2906068199999998,
"min": 0.6378379608000001,
"max": 0.6803069558000001,
"times": [
0.6491996298000001,
0.6801263668,
0.6803069558000001,
0.6640151928000001,
0.6685365738000001,
0.6460170338000001,
0.6765003728000001,
0.6378379608000001,
0.6772242028000001,
0.6456495408
]
},
{
"command": "pnpr@main",
"mean": 0.6462483329,
"stddev": 0.009737286646791908,
"median": 0.6448376463000001,
"user": 0.32767478000000005,
"system": 1.2854211199999999,
"min": 0.6362040578000001,
"max": 0.6696410678000001,
"times": [
0.6414370778,
0.6393619808000001,
0.6463377128000001,
0.6552286038,
0.6696410678000001,
0.6447204848000001,
0.6449548078,
0.6392331258,
0.6362040578000001,
0.6453644098000001
]
}
]
} |
|
| Branch | pr/12566 |
| 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 | 4,161.07 ms(-1.33%)Baseline: 4,217.25 ms | 5,060.70 ms (82.22%) |
| isolated-linker.fresh-install.cold-cache.hot-store | 📈 view plot 🚷 view threshold | 2,966.23 ms(-1.34%)Baseline: 3,006.54 ms | 3,607.85 ms (82.22%) |
| isolated-linker.fresh-install.hot-cache.hot-store | 📈 view plot 🚷 view threshold | 1,307.84 ms(-1.44%)Baseline: 1,326.93 ms | 1,592.32 ms (82.13%) |
| isolated-linker.fresh-restore.cold-cache.cold-store | 📈 view plot 🚷 view threshold | 3,788.63 ms(-4.42%)Baseline: 3,963.76 ms | 4,756.52 ms (79.65%) |
| isolated-linker.fresh-restore.hot-cache.hot-store | 📈 view plot 🚷 view threshold | 631.76 ms(+1.46%)Baseline: 622.68 ms | 747.22 ms (84.55%) |
|
| Branch | pr/12566 |
| Testbed | pnpr |
⚠️ WARNING: No Threshold found!Without a Threshold, no Alerts will ever be generated.
Click here to create a new Threshold
For more information, see the Threshold documentation.
To only post results if a Threshold exists, set the--ci-only-thresholdsflag.
Click to view all benchmark results
| Benchmark | Latency | milliseconds (ms) |
|---|---|---|
| isolated-linker.fresh-install.cold-cache.cold-store | 📈 view plot | 2,141.29 ms |
| isolated-linker.fresh-install.cold-cache.hot-store | 📈 view plot | 662.54 ms |
| isolated-linker.fresh-install.hot-cache.hot-store | 📈 view plot | 673.57 ms |
| isolated-linker.fresh-restore.cold-cache.cold-store | 📈 view plot | 2,146.39 ms |
| isolated-linker.fresh-restore.hot-cache.hot-store | 📈 view plot | 722.92 ms |
|
Addressed Qodo's redundant OSV time-check finding in Verification run locally:
Written by an agent (Codex, gpt-5). |
|
Code review by qodo was updated up to the latest commit 6b07a50 |
1 similar comment
|
Code review by qodo was updated up to the latest commit 6b07a50 |
Summary
Closes #12561.
This applies pnpr's local OSV npm database to the registry serving surface, not just the resolver endpoints. Registry-enabled servers now load the OSV index when
osv.enabledis set, packument-style responses omit vulnerable versions, and tarball requests for vulnerable versions are refused before reading from cache or proxying upstream.Squash Commit Body
Checklist
pnpr/; pnpr has no TypeScript CLI orpacquet/counterpart.Written by an agent (Codex, gpt-5).
Summary by CodeRabbit