Skip to content

JSON-LD: time out remote context fetches and remember failures - #4617

Open
reinkrul wants to merge 2 commits into
masterfrom
fix/4615-jsonld-context-timeout
Open

reinkrul wants to merge 2 commits into
masterfrom
fix/4615-jsonld-context-timeout

Conversation

@reinkrul

@reinkrul reinkrul commented Oct 8, 2026

Copy link
Copy Markdown
Member

Fixes #4615

Problem

Remote JSON-LD contexts were fetched with http.DefaultClient, which has no response timeout. Credentials are verified while network transactions are processed, so in non-strict mode a context server that never answers stopped network synchronization at the first credential referring to it. Nothing was logged. Failed fetches were also not remembered, so a timeout alone would still cost one timeout per credential.

Changes

  • jsonld/ldutils.go
    • Remote contexts are fetched through client.New(5s), the node's strict HTTP client, wrapped as the transport of the *http.Client json-gold needs. This gives the timeout, the SSRF dial guard, the strict-mode URL checks on every redirect, the 1 MiB response limit, and the node's own User-Agent.
    • New failureCachingLoader directly in front of json-gold's remote loader. A URL that failed is not fetched again for 5 minutes; loads return the original *ld.JsonLdError immediately. The VCR ambassador therefore still treats it as recoverable and retries the event later. One warning per failure, debug for skipped fetches. Expired entries are pruned when a new failure is recorded.
    • Embedded and locally mapped contexts never reach the new loader. NewContextLoader's signature is unchanged.
  • Docs: remote context fetching in non-strict mode, timeout and failure cache.
  • Release notes.

Behavior changes beyond the fix

  • In strict mode, an allow-listed remote context with an http:// URL or a reserved hostname is now rejected by the strict client's URL checks. The defaults are embedded, so this only affects custom allow-list entries.
  • The User-Agent changes from Go-http-client/1.1 to nuts-node-refimpl/<version>.

Testing

  • Server that never answers: the load fails with LoadingDocumentFailed after the timeout.
  • Failing server is hit once for two loads, both return the same error.
  • Request carries the node's User-Agent.
  • failureCachingLoader unit tests: expiry, successes not remembered, per-URL, pruning.
  • go test -race -count=3 ./jsonld/ and go test ./vcr/... ./auth/... ./discovery/... ./vdr/... pass.

Backport

V6.2 and V5.4, as listed in #4615. To be prepared once this is reviewed.

Assisted by AI

A remote JSON-LD context fetch had no timeout. In non-strict mode, a context
server that never answered blocked network synchronization at the first
credential referring to it. Fetches now go through the node's strict HTTP
client with a 5 second timeout, and a failed URL is not fetched again for
5 minutes.

Fixes #4615

Assisted by AI
@qltysh

qltysh Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

0 new issues

Tool Category Rule Count

Comment thread jsonld/ldutils.go
// json-gold needs a *http.Client, so the node's strict HTTP client (timeout, SSRF guard, URL checks, response size limit)
// is wrapped as its transport.
func newRemoteContextHTTPClient() *http.Client {
return &http.Client{Transport: strictClientTransport{client: client.New(remoteContextTimeout)}}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This nests two http.Clients: the outer one json-gold needs, and the one inside
StrictHTTPClient. All the real behaviour lives in the inner one. It applies the
5s timeout, follows redirects with checkRedirect, and buffers the body before
RoundTrip returns, so the outer client only ever sees a final non-redirect
response. Its Timeout and CheckRedirect are therefore unused on purpose.

Worth stating that in the comment, because the next reader will see
&http.Client{Transport: ...} with no Timeout and add one, and then there are
two timeouts that disagree. Suggested wording:

// The outer client's Timeout and CheckRedirect are intentionally unset:
// the strict client inside the transport owns both, follows redirects
// itself, and has read the body by the time RoundTrip returns.

Also note that this RoundTrip bends the http.RoundTripper contract (it follows
redirects and returns errors for oversized bodies), so it should stay private
to this package rather than become a general adapter. If we want a real
*http.Client with the strict protections, that belongs in http/client as a
per-hop transport, and pki/validator.go (CRL fetching on http.DefaultTransport,
no SSRF guard, no body limit) would be the second consumer.

Comment thread jsonld/ldutils.go
type failureCachingLoader struct {
ttl time.Duration
nextLoader ld.DocumentLoader
mutex sync.Mutex

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This type copies much of the already used patrickmn/go-cache which also has a TTL option. I propose to used that one here as well instead of rolling our own logic for that.

@stevenvegt

Copy link
Copy Markdown
Member

FYI: I've created two related issues #4619 and #4620.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JSON-LD: remote context fetch has no timeout and no failure cache, a dead context server stalls network sync

2 participants