Skip to content

fix(pnpr): enforce bearer-token readonly and CIDR restrictions#12574

Merged
zkochan merged 3 commits into
mainfrom
vuln-034
Jun 22, 2026
Merged

fix(pnpr): enforce bearer-token readonly and CIDR restrictions#12574
zkochan merged 3 commits into
mainfrom
vuln-034

Conversation

@zkochan

@zkochan zkochan commented Jun 22, 2026

Copy link
Copy Markdown
Member

Summary

Fixes GHSA-rp44-v426-6m56 / CAND-PNPM-034 in pnpr (the Rust registry server).

Bearer-token records carry readonly and cidr_whitelist restrictions — they are persisted, loaded, and surfaced by npm token list — but nothing ever enforced them during authorization. The token lookup on the auth hot path resolved a raw token to just its username and dropped the restrictions, so a token marked read-only or pinned to a network could still publish and mutate packages from anywhere.

The fix

TokenBackend::lookup_record (a default method reusing each backend's existing find_by_key) resolves the full TokenRecord — username plus restrictions — so the local SQLite store and the libsql/sqlx backends all load readonly and cidr_whitelist with no per-backend change.

Enforcement runs in an axum middleware layer ahead of every route handler, so a restricted token is rejected before a write handler buffers its (up to 100 MiB) request body:

  • Read-only tokens are confined to non-mutating methods. Every write surface (publish, unpublish, dist-tag, adduser, logout, token revoke) is a PUT or DELETE and returns 403; GET/HEAD/OPTIONS and the resolver POSTs pass.
  • CIDR-pinned tokens are checked against the real socket peer address (a PeerAddr ConnectInfo newtype wired through axum::serve), never a client-supplied X-Forwarded-For. CIDR matching is done in-house (no new dependency) and covers IPv4/IPv6, /0/32//128, bare-host exact match, and IPv4-mapped IPv6 peers. A malformed entry or an unavailable peer fails closed.

Single-resolution refactor

The same middleware is now the one place a request is authenticated: it resolves the Authorization header to an Identity (looking a bearer token up as a full record so the restrictions above are enforced there) and stashes it in request extensions. Handlers read it back through an AuthedCaller extractor instead of each re-inspecting the header. This removes the second token lookup authed requests used to pay (a real round-trip on the remote libsql backend) and the policy/identity race two independent lookups allowed; the per-handler enforce_access / resolve_identity / caller_username helpers collapse into the existing synchronous authorize() and a pure require_caller() check.

Basic-auth, anonymous, and unknown/revoked tokens pass through unchanged and remain subject to the per-package access policy. A backing-store failure during lookup surfaces as a 5xx rather than silently skipping the check.

This is pnpr-only; it does not change the pnpm CLI or pacquet behavior.

Squash Commit Body

pnpr persisted and surfaced each bearer token's readonly and cidr_whitelist
restrictions but never applied them during authorization, so a token marked
read-only or pinned to a network could still publish and mutate packages from
anywhere (GHSA-rp44-v426-6m56 / CAND-PNPM-034).

TokenBackend::lookup_record (a default method reusing find_by_key) resolves
the full record, and an axum middleware authenticates every request once:
it resolves the caller, enforces the bearer token's read-only / CIDR
restrictions before a write handler buffers its body, and stashes the
resolved Identity in request extensions for handlers to read via an
AuthedCaller extractor.

- Read-only tokens are limited to non-mutating methods (PUT/DELETE -> 403).
- CIDR-pinned tokens are checked against the real socket peer (ConnectInfo),
  never a forwarding header; CIDR matching handles IPv4/IPv6, /0../32 and /128,
  bare hosts, and IPv4-mapped IPv6 peers. Malformed entries and an unavailable
  peer fail closed.

Resolving the caller once removes the second token lookup authed requests
previously paid (a round-trip on the remote libsql backend) and the
policy/identity race two independent lookups allowed. Basic-auth, anonymous,
and unknown tokens pass through unchanged and stay subject to the per-package
access policy; a store failure surfaces as a 5xx. pnpr-only; no change to the
pnpm CLI or pacquet.

Checklist

  • The change is registry-only (pnpr/), which has no pnpm-CLI or pacquet
    counterpart to mirror, so no TypeScript/pacquet port is needed.
  • No changeset: pnpr is a Rust crate, not a published npm package in the
    changeset/release flow.
  • Added or updated tests (auth lookup_record, CIDR matching edge cases,
    method/header classification, end-to-end restriction enforcement, and that
    the resolved identity reaches handlers).
  • No documentation changes needed.

Written by an agent (Claude Code, claude-opus-4-8).

Summary by CodeRabbit

Release Notes

  • New Features

    • Read-only bearer tokens are now fully enforced: write operations are denied while reads continue to work.
    • Token-based network restrictions now support CIDR allowlists to limit access by client IP range.
  • Security / Access Control

    • Enforced token restrictions earlier in request handling, including correct handling when peer IP information is unavailable.
    • Requests with CIDR mismatches are denied, and spoofed client IP headers no longer bypass restrictions.
  • Tests / Quality

    • Expanded automated coverage for token restrictions, CIDR matching, and bearer parsing.

zkochan added 2 commits June 22, 2026 13:36
pnpr persisted and surfaced each bearer token's `readonly` and
`cidr_whitelist` restrictions but never applied them during
authorization, so a token marked read-only or pinned to a network could
still publish and mutate packages from anywhere
(GHSA-rp44-v426-6m56 / CAND-PNPM-034).

The token lookup on the auth hot path resolved a raw token to just its
username and dropped the restriction fields. This adds
`TokenBackend::lookup_record`, a default trait method that resolves the
full `TokenRecord` (username plus restrictions) by reusing each
backend's existing `find_by_key`, so the local SQLite store and the
libsql/sqlx backends all load `readonly` and `cidr_whitelist` with no
per-backend change.

Enforcement runs in an axum middleware layer ahead of every route
handler, so a restricted token is rejected before a write handler
buffers its (up to 100 MiB) request body:

- Read-only tokens are confined to non-mutating methods. Every write
  surface (publish, unpublish, dist-tag, adduser, logout, token revoke)
  is a PUT or DELETE and returns 403; GET/HEAD/OPTIONS and the resolver
  POSTs pass.
- CIDR-pinned tokens are checked against the real socket peer address,
  captured via a `PeerAddr` `ConnectInfo` newtype wired through
  `axum::serve` (the blanket impl axum ships covers only the bare
  `TcpListener`, not our `NodelayTcpListener` wrapper). Client-supplied
  forwarding headers are never trusted. CIDR matching is done in-house
  (no new dependency) and handles IPv4/IPv6, /0 through /32 and /128,
  bare-host exact match, and IPv4-mapped IPv6 peers. A malformed entry
  or an unavailable peer fails closed.

Basic-auth, anonymous, and unknown or revoked tokens pass through
unchanged and remain subject to the per-package access policy. A
backing-store failure during lookup surfaces as a 5xx rather than
silently skipping the check.

This is pnpr-only and does not change the pnpm CLI or pacquet.

Written by an agent (Claude Code, claude-opus-4-8).
Reorder the PeerAddr derive list to the workspace's prefix_then_alphabetical
order, move the restricted-token test seam out of the auth source into a
small in-test TokenBackend mock (the perfectionist lint keeps test code in
external modules), and return an error instead of unimplemented! in that
mock's unused issue path.

Written by an agent (Claude Code, claude-opus-4-8).
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

fix(pnpr): enforce bearer-token readonly and CIDR restrictions
🐞 Bug fix 🧪 Tests 🕐 40+ Minutes

Grey Divider

Description

• Add full token-record lookup to preserve readonly and CIDR restrictions.
• Enforce bearer-token restrictions in middleware before buffering large write bodies.
• Add CIDR parsing/matching and end-to-end tests for enforcement and edge cases.
Diagram

graph TD
  A["HTTP request"] --> B(["Axum middleware: token gate"]) --> C["Route handler"]
  B --> D["TokenBackend.lookup_record"] --> E[("Token store")]
  D --> F{"Readonly?"} --> G{"CIDR allowed?"} --> C
  F --> H["Reject 403"]
  G --> H

  subgraph Legend
    direction LR
    _req["Request/Handler"] ~~~ _mw(["Middleware"]) ~~~ _dec{"Decision"} ~~~ _db[("Store")
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Enforce restrictions inside identify()/authorization logic
  • ➕ Centralizes all auth decisions in one place (identity + access policy).
  • ➕ Avoids adding a separate middleware stage.
  • ➖ Harder to ensure early rejection before large request bodies are buffered.
  • ➖ Likely requires refactoring identity types to carry restrictions everywhere.
2. Use a CIDR library (e.g., ipnet / cidr / ipnetwork)
  • ➕ Less bespoke parsing and bitmask logic to audit/maintain.
  • ➕ Potentially broader coverage and fewer edge-case bugs.
  • ➖ Adds a new dependency in a security-critical path.
  • ➖ Must still define fail-closed behavior and IPv4-mapped IPv6 handling.
3. Have TokenBackend::lookup return TokenRecord instead of username
  • ➕ Eliminates dual lookup APIs and the risk of dropping restrictions again.
  • ➕ Makes restriction data available wherever bearer auth is used.
  • ➖ Breaking trait/API change with broader churn across backends and call sites.
  • ➖ More invasive than adding a default lookup_record() method.

Recommendation: The PR’s approach is the best tradeoff for a vuln fix: add a default TokenBackend::lookup_record() (minimizing backend churn) and enforce in middleware to fail fast before large bodies are read. The in-house CIDR matcher is acceptable here because it’s small, covered by targeted tests (IPv4/IPv6, boundary prefixes, malformed entries), and explicitly fail-closed; a third-party CIDR crate was the main alternative worth considering.

Files changed (4) +493 / -10

Bug fix (2) +195 / -7
auth.rsAdd TokenBackend::lookup_record to return full TokenRecord +10/-0

Add TokenBackend::lookup_record to return full TokenRecord

• Introduces a default trait method that resolves a raw token to a full TokenRecord (including readonly and cidr_whitelist). The implementation reuses find_by_key(sha256(raw)) to avoid per-backend changes while preserving restriction data.

pnpr/crates/pnpr/src/auth.rs

server.rsEnforce token restrictions via middleware using real peer address +185/-7

Enforce token restrictions via middleware using real peer address

• Wires ConnectInfo into axum::serve via a PeerAddr newtype and Connected impl for the custom listener wrapper. Adds an axum middleware that (1) extracts Bearer tokens, (2) loads TokenRecord via lookup_record, (3) blocks mutating methods for readonly tokens, and (4) checks CIDR whitelists against the socket peer IP with fail-closed behavior; includes in-house IPv4/IPv6 CIDR matching helpers.

pnpr/crates/pnpr/src/server.rs

Tests (2) +298 / -3
tests.rsTest that lookup_record preserves readonly/CIDR restrictions +29/-2

Test that lookup_record preserves readonly/CIDR restrictions

• Adds a tokio test seeding an in-memory TokenStore with a restricted TokenRecord and asserting lookup_record returns the restriction fields intact. Also verifies unknown tokens return None.

pnpr/crates/pnpr/src/auth/tests.rs

tests.rsAdd CIDR/method/header unit tests and middleware enforcement tests +269/-1

Add CIDR/method/header unit tests and middleware enforcement tests

• Adds unit tests for CIDR matching (IPv4/IPv6, /0 and host prefixes, malformed entries, IPv4-mapped IPv6 normalization) and for write-method/header parsing. Adds end-to-end middleware tests using a one-token backend to validate readonly and CIDR enforcement, peer-address requirements, and that X-Forwarded-For cannot bypass CIDR pinning.

pnpr/crates/pnpr/src/server/tests.rs

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c65d64fe-0239-4eb8-ac85-3e7e30ebb9db

📥 Commits

Reviewing files that changed from the base of the PR and between ccecc85 and d7b6ecf.

📒 Files selected for processing (2)
  • pnpr/crates/pnpr/src/server.rs
  • pnpr/crates/pnpr/src/server/tests.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • pnpr/crates/pnpr/src/server/tests.rs
  • pnpr/crates/pnpr/src/server.rs

📝 Walkthrough

Walkthrough

Adds a lookup_record method to the TokenBackend trait that resolves a raw bearer token to its full TokenRecord. Introduces a PeerAddr type for non-spoofable socket peer IP via Axum's ConnectInfo. Implements a new authenticate Axum middleware that enforces read-only and CIDR-whitelist token restrictions before request handlers execute, backed by CIDR/IP parsing helpers. Refactors all route handlers and service helpers across the API (read, publish, account, dist-tags) to consume middleware-resolved Identity instead of reading authorization headers directly. Validates all behavior with CIDR/IP unit tests, helper unit tests, and comprehensive integration tests.

Changes

Bearer Token Self-Restrictions Enforcement

Layer / File(s) Summary
TokenBackend::lookup_record trait method and unit test
pnpr/crates/pnpr/src/auth.rs, pnpr/crates/pnpr/src/auth/tests.rs
Adds lookup_record to TokenBackend (SHA-256 hash → find_by_key) with Ok(None)/Err semantics, and verifies it against a seeded TokenStore with readonly and cidr_whitelist fields, including unknown-token None case.
Server imports and type definitions update
pnpr/crates/pnpr/src/server.rs
Adds TokenRecord to auth imports; introduces Axum ConnectInfo types and std utilities (IpAddr, SocketAddr) needed for peer-address extraction and CIDR matching logic.
PeerAddr type and ConnectInfo server wiring
pnpr/crates/pnpr/src/server.rs
Introduces PeerAddr(SocketAddr) struct with Connected impl for NodelayTcpListener; updates both serve and serve_listener to use into_make_service_with_connect_info::<PeerAddr>(); registers authenticate as a router middleware layer.
authenticate middleware and CIDR/IP restriction helpers
pnpr/crates/pnpr/src/server.rs
Implements middleware (bearer extraction, lookup_record, readonly/write-method rejection, CIDR allowlist enforcement) and helpers: bearer_credentials, is_write_method, canonical_ip (IPv4-mapped normalization), cidr_whitelist_allows, cidr_contains, parse_prefix, and bit-mask utilities.
Read API handlers and packument-serving helpers refactoring
pnpr/crates/pnpr/src/server.rs
Refactors GET route handlers and helpers (serve_packument, serve_version_manifest, serve_tarball, serve_search) to accept AuthedCaller(identity) or &Identity, and updates read authorization via authorize(..., Action::Access).
Publish API handlers and validation helpers refactoring
pnpr/crates/pnpr/src/server.rs
Refactors PUT/DELETE route handlers and helpers (publish_package, validate_publish_doc, update_packument, delete_package, delete_tarball) to accept AuthedCaller(identity) or &Identity, and updates publish authorization via authorize(..., Action::Publish).
User and token account endpoints refactoring
pnpr/crates/pnpr/src/server.rs
Refactors account route handlers and helpers (serve_whoami, serve_profile, list_tokens, revoke_token_by_key, logout) to accept &Identity; introduces synchronous require_caller(identity, resource) for unauthenticated-request rejection on account-scoped operations.
Dist-tags operation helpers refactoring
pnpr/crates/pnpr/src/server.rs
Refactors dist-tag helpers (get_dist_tags, set_dist_tag, remove_dist_tag, update_dist_tag) to accept &Identity and updates publish authorization via authorize(..., Action::Publish) for tag mutations.
CIDR/IP and integration test suite
pnpr/crates/pnpr/src/server/tests.rs
Adds CIDR/IP unit tests (IPv4/IPv6, boundaries, family-mismatch fail-closed, canonical normalization), helper unit tests (bearer_credentials, is_write_method), end-to-end test harness with OneToken backend, and integration tests covering readonly enforcement, CIDR allow/deny/fail-closed, and X-Forwarded-For spoofing resistance.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Listener as TCP Listener
  participant Middleware as authenticate middleware
  participant TokenBackend
  participant Handler as Route Handler
  participant Client2

  Listener->>Middleware: accept peer, inject ConnectInfo<PeerAddr>
  Client->>Middleware: HTTP request + Authorization: Bearer <token>
  Middleware->>TokenBackend: lookup_record(raw_token)
  alt store error
    TokenBackend-->>Middleware: Err
    Middleware-->>Client: 500 Internal Server Error
  else token not found
    TokenBackend-->>Middleware: Ok(None)
    Middleware->>Handler: forward (anonymous identity)
    Handler-->>Client: response
  else token found with TokenRecord
    TokenBackend-->>Middleware: Ok(Some(TokenRecord))
    alt readonly=true AND is_write_method(http_method)
      Middleware-->>Client: 403 Forbidden
    else cidr_whitelist non-empty AND peer IP outside range
      Middleware-->>Client: 403 Forbidden
    else all checks pass
      Middleware->>Handler: forward with Identity in extensions
      Handler-->>Client2: response
    end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Possibly related PRs

  • pnpm/pnpm#12206: The main PR's new TokenBackend::lookup_record and server-side auth middleware wiring directly extend this PR's async TokenBackend trait and identity-resolution refactor.
  • pnpm/pnpm#12011: This PR's new token-record lookup and middleware-resolved identity support enables the retrieved PR's whoami/profile/token CRUD handlers that require non-header-based caller resolution and token revocation by raw token.

Suggested labels

product: pnpr

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main changes: enforcing bearer-token readonly and CIDR restrictions, which is the core objective of this security fix.
Description check ✅ Passed The description fully addresses the template requirements with a clear summary, detailed commit body explaining rationale and design decisions, and a comprehensive checklist that accurately reflects the scope and nature of the changes.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch vuln-034

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 22, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (6) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Identity cloned from extensions 🐞 Bug ➹ Performance
Description
AuthedCaller extraction uses .cloned() on the value stored in request extensions, which clones
Identity and therefore clones the username: String on every request that hits a registry
handler. This adds avoidable per-request heap work on a pnpr hot path that the middleware otherwise
optimized to a single auth-backend lookup.
Code

pnpr/crates/pnpr/src/server.rs[R2033-2047]

+impl<RouterState: Send + Sync> FromRequestParts<RouterState> for AuthedCaller {
+    type Rejection = Response;
+
+    async fn from_request_parts(
+        parts: &mut Parts,
+        _state: &RouterState,
+    ) -> Result<Self, Self::Rejection> {
+        // The middleware runs on every route, so the context is always
+        // present; a miss means a wiring bug, surfaced as a 5xx.
+        parts.extensions.get::<AuthedCaller>().cloned().ok_or_else(|| {
+            error_response(&RegistryError::Internal {
+                reason: "authentication middleware did not run".to_string(),
+            })
+        })
+    }
Evidence
The extractor clones the stored AuthedCaller, and Identity::User contains an owned String, so
this becomes a per-request string clone/allocation on the handler path.

pnpr/crates/pnpr/src/server.rs[2033-2047]
pnpr/crates/pnpr/src/policy.rs[81-90]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`AuthedCaller` currently clones the stored identity from `request.extensions`, which clones the underlying `String` username per request.
### Issue Context
The middleware already computed and stored the identity once per request; handlers should be able to retrieve it without cloning allocations.
### Fix Focus Areas
- pnpr/crates/pnpr/src/server.rs[2033-2047]
### Implementation notes
- Prefer moving the value out of extensions:
- In `FromRequestParts`, use `parts.extensions.remove::<AuthedCaller>()` instead of `get(...).cloned()`.
- This avoids cloning `Identity`/`String` while still failing with a 5xx if the middleware didn’t run.
- Alternative if multiple extractions are expected: store `Arc<Identity>` (or `Arc<str>` for username) in extensions so cloning is cheap.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. CIDR parsed every request 🐞 Bug ➹ Performance
Description
CIDR-pinned token enforcement reparses each whitelist entry (IpAddr parse + prefix/mask
computation) on every request, making authorization cost scale with whitelist length. This is
avoidable work on a hot path and can become significant for long whitelists or high RPS.
Code

pnpr/crates/pnpr/src/server.rs[R2191-2197]

+/// Whether `peer` falls inside any range of a token's CIDR whitelist. An
+/// IPv4-mapped IPv6 peer is normalized to its IPv4 form first, so a
+/// dual-stack listener still matches plain IPv4 ranges.
+fn cidr_whitelist_allows(whitelist: &[String], peer: SocketAddr) -> bool {
+    let peer = canonical_ip(peer.ip());
+    whitelist.iter().any(|entry| cidr_contains(entry.trim(), peer))
+}
Evidence
When CIDR restrictions are present, check_token_restrictions calls cidr_whitelist_allows, which
iterates the string whitelist and parses each entry via cidr_contains on every request.

pnpr/crates/pnpr/src/server.rs[2127-2137]
pnpr/crates/pnpr/src/server.rs[2191-2236]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`cidr_whitelist_allows()` walks the whitelist and calls `cidr_contains()`, which parses the CIDR string every time a request is authorized. That repeats identical parsing work across requests using the same token.
### Issue Context
This runs inside `check_token_restrictions()` during bearer-token auth, so it’s on the authorization hot path.
### Fix Focus Areas
- pnpr/crates/pnpr/src/server.rs[2115-2137]
- pnpr/crates/pnpr/src/server.rs[2191-2257]
### Suggested fix approach
- Introduce a small bounded cache keyed by token key/hash (e.g., SHA-256 hex) that stores a pre-parsed whitelist representation (e.g., `Vec<(IpAddr, u8)>` or `Vec<CidrRange>`).
- Populate the cache when a token record is first resolved, and reuse it for subsequent requests using the same token.
- Ensure the cache is size-bounded (to avoid memory growth) and that malformed entries still fail closed (cache should store only successfully parsed entries; any parse failure should keep the token denied or mark the cache entry as "deny").

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Auth lookup on resolver 🐞 Bug ⛨ Security
Description
The new enforce_token_restrictions middleware is applied to the entire router, so any request that
includes an Authorization: Bearer … header will trigger tokens.lookup_record() even on
/-/pnpr/* resolver and /-/ping routes that explicitly don’t consult auth. This expands an
attacker-controlled availability/perf risk (spam resolver/ping with random Bearer values to force DB
lookups) and adds avoidable latency/load on libsql/sqlx backends.
Code

pnpr/crates/pnpr/src/server.rs[R316-320]

+        // Enforce bearer-token read-only / CIDR restrictions ahead of
+        // every handler, so a restricted token is rejected before a write
+        // handler buffers its (up to 100 MiB) request body. Inside the
+        // trace layer below, so a rejection is still one access record.
+        .layer(axum::middleware::from_fn_with_state(state.clone(), enforce_token_restrictions))
Evidence
enforce_token_restrictions is installed as a router-wide layer, while the same router mounts
/-/ping and the resolver /-/pnpr/* endpoints. The resolver handler explicitly documents that it
has no access gate, so the new Bearer-triggered token lookup is extra work on routes that previously
avoided auth-store reads.

pnpr/crates/pnpr/src/server.rs[260-323]
pnpr/crates/pnpr/src/server.rs[2620-2631]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`enforce_token_restrictions` is layered on the top-level router, so Bearer-present requests to endpoints that do not use auth (e.g. `/-/pnpr/v0/resolve`) still incur an auth-backend lookup. This increases unnecessary auth DB traffic and expands the DoS surface (attacker can force DB reads by sending random Bearer tokens to resolver/ping).
### Issue Context
The resolver handler explicitly states it has no per-package read gate and uses forwarded credentials for private packages; it should not require/trigger token backend reads unless the registry surface is actually being authorized.
### Fix Focus Areas
- pnpr/crates/pnpr/src/server.rs[260-323]
- pnpr/crates/pnpr/src/server.rs[2620-2631]
### Suggested fix direction
- Apply the `enforce_token_restrictions` layer only to the registry sub-router (i.e., wrap only the registry routes before merging into the main router), **or**
- Add a cheap path-based early return in `enforce_token_restrictions` to skip enforcement for `/-/pnpr` (and optionally `/-/ping`) routes, so resolver traffic can’t be used to amplify auth-backend load.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (2)
4. Duplicate bearer token lookups ✓ Resolved 🐞 Bug ➹ Performance
Description
enforce_token_restrictions() always calls tokens.lookup_record() for Bearer requests, but the normal
auth path still calls identify() which performs tokens.lookup() again, doubling token-backend work
per Bearer request. For SQL/libsql backends this becomes two DB reads per request, increasing
latency/load on a pnpr hot path and making auth timeouts more likely under load.
Code

pnpr/crates/pnpr/src/server.rs[R2125-2131]

+    let record = match state.inner.auth.tokens.lookup_record(raw_token).await {
+        Ok(Some(record)) => record,
+        // No record means nothing to enforce here; a store failure fails
+        // closed with a 5xx rather than silently skipping the check.
+        Ok(None) => return next.run(request).await,
+        Err(err) => return error_response(&err),
+    };
Evidence
The new middleware always resolves a Bearer token to a full record via lookup_record(), but later
identity resolution still runs through identify(), which for Bearer calls tokens.lookup(); this
creates two backend lookups for the same token on a single request path.

pnpr/crates/pnpr/src/server.rs[2112-2131]
pnpr/crates/pnpr/src/server.rs[1215-1225]
pnpr/crates/pnpr/src/auth.rs[629-646]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`enforce_token_restrictions` performs a Bearer token backend lookup (`lookup_record`) but request handlers still resolve identity via `identify(...)`, which triggers a second backend lookup (`lookup`). This doubles token-store work on the hot path for every Bearer-authenticated request.
### Issue Context
- The middleware needs the full `TokenRecord` for restriction enforcement.
- Handlers later need the username/identity for access-policy checks and auth-required endpoints.
### Fix Focus Areas
- Prefer reusing the middleware’s already-fetched `TokenRecord`/username (e.g., stash it in `Request` extensions) so downstream auth resolution can avoid a second backend call for Bearer.
- Update the auth resolution helpers (e.g., `resolve_identity` / `caller_username`) to consult the cached value first for Bearer, while keeping Basic/anonymous behavior unchanged.
#### Code pointers
- pnpr/crates/pnpr/src/server.rs[2112-2157]
- pnpr/crates/pnpr/src/server.rs[1215-1225]
- pnpr/crates/pnpr/src/auth.rs[629-646]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. CIDR gate needs ConnectInfo 🐞 Bug ☼ Reliability
Description
CIDR-pinned tokens are rejected whenever ConnectInfo<PeerAddr> is missing, but only
serve()/serve_listener() are updated to install connect info; embedders serving router_with_auth()
directly without into_make_service_with_connect_info::<PeerAddr>() will see all CIDR-restricted
tokens fail. This is a reliability/compatibility footgun because the failure mode is an
indistinguishable 403 instead of an explicit “server misconfigured / peer unavailable” error.
Code

pnpr/crates/pnpr/src/server.rs[R2141-2147]

+    if !record.cidr_whitelist.is_empty() {
+        // The peer address comes from the accepted socket (`ConnectInfo`),
+        // never a client-supplied forwarding header. When it's absent the
+        // restriction can't be evaluated, so the token is refused.
+        let peer = request.extensions().get::<ConnectInfo<PeerAddr>>().map(|info| info.0.0);
+        let allowed = peer.is_some_and(|addr| cidr_whitelist_allows(&record.cidr_whitelist, addr));
+        if !allowed {
Evidence
The CIDR gate explicitly denies when ConnectInfo<PeerAddr> is absent. ConnectInfo is only installed
in the built-in serve paths via into_make_service_with_connect_info::<PeerAddr>(); embedders
constructing/serving the Router directly won’t get it, triggering the deny branch for all
CIDR-restricted tokens.

pnpr/crates/pnpr/src/server.rs[2141-2154]
pnpr/crates/pnpr/src/server.rs[371-395]
pnpr/crates/pnpr/src/server.rs[157-197]
pnpr/crates/pnpr/src/server.rs[483-497]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
CIDR enforcement fails closed when `ConnectInfo<PeerAddr>` is absent, which is correct for security but easy for embedders to trigger accidentally by serving the router without `into_make_service_with_connect_info::<PeerAddr>()`. Today this shows up as a 403 that looks like a legitimate policy denial.
### Issue Context
`router()` / `router_with_auth()` are public entry points documented for embedders. Only the built-in `serve()` / `serve_listener()` paths attach connect info.
### Fix Focus Areas
- Make the misconfiguration obvious:
- Option A: return a 5xx with a clear message when `cidr_whitelist` is non-empty but peer info is unavailable (instead of a generic 403).
- Option B: emit a structured warning log in that branch including guidance (`into_make_service_with_connect_info::<PeerAddr>()`).
- Consider documenting the requirement in the public router constructors’ rustdocs so embedders don’t miss it.
#### Code pointers
- pnpr/crates/pnpr/src/server.rs[2112-2154]
- pnpr/crates/pnpr/src/server.rs[371-395]
- pnpr/crates/pnpr/src/server.rs[157-197]
- pnpr/crates/pnpr/src/server.rs[483-497]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

6. PATCH treated as write 🐞 Bug ⚙ Maintainability
Description
is_write_method() treats PATCH as a mutating method even though the router only wires write
surfaces as PUT/DELETE, and the comment above claims writes are only PUT/DELETE. This
mismatch can create surprising readonly-token rejections if a non-mutating PATCH endpoint is added
later, or can mislead future changes/tests.
Code

pnpr/crates/pnpr/src/server.rs[R2183-2189]

+/// Whether `method` mutates registry state. Every write surface (publish,
+/// unpublish, dist-tag add/remove, adduser, logout, token revoke) is a
+/// PUT or DELETE; reads and the resolver POSTs are not. A read-only token
+/// is confined to the non-mutating methods.
+fn is_write_method(method: &Method) -> bool {
+    matches!(*method, Method::PUT | Method::DELETE | Method::PATCH)
+}
Evidence
The helper explicitly includes Method::PATCH, while the routing setup only attaches write handlers
using .put(...) and .delete(...) (no .patch(...) routes).

pnpr/crates/pnpr/src/server.rs[2183-2189]
pnpr/crates/pnpr/src/server.rs[294-313]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`is_write_method` includes `PATCH` but surrounding documentation claims all mutating surfaces are `PUT`/`DELETE`, and current routing shows only `PUT`/`DELETE` for writes.
### Issue Context
This is a minor drift issue today, but it can trip future work (unexpected 403s for readonly tokens on PATCH endpoints, or confusion about intended behavior).
### Fix Focus Areas
- pnpr/crates/pnpr/src/server.rs[2183-2189]
- pnpr/crates/pnpr/src/server.rs[294-313]
### Implementation notes
Choose one:
- If PATCH is not intended: remove `Method::PATCH` from `is_write_method`.
- If PATCH is intended as a write-class method: update the comment to include PATCH and add/adjust tests accordingly.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Eager auth header allocation 🐞 Bug ➹ Performance
Description
authenticate() copies the full Authorization header into an owned String for every request,
adding an avoidable heap allocation/copy on the request hot path. This extra work is paid even when
credentials are invalid and will be rejected, reducing throughput under load.
Code

pnpr/crates/pnpr/src/server.rs[R2067-2073]

+    let header = request
+        .headers()
+        .get(header::AUTHORIZATION)
+        .and_then(|value| value.to_str().ok())
+        .map(str::to_owned);
+    let method = request.method().clone();
+    let peer = request.extensions().get::<ConnectInfo<PeerAddr>>().map(|info| info.0.0);
Evidence
The middleware allocates a new String from the header value (map(str::to_owned)), and it is
installed as a router layer so the allocation occurs on every request that reaches the router.

pnpr/crates/pnpr/src/server.rs[2063-2073]
pnpr/crates/pnpr/src/server.rs[315-323]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`authenticate()` currently does `to_str().ok().map(str::to_owned)`, which allocates and copies the whole `Authorization` header value on every request before any auth decision is made.
### Issue Context
This middleware is installed as a router layer, so it runs on the hot path for all requests.
### Fix Focus Areas
- pnpr/crates/pnpr/src/server.rs[2063-2081]
- pnpr/crates/pnpr/src/server.rs[2090-2109]
### Suggested fix approach
- In `authenticate()`, clone the header as a `HeaderValue` (cheap) rather than allocating a `String`:
- `let header = request.headers().get(header::AUTHORIZATION).cloned();`
- Update `resolve_caller` to accept `Option<HeaderValue>` (or similar owned type).
- For the Bearer path, parse/extract credentials and compute the SHA-256 digest immediately, then call `tokens.find_by_key(&digest)` to avoid retaining/allocating the full raw token across an `await`.
- For the Basic path, parse/decode from the owned `HeaderValue` bytes and pass only the minimal owned pieces (username/password) into any awaited backend calls.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

@github-actions github-actions Bot added the reviewed: coderabbit CodeRabbit submitted an approving review label Jun 22, 2026
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit ccecc85

@codecov-commenter

codecov-commenter commented Jun 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.17225% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.05%. Comparing base (1c04a00) to head (d7b6ecf).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
pnpr/crates/pnpr/src/server.rs 96.13% 8 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #12574      +/-   ##
==========================================
+ Coverage   87.99%   88.05%   +0.06%     
==========================================
  Files         324      326       +2     
  Lines       46008    46415     +407     
==========================================
+ Hits        40483    40870     +387     
- Misses       5525     5545      +20     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Authentication now happens once, in the request middleware: it resolves
the Authorization header to an Identity (looking a bearer token up as a
full record so its read-only / CIDR restrictions are enforced there),
and stashes that Identity in request extensions. Handlers read it back
through an AuthedCaller extractor instead of each re-inspecting the
header.

This removes the second token lookup that authed requests previously
paid (middleware gate plus the handler's own resolution) — a real
round-trip for the remote libsql backend — and the policy/identity race
that two independent lookups allowed. The per-handler enforce_access /
resolve_identity / caller_username helpers collapse into the existing
synchronous authorize() and a pure require_caller() check against the
already-resolved identity.

Behavior is unchanged: the same 401/403/5xx outcomes, the same
per-package policy, and the same read-only / CIDR enforcement, now driven
from one place.

Written by an agent (Claude Code, claude-opus-4-8).
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit d7b6ecf

1 similar comment
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit d7b6ecf

@github-actions

Copy link
Copy Markdown
Contributor

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

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 4.399 ± 0.182 4.269 4.873 1.51 ± 0.09
pacquet@main 4.455 ± 0.150 4.328 4.816 1.53 ± 0.09
pnpr@HEAD 2.933 ± 0.147 2.776 3.197 1.01 ± 0.07
pnpr@main 2.913 ± 0.134 2.778 3.188 1.00
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 4.399437346079999,
      "stddev": 0.18161216626705762,
      "median": 4.32663907088,
      "user": 3.6947663200000003,
      "system": 3.3873963599999994,
      "min": 4.2690582378799995,
      "max": 4.87252955288,
      "times": [
        4.87252955288,
        4.322318999879999,
        4.36115765688,
        4.466671904879999,
        4.481496369879999,
        4.2690582378799995,
        4.32401237588,
        4.27806408788,
        4.28979850888,
        4.32926576588
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 4.45503097498,
      "stddev": 0.1498273889623892,
      "median": 4.392776510879999,
      "user": 3.75682172,
      "system": 3.433847759999999,
      "min": 4.32767041188,
      "max": 4.81604443188,
      "times": [
        4.81604443188,
        4.53180308388,
        4.38086097388,
        4.44820673988,
        4.3582860308799996,
        4.404692047879999,
        4.34337149688,
        4.37158010888,
        4.56779442388,
        4.32767041188
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 2.9328507220800004,
      "stddev": 0.14652670598717019,
      "median": 2.90435282138,
      "user": 2.72554682,
      "system": 3.0131685599999996,
      "min": 2.77569273688,
      "max": 3.19748778088,
      "times": [
        3.19748778088,
        2.88076446688,
        2.83126539588,
        2.92794117588,
        2.79460937388,
        3.15014716588,
        2.95963784688,
        2.99740018788,
        2.81356108988,
        2.77569273688
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 2.91269451668,
      "stddev": 0.13422940927482258,
      "median": 2.8547182488800003,
      "user": 2.76234572,
      "system": 3.0078916599999994,
      "min": 2.77846751188,
      "max": 3.18819960388,
      "times": [
        2.82385403288,
        2.86511005788,
        3.08706894988,
        2.84432643988,
        3.18819960388,
        2.9081502338800003,
        2.8255144478800003,
        2.9902940998800003,
        2.77846751188,
        2.8159597888800003
      ]
    }
  ]
}

Scenario: Isolated linker: fresh restore, hot cache + hot store

Command Mean [ms] Min [ms] Max [ms] Relative
pacquet@HEAD 646.0 ± 9.3 628.1 656.6 1.00
pacquet@main 646.9 ± 11.5 627.9 663.8 1.00 ± 0.02
pnpr@HEAD 715.3 ± 95.9 667.9 985.1 1.11 ± 0.15
pnpr@main 726.3 ± 64.7 687.7 907.2 1.12 ± 0.10
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 0.6459712147200001,
      "stddev": 0.009293329523401821,
      "median": 0.6482859358200002,
      "user": 0.39606252,
      "system": 1.3212601,
      "min": 0.6280953138200001,
      "max": 0.65664798982,
      "times": [
        0.6518051998200001,
        0.6513074108200001,
        0.6419648248200001,
        0.6556161018200001,
        0.6457579198200001,
        0.6339888508200001,
        0.64371458382,
        0.6280953138200001,
        0.6508139518200001,
        0.65664798982
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 0.64690693702,
      "stddev": 0.011511484939610772,
      "median": 0.6484007538200001,
      "user": 0.39706012,
      "system": 1.3219697999999998,
      "min": 0.6278555628200001,
      "max": 0.6637676798200001,
      "times": [
        0.6525698628200001,
        0.6375752238200001,
        0.6278555628200001,
        0.6496238688200001,
        0.6637676798200001,
        0.6349749478200001,
        0.6625467888200001,
        0.6504548548200001,
        0.6471776388200001,
        0.64252294182
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 0.71525289452,
      "stddev": 0.0958817421689313,
      "median": 0.68925146382,
      "user": 0.41202201999999993,
      "system": 1.3622793999999998,
      "min": 0.6679028548200001,
      "max": 0.9851431058200001,
      "times": [
        0.70511895682,
        0.6679028548200001,
        0.6914089938200001,
        0.6947806498200001,
        0.67668259082,
        0.6870939338200001,
        0.7054096568200001,
        0.6686385938200001,
        0.67034960882,
        0.9851431058200001
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 0.7263077014200001,
      "stddev": 0.06472210069978467,
      "median": 0.7048637668200001,
      "user": 0.41079901999999996,
      "system": 1.3650557999999997,
      "min": 0.68773153082,
      "max": 0.9072144248200001,
      "times": [
        0.7174327058200001,
        0.7122723298200001,
        0.6980663588200001,
        0.69958475482,
        0.68773153082,
        0.7084128768200001,
        0.6993948948200001,
        0.9072144248200001,
        0.7316524808200001,
        0.70131465682
      ]
    }
  ]
}

Scenario: Isolated linker: fresh install, cold cache + cold store

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 4.785 ± 0.052 4.695 4.880 1.70 ± 0.08
pacquet@main 4.703 ± 0.066 4.626 4.826 1.67 ± 0.08
pnpr@HEAD 2.820 ± 0.125 2.740 3.153 1.00
pnpr@main 2.906 ± 0.153 2.708 3.158 1.03 ± 0.07
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 4.784806553939999,
      "stddev": 0.051692890027121005,
      "median": 4.78401565964,
      "user": 3.9123415199999996,
      "system": 3.41246942,
      "min": 4.69533308564,
      "max": 4.880458217639999,
      "times": [
        4.7490338336399995,
        4.73942480564,
        4.815439555639999,
        4.76733454764,
        4.78122701764,
        4.69533308564,
        4.80455914364,
        4.78680430164,
        4.82845103064,
        4.880458217639999
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 4.702901542739999,
      "stddev": 0.06595937565760361,
      "median": 4.68297182164,
      "user": 3.8843282199999996,
      "system": 3.35995182,
      "min": 4.6262130376399995,
      "max": 4.826499612639999,
      "times": [
        4.73991311364,
        4.68163800464,
        4.78369041664,
        4.633630935639999,
        4.73079787064,
        4.6262130376399995,
        4.826499612639999,
        4.67218001664,
        4.65014678064,
        4.68430563864
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 2.8203174957400003,
      "stddev": 0.12530473719722843,
      "median": 2.76862161414,
      "user": 2.59509322,
      "system": 2.9365975200000003,
      "min": 2.73953666764,
      "max": 3.15275536764,
      "times": [
        2.7666446596400003,
        2.7631507756400002,
        2.77059856864,
        3.15275536764,
        2.73953666764,
        2.79085246564,
        2.84875769764,
        2.74545613664,
        2.87695020064,
        2.74847241764
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 2.90633005734,
      "stddev": 0.15254060861757407,
      "median": 2.9007390866400002,
      "user": 2.6057043199999996,
      "system": 2.9229999199999996,
      "min": 2.70839745764,
      "max": 3.15783312664,
      "times": [
        2.7579022176400003,
        2.7441221176400004,
        2.97251110964,
        3.15783312664,
        3.06888513564,
        2.86560446564,
        2.9358737076400003,
        2.70839745764,
        2.8153419096400003,
        3.03682932564
      ]
    }
  ]
}

Scenario: Isolated linker: fresh install, hot cache + hot store

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 1.377 ± 0.015 1.349 1.405 1.95 ± 0.25
pacquet@main 1.451 ± 0.068 1.407 1.638 2.05 ± 0.28
pnpr@HEAD 0.709 ± 0.027 0.673 0.755 1.00 ± 0.13
pnpr@main 0.707 ± 0.090 0.666 0.962 1.00
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 1.3768552383400001,
      "stddev": 0.015458850902761808,
      "median": 1.37408230384,
      "user": 1.41905026,
      "system": 1.71776352,
      "min": 1.3489553138400001,
      "max": 1.40517285584,
      "times": [
        1.40517285584,
        1.3902190568400001,
        1.37043762184,
        1.36637611684,
        1.37470144884,
        1.39122850284,
        1.3489553138400001,
        1.37496138484,
        1.3730369228400001,
        1.3734631588400001
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 1.45101749064,
      "stddev": 0.06753995673148579,
      "median": 1.4369207243400002,
      "user": 1.4281841599999998,
      "system": 1.7814400200000002,
      "min": 1.40661960284,
      "max": 1.6382063068400001,
      "times": [
        1.42621018884,
        1.41379244284,
        1.6382063068400001,
        1.44761934384,
        1.45006461684,
        1.44169645784,
        1.40661960284,
        1.4410097638400001,
        1.41212449784,
        1.43283168484
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 0.70904035164,
      "stddev": 0.026980709483193643,
      "median": 0.7062568768400002,
      "user": 0.3907085599999999,
      "system": 1.32451872,
      "min": 0.67346204084,
      "max": 0.7551644358400001,
      "times": [
        0.67346204084,
        0.68966477284,
        0.6822361388400001,
        0.6959907578400001,
        0.72060276584,
        0.7551644358400001,
        0.69277382084,
        0.7458905958400001,
        0.7180951918400001,
        0.7165229958400001
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 0.7073868147400002,
      "stddev": 0.08969702027882301,
      "median": 0.68079027684,
      "user": 0.37726216,
      "system": 1.30058082,
      "min": 0.6661453378400001,
      "max": 0.9617794658400001,
      "times": [
        0.6822616698400001,
        0.6865405628400001,
        0.6746425008400001,
        0.6661453378400001,
        0.6816983618400001,
        0.6798821918400001,
        0.6717276858400001,
        0.9617794658400001,
        0.6926138618400001,
        0.6765765088400001
      ]
    }
  ]
}

Scenario: Isolated linker: fresh install, cold cache + hot store

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 3.094 ± 0.030 3.051 3.132 4.51 ± 0.09
pacquet@main 3.061 ± 0.034 3.027 3.130 4.46 ± 0.09
pnpr@HEAD 0.686 ± 0.011 0.670 0.707 1.00
pnpr@main 0.699 ± 0.069 0.669 0.892 1.02 ± 0.10
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 3.09395541446,
      "stddev": 0.03016694700289155,
      "median": 3.09432818386,
      "user": 1.9025015599999997,
      "system": 1.9809387199999997,
      "min": 3.05094482686,
      "max": 3.1324522998599997,
      "times": [
        3.10124459086,
        3.0660358298599997,
        3.12147940286,
        3.11024828586,
        3.1324522998599997,
        3.08170181686,
        3.08741177686,
        3.13149992586,
        3.05653538886,
        3.05094482686
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 3.06060899366,
      "stddev": 0.03394448419068206,
      "median": 3.0513744983599995,
      "user": 1.8323301599999997,
      "system": 1.9866463200000002,
      "min": 3.02660759286,
      "max": 3.13011649586,
      "times": [
        3.05921912786,
        3.0403151458599997,
        3.11282446886,
        3.0313060948599997,
        3.02660759286,
        3.05100883086,
        3.0517401658599996,
        3.13011649586,
        3.05627882386,
        3.04667318986
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 0.6855675983599999,
      "stddev": 0.011076509220762686,
      "median": 0.68560381486,
      "user": 0.36308916,
      "system": 1.3187819199999997,
      "min": 0.66959577386,
      "max": 0.70678043186,
      "times": [
        0.67325549886,
        0.68693821686,
        0.67839086586,
        0.68766388186,
        0.68664498286,
        0.69899169986,
        0.70678043186,
        0.68285198486,
        0.68456264686,
        0.66959577386
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 0.69880834786,
      "stddev": 0.06851674717670805,
      "median": 0.67684498036,
      "user": 0.36801026000000003,
      "system": 1.2933537199999998,
      "min": 0.66876854886,
      "max": 0.89225161186,
      "times": [
        0.67764536686,
        0.69930247286,
        0.68094253286,
        0.67330922286,
        0.67318128086,
        0.66899248086,
        0.66876854886,
        0.89225161186,
        0.67653215086,
        0.67715780986
      ]
    }
  ]
}

@github-actions

Copy link
Copy Markdown
Contributor

🐰 Bencher Report

Branchpr/12574
Testbedpacquet
Click to view all benchmark results
BenchmarkLatencyBenchmark Result
milliseconds (ms)
(Result Δ%)
Upper Boundary
milliseconds (ms)
(Limit %)
isolated-linker.fresh-install.cold-cache.cold-store📈 view plot
🚷 view threshold
4,784.81 ms
(+12.04%)Baseline: 4,270.51 ms
5,124.61 ms
(93.37%)
isolated-linker.fresh-install.cold-cache.hot-store📈 view plot
🚷 view threshold
3,093.96 ms
(+2.27%)Baseline: 3,025.14 ms
3,630.16 ms
(85.23%)
isolated-linker.fresh-install.hot-cache.hot-store📈 view plot
🚷 view threshold
1,376.86 ms
(+3.35%)Baseline: 1,332.21 ms
1,598.65 ms
(86.13%)
isolated-linker.fresh-restore.cold-cache.cold-store📈 view plot
🚷 view threshold
4,399.44 ms
(+11.37%)Baseline: 3,950.19 ms
4,740.22 ms
(92.81%)
isolated-linker.fresh-restore.hot-cache.hot-store📈 view plot
🚷 view threshold
645.97 ms
(+4.72%)Baseline: 616.87 ms
740.24 ms
(87.26%)
🐰 View full continuous benchmarking report in Bencher

@github-actions

Copy link
Copy Markdown
Contributor

🐰 Bencher Report

Branchpr/12574
Testbedpnpr

⚠️ 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-thresholds flag.

Click to view all benchmark results
BenchmarkLatencymilliseconds (ms)
isolated-linker.fresh-install.cold-cache.cold-store📈 view plot
⚠️ NO THRESHOLD
2,820.32 ms
isolated-linker.fresh-install.cold-cache.hot-store📈 view plot
⚠️ NO THRESHOLD
685.57 ms
isolated-linker.fresh-install.hot-cache.hot-store📈 view plot
⚠️ NO THRESHOLD
709.04 ms
isolated-linker.fresh-restore.cold-cache.cold-store📈 view plot
⚠️ NO THRESHOLD
2,932.85 ms
isolated-linker.fresh-restore.hot-cache.hot-store📈 view plot
⚠️ NO THRESHOLD
715.25 ms
🐰 View full continuous benchmarking report in Bencher

@zkochan zkochan merged commit 4c4acb6 into main Jun 22, 2026
29 of 31 checks passed
@zkochan zkochan deleted the vuln-034 branch June 22, 2026 13:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

product: pnpr reviewed: coderabbit CodeRabbit submitted an approving review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants