Skip to content

feat(pacquet-cli): port audit signatures#12663

Merged
zkochan merged 3 commits into
mainfrom
feat/pacquet-audit-signatures
Jun 25, 2026
Merged

feat(pacquet-cli): port audit signatures#12663
zkochan merged 3 commits into
mainfrom
feat/pacquet-audit-signatures

Conversation

@zkochan

@zkochan zkochan commented Jun 25, 2026

Copy link
Copy Markdown
Member

Summary

Ports pnpm's pnpm audit signatures command to pacquet. The TypeScript CLI already ships this command; this PR brings the Rust port to parity.

For every installed package, the package's own registry is queried for its signing keys (/-/npm/v1/keys) and full packument. A package is verified as soon as one of its dist.signatures validates — over the message name@version:integrity — against a trusted ECDSA‑P256 key. Registries that advertise no signing keys are skipped (no trust root); a package whose registry provides keys but whose signature is absent is reported as missing, and one whose signature is present but does not validate as invalid (a tamper signal). Exit code is 1 when anything is missing or invalid, and --json emits the structured report.

The port mirrors pnpm's deps.security.signatures package and the auditSignatures command handler, including per‑package packument‑error handling, the "one valid signature wins / prefer the tamper reason" logic, the key‑expiry consistency check, encodeURIComponent‑faithful packument URLs, the report wording, the error codes (ERR_PNPM_AUDIT_SIGNATURE_KEYS_FETCH_FAIL, ERR_PNPM_AUDIT_SIGNATURE_PACKUMENT_FETCH_FAIL, ERR_PNPM_AUDIT_NO_PACKAGES), and the JSON result shape.

ECDSA‑P256/SHA‑256 verification uses the p256 (RustCrypto) crate, which was already present transitively in the lockfile and is now a direct dependency of pacquet-cli.

Squash Commit Body

Port pnpm's `pnpm audit signatures` registry-signature verification to
pacquet. For every installed package, the package's own registry is
queried for its signing keys (`/-/npm/v1/keys`) and full packument; the
package is verified as soon as one of its `dist.signatures` validates
over the message `name@version:integrity` against a trusted ECDSA-P256
key. Registries that advertise no signing keys are skipped (no trust
root); a package whose registry provides keys but whose signature is
absent is reported as missing, and one whose signature is present but
does not validate as invalid (a tamper signal). Exit code 1 when any
package is missing or invalid; `--json` emits the structured report.

Mirrors pnpm's deps.security.signatures package and the auditSignatures
command handler, including the per-package packument-error handling, the
"one valid signature wins / prefer the tamper reason" logic, the
key-expiry consistency check, encodeURIComponent-faithful packument URLs,
the report wording, the error codes, and the JSON result shape.

Adds the `p256` (RustCrypto) crate for ECDSA-P256/SHA-256 verification;
it was already present transitively in the lockfile.

Checklist

  • The change is implemented in both the TypeScript CLI and the Rust
    pacquet/ port, or the description notes what still needs porting.
  • Added or updated tests.

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

Summary by CodeRabbit

  • New Features

    • Added full support for pacquet audit signatures, verifying installed package signatures using registry ECDSA-P256 keys.
    • Outputs both pretty JSON and human-readable reports, including audited/verified/missing/invalid counts and issue details.
  • Bug Fixes

    • Improved audit signatures argument/subcommand handling, rejecting extra or unknown operands with clear “unknown” messaging.
    • Added a dedicated error when there are no installed packages available to audit.
  • Tests

    • Expanded unit and end-to-end coverage for valid/invalid/missing signatures, empty signing-key scenarios, missing packages, and output formatting.

Port pnpm's `pnpm audit signatures` registry-signature verification to
pacquet. For every installed package, the package's own registry is
queried for its signing keys (`/-/npm/v1/keys`) and full packument; the
package is verified as soon as one of its `dist.signatures` validates
over the message `name@version:integrity` against a trusted ECDSA-P256
key. Registries that advertise no signing keys are skipped (no trust
root); a package whose registry provides keys but whose signature is
absent is reported as missing, and one whose signature is present but
does not validate as invalid (a tamper signal). Exit code 1 when any
package is missing or invalid; `--json` emits the structured report.

Mirrors pnpm's deps.security.signatures package and the auditSignatures
command handler, including the per-package packument-error handling, the
"one valid signature wins / prefer the tamper reason" logic, the
key-expiry consistency check, encodeURIComponent-faithful packument URLs,
the report wording, the error codes (ERR_PNPM_AUDIT_SIGNATURE_KEYS_FETCH_FAIL,
ERR_PNPM_AUDIT_SIGNATURE_PACKUMENT_FETCH_FAIL, ERR_PNPM_AUDIT_NO_PACKAGES),
and the JSON result shape.

Adds the `p256` (RustCrypto) crate for ECDSA-P256/SHA-256 verification;
it was already present transitively in the lockfile.
@coderabbitai

coderabbitai Bot commented Jun 25, 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: 155feae1-d7a7-48a8-89a9-cd807cf9c6a9

📥 Commits

Reviewing files that changed from the base of the PR and between 153c997 and 527e661.

📒 Files selected for processing (2)
  • pacquet/crates/cli/src/cli_args/audit/signatures.rs
  • pacquet/crates/cli/tests/audit.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • pacquet/crates/cli/tests/audit.rs
  • pacquet/crates/cli/src/cli_args/audit/signatures.rs

📝 Walkthrough

Walkthrough

The CLI now supports pacquet audit signatures. It adds p256, routes the subcommand through signature verification, fetches registry keys and packuments, renders audit results, and expands unit and end-to-end tests.

Changes

Signature audit support

Layer / File(s) Summary
Dependency and subcommand wiring
Cargo.toml, pacquet/crates/cli/Cargo.toml, pacquet/crates/cli/src/cli_args/audit.rs
Workspace and CLI dependencies add p256, and audit imports the signatures module and dispatches the signatures subcommand.
Audit orchestration
pacquet/crates/cli/src/cli_args/audit.rs, pacquet/crates/cli/tests/audit.rs
The signature audit command builds package inputs from lockfile data, handles empty installs, and the CLI tests exercise verified, missing, invalid, empty, and error cases.
Signature verification engine
pacquet/crates/cli/src/cli_args/audit/signatures.rs, pacquet/crates/cli/src/cli_args/audit/signatures/tests.rs
The verification code defines audit types, validates ECDSA-P256 signatures against registry keys, and the unit tests cover matching, expiry, and failure selection.
Registry I/O and rendering
pacquet/crates/cli/src/cli_args/audit/signatures.rs, pacquet/crates/cli/src/cli_args/audit/signatures/tests.rs
Registry key and packument fetching, output rendering, and helper tests cover endpoint parsing, name encoding, timestamp parsing, and empty-result output.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • pnpm/pnpm#11405: Both PRs implement audit signatures by fetching registry signing keys and verifying ECDSA-P256 signatures from packument dist.signatures.
  • pnpm/pnpm#12637: This PR overlaps the same CLI audit entrypoint and dispatches audit signatures through the new verification flow.

Suggested labels

product: pacquet

✨ 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 feat/pacquet-audit-signatures

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.

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

Copy link
Copy Markdown

PR Summary by Qodo

Port pacquet audit signatures with ECDSA-P256 registry signature verification
✨ Enhancement 🧪 Tests ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

Description

• Add pacquet audit signatures to verify installed packages against registry signing keys.
• Fetch registry keys and packuments, then validate dist.signatures over name@version:integrity.
• Emit pnpm-compatible text/JSON reports and exit 1 on missing/invalid signatures.
Diagram

graph TD
  A["pacquet CLI: audit signatures"] --> B["Lockfile: package list"] --> C{{"Package registry"}} --> D["Fetch signing keys"] --> E["Fetch packument"] --> F["Verify ECDSA-P256"] --> G["Render report / JSON"]
  D -. "404/400 => skip" .-> G

  subgraph Legend
    direction LR
    _cli["CLI / module"] ~~~ _ext{{"External service"}}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Use `ring`/`webpki` for ECDSA verification
  • ➕ Widely deployed crypto implementation with strong review pedigree
  • ➕ Potentially fewer parsing/format pitfalls depending on supported encodings
  • ➖ May complicate SPKI/DER handling and signature format compatibility vs pnpm behavior
  • ➖ Harder to mirror pnpm’s exact acceptance rules if APIs are more restrictive
2. Treat malformed key/signature material as hard errors
  • ➕ Stricter security posture; fails fast on unexpected registry data
  • ➕ Simplifies reasoning about ‘silent’ non-matches
  • ➖ Behavior would diverge from pnpm’s “one valid signature wins” tolerance
  • ➖ A single bad key/signature entry could block auditing otherwise valid packages

Recommendation: Keep the current approach: using p256 plus tolerant per-signature handling best matches the stated goal (pnpm parity), preserves key-rotation behavior (“one valid signature wins”), and avoids making audits fragile to individual malformed entries. The considered alternatives either risk compatibility drift (different crypto APIs/encodings) or change failure semantics in ways that would surprise users coming from pnpm.

Files changed (6) +1138 / -14

Enhancement (2) +721 / -11
audit.rsEnable 'audit signatures' subcommand and route packages to registries +87/-11

Enable 'audit signatures' subcommand and route packages to registries

• Adds a 'signatures' module and implements 'run_signatures' to collect installed packages from the lockfile, select per-package registries, and execute signature verification. Introduces 'ERR_PNPM_AUDIT_NO_PACKAGES' handling and rejects extra subcommand args for parity with pnpm behavior.

pacquet/crates/cli/src/cli_args/audit.rs

signatures.rsImplement registry keys/packument fetching and ECDSA signature validation +634/-0

Implement registry keys/packument fetching and ECDSA signature validation

• Implements the full 'pacquet audit signatures' flow: fetch '/-/npm/v1/keys' per registry, fetch and parse packuments per (registry,name), and validate 'dist.signatures' over 'name@version:integrity'. Produces pnpm-compatible missing/invalid/verified reporting (text + JSON), includes key-expiry consistency checks, and adds pnpm-aligned error codes for keys/packument failures.

pacquet/crates/cli/src/cli_args/audit/signatures.rs

Tests (2) +415 / -3
tests.rsAdd unit tests for signature verification and rendering behavior +156/-0

Add unit tests for signature verification and rendering behavior

• Adds focused unit tests for ECDSA verification, invalid/unknown-key reason selection, key-expiry behavior, URL encoding parity, timestamp parsing, and “no signing keys” output rendering.

pacquet/crates/cli/src/cli_args/audit/signatures/tests.rs

audit.rsAdd integration tests for 'pacquet audit signatures' against a mocked registry +259/-3

Add integration tests for 'pacquet audit signatures' against a mocked registry

• Replaces the previous 'unsupported' assertion with end-to-end tests that drive the CLI against a mock registry, covering verified output, '--json' shape, missing and invalid signature exit code 1, registry-without-keys skip behavior, keys endpoint hard failure, no-packages error, and extra-argument rejection.

pacquet/crates/cli/tests/audit.rs

Other (2) +2 / -0
Cargo.tomlAdd direct 'p256' dependency for ECDSA-P256 verification +1/-0

Add direct 'p256' dependency for ECDSA-P256 verification

• Introduces 'p256' as a workspace dependency (with ecdsa/pkcs8) to support registry signature verification. This makes the crypto requirement explicit rather than relying on transitive resolution.

Cargo.toml

Cargo.tomlWire 'p256' into pacquet CLI crate dependencies +1/-0

Wire 'p256' into pacquet CLI crate dependencies

• Adds the 'p256' workspace dependency to the CLI crate so the new 'audit signatures' implementation can verify ECDSA-P256 signatures.

pacquet/crates/cli/Cargo.toml

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

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

Copy link
Copy Markdown

Code Review by Qodo

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

Grey Divider


Action required

1. Reqwest error leaks creds ✓ Resolved 🐞 Bug ⛨ Security
Description
SignaturesError::{KeysNetwork,PackumentNetwork} formats and surfaces the raw reqwest::Error
({source}), which can include the full request URL; if the configured registry URL contains
user:pass@, those credentials can leak into terminal/CI error output. This bypasses the repo’s
existing redact_url_credentials convention used in retry/logging paths.
Code

pacquet/crates/cli/src/cli_args/audit/signatures.rs[R118-148]

+    #[display("Failed to request the registry keys endpoint (at {url}): {source}")]
+    #[diagnostic(code(ERR_PNPM_AUDIT_SIGNATURE_KEYS_FETCH_FAIL))]
+    KeysNetwork {
+        url: String,
+        #[error(source)]
+        source: reqwest::Error,
+    },
+
+    #[display("The registry keys endpoint (at {url}) responded with {status}: {body}")]
+    #[diagnostic(code(ERR_PNPM_AUDIT_SIGNATURE_KEYS_FETCH_FAIL))]
+    KeysBadStatus { url: String, status: u16, body: String },
+
+    #[display(
+        "The registry keys endpoint (at {url}) returned invalid JSON: {reason}. Response body: {body}"
+    )]
+    #[diagnostic(code(ERR_PNPM_AUDIT_SIGNATURE_KEYS_FETCH_FAIL))]
+    KeysInvalidJson { url: String, reason: String, body: String },
+
+    #[display(
+        "The registry keys endpoint (at {url}) returned an unexpected body. Expected an object with a keys array; got: {body}"
+    )]
+    #[diagnostic(code(ERR_PNPM_AUDIT_SIGNATURE_KEYS_FETCH_FAIL))]
+    KeysUnexpectedBody { url: String, body: String },
+
+    #[display("Failed to request the packument endpoint (at {url}): {source}")]
+    #[diagnostic(code(ERR_PNPM_AUDIT_SIGNATURE_PACKUMENT_FETCH_FAIL))]
+    PackumentNetwork {
+        url: String,
+        #[error(source)]
+        source: reqwest::Error,
+    },
Evidence
The new error variants embed and display reqwest::Error directly, while existing network retry
logging explicitly redacts formatted reqwest errors, implying they can contain credential-bearing
URLs. This makes the new audit-signatures errors a regression in secret redaction coverage.

pacquet/crates/cli/src/cli_args/audit/signatures.rs[115-148]
pacquet/crates/network/src/retry.rs[157-167]
pacquet/crates/network/src/auth.rs[460-466]

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

## Issue description
`SignaturesError::{KeysNetwork, PackumentNetwork}` currently includes a `reqwest::Error` as `source` and prints it via the derived `Display` (`... {source}`). `reqwest::Error` formatting can include the URL, so registries configured with embedded credentials (e.g. `https://user:pass@host/`) may leak secrets to stderr.
### Issue Context
The codebase already treats formatted reqwest errors as potentially credential-bearing and redacts them in retry logging.
### Fix Focus Areas
- pacquet/crates/cli/src/cli_args/audit/signatures.rs[115-165]
### Implementation notes
- Ensure *all* user-facing formatting of the underlying reqwest error is passed through `pacquet_network::redact_url_credentials`.
- Also ensure the error cause-chain printed by miette cannot re-introduce the unredacted `reqwest::Error`. Options:
- Store a redacted string instead of `reqwest::Error` in these variants (drop source chaining for these cases), or
- Wrap `reqwest::Error` in a newtype whose `Display` redacts (and use that wrapper as the error source), so both the top-level message and the cause-chain are safe.
- Add/adjust a unit/integration test that sets a registry URL containing `user:pass@` and asserts the printed error output contains the redacted form (no userinfo).

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


2. Packument 404 skipped 🐞 Bug ⛨ Security
Description
When fetch_packument returns Ok(None) (e.g. HTTP 404), verify_signatures ignores it and does
not record an invalid/missing result or increment audited. A registry that advertises signing keys
can thus omit packuments (404) and cause installed packages to be silently excluded from signature
auditing, producing a false-clean report.
Code

pacquet/crates/cli/src/cli_args/audit/signatures.rs[R207-215]

+        match packuments.get(&(pkg.registry.clone(), pkg.name.clone())) {
+            Some(Err(reason)) => {
+                result.invalid.push(issue(pkg, None, None, Some(reason.clone())));
+            }
+            Some(Ok(None)) | None => {}
+            Some(Ok(Some(packument))) => {
+                result.audited += 1;
+                process_version(pkg, packument, keys, &mut result);
+            }
Evidence
fetch_packument maps HTTP 404 to Ok(None), and the main auditing loop drops Ok(None) results
without producing any output entry, so packages can be excluded from the audit despite being
selected for packument fetching.

pacquet/crates/cli/src/cli_args/audit/signatures.rs[188-216]
pacquet/crates/cli/src/cli_args/audit/signatures.rs[461-512]

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

## Issue description
`fetch_packument` can return `Ok(None)` (notably on HTTP 404), and `verify_signatures` currently treats `Ok(None)` as a no-op. This allows packages from registries *with signing keys* to be silently skipped (not audited, not marked invalid), which can yield false-clean results.
### Issue Context
This command is a security verification gate; “registry has signing keys” should imply that every installed package from that registry either verifies, is missing a signature, or is invalid / unverifiable. A 404 packument for an installed dependency should be surfaced as an invalid audit entry (or at minimum counted as audited and reported as unverifiable).
### Fix Focus Areas
- pacquet/crates/cli/src/cli_args/audit/signatures.rs[207-216]
- pacquet/crates/cli/src/cli_args/audit/signatures.rs[461-512]
### Suggested fix
- Decide how to represent a missing packument (404) for an installed package:
- Prefer: record `invalid` with a clear reason (e.g. "Missing registry metadata for name@version" or "Packument not found (404)") and increment `audited`.
- Update the `match` in `verify_signatures` so `Some(Ok(None))` produces an invalid issue for the specific `pkg` (instead of being ignored).
- Optionally change `fetch_packument` to treat 404 as an error type that carries status/body (or keep `Ok(None)` but handle it explicitly in `verify_signatures`).

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


3. Unbounded response buffering 🐞 Bug ⛨ Security
Description
fetch_registry_keys and fetch_packument fully buffer attacker-controlled registry responses via
response.text().await without any size cap, allowing a malicious/compromised registry to drive
memory pressure or OOM during pacquet audit signatures. The buffered body is then parsed and
sometimes re-stringified, further multiplying CPU/memory cost on large responses.
Code

pacquet/crates/cli/src/cli_args/audit/signatures.rs[R479-483]

+    let status = response.status().as_u16();
+    let body = response
+        .text()
+        .await
+        .map_err(|source| SignaturesError::PackumentNetwork { url: display_url.clone(), source })?;
Evidence
The added code reads the full HTTP body into a String before any truncation and without checking
content length; this is attacker-controlled input from the registry. Existing code demonstrates
pacquet’s stance that remote size signals are untrusted and should be guarded to avoid crashing
allocations.

pacquet/crates/cli/src/cli_args/audit/signatures.rs[399-507]
pacquet/crates/tarball/src/lib.rs[330-365]

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

## Issue description
`fetch_registry_keys` / `fetch_packument` call `response.text().await` on registry-controlled endpoints with no explicit maximum size. This can OOM/crash the CLI (DoS) if the response body is extremely large.
### Issue Context
Other pacquet network ingestion paths treat remote size signals as untrusted and guard allocations to avoid abort/OOM. The signatures audit should similarly bound memory used for keys and packument bodies.
### Fix Focus Areas
- pacquet/crates/cli/src/cli_args/audit/signatures.rs[399-507]
- pacquet/crates/cli/src/cli_args/audit/signatures.rs[539-541]
### Implementation notes
- Add a max-bytes limit for keys and packument responses (could be different limits). Prefer:
- Check `response.content_length()` and fail early if above limit.
- If unknown, stream bytes and stop once the cap is exceeded.
- Avoid `value.to_string()` on unexpected-body paths; use the already-read (and capped) raw body snippet for diagnostics rather than re-serializing potentially huge JSON.
- Consider deserializing directly from the raw JSON string into the target struct to avoid building/cloning a full `serde_json::Value` tree when possible.

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


View more (2)
4. Permit dropped early ✓ Resolved 🐞 Bug ☼ Reliability
Description
fetch_registry_keys / fetch_packument discard the ThrottledClientGuard from send_with_retry
before reading the body, releasing the semaphore permit while the socket is still draining. This can
exceed configured network concurrency and trigger FD/socket exhaustion (and defeats throttling)
during large audit signatures runs.
Code

pacquet/crates/cli/src/cli_args/audit/signatures.rs[R408-423]

+    let (_, response) =
+        send_with_retry(http_client, &keys_url, retry_opts_from_config(config), |client| {
+            let mut request = client.get(&keys_url).header("accept", "application/json");
+            if let Some(value) = &authorization {
+                request = request.header("authorization", value);
+            }
+            request
+        })
+        .await
+        .map_err(|source| SignaturesError::KeysNetwork { url: display_url.clone(), source })?;
+
+    let status = response.status().as_u16();
+    let body = response
+        .text()
+        .await
+        .map_err(|source| SignaturesError::KeysNetwork { url: display_url.clone(), source })?;
Evidence
The signatures code drops the guard immediately, but send_with_retry and ThrottledClientGuard
docs state the guard must be held through body streaming; the resolver’s metadata fetch shows the
correct pattern (hold guard until after text().await, then drop).

pacquet/crates/cli/src/cli_args/audit/signatures.rs[399-483]
pacquet/crates/network/src/retry.rs[111-121]
pacquet/crates/network/src/lib.rs[132-146]
pacquet/crates/resolving-npm-resolver/src/fetch_full_metadata.rs[97-132]

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

## Issue description
`send_with_retry` returns a `ThrottledClientGuard` whose permit must remain held until the response body is fully consumed. In `fetch_registry_keys` and `fetch_packument`, the guard is ignored (`let (_, response) = ...`) and dropped before `response.text().await`, breaking the semaphore’s concurrency guarantee and risking `EMFILE`/connection storms under fan-out.
## Issue Context
The network crate explicitly documents this contract and already demonstrates the correct pattern in `fetch_full_metadata`.
## Fix Focus Areas
- pacquet/crates/cli/src/cli_args/audit/signatures.rs[399-508]
## Suggested change
- Change both call sites to bind the guard (e.g. `let (_guard, response) = ...;`) and keep it in scope until after `response.text().await` completes.
- If you later `spawn_blocking` JSON parse (recommended), drop the guard immediately after buffering the body (matching `fetch_full_metadata`).

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


5. Scoped auth not applied 🐞 Bug ⛨ Security
Description
fetch_packument uses auth_headers.for_url(®istry_url) instead of
for_url_with_package(&packument_url, Some(name)), so scoped packages may not send the correct
per-scope credentials. This can cause packument fetch failures and incorrect audit signatures
results (false invalid/missing) for private/scoped registries.
Code

pacquet/crates/cli/src/cli_args/audit/signatures.rs[R464-473]

+    let registry_url = with_trailing_slash(registry);
+    let packument_url = format!("{registry_url}{}", encode_package_name(name));
+    let display_url = redact_url_credentials(&packument_url);
+    let authorization = config.auth_headers.for_url(&registry_url);
+    let (_, response) =
+        send_with_retry(http_client, &packument_url, retry_opts_from_config(config), |client| {
+            let mut request = client.get(&packument_url).header("accept", "application/json");
+            if let Some(value) = &authorization {
+                request = request.header("authorization", value);
+            }
Evidence
The auth module provides a package-aware lookup specifically to prefer scoped credentials, and the
resolver already uses it for packument requests; the new signatures code does not.

pacquet/crates/cli/src/cli_args/audit/signatures.rs[458-475]
pacquet/crates/network/src/auth.rs[193-223]
pacquet/crates/resolving-npm-resolver/src/fetch_full_metadata.rs[97-103]

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

## Issue description
Packument fetches should resolve Authorization headers using package-aware logic so scoped packages can use scoped credentials. Using `for_url(&registry_url)` bypasses `for_url_with_package`’s scope preference, diverging from existing packument fetch behavior in the resolver.
## Issue Context
`AuthHeaders::for_url` is defined as `for_url_with_package(url, None)`, while `for_url_with_package` explicitly prefers scoped credentials for scoped packages. The resolver’s `fetch_full_metadata` uses the package-aware lookup.
## Fix Focus Areas
- pacquet/crates/cli/src/cli_args/audit/signatures.rs[458-476]
## Suggested change
- Replace packument auth resolution with something like:
- `let authorization = config.auth_headers.for_url_with_package(&packument_url, Some(name));`
- (or, if you want to match other code, pass the final request URL and `Some(name)`)
- Keep keys endpoint behavior as-is unless you intentionally want package-scope auth for keys too.

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



Remediation recommended

6. Eager packument caching spikes memory 🐞 Bug ➹ Performance
Description
verify_signatures() fetches and parses every needed full packument and retains them all in a
HashMap before processing any package, which can significantly increase peak memory and slow large
audits. This is especially risky because packument bodies/parses can be multi‑MB in this repo’s own
resolver implementation.
Code

pacquet/crates/cli/src/cli_args/audit/signatures.rs[R184-199]

+    // Only fetch packuments for registries that advertise signing keys; a
+    // registry without keys is skipped entirely.
+    let needed: BTreeSet<(&str, &str)> = packages
+        .iter()
+        .filter(|pkg| keys_by_registry.get(&pkg.registry).is_some_and(|keys| !keys.is_empty()))
+        .map(|pkg| (pkg.registry.as_str(), pkg.name.as_str()))
+        .collect();
+    let packument_fetches = needed.into_iter().map(|(registry, name)| async move {
+        let result = fetch_packument(name, registry, config, http_client)
+            .await
+            .map_err(|err| err.to_string());
+        ((registry.to_string(), name.to_string()), result)
+    });
+    let packuments: HashMap<(String, String), Result<Option<Packument>, String>> =
+        futures_util::future::join_all(packument_fetches).await.into_iter().collect();
+
Evidence
The implementation builds packument_fetches for all needed (registry,name) pairs and join_all
collects them into a HashMap before any per-package processing begins. Separately, the resolver’s
full-metadata fetch path documents that packument parses can be multi‑MB, which makes retaining many
parsed packuments simultaneously a peak-memory risk.

pacquet/crates/cli/src/cli_args/audit/signatures.rs[165-215]
pacquet/crates/resolving-npm-resolver/src/fetch_full_metadata.rs[126-130]

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

## Issue description
`verify_signatures()` currently prefetches all packuments, stores them in a `HashMap`, and only then iterates packages to verify signatures. Because packuments can be large (multi‑MB) and the number of distinct `(registry, name)` pairs can be high in large monorepos, this increases peak memory usage and can degrade performance.
## Issue Context
This code is on a CLI audit path, but it’s still user-facing and can be run in CI on large dependency graphs.
## Fix Focus Areas
- pacquet/crates/cli/src/cli_args/audit/signatures.rs[165-215]
## Suggested approach
- Group `packages` by `(registry, name)` and collect the list of installed versions for that pair.
- Fetch one packument per group and immediately process all requested versions for that packument, then drop it (do not store all packuments in a global map).
- If you still need concurrency, use a bounded concurrency stream (e.g. `futures_util::stream::iter(groups).map(...).buffer_unordered(N)`) so only a limited number of packuments are held/parsed concurrently.
- Preserve current error semantics (keys endpoint fatal; packument failures recorded per affected packages).

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


7. JSON leaks tarball URL 🐞 Bug ⛨ Security
Description
SignatureIssue.resolved is populated from the registry packument’s dist.tarball and emitted in
pacquet audit signatures --json, which can disclose sensitive URL userinfo or registry-provided
signed URL tokens into CI logs/artifacts when a package is missing/invalid.
Code

pacquet/crates/cli/src/cli_args/audit/signatures.rs[R228-263]

+    let version = packument.versions.get(&pkg.version);
+    let published_at =
+        packument.time.get(&pkg.version).and_then(serde_json::Value::as_str).map(str::to_string);
+    let dist = version.and_then(|version| version.dist.as_ref());
+    let integrity = dist.and_then(|dist| dist.integrity.clone());
+    let resolved = dist.and_then(|dist| dist.tarball.clone());
+    let raw_signatures = dist.and_then(|dist| dist.signatures.as_ref());
+
+    if raw_signatures.is_some_and(|value| !value.is_array()) {
+        result.invalid.push(issue(pkg, integrity, resolved, Some(malformed_reason(pkg))));
+        return;
+    }
+    let mut signatures = Vec::new();
+    if let Some(serde_json::Value::Array(elements)) = raw_signatures {
+        for element in elements {
+            let Ok(signature) = serde_json::from_value::<PackageSignature>(element.clone()) else {
+                result.invalid.push(issue(pkg, integrity, resolved, Some(malformed_reason(pkg))));
+                return;
+            };
+            signatures.push(signature);
+        }
+    }
+
+    if version.is_none() {
+        let reason = format!("Missing registry metadata for {}@{}", pkg.name, pkg.version);
+        result.invalid.push(issue(pkg, None, None, Some(reason)));
+        return;
+    }
+    let Some(integrity) = integrity else {
+        result.missing.push(issue(pkg, None, resolved, None));
+        return;
+    };
+    if signatures.is_empty() {
+        result.missing.push(issue(pkg, Some(integrity), resolved, None));
+        return;
+    }
Evidence
resolved is taken directly from the packument’s dist.tarball and stored into SignatureIssue,
which is then pretty-printed to stdout under --json.

pacquet/crates/cli/src/cli_args/audit/signatures.rs[44-58]
pacquet/crates/cli/src/cli_args/audit/signatures.rs[89-97]
pacquet/crates/cli/src/cli_args/audit/signatures.rs[228-263]
pacquet/crates/cli/src/cli_args/audit.rs[366-376]

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

## Issue description
`SignatureIssue.resolved` is filled from `dist.tarball` and serialized in `--json` output. If the tarball URL contains `user:pass@` or other sensitive URL components (common in some private registry/CDN setups), `pacquet audit signatures --json` will print them to stdout.
### Issue Context
- `resolved` is derived from registry-provided packument metadata (`dist.tarball`).
- The JSON report is intended for machine/CI consumption, where stdout is frequently persisted.
### Fix Focus Areas
- pacquet/crates/cli/src/cli_args/audit/signatures.rs[228-263]
- pacquet/crates/cli/src/cli_args/audit/signatures.rs[366-380]
- pacquet/crates/cli/src/cli_args/audit.rs[366-376]
### Suggested fix
- Before storing `resolved` into `SignatureIssue`, redact URL credentials at minimum (e.g. `pacquet_network::redact_url_credentials(&resolved)`), so `user:pass@` cannot be leaked.
- If you want stronger protection, consider additionally stripping query/fragment for `resolved` in JSON output (or gating full URLs behind an explicit verbose/debug flag), but keep parity/contract expectations in mind.

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


8. Registry creds leak in report 🐞 Bug ⛨ Security
Description
The signatures report prints and JSON-serializes SignatureIssue.registry verbatim, which is
derived from Config::resolved_registries(); if a registry URL contains embedded user:pass@,
those credentials will be emitted to stdout/JSON. This can leak secrets into CI logs or stored
artifacts.
Code

pacquet/crates/cli/src/cli_args/audit/signatures.rs[R606-618]

+fn issue_table(issues: &[SignatureIssue], with_reason: bool) -> String {
+    use tabled::{builder::Builder, settings::Style};
+
+    let mut builder = Builder::default();
+    for issue in issues {
+        let package = red(&format!("{}@{}", issue.name, issue.version));
+        if with_reason {
+            let reason =
+                issue.reason.clone().unwrap_or_else(|| "Invalid registry signature".to_string());
+            builder.push_record(vec![package, issue.registry.clone(), reason]);
+        } else {
+            builder.push_record(vec![package, issue.registry.clone()]);
+        }
Evidence
The registry URL flows from config (resolved_registriespick_registry_for_package) into
SignaturePackage.registry, is copied into SignatureIssue.registry, and is printed directly in
the table. The project includes a dedicated URL-credential redaction helper, showing that
credential-bearing URLs are an expected risk.

pacquet/crates/cli/src/cli_args/audit.rs[334-359]
pacquet/crates/cli/src/cli_args/audit/signatures.rs[368-381]
pacquet/crates/cli/src/cli_args/audit/signatures.rs[606-618]
pacquet/crates/network/src/auth.rs[460-466]

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

## Issue description
`SignatureIssue.registry` is populated from config registry URLs and is emitted in both human output (table) and `--json` output without redaction. Embedded URL credentials can therefore be exposed.
### Issue Context
The repo already has `pacquet_network::redact_url_credentials` to prevent leaking `user:pass@` in logs/errors; `audit signatures` should apply the same protection to its reporting output.
### Fix Focus Areas
- pacquet/crates/cli/src/cli_args/audit/signatures.rs[368-381]
- pacquet/crates/cli/src/cli_args/audit/signatures.rs[606-618]
- pacquet/crates/cli/src/cli_args/audit.rs[334-359]
### Implementation notes
- Redact `registry` before printing in `issue_table()` at minimum.
- Prefer also redacting `registry` before JSON serialization (`--json`) to avoid leaking secrets to machine-consumed artifacts.
- Add a test that uses a registry URL like `https://user:[email protected]/` and asserts stdout/JSON does not contain `user:pass@` (and ideally matches the redacted form).

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


View more (5)
9. Key lookup is O(n²) 🐞 Bug ➹ Performance
Description
verify_package_signatures linearly scans keys (keys.iter().find(...)) for each signature
entry, making verification O(keys×signatures) per package. This is avoidable overhead in the hot
loop and becomes noticeable if a registry returns many keys or a packument carries many signatures.
Code

pacquet/crates/cli/src/cli_args/audit/signatures.rs[R298-306]

+    let mut failures = Vec::new();
+    for signature in signatures {
+        let Some(key) = keys.iter().find(|key| key.keyid == signature.keyid) else {
+            failures.push(format!(
+                "{}@{} has a registry signature with keyid {} but no corresponding public key can be found",
+                pkg.name, pkg.version, signature.keyid,
+            ));
+            continue;
+        };
Evidence
The per-signature loop does a linear scan of keys to find a matching keyid, creating a nested
iteration across registry-provided arrays.

pacquet/crates/cli/src/cli_args/audit/signatures.rs[287-332]

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

## Issue description
Signature verification repeatedly searches the `keys` slice with a linear scan for every signature, causing O(keys×signatures) behavior.
### Issue Context
`keys` comes from the registry keys endpoint and `signatures` from the packument. Even if typical sizes are small, this is the inner loop of `audit signatures` and is straightforward to optimize.
### Fix Focus Areas
- pacquet/crates/cli/src/cli_args/audit/signatures.rs[287-332]
### Suggested fix
- Build a `HashMap<&str, &RegistryKey>` (or `HashMap<String, RegistryKey>`) once per registry (or once per `process_version` call) keyed by `keyid`.
- Replace `keys.iter().find(...)` with O(1) lookup.
- (Optional) Pre-decode/parse verifying keys once per registry keyid if profiling shows `VerifyingKey::from_public_key_der` dominates.

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


10. Unescaped terminal control output 🐞 Bug ⛨ Security
Description
Registry-derived text (including truncated response bodies) can flow into SignatureIssue.reason
and is rendered into the human-readable table without stripping control characters/escape sequences.
A malicious registry can inject terminal control sequences into CI logs/terminal output to confuse
operators (e.g., spoof/obscure parts of the report).
Code

pacquet/crates/cli/src/cli_args/audit/signatures.rs[R612-616]

+        if with_reason {
+            let reason =
+                issue.reason.clone().unwrap_or_else(|| "Invalid registry signature".to_string());
+            builder.push_record(vec![package, issue.registry.clone(), reason]);
+        } else {
Evidence
The code embeds truncated registry response text into error strings, converts those errors to
strings for per-package reporting, and then prints the reason into a table without escaping control
characters.

pacquet/crates/cli/src/cli_args/audit/signatures.rs[193-217]
pacquet/crates/cli/src/cli_args/audit/signatures.rs[429-447]
pacquet/crates/cli/src/cli_args/audit/signatures.rs[539-541]
pacquet/crates/cli/src/cli_args/audit/signatures.rs[606-623]

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

## Issue description
The report renderer prints `issue.reason` verbatim into a table. `issue.reason` can include registry-controlled content (via error messages that embed `sanitize_body(&body)`), and `sanitize_body` only truncates rather than escaping/removing control characters.
### Issue Context
This is a terminal/log integrity issue: ANSI escape sequences or other control characters can alter how output is displayed in terminals and CI logs.
### Fix Focus Areas
- pacquet/crates/cli/src/cli_args/audit/signatures.rs[429-447]
- pacquet/crates/cli/src/cli_args/audit/signatures.rs[487-506]
- pacquet/crates/cli/src/cli_args/audit/signatures.rs[539-541]
- pacquet/crates/cli/src/cli_args/audit/signatures.rs[606-623]
### Implementation notes
- Harden `sanitize_body` (or add a new `sanitize_for_terminal`) to remove/escape:
- ASCII control chars (at minimum `\x1b`), and optionally all `char::is_control()` except `\n`/`\t`.
- Consider stripping ANSI sequences (e.g., via `strip-ansi-escapes`) before placing text into `reason`.
- Apply the sanitization at the point where registry content is embedded into user-facing strings (errors + table rendering).

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


11. No body-read retry 🐞 Bug ☼ Reliability
Description
audit signatures uses send_with_retry for the request but does not wrap body consumption/JSON
parsing in retry_async, so mid-stream resets or transient decode issues fail the audit (keys) or
spuriously mark packages invalid. This diverges from other pacquet metadata fetchers that explicitly
re-issue the whole request when body read/JSON parse fails.
Code

pacquet/crates/cli/src/cli_args/audit/signatures.rs[R468-483]

+    let (_, response) =
+        send_with_retry(http_client, &packument_url, retry_opts_from_config(config), |client| {
+            let mut request = client.get(&packument_url).header("accept", "application/json");
+            if let Some(value) = &authorization {
+                request = request.header("authorization", value);
+            }
+            request
+        })
+        .await
+        .map_err(|source| SignaturesError::PackumentNetwork { url: display_url.clone(), source })?;
+
+    let status = response.status().as_u16();
+    let body = response
+        .text()
+        .await
+        .map_err(|source| SignaturesError::PackumentNetwork { url: display_url.clone(), source })?;
Evidence
The signatures code reads the body and parses JSON without retry_async, while the network module’s
docs and existing metadata fetchers establish that body read/parse failures are expected to be
retried at a second layer.

pacquet/crates/cli/src/cli_args/audit/signatures.rs[399-507]
pacquet/crates/network/src/retry.rs[11-21]
pacquet/crates/resolving-npm-resolver/src/fetch_full_metadata.rs[89-147]

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

## Issue description
`send_with_retry` retries request/transport/status failures, but response body streaming and JSON parsing occur outside that retry loop. Transient body-read failures (RST mid-stream, decode error) or truncated JSON can currently fail the audit without re-fetch.
### Issue Context
The `pacquet_network` crate explicitly documents `retry_async` as the companion for retrying body-consumption/parsing failures.
### Fix Focus Areas
- pacquet/crates/cli/src/cli_args/audit/signatures.rs[399-507]
- pacquet/crates/network/src/retry.rs[11-21]
### Implementation notes
- Wrap the whole "request + body read + JSON parse" in `retry_async` for both keys and packument fetches.
- Use a retry predicate similar to resolver metadata fetch:
- Retry `reqwest` body-read/decompression errors.
- Retry `serde_json::Error` decode errors (often indicates truncated body).
- Do NOT retry deterministic schema validation failures (unexpected-body shape) unless you have evidence they can be transient.
- Ensure the error mapping preserves the existing `ERR_PNPM_AUDIT_SIGNATURE_*` diagnostics.

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


12. JSON parse blocks runtime 🐞 Bug ➹ Performance
Description
fetch_packument and fetch_registry_keys parse JSON on the async runtime thread after buffering
the response body, which can pin tokio workers when packuments are large. This degrades audit
throughput/latency and can stall other concurrent work on the same runtime.
Code

pacquet/crates/cli/src/cli_args/audit/signatures.rs[R495-507]

+    let value: serde_json::Value =
+        serde_json::from_str(&body).map_err(|err| SignaturesError::PackumentInvalidJson {
+            url: display_url.clone(),
+            reason: err.to_string(),
+            body: sanitize_body(&body),
+        })?;
+    let parsed: Packument = serde_json::from_value(value.clone()).map_err(|_| {
+        SignaturesError::PackumentUnexpectedBody {
+            url: display_url,
+            body: sanitize_body(&value.to_string()),
+        }
+    })?;
+    Ok(Some(parsed))
Evidence
The new signatures fetchers parse JSON inline, while the resolver’s packument fetch explicitly drops
the network guard then parses in a blocking task to avoid stalling tokio workers on multi‑MB JSON.

pacquet/crates/cli/src/cli_args/audit/signatures.rs[437-447]
pacquet/crates/cli/src/cli_args/audit/signatures.rs[495-507]
pacquet/crates/resolving-npm-resolver/src/fetch_full_metadata.rs[126-134]

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

## Issue description
Deserializing large JSON (packuments) on tokio worker threads can block the reactor and reduce throughput. The resolver code already uses `spawn_blocking` for this exact reason.
## Issue Context
This command fetches *full* packuments for many packages, so worst-case body sizes can be significant.
## Fix Focus Areas
- pacquet/crates/cli/src/cli_args/audit/signatures.rs[437-507]
## Suggested change
- After `response.text().await` completes (with the guard still held until that point), drop the guard and parse via `tokio::task::spawn_blocking`.
- Consider parsing directly into `RegistryKeysResponse` / `Packument` (instead of `Value` + `from_value(value.clone())`) while still preserving your error-body reporting strategy.

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


13. Unbounded base64 decode 🐞 Bug ⛨ Security
Description
verify_one base64-decodes registry-provided public keys and signatures without any size caps,
allowing large allocations from attacker-controlled inputs. A malicious/compromised registry can use
oversized key/sig fields to drive memory pressure or OOM during audit signatures.
Code

pacquet/crates/cli/src/cli_args/audit/signatures.rs[R345-351]

+fn verify_one(public_key_base64: &str, message: &str, signature_base64: &str) -> bool {
+    let engine = base64::engine::general_purpose::STANDARD;
+    let Ok(key_der) = engine.decode(public_key_base64) else { return false };
+    let Ok(verifying_key) = VerifyingKey::from_public_key_der(&key_der) else { return false };
+    let Ok(signature_der) = engine.decode(signature_base64) else { return false };
+    let Ok(signature) = Signature::from_der(&signature_der) else { return false };
+    verifying_key.verify(message.as_bytes(), &signature).is_ok()
Evidence
The new verification helper decodes untrusted base64 strings into owned buffers directly; these
values come from the remote keys/packument JSON fields.

pacquet/crates/cli/src/cli_args/audit/signatures.rs[342-351]

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

## Issue description
`verify_one` decodes base64 strings from registry responses straight into `Vec<u8>` with no length validation. Since these are remote inputs, an oversized string can force large allocations.
## Issue Context
For ECDSA P-256:
- SPKI public keys are small (on the order of ~100 bytes DER)
- DER-encoded signatures are also small (tens of bytes)
So it’s safe to enforce conservative upper bounds.
## Fix Focus Areas
- pacquet/crates/cli/src/cli_args/audit/signatures.rs[342-351]
## Suggested change
- Add a fast length check before decode (e.g. reject/return false if `public_key_base64.len()` or `signature_base64.len()` exceeds a conservative threshold like 8–16KB).
- Optionally also cap decoded length (after decode) before attempting `from_public_key_der` / `Signature::from_der`.
- Keep behavior as non-fatal (return false) to preserve “one bad key doesn’t abort audit” semantics.

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



Informational

14. Repeated public key parsing 🐞 Bug ➹ Performance
Description
verify_one base64-decodes and DER-parses the registry public key on every signature check, adding
avoidable CPU cost in the per-signature hot loop during large audits.
Code

pacquet/crates/cli/src/cli_args/audit/signatures.rs[R343-350]

+fn verify_one(public_key_base64: &str, message: &str, signature_base64: &str) -> bool {
+    let engine = base64::engine::general_purpose::STANDARD;
+    let Ok(key_der) = engine.decode(public_key_base64) else { return false };
+    let Ok(verifying_key) = VerifyingKey::from_public_key_der(&key_der) else { return false };
+    let Ok(signature_der) = engine.decode(signature_base64) else { return false };
+    let Ok(signature) = Signature::from_der(&signature_der) else { return false };
+    verifying_key.verify(message.as_bytes(), &signature).is_ok()
+}
Evidence
verify_one performs base64 decode and SPKI parsing every time it is invoked, and it is invoked
from the per-signature loop in verify_package_signatures.

pacquet/crates/cli/src/cli_args/audit/signatures.rs[285-330]
pacquet/crates/cli/src/cli_args/audit/signatures.rs[340-350]

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

## Issue description
Public keys are repeatedly decoded and parsed inside `verify_one`, even though keys are registry-level data reused across many package/version signature checks. This adds unnecessary CPU overhead (base64 + ASN.1/SPKI parsing) proportional to the number of signatures evaluated.
### Issue Context
- `verify_one()` is called from inside the per-signature loop.
- Registry keys are fetched once per registry and could be pre-processed once per run.
### Fix Focus Areas
- pacquet/crates/cli/src/cli_args/audit/signatures.rs[285-338]
- pacquet/crates/cli/src/cli_args/audit/signatures.rs[340-350]
- pacquet/crates/cli/src/cli_args/audit/signatures.rs[165-220]
### Suggested fix
- When building `keys_by_registry`, additionally build a `HashMap<keyid, VerifyingKey>` (or `Result<VerifyingKey, _>` where invalid keys simply don’t enter the trusted set).
- Change verification to look up the pre-parsed `VerifyingKey` and only decode/parse the signature per entry.
- Keep current behavior where malformed key/signature material counts as a non-match (does not abort the audit).

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


15. Packument map lookup clones 🐞 Bug ➹ Performance
Description
The per-package packument lookup clones pkg.registry and pkg.name to build a temporary `(String,
String)` key on every iteration. This adds avoidable allocations proportional to the number of
audited packages.
Code

pacquet/crates/cli/src/cli_args/audit/signatures.rs[207]

+        match packuments.get(&(pkg.registry.clone(), pkg.name.clone())) {
Evidence
The main processing loop constructs a temporary owned tuple key for every lookup instead of using a
borrowed lookup path.

pacquet/crates/cli/src/cli_args/audit/signatures.rs[202-216]

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

## Issue description
`packuments.get(&(pkg.registry.clone(), pkg.name.clone()))` allocates two new `String`s for every lookup in the main loop.
### Issue Context
This runs once per audited `name@version` entry. For large lockfiles, it is measurable overhead and easy to avoid.
### Fix Focus Areas
- pacquet/crates/cli/src/cli_args/audit/signatures.rs[199-216]
### Suggested fix
- Restructure `packuments` to allow borrowed lookups without cloning, e.g.:
- `HashMap<String, HashMap<String, Result<Option<Packument>, String>>>` and then `packuments.get(&pkg.registry).and_then(|m| m.get(&pkg.name))`.
- Alternatively, introduce a small key type that supports `Borrow` so `HashMap::get` can accept `(&str, &str)` without allocation.

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


Grey Divider

Qodo Logo

Comment thread pacquet/crates/cli/src/cli_args/audit/signatures.rs Outdated
Comment thread pacquet/crates/cli/src/cli_args/audit/signatures.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 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/cli/src/cli_args/audit.rs`:
- Around line 345-358: The audit path is losing each installed package’s
resolved registry because `lockfile_to_audit_request` collapses entries to
name/version pairs and `pick_registry_for_package` is then called with no
per-package resolution. Update the audit construction in `audit.rs` so
`SignaturePackage` is built while the lockfile still exposes
`SpecifierAndResolution`/resolution metadata, and pass that resolution into
`pick_registry_for_package` instead of `None`. Add a regression test for a
dependency resolved from a custom registry to verify the package is audited
against its own registry.

In `@pacquet/crates/cli/src/cli_args/audit/signatures.rs`:
- Around line 4-7: The module docs in signatures.rs reference pinned upstream
GitHub URLs that now 404, so update the two links in the doc comment to valid
upstream locations or a current commit hash. Keep the references aligned with
the existing pnpm parity docs for `@pnpm/deps.security.signatures` and the audit
signatures command, and verify the symbols in the comment remain accurate after
the URL update.
- Around line 193-214: The signature audit flow in `audit/signatures.rs` is
silently skipping packages when `fetch_packument` returns `Ok(None)` for a 404,
which lets `audit signatures` succeed without verifying those packages. Update
the `packuments` handling in the `SignatureVerificationResult` loop so that a
missing packument for a registry with signing keys is treated as an
invalid/per-package failure via `issue(...)`, not as a no-op; only skip packages
when `keys_by_registry` has no keys. Keep the behavior in `process_version` and
the `fetch_packument` call path aligned with pnpm semantics and fail closed for
missing or malformed registry data.
- Around line 539-540: The sanitize_body helper currently only truncates
attacker-controlled registry text but still passes through ANSI/control bytes
into user-facing diagnostics. Update sanitize_body in signatures::sanitize_body
to escape or remove control characters before truncation/rendering, so any
registry body displayed in audit output is safe to print in a terminal. Keep the
change localized to this helper and ensure all callers that render registry
bodies inherit the sanitization.
🪄 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: 7c878cc5-0502-48fb-a15f-de968a91854a

📥 Commits

Reviewing files that changed from the base of the PR and between 47685fd and ab9aca2.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock, !Cargo.lock
📒 Files selected for processing (6)
  • Cargo.toml
  • pacquet/crates/cli/Cargo.toml
  • pacquet/crates/cli/src/cli_args/audit.rs
  • pacquet/crates/cli/src/cli_args/audit/signatures.rs
  • pacquet/crates/cli/src/cli_args/audit/signatures/tests.rs
  • pacquet/crates/cli/tests/audit.rs

Comment thread pacquet/crates/cli/src/cli_args/audit.rs
Comment thread pacquet/crates/cli/src/cli_args/audit/signatures.rs Outdated
Comment thread pacquet/crates/cli/src/cli_args/audit/signatures.rs
Comment thread pacquet/crates/cli/src/cli_args/audit/signatures.rs Outdated
Comment thread pacquet/crates/cli/src/cli_args/audit/signatures.rs Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

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

@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Micro-Benchmark Results

Linux

group                          main                                   pr
-----                          ----                                   --
tarball/download_dependency    1.00      7.2±0.17ms   600.8 KB/sec    1.02      7.4±0.55ms   586.9 KB/sec

- Hold the ThrottledClient permit through the keys/packument body read
  instead of dropping it early, so the network-concurrency bound holds
  under the audit fan-out.
- Sanitize control characters in registry response bodies surfaced in
  diagnostics by reusing audit's sanitize_response_body helper, with a
  regression test for the keys-endpoint error path.
- Fix the upstream pnpm doc links (the files live under pnpm11/).
@github-actions github-actions Bot added the reviewed: coderabbit CodeRabbit submitted an approving review label Jun 25, 2026
@zkochan

zkochan commented Jun 25, 2026

Copy link
Copy Markdown
Member Author

Thanks for the reviews. Addressed the automated feedback in 153c997.

Fixed

  • Throttle permit dropped earlyfetch_registry_keys/fetch_packument now hold the ThrottledClientGuard through the body read, so the network-concurrency bound holds under the audit fan-out.
  • Control chars in registry bodies — now reuse audit's sanitize_response_body (escapes control chars + truncates) for every registry body surfaced in a diagnostic; added a regression test on the keys-endpoint error path.
  • 404'd upstream doc links — corrected to include the pnpm11/ path prefix.

Declining (would diverge from pnpm — pacquet's cardinal rule is to match the TS CLI exactly)

  • Per-package registry resolution — pnpm's auditSignatures routes by name only (pickRegistryForPackage(opts.registries, name)); lockfileToAuditRequest already discards per-package resolution upstream. Passing None is the faithful port.
  • Packument 404 → skip — pnpm's verifySignatures does if (!packument) return on a 404 packument (fail-open for unpublished / other-source packages). Non-404/non-200 and malformed bodies are still reported as failures.
  • Packument auth via for_url — pnpm uses getAuthHeader(registryUrl) (registry-base URL), which for_url(&registry_url) mirrors exactly. Scoped packages are already routed to their scoped registry URL, so the token still resolves.
  • Unbounded response buffering — pnpm and pacquet's existing audit/metadata paths all buffer bodies without a size cap from the same trust domain that serves tarballs; a body-size guard would belong repo-wide as a separate change, not only here.

Left the four declined threads open for a maintainer to weigh in on the parity rationale.


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

Comment thread pacquet/crates/cli/src/cli_args/audit/signatures.rs
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 153c997

@codecov-commenter

codecov-commenter commented Jun 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 86.93587% with 55 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.00%. Comparing base (dd8ea85) to head (527e661).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
...acquet/crates/cli/src/cli_args/audit/signatures.rs 84.95% 54 Missing ⚠️
pacquet/crates/cli/src/cli_args/audit.rs 98.38% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #12663      +/-   ##
==========================================
+ Coverage   87.98%   88.00%   +0.02%     
==========================================
  Files         352      354       +2     
  Lines       52478    53103     +625     
==========================================
+ Hits        46171    46734     +563     
- Misses       6307     6369      +62     

☔ 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.

Comment thread pacquet/crates/cli/src/cli_args/audit/signatures.rs Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 153c997

…work errors

The KeysNetwork/PackumentNetwork variants carried the raw reqwest::Error
as a diagnostic source and printed it via Display, which can embed the
request URL — leaking `user:pass@` userinfo from a credentialed registry
URL into stderr/CI output. Store the error as a string already passed
through redact_url_credentials instead, dropping the reqwest::Error
source so neither the message nor the miette cause chain can leak.

Adds an integration test asserting a credentialed registry URL does not
leak its userinfo on a transport error.
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 527e661

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

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 527e661

@zkochan
zkochan added this pull request to the merge queue Jun 25, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Integrated-Benchmark Report (Linux)

Commit: 527e66183c14

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.777 ± 0.109 4.670 4.960 1.56 ± 0.07
pacquet@main 4.806 ± 0.189 4.638 5.199 1.57 ± 0.09
pnpr@HEAD 3.055 ± 0.114 2.937 3.274 1.00
pnpr@main 3.065 ± 0.146 2.909 3.327 1.00 ± 0.06
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 4.77667134318,
      "stddev": 0.10863032445381617,
      "median": 4.7290127125799994,
      "user": 3.89471946,
      "system": 3.5777531199999997,
      "min": 4.66979062558,
      "max": 4.96039502258,
      "times": [
        4.72549967258,
        4.67012219358,
        4.87748416258,
        4.96039502258,
        4.80205072458,
        4.73252575258,
        4.68801209258,
        4.66979062558,
        4.71408724558,
        4.92674593958
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 4.8062456539800005,
      "stddev": 0.18939832200280338,
      "median": 4.74041210108,
      "user": 3.9380135600000004,
      "system": 3.5515420199999994,
      "min": 4.63797828258,
      "max": 5.19897262958,
      "times": [
        4.74577390358,
        5.19897262958,
        4.63797828258,
        4.66249963158,
        5.04408111158,
        4.69118728358,
        4.65108750158,
        4.76557798958,
        4.93024790758,
        4.73505029858
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 3.05539781178,
      "stddev": 0.11387455926529064,
      "median": 3.02266136608,
      "user": 2.7249750600000007,
      "system": 3.1210342199999994,
      "min": 2.93696446958,
      "max": 3.27378597558,
      "times": [
        2.93696446958,
        3.05450249858,
        3.27378597558,
        3.03346451058,
        2.96552894858,
        3.13185016058,
        2.99103847258,
        3.01185822158,
        3.20704836458,
        2.94793649558
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 3.0653909797800005,
      "stddev": 0.1455711659103221,
      "median": 3.04725675708,
      "user": 2.7921319600000003,
      "system": 3.0781005199999996,
      "min": 2.90896153558,
      "max": 3.3268727395799997,
      "times": [
        2.9455078975799998,
        3.25643097058,
        2.90896153558,
        3.1027850995799997,
        2.96088405058,
        2.91084019058,
        3.3268727395799997,
        3.08888175958,
        3.00563175458,
        3.14711379958
      ]
    }
  ]
}

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

Command Mean [ms] Min [ms] Max [ms] Relative
pacquet@HEAD 672.1 ± 19.9 646.2 711.2 1.00
pacquet@main 695.4 ± 93.1 649.1 955.6 1.03 ± 0.14
pnpr@HEAD 718.4 ± 27.0 684.0 772.9 1.07 ± 0.05
pnpr@main 770.0 ± 92.0 699.1 1014.2 1.15 ± 0.14
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 0.67209119594,
      "stddev": 0.019896503901143187,
      "median": 0.66828133634,
      "user": 0.41886304,
      "system": 1.3598305199999998,
      "min": 0.64616379584,
      "max": 0.7112326088400001,
      "times": [
        0.68775924184,
        0.67970805984,
        0.67809048384,
        0.65696030484,
        0.65840990684,
        0.6576101818400001,
        0.65847218884,
        0.6865051868400001,
        0.64616379584,
        0.7112326088400001
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 0.6954308971400001,
      "stddev": 0.09310415485022885,
      "median": 0.66354601034,
      "user": 0.39616573999999993,
      "system": 1.36836902,
      "min": 0.64913723284,
      "max": 0.9556051828400001,
      "times": [
        0.66999546884,
        0.6548723658400001,
        0.6725629068400001,
        0.66945447784,
        0.6576375428400001,
        0.64913723284,
        0.6564101198400001,
        0.9556051828400001,
        0.71164825084,
        0.65698542284
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 0.71840622004,
      "stddev": 0.026952201241279235,
      "median": 0.71402206534,
      "user": 0.42604823999999997,
      "system": 1.3741570200000002,
      "min": 0.68401045884,
      "max": 0.77286657284,
      "times": [
        0.7073682898400001,
        0.68698607984,
        0.6995438878400001,
        0.74085550584,
        0.77286657284,
        0.73150432584,
        0.7144665598400001,
        0.68401045884,
        0.73288294884,
        0.7135775708400001
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 0.76999530364,
      "stddev": 0.0920302764894872,
      "median": 0.7304392363400001,
      "user": 0.4083236399999999,
      "system": 1.40806222,
      "min": 0.6990741298400001,
      "max": 1.0141580938399999,
      "times": [
        0.7257197738400001,
        0.79011445684,
        0.7828436188400001,
        0.72644657584,
        0.6990741298400001,
        0.7344318968400001,
        0.7148135308400001,
        1.0141580938399999,
        0.79126416984,
        0.7210867898400001
      ]
    }
  ]
}

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

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 4.843 ± 0.026 4.802 4.893 1.48 ± 0.05
pacquet@main 4.870 ± 0.045 4.781 4.938 1.48 ± 0.05
pnpr@HEAD 3.279 ± 0.103 3.151 3.453 1.00
pnpr@main 3.295 ± 0.092 3.201 3.417 1.00 ± 0.04
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 4.8434060927200004,
      "stddev": 0.0260432508511132,
      "median": 4.84399815232,
      "user": 3.8959783000000003,
      "system": 3.48686654,
      "min": 4.801624154820001,
      "max": 4.89287067782,
      "times": [
        4.820819383820001,
        4.83540369682,
        4.89287067782,
        4.854343211820001,
        4.801624154820001,
        4.861258808820001,
        4.82911420782,
        4.86048552082,
        4.85259260782,
        4.825548656820001
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 4.869607389920001,
      "stddev": 0.044569044710573755,
      "median": 4.87937942082,
      "user": 3.9518773000000005,
      "system": 3.4660306400000005,
      "min": 4.780721074820001,
      "max": 4.937759642820001,
      "times": [
        4.780721074820001,
        4.853138184820001,
        4.8169723948200005,
        4.87762079182,
        4.937759642820001,
        4.86579977782,
        4.8827524468200005,
        4.904989312820001,
        4.8811380498200005,
        4.895182222820001
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 3.27937211382,
      "stddev": 0.10295841196803845,
      "median": 3.24033375632,
      "user": 2.8428201999999994,
      "system": 3.27083784,
      "min": 3.15107797882,
      "max": 3.45348654482,
      "times": [
        3.15107797882,
        3.40895081582,
        3.20415666582,
        3.45348654482,
        3.33353612782,
        3.36405077382,
        3.23427111782,
        3.24639639482,
        3.19775683582,
        3.20003788282
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 3.2951446102199995,
      "stddev": 0.0915350530521153,
      "median": 3.27109635482,
      "user": 2.8837923999999995,
      "system": 3.27894314,
      "min": 3.20135053282,
      "max": 3.41749121582,
      "times": [
        3.30309730682,
        3.21378447282,
        3.20943573682,
        3.20135053282,
        3.21186305482,
        3.23909540282,
        3.41749121582,
        3.41291614782,
        3.33884044882,
        3.40357178282
      ]
    }
  ]
}

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

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 1.428 ± 0.016 1.405 1.459 1.00
pacquet@main 1.443 ± 0.048 1.404 1.576 1.01 ± 0.04
pnpr@HEAD 1.489 ± 0.086 1.434 1.731 1.04 ± 0.06
pnpr@main 1.467 ± 0.050 1.432 1.603 1.03 ± 0.04
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 1.42787691502,
      "stddev": 0.01593119892757247,
      "median": 1.4294108586199998,
      "user": 1.4287266799999998,
      "system": 1.7506598,
      "min": 1.40499030862,
      "max": 1.45879294862,
      "times": [
        1.4407914126199999,
        1.40499030862,
        1.45879294862,
        1.43427229862,
        1.4267226686199999,
        1.4298377576199999,
        1.41109018562,
        1.41089567762,
        1.43239193262,
        1.42898395962
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 1.44312442692,
      "stddev": 0.04849395894004138,
      "median": 1.43601571662,
      "user": 1.3959339800000001,
      "system": 1.7861824,
      "min": 1.4041857846199999,
      "max": 1.5763690526199998,
      "times": [
        1.4041857846199999,
        1.5763690526199998,
        1.43725716062,
        1.4399422336199998,
        1.43763885262,
        1.42227406362,
        1.43477427262,
        1.44353959562,
        1.41484013762,
        1.42042311562
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 1.4892628782200001,
      "stddev": 0.08646838683513486,
      "median": 1.46368009562,
      "user": 0.6341421799999999,
      "system": 1.5882144,
      "min": 1.43427252262,
      "max": 1.73116201162,
      "times": [
        1.48176292462,
        1.73116201162,
        1.4749870106199998,
        1.45285086662,
        1.48531828462,
        1.45579149562,
        1.45048622062,
        1.43427252262,
        1.45442874962,
        1.47156869562
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 1.46666715212,
      "stddev": 0.050280346212067055,
      "median": 1.45296389712,
      "user": 0.6051898799999998,
      "system": 1.5741695,
      "min": 1.4324881376199998,
      "max": 1.6032349346199999,
      "times": [
        1.46167100862,
        1.6032349346199999,
        1.4786662826199999,
        1.43646706662,
        1.4324881376199998,
        1.45561145162,
        1.43380680362,
        1.45031634262,
        1.44709003662,
        1.46731945662
      ]
    }
  ]
}

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

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 3.132 ± 0.040 3.093 3.210 2.22 ± 0.05
pacquet@main 3.050 ± 0.064 2.960 3.182 2.16 ± 0.06
pnpr@HEAD 1.412 ± 0.023 1.375 1.442 1.00
pnpr@main 1.454 ± 0.090 1.384 1.687 1.03 ± 0.07
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 3.1324814292,
      "stddev": 0.04027287736242253,
      "median": 3.126894444,
      "user": 1.91312358,
      "system": 2.02428424,
      "min": 3.0929927520000002,
      "max": 3.209608193,
      "times": [
        3.209608193,
        3.095381994,
        3.0929927520000002,
        3.102553586,
        3.131888723,
        3.096192662,
        3.121900165,
        3.146055579,
        3.1396078800000002,
        3.1886327580000002
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 3.0495278430000003,
      "stddev": 0.06406198912224452,
      "median": 3.0531972085,
      "user": 1.80710858,
      "system": 1.99655174,
      "min": 2.960083344,
      "max": 3.1817865850000002,
      "times": [
        3.087597405,
        3.017478718,
        3.001776516,
        3.05851908,
        3.047875337,
        3.093111951,
        2.982593976,
        3.1817865850000002,
        3.064455518,
        2.960083344
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 1.4123016696000001,
      "stddev": 0.023433667894945477,
      "median": 1.4164311595,
      "user": 0.60104008,
      "system": 1.5673545399999997,
      "min": 1.37502625,
      "max": 1.441555507,
      "times": [
        1.423920328,
        1.441555507,
        1.37502625,
        1.408941991,
        1.426988479,
        1.429405145,
        1.386394337,
        1.440358763,
        1.395078558,
        1.395347338
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 1.4542568374000002,
      "stddev": 0.08964804228976557,
      "median": 1.4222568525,
      "user": 0.59544648,
      "system": 1.57830784,
      "min": 1.384366437,
      "max": 1.687471762,
      "times": [
        1.499061367,
        1.687471762,
        1.460627219,
        1.464911427,
        1.413621051,
        1.405068908,
        1.430892654,
        1.395959139,
        1.384366437,
        1.4005884100000001
      ]
    }
  ]
}

@github-actions

Copy link
Copy Markdown
Contributor

🐰 Bencher Report

Branchpr/12663
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,843.41 ms
(+2.20%)Baseline: 4,739.14 ms
5,686.96 ms
(85.17%)
isolated-linker.fresh-install.cold-cache.hot-store📈 view plot
🚷 view threshold
3,132.48 ms
(+3.82%)Baseline: 3,017.18 ms
3,620.62 ms
(86.52%)
isolated-linker.fresh-install.hot-cache.hot-store📈 view plot
🚷 view threshold
1,427.88 ms
(+6.42%)Baseline: 1,341.77 ms
1,610.13 ms
(88.68%)
isolated-linker.fresh-restore.cold-cache.cold-store📈 view plot
🚷 view threshold
4,776.67 ms
(+2.01%)Baseline: 4,682.45 ms
5,618.94 ms
(85.01%)
isolated-linker.fresh-restore.hot-cache.hot-store📈 view plot
🚷 view threshold
672.09 ms
(+5.30%)Baseline: 638.28 ms
765.94 ms
(87.75%)
🐰 View full continuous benchmarking report in Bencher

@github-actions

Copy link
Copy Markdown
Contributor

🐰 Bencher Report

Branchpr/12663
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
3,279.37 ms
isolated-linker.fresh-install.cold-cache.hot-store📈 view plot
⚠️ NO THRESHOLD
1,412.30 ms
isolated-linker.fresh-install.hot-cache.hot-store📈 view plot
⚠️ NO THRESHOLD
1,489.26 ms
isolated-linker.fresh-restore.cold-cache.cold-store📈 view plot
⚠️ NO THRESHOLD
3,055.40 ms
isolated-linker.fresh-restore.hot-cache.hot-store📈 view plot
⚠️ NO THRESHOLD
718.41 ms
🐰 View full continuous benchmarking report in Bencher

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jun 25, 2026
@zkochan
zkochan merged commit c50c6aa into main Jun 25, 2026
33 checks passed
@zkochan
zkochan deleted the feat/pacquet-audit-signatures branch June 25, 2026 16:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

product: pacquet reviewed: coderabbit CodeRabbit submitted an approving review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants