Named-Graph ACLs

Each graph is a security boundary.

0/4 done

Theory

Named graphs aren't just an organisational tool — they're the natural unit of access control. Put each tenant, project or user in their own graph, then express policy as: agent A may read graphs G1,G2 and write G2.

Most triple stores support this directly (Stardog, GraphDB, Virtuoso). Solid expresses it as Web Access Control documents next to each resource.

Worked example — a WAC ACL document

Worked example — a Web Access Control (WAC) document.

In Solid, the policy itself is RDF sitting next to the resource it guards. This .acl says Alice owns the graph (read+write+control); Bob may only read it:

@prefix acl: <http://www.w3.org/ns/auth/acl#> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .

:owner a acl:Authorization ;
  acl:agent <https://alice.example/#me> ;
  acl:accessTo <./tenant-a> ;
  acl:mode acl:Read, acl:Write, acl:Control .

:reader a acl:Authorization ;
  acl:agent <https://bob.example/#me> ;
  acl:accessTo <./tenant-a> ;
  acl:mode acl:Read .

The decision is data, not code: to audit who can touch tenant-a you simply query the ACL graph. acl:Control is the dangerous one — it lets an agent rewrite the ACL itself, so grant it sparingly.

Theory

Going deeper — the confused-deputy trap

Named-graph ACLs only hold if the caller never chooses their own graph IRI. The classic failure is the confused deputy: your service runs with broad privileges and faithfully executes whatever GRAPH ?g { ... } the user supplied — so the user borrows the service's authority to read every tenant. The fix is structural, not a filter:

  • Bind ?tenantGraph from the authenticated session, server-side, before the query template is assembled. The user supplies data, never the graph IRI.
  • Prefer per-tenant credentials where the store enforces graph access itself (WAC/ACP, or store-native graph permissions), so a bug in your query layer can't escalate.
  • Deny by default. A request with no resolved tenant graph should return nothing, not everything.

WAC (Web Access Control, ACL-based) and ACP (Access Control Policy, the newer policy-language model) are the two Solid standards that encode exactly this so you don't reinvent it per app.

Visualization

Query Pattern match Bindings Result

Per-tenant data lives in its own named graph; a query may only GRAPH over graphs the caller is authorised for.

Operationally, the access decision sits in one place: the layer that binds ?tenantGraph from the authenticated session. If that binding is correct, the SPARQL engine itself does the enforcement — there's no clever query a caller can write that escapes the GRAPH block, because they never get to choose which graph IRI goes in.

Reflect

Think about your current multi-tenant system. If an attacker controlled the value that ends up in the GRAPH slot, what would they reach?

The honest test of a graph-isolation design is: could a malicious request name a graph it shouldn't? If the graph IRI is ever derived from request input — a header, a path segment, a JWT claim you didn't verify — the answer is yes, and the isolation is theatre.

  • Where does your tenant-graph IRI come from today — session or request?
  • What does a request with no resolved tenant graph return: nothing, or everything?

Reading in progress · 0 of 4 activities done