Skip to content

The Linked Data proxy delegates writes to other instances - #525

Open
namedgraph wants to merge 3 commits into
developfrom
test/cross-instance-federation
Open

namedgraph wants to merge 3 commits into
developfrom
test/cross-instance-federation

Conversation

@namedgraph

Copy link
Copy Markdown
Member

Problem

Since 6.2.4 (1d71d036e), the Linked Data proxy delegates a signed-in caller only to this instance's own dataspaces. A write to another LinkedDataHub instance went out anonymous and was refused with 403, which broke "Add data" to a foreign document (CHANGELOG 6.0: "Add data accepts foreign target documents").

The reason given for the restriction was that "external sites can't verify a delegation anyway", and that doesn't hold. WebIDFilter verifies On-Behalf-Of by dereferencing the caller's and the secretary's WebID documents, which are public, so any instance can do it.

The HTTP tests missed it because the stack ran one platform instance: test.localhost and packages.localhost are dataspaces of the same instance, so they always counted as "own".

Change

ProxyRequestFilter picks a client for each redirect hop:

Target Before Now
This instance's dataspaces certificate + On-Behalf-Of (+ ID token cookie) unchanged
Another origin, PUT/POST/PATCH/DELETE anonymous certificate + On-Behalf-Of
Another origin, GET/HEAD anonymous anonymous (browsing doesn't announce the caller)

The ID token is a bearer credential, so it still goes only to this instance's dataspaces. An OAuth caller writing to another instance is named in On-Behalf-Of, like a WebID caller; their agent document carries <secretary> acl:delegates <agent> too (LoginBase).

Tests

  • Second instance in the HTTP-tests stack: a linkeddatahub-remote service, with its own dataspaces (remote.localhost, admin.remote.localhost), owner, secretary and fuseki datasets. nginx routes it through an include /etc/nginx/extra.d/*.conf; in docker-compose.yml, which does nothing when the directory is empty, so nothing needs to change in the copies of the compose file.
  • federation/PATCH-cross-instance-delegated.sh: a PATCH as A's owner to a document on B that grants A's owner write.
    • Control: sent straight to B, expects 204.
    • Assertion: the same PATCH through A's proxy, expects 204.
  • Results:
    • The test commit alone was red on CI: direct 204, proxied 403.
    • With the fix, CI is green: 0 failed.
  • The OAuth caller's cross-instance path follows from the code above but has no test, because the HTTP tests have no OAuth fixture.

Related

🤖 Generated with Claude Code

namedgraph and others added 2 commits November 6, 2026 22:55
The http-tests stack ran one platform container, so every dataspace a federation test wrote to was one
of the proxying instance's own, and delegation to another instance was never exercised. A second
instance, linkeddatahub-remote, serves remote.localhost and admin.remote.localhost with its own
secretary, owner and Fuseki datasets. nginx routes those hosts to it through server blocks the
overlay adds via a new, by default empty, include in the main config. It reaches the first
instance's WebIDs through the Docker host, hence ALLOW_INTERNAL_URLS on this test-only service.

federation/PATCH-cross-instance-delegated.sh has B grant A's owner write on a document, then writes
it twice. Directly with A's owner certificate it succeeds, so B verifies A's WebIDs. Through A's
Linked Data proxy it is refused with 403, because since 1d71d03 the proxy delegates (the
secretary's certificate and On-Behalf-Of) only to its own dataspaces, and B sees an anonymous write.
The test asserts the proxied write succeeds, so it fails until cross-instance delegation is restored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An authenticated caller's PUT, POST, PATCH or DELETE through the proxy goes
out with the platform's certificate and On-Behalf-Of naming the caller, to
another instance too, which verifies the delegation by dereferencing the
caller's and the secretary's WebIDs. Since 1d71d03 only this instance's
own dataspaces were delegated, so a cross-instance write arrived anonymous
and was refused. Reads of other origins stay anonymous, and the ID token
still goes to this instance's dataspaces only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ext hop

The second instance gets a test redirector, /see-other/<path>, answering any
method with 303 See Other to /<path>. A's owner POSTs to it through A's proxy,
redirected to a document on B that only A's owner may read: the GET the POST
continues as is a read of a foreign origin, which goes out anonymous and is
refused. It is delegated instead, since the proxy decides delegation from the
request's method rather than the hop's, and reads the document.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

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.

1 participant