Description
Network.connectToDID (network/network.go) iterates a discovered peer's node.Service entries and calls service.UnmarshalServiceEndpoint(&nutsCommUrl) directly on the raw NutsComm service entry.
This works for a literal serviceEndpoint (e.g. "grpc://example.com:5555"), but not when the serviceEndpoint is itself a DID URL reference, e.g.:
"serviceEndpoint": "did:nuts:C79u41fohcBHt7EwBF7KQdkhqerLCvAceGuLrEjHUq3S/serviceEndpoint?type=NutsComm"
This is a valid, spec-compliant way to publish a service endpoint (an indirection to another DID document's service) — used e.g. by a care organization's (child) DID document referencing its vendor's (parent) DID document. UnmarshalServiceEndpoint tries to parse this string directly as an absolute URL and fails, which is logged (at Debug level) and that service entry is silently skipped:
msg="Failed to extract NutsComm address from service" did="did:nuts:..." error="url must contain scheme and host"
Because the error is Debug-level and per-service, the node never surfaces that it failed to resolve that DID's NutsComm address.
Root cause
connectToDID reads the service entry directly instead of resolving it. Compare with Network.checkNodeDIDHealth, which correctly follows DID URL service references before parsing:
serviceRef := resolver.MakeServiceReference(nodeDID, transport.NutsCommServiceType)
nutsCommService, err := n.serviceResolver.Resolve(serviceRef, resolver.DefaultMaxServiceReferenceDepth)
connectToDID has access to the same n.serviceResolver but never uses it.
Impact
Discovery through a care organization's (child) DID document that references its vendor's (parent) DID document's NutsComm service fails via this path. In practice this is likely low severity: the vendor's (parent) DID document is discovered separately and publishes the endpoint as a literal URL, so the underlying node still gets discovered and connected to through that document. The gap is that discovery specifically through the child DID document silently fails instead of resolving the reference, which is still worth fixing since it's a valid, spec-compliant endpoint format.
Suggested fix
In connectToDID, resolve the service via n.serviceResolver.Resolve(...) (as checkNodeDIDHealth already does) before unmarshalling into transport.NutsCommURL, instead of unmarshalling the raw did.Service entry directly.
Assisted by AI
Description
Network.connectToDID(network/network.go) iterates a discovered peer'snode.Serviceentries and callsservice.UnmarshalServiceEndpoint(&nutsCommUrl)directly on the rawNutsCommservice entry.This works for a literal
serviceEndpoint(e.g."grpc://example.com:5555"), but not when theserviceEndpointis itself a DID URL reference, e.g.:This is a valid, spec-compliant way to publish a service endpoint (an indirection to another DID document's service) — used e.g. by a care organization's (child) DID document referencing its vendor's (parent) DID document.
UnmarshalServiceEndpointtries to parse this string directly as an absolute URL and fails, which is logged (at Debug level) and that service entry is silently skipped:Because the error is Debug-level and per-service, the node never surfaces that it failed to resolve that DID's NutsComm address.
Root cause
connectToDIDreads the service entry directly instead of resolving it. Compare withNetwork.checkNodeDIDHealth, which correctly follows DID URL service references before parsing:connectToDIDhas access to the samen.serviceResolverbut never uses it.Impact
Discovery through a care organization's (child) DID document that references its vendor's (parent) DID document's
NutsCommservice fails via this path. In practice this is likely low severity: the vendor's (parent) DID document is discovered separately and publishes the endpoint as a literal URL, so the underlying node still gets discovered and connected to through that document. The gap is that discovery specifically through the child DID document silently fails instead of resolving the reference, which is still worth fixing since it's a valid, spec-compliant endpoint format.Suggested fix
In
connectToDID, resolve the service vian.serviceResolver.Resolve(...)(ascheckNodeDIDHealthalready does) before unmarshalling intotransport.NutsCommURL, instead of unmarshalling the rawdid.Serviceentry directly.Assisted by AI