Fix did:webvh implicit services and path resolution - #142
Conversation
moisesja
left a comment
There was a problem hiding this comment.
Reviewed the full diff and independently verified the claims: on the PR head I ran all 15 new Issue136 dereferencer tests, the 6 new WebVh tests, and the full Core (433/433) and WebVh (443/443) suites — all green. The security tests are real, not aspirational: the ///host, scheme-in-path, %2e%2e traversal, and %0d%0a uri-list injection cases actually exercise .NET 10 Uri normalization behavior and pass. The core design is right — projecting implicit services at resolution time without touching signed did.jsonl state (locked in by Issue136_Resolve_MaterializesImplicitServicesWithoutChangingSignedState), authority pinning including userinfo, and post-normalization base-path containment.
Four specific issues, none blocking, but 1 and 2 deserve action before merge:
1. The ConstructServiceUrl change is a cross-method behavior change hiding in a did:webvh CHANGELOG bullet. DefaultDidUrlDereferencer.cs:405-410 now preserves an endpoint fragment for every DID method's ?service= / ?serviceType= dereference, not just webvh paths. Previously, endpoint https://ex.com/base#frag plus a relativeRef silently dropped the fragment via RFC 3986 resolution — contradicting the method's own doc comment, so the fix is correct — but did:key/did:peer/did:ethr consumers will see different output. The CHANGELOG entry scopes everything under "did:webvh implicit services"; this needs its own unscoped line so a consumer diffing 3.2→3.3 behavior can find it.
2. invalidDid is the wrong error code at DefaultDidUrlDereferencer.cs:127. When an explicit #files service has an ftp:// or non-URI endpoint, the DID is not invalid — the selected service is unusable. Per the DID Resolution error registry, invalidDid means the input DID string failed syntax. notFound or internalError is more accurate, and the new test currently locks the wrong code in. Related asymmetry: a set-valued serviceEndpoint is legal per DID Core and the ?service= flow handles sets (lines 218-228), but the path flow hard-fails it. If deliberately out of scope, say so in the CHANGELOG line ("Unsupported explicit endpoint shapes fail") with the corrected error code.
3. ContainsService and FindServiceById disagree on bare ids. ImplicitWebVhServices.cs:50-61 matches only #files, <docId>#files, <requestedDid>#files, but NormalizeServiceId (DefaultDidUrlDereferencer.cs:279-286) also treats a bare files (no #, no :) as <did>#files. A controller-authored service with id: "files" escapes the dedup check, so materialization adds #files beside it and the resolved document carries two services that normalize to the same absolute id — a spec-invalid document. Dereferencing still picks the explicit one by list order, so no wrong redirect, but either normalize consistently or document why bare ids are out of scope.
4. parsed.Did.Method == "webvh" hardcodes method knowledge into NetDid.Core (DefaultDidUrlDereferencer.cs:94). The gating is deliberate and correctly prevents other methods from gaining path semantics, but the moment a second method wants conventional path dispatch this should become a capability flag on the method/resolution metadata, not a string comparison in Core. Fine for now; flagging so it doesn't ossify.
One confirmation question: /whois/ and /whois?x fall through to the #files path handler because the whois check is exact-match on /whois (line 118). That reads as the correct reading of the spec's "special path", but confirm it's intentional rather than incidental.
Everything else — resource-base derivation reusing the hardened ValidateDomain/ValidatePathSegments, .well-known exclusion for root DIDs, did:web artifact projection with subject-bound id rewriting, historical-version materialization — checks out against issue #136 and the spec text quoted there.
Generated by Claude Code
|
Addressed in 7912d22. Thanks for the detailed review.
The adversarial follow-up also found and fixed two related edges: encoded-fragment precedence and a caller-owned URI set being mutated to empty after construction. Verification: Release build 0 warnings / 0 errors; 1,738 passed, 0 failed, 7 environment-skipped; focused Core 21/21 and WebVh 8/8; all five Release samples passed; final adversarial verdict clean. |
moisesja
left a comment
There was a problem hiding this comment.
Re-reviewed at 7912d22 — verdict: approved (GitHub won't accept a formal Approve from the PR author's own credentials, so recording it here). All five round-1 items are resolved — four fixed, one correctly rebutted — and I verified each independently rather than taking the response at its word.
On invalidDid (item 2): you're right and my round-1 suggestion was wrong. I fetched did:webvh v1.0 and both algorithms state it verbatim — DID URL Path Resolution: "If the scheme of the serviceEndpoint is not supported by the resolver (e.g., non-HTTP(S) protocol), the resolver MUST return an invalidDid error"; WHOIS Resolution has the same MUST. The method-specific algorithm overrides the generic registry's narrower reading, and the tasks/lessons.md entry capturing "check the method-specific algorithm before the generic registry" is the right takeaway. The legal-shape gap I flagged alongside it is genuinely closed: URI-set endpoints now produce per-entry validated CRLF uri-lists, and the fail-closed choice (one bad set entry poisons the whole result rather than returning the good entries) is defensible and now documented.
Verified fixed, with the right mechanism:
- Cross-method fragment change now has its own unscoped CHANGELOG bullet plus two non-webvh regressions. The rework is better than what I asked for: applying the fragment as a URI component via
UriBuilderafter RFC 3986 resolution (DefaultDidUrlDereferencer.cs,ConstructServiceUrl) closes the encoded-%23-in-relativeRefoverride that the round-1 string-concatenation version would have allowed — andIssue136_ServiceTypeQuery_EncodedFragmentCannotOverrideEndpointFragmentlocks it in. - Bare-id dedup is fixed on both sides:
ContainsServicenow accepts the bare spelling, andCanonicalizeConventionalId/RewriteServiceIdcanonicalizefiles/whoisto fragment form in resolved projection and the parallel did:web artifact, so the duplicate-normalized-id document can no longer be produced. Fail-first tests cover both surfaces. - Method gate comment now records the capability-flag exit criterion. Good enough.
/whoismatching confirmed intentional with tests for both/whois?x=1(still#whois) and/whois/(ordinary#filespath), plus README/PRD language.
The extra adversarial catch — a caller-owned endpoint set mutated to empty after construction now snapshots and fails closed — is a real TOCTOU-shaped edge and the test proves it.
Independent verification at 7912d22: focused Core Issue136 21/21 and WebVh 8/8; full Core 439/439, WebVh 445/445, W3C conformance 233/233 — all matching your claimed counts exactly. No further changes requested.
Generated by Claude Code
Rebase onto main (PR #142). Conflict resolutions: - NetDidPRD.md: kept this branch's witness wire-format text in resolution step 8 and main's rewritten steps 9-10 (implicit service projection + IncludeLog artifacts). - CHANGELOG.md: merged the two [Unreleased] "Fixed" sections into one, main's #136 entries first, then the #135 entries. - tasks/webvh-vector-divergence.md: regenerated from the combined state rather than resolved by hand - it is generated output, and both changes affect it. Witnessed happy-path replays stay 10/10 accepted alongside #136's service projection; 21/21 negatives still rejected. Re-verified after the rebase: 0-warning Release build; 1,760 tests green (Core 439, WebVh 467, Ethr 490, W3C 233/233, Key 52, Peer 48, DI 20, Ethr.Integration 11 +7 opt-in skips); all 5 samples exit 0; git diff --check clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes #136
Summary
#filesand Linked-VP#whoisservices into successful current and historical did:webvh resolution without mutating signed log state/whoisprecedence and constrained HTTP(S) redirect constructionSecurity and compatibility
relativeRef, cannot replace the selected authority or escape the deployment resource base, reject URI-list control injection, and preserve endpoint-fragment precedenceVerification
Known pre-existing warning
A normal audited restore reports NU1903 for Testcontainers 4.13.0's transitive SSH.NET 2025.1.0 dependency. It predates and is unrelated to issue #136, so the dependency upgrade is intentionally left for a focused security PR; an audit-disabled restore/build confirms this change has zero source/compiler warnings.