Skip to content

dpp-vault: public_read_handler serves the live row with a frozen publicJwsSignature; only the resolver renders from the signed payload #27

Description

@LKSNDRTMLKV

Background

Two public surfaces serve a passport, and they are not built the same way:

  • Resolver (/dpp/{id}, /01/{gtin}): verifies publicJwsSignature, then renders from the decoded signed payload (dpp-resolver/src/infra/did.rs:102-104, resolve_json.rs:60). Body and proof can never disagree.
  • Vault (/public/dpp/{id}, public_read_handler in crates/dpp-vault/src/handlers/public_read.rs): looks up the live row (find_by_id_any_status), serializes it, and redacts it to the Public tier via public_view(&full, ...). The redacted body still carries publicJwsSignature — a frozen proof attached to a live, potentially-since-changed body.

Impact

Public-tier fields that are mutable after publish (lintResultodal-node/dpp-core#40 — plus status, qrCodeUrl, publishedAt, updatedAt) can diverge from what publicJwsSignature actually signed at publish time. Anyone who fetches from the vault's public route (as opposed to the resolver) and verifies the embedded signature against the current body will get a mismatch that isn't tampering — it's just two different code paths serving the "same" public view differently.

The resolver is the canonical/intended verification path, so this is not a spoofing hole. It is a correctness/consistency defect on a secondary public route that undermines the "every served field is bound to a signed proof" promise for anyone who verifies off the vault endpoint directly.

Recommended fix

Preferred: have public_read_handler render from the signed publicJwsSignature payload the same way the resolver does, so both public surfaces are a single code path with byte-identical output. Alternative if it must serve the live row: strip publicJwsSignature from that response so nothing invites a consumer to verify a body that was never signed in this shape.

Found during a 2026-07-18 full-codebase review; verified directly against current main.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions