fix(node): fail closed when a recipient DID can't be resolved (#47)#67
fix(node): fail closed when a recipient DID can't be resolved (#47)#67beardthelion wants to merge 4 commits into
Conversation
encrypt_and_pin resolved recipient DIDs with filter_map, silently dropping any that failed to resolve and sealing the envelope to the remaining subset while still recording the full intended recipient set as covered. A reader whose DID could not be resolved at seal time (any did:web/did:gitlawb, or a malformed did:key) was then permanently locked out: the dedup compares the recorded full set against the desired set, matches, and never re-seals. Resolve all recipient DIDs up front via resolve_all_recipients; if any fail, log and skip the blob rather than seal to a partial set. Nothing partial is recorded, so the dedup stays honest and the blob re-seals on a later push once resolution is available. Add a bounded per-blob warn plus one aggregate coverage warn so a fully-migrated did:gitlawb org (zero encrypted-pin coverage) is one greppable line, not a scrape, and give the previously silent read_object and pin_git_object failure arms warn logs.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthrough
ChangesFail-closed recipient resolution and sealing decisions
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Reconciles encrypted_pin.rs with #40 (blind recipient identities, now on main). The encrypt_and_pin body auto-merged cleanly: #67's fail-closed resolve_all_recipients gate now sits on top of #40's opaque recipients_tag structure (tag unchanged -> skip; tag changed -> require full recipient resolution before sealing). Two spots needed manual resolution: - kept #67's resolve_all_recipients helper (the merged body calls it) and took #40's encrypt_and_pin doc (the merged fn returns (oid, cid) and never returns recipient identities). - merged both test groups under one generic set<S: AsRef<str>> helper so the #47 fail-closed tests and the recipients_tag tests coexist.
The fail-closed invariant lived only in resolve_all_recipients, tested in isolation; nothing proved encrypt_and_pin acts on it (a refactor falling through to a partial seal would pass every test). encrypt_and_pin needs a live Postgres Db, which CI does not provide, so it can't be tested directly. Extract the per-blob seal/skip decision into a pure plan_seal() -> SealPlan (no DB, no IO), behaviour-preserving, and unit-test its branches: seals only when all recipients resolve, fails closed on any unresolvable DID, skips the empty set, skips an unchanged tag, re-seals a changed set.
|
fix failing tests |
|
Pushed ca53afd: that was the fmt gate, not the tests (stable + beta were already green). |
jatmn
left a comment
There was a problem hiding this comment.
Validation notes
- I checked the repo docs (
README.md,docs/RUN-A-NODE.md) and CI config (.github/workflows/pr-checks.yml) for a stated Git requirement and did not find one. - The test job runs on
ubuntu-latestand executescargo test --workspace, but it does not pin or document a Git version. - In my review environment (Windows,
git version 2.53.0.windows.2),cargo test -p gitlawb-node sync -- --nocapturefailed because this Git build rejectedgit clone --mirror --filter=blob:limit=10gfromcrates/gitlawb-node/src/sync.rs:445-454. - Since the repo does not document a supported Git version and CI runs on a different OS, I do not have enough evidence to call that a PR bug rather than an environment mismatch.
|
Good catch on the missing Git-version floor. That |
|
@kevincodex1 this is ready. CI is green across the board (stable/beta, fmt+clippy, build, audit, MSRV, CodeQL, CodeRabbit clean) and mergeable. jatmn's note is a portability observation about sync.rs, which this PR doesn't touch; I'm tracking it as a separate issue. |
Summary
encrypted_pin::encrypt_and_pinsealed a withheld blob to whatever subset of its recipient DIDs resolved locally and recorded the full intended set as covered, permanently locking out any reader whose DID could not be resolved at seal time. This makes sealing fail closed: resolve all recipients or skip the blob.Motivation & context
Closes #47
did_to_keyonly resolvesdid:key;did:web/did:gitlawb(the canonical identity actors migrate to once DHT anchoring is live) and any malformeddid:keyreturnNone. The oldfilter_mapdropped those silently and sealed to the rest as long as one key resolved, then recorded the full DID set. Because the dedup compares the recorded set against the desired set, it matched on the next push and never re-sealed, so the dropped reader stayed locked out forever with no signal.Kind of change
What changed
gitlawb-node (
crates/gitlawb-node/src/encrypted_pin.rs):resolve_all_recipients: resolves every recipient DID all-or-nothing, returningErr(unresolved)listing the DIDs that failed.encrypt_and_pinnow skips the blob on any failure instead of sealing to a partial set. Nothing partial is recorded, so the dedup stays honest and the blob re-seals on a later push once resolution is available (no automatic backfill).did:keyis not DHT-pending).did:gitlawborg dropping to zero encrypted-pin coverage is a single greppable line rather than a per-oid scrape.read_objectandpin_git_objectfailure arms warn logs.Fail-closed is deliberate: a blob with an unresolvable reader gets no encrypted recovery envelope until resolution exists, but it is never leaked in plaintext (it was already pinned as withheld upstream). This trades recovery coverage during a transition for a clean audit invariant: a recorded
encrypted_blobsrow always means every authorized reader can recover the blob.How a reviewer can verify
The core regression is
mixed_set_with_did_gitlawb_fails_closed: a resolvabledid:keysubset plus onedid:gitlawbmust returnErrnaming only the unresolvable DID, never a sealable key set.malformed_did_key_fails_closedcovers the corrupt-did:keycase.Before you request review
cargo test --workspacepasses locallycargo fmt --allandcargo clippy --workspace --all-targets -- -D warningsare cleanfeat(...),fix(...),docs(...)).env.exampleupdated if behavior or config changed (or N/A)Protocol & signing impact
did:key, Ed25519 / RFC 9421 signatures, UCAN, ref certs, or P2P wire formatsThis reads DID strings to resolve recipient keys but changes no signature, wire-format, or identity logic; existing envelopes and recorded recipient sets are untouched.
Notes for reviewers
The peer-sync path (
sync.rsrecords an empty recipient set for replicated envelopes via an overwriting upsert) interacts with the dedup but self-heals on the next push; it is pre-existing and out of scope here. A typed resolution-failure reason (distinguishing "method not resolvable yet" from "malformeddid:key") is a possible follow-up; fail-closed behavior is identical for both today.Summary by CodeRabbit
Improvements
Tests