This specification defines an HTTP endpoint through which a client can evaluate read-only SPARQL queries over the RDF content of a Solid storage (a Pod), receiving results computed exclusively from the resources the requesting agent is authorized to read. The endpoint implements the SPARQL 1.1 Protocol query operation [[SPARQL11-PROTOCOL]]; requests use the Solid server's authentication layer, as for any other Solid resource request [[SOLID-PROTOCOL]]; and authorization is the storage's existing access control system ([[WAC]] or [[ACP]]), enforced by restricting the queried RDF dataset — one named graph per readable RDF resource, with an empty default graph — before query evaluation. The specification defines the mapping from Pod content to the queried dataset, the construction of the per-request authorized dataset, an opt-in union default graph mode, and a set of existence-non-disclosure invariants ensuring that a query can never reveal the existence or contents of a resource the requester cannot read.

This document specifies a read interface layered on Solid's existing authentication and authorization substrate. It does not define or require a particular authentication mechanism, does not define a new authorization model, and does not define a new wire protocol.

This draft was prepared with the assistance of AI systems operated on behalf of the editor (design: Claude Opus 4.8; Editor's Draft and final design resolutions: Claude Fable 5). The editor is responsible for all content. Contributions and issues are welcome on the GitHub repository.

Introduction

Motivation

The Solid Protocol [[SOLID-PROTOCOL]] gives agents fine-grained, resource-level access to RDF data in a storage, but its read interface is resource-at-a-time: a client that wants to answer a question spanning many resources must traverse and fetch each one (for example via client-side link-traversal query engines). Solid has no ratified server-side query surface. This is costly for clients, for servers, and for networks, and it makes whole classes of applications — search, aggregation, cross-resource joins over one's own data — needlessly hard to build.

SPARQL [[SPARQL11-QUERY]] is the W3C standard query language for RDF, with a stable, universally implemented HTTP protocol [[SPARQL11-PROTOCOL]]. This specification brings the two together: a per-storage SPARQL query endpoint whose observable behaviour is indistinguishable from evaluating the query over only the resources the requester could have fetched individually. A conforming endpoint adds query capability without adding any disclosure capability.

Relationship to the Solid Protocol and access control

This specification composes with, and does not modify, the Solid Protocol [[SOLID-PROTOCOL]], Web Access Control [[WAC]], and the Access Control Policy language [[ACP]]. Authentication on the query endpoint is delegated to the authentication layer the Solid server uses for other resource requests; this specification neither defines nor mandates that layer's mechanism. Authorization decisions are made by the storage's existing access control system; this specification consumes those decisions — it introduces no new access modes, no new policy language, and no new identity mechanism. The single governing principle is GET-equivalence: a triple is visible to a query if and only if the requester is authorized to read the resource that asserts it.

Non-goals

Terminology

This section defines the terms used in this specification. Terms defined by RDF 1.1 [[RDF11-CONCEPTS]], SPARQL 1.1 [[SPARQL11-QUERY]] [[SPARQL11-PROTOCOL]], and the Solid Protocol [[SOLID-PROTOCOL]] are used with their definitions from those specifications; the most load-bearing are restated here for convenience.

agent
A person, social entity, or software identified by a WebID [[WEBID]], per the Solid Protocol.
requester
The agent on whose behalf a query request is made: the authenticated agent established by the Solid server's authentication layer, or, for an unauthenticated request, the public (unauthenticated) agent.
storage
A space of URIs in which resources are controlled by a Solid server, as defined by the Solid Protocol (colloquially, a Pod).
resource
An HTTP resource in a storage, identified by an IRI without a fragment.
RDF source
A resource whose state is fully represented as an RDF graph (cf. LDP RDF Source [[LDP]]). Containers and RDF-bearing auxiliary resources (such as ACL resources) are RDF sources.
readable (by an agent)
A resource is readable by an agent if and only if the storage's access control system would authorize a GET request on that resource by that agent. For resources governed by [[WAC]] this is the acl:Read access mode resolved through the acl:accessTo/acl:default walk; for auxiliary resources it is whatever access the governing specification requires to read them (for example, reading a WAC ACL resource requires acl:Control); for [[ACP]] it is the Read access mode granted by the applicable policies.
GET-equivalence
The governing visibility principle of this specification: an RDF triple is visible to a query by a given requester if and only if the resource asserting that triple is readable by that requester.
resource graph
The RDF graph derived from an RDF source's representation by the mapping of (including the flattening rule of ).
inner named graph
A named graph occurring inside a single resource's representation, when that representation's concrete syntax serializes an RDF dataset rather than an RDF graph — for example a JSON-LD node object carrying both @id and @graph [[JSON-LD11]].
authorized dataset
The RDF dataset [[RDF11-CONCEPTS]] [[RDF11-DATASETS]] over which a given request's query is evaluated: an empty default graph plus one named graph per RDF source in the storage that is readable by the requester, as constructed in . Where the request supplies an explicit dataset description, it is resolved against the authorized dataset per .
query service
The SPARQL Protocol service defined by this specification: a SPARQL 1.1 Protocol query service [[SPARQL11-PROTOCOL]] scoped to a single storage and enforcing the authorization and non-disclosure requirements of this specification. Also referred to as the query endpoint.
union default graph mode
The opt-in, per-query mode of in which the default graph of the query's dataset is the merge of all named graphs of the authorized dataset.

Namespaces and minted terms

This specification mints terms in the namespace http://www.w3.org/ns/solid/sparql#, written below with the prefix solid-sparql:.

TermTypeDefinition
solid-sparql:endpoint Link relation (extension relation type per [[RFC8288]]) and RDF property Relates a storage (or a resource within it) to the query service for that storage. See .
solid-sparql:union-default-graph Reserved graph IRI When used as a default-graph IRI of a query's dataset description, requests union default graph mode for that query. See .
solid-sparql:UnionDefaultGraphOptIn Instance of sd:Feature [[SPARQL11-SERVICE-DESCRIPTION]] Advertises, in a service description, that the service implements the opt-in union default graph mode. See .

These terms are proposed for the Solid vocabulary namespace and are subject to Solid Community Group adoption. Until the namespace document is published, implementations should treat the IRIs as defined by this specification.

Other namespace prefixes used in this document:

PrefixNamespace IRI
sd:http://www.w3.org/ns/sparql-service-description#
acl:http://www.w3.org/ns/auth/acl#
foaf:http://xmlns.com/foaf/0.1/
dct:http://purl.org/dc/terms/
schema:https://schema.org/

This specification defines the following conformance classes:

server
A Solid SPARQL query server: a Solid storage server exposing a query service. A conforming server satisfies every normative requirement of this specification that is addressed to servers or to the query service.
client
A client of a query service. A conforming SPARQL 1.1 Protocol client [[SPARQL11-PROTOCOL]] can use a query service when it can meet the deployment's authentication requirements, if any. This specification does not require a conforming client to implement any particular authentication mechanism [[SOLID-PROTOCOL]]. Its requirements addressed to clients are the small number of keyword statements explicitly directed at clients.

The Query Endpoint

Location and scope

A server MAY expose a query service for a storage. A query service MUST be scoped to exactly one storage: its authorized dataset is derived exclusively from the resources of that storage. A server MUST NOT expose, through a single query service, graphs derived from resources of more than one storage.

The IRI of the query service is determined by the server. It MAY be any IRI; it need not be inside the storage's URI space.

A common convention is <storage root>sparql — for example https://alice.pod.example/sparql for the storage https://alice.pod.example/. Clients discover the endpoint via and must not assume this convention.

Discovery

A server exposing a query service MUST advertise it by including, in responses to GET and HEAD requests on the storage's root container, an HTTP Link header [[RFC8288]] whose target is the query service IRI and whose relation type is http://www.w3.org/ns/solid/sparql#endpoint.

HTTP/1.1 200 OK
Link: <https://alice.pod.example/sparql>; rel="http://www.w3.org/ns/solid/sparql#endpoint"

A server SHOULD also assert the endpoint in the storage description resource [[SOLID-PROTOCOL]], using the relation IRI as an RDF property:

@prefix solid-sparql: <http://www.w3.org/ns/solid/sparql#> .

<https://alice.pod.example/> solid-sparql:endpoint <https://alice.pod.example/sparql> .

A server MAY include the Link header in responses for other resources of the storage.

The advertisement of the query service, like the service description (), MUST NOT vary in a way that discloses the existence or contents of resources the requester cannot read.

Protocol operations

A query service MUST implement the query operation of the SPARQL 1.1 Protocol [[SPARQL11-PROTOCOL]], including all three of its bindings:

A query service MUST support the SPARQL 1.1 Query language [[SPARQL11-QUERY]] for the query operand, including the SELECT, CONSTRUCT, ASK, and DESCRIBE query forms.

A query service MUST support the default-graph-uri and named-graph-uri protocol parameters. As required by [[SPARQL11-PROTOCOL]], a dataset description supplied via protocol parameters takes precedence over one supplied in the query string (FROM/FROM NAMED). All dataset descriptions, however supplied, are resolved through the authorization rules of ; a query service MUST NOT reject a request because its dataset description references a graph the requester cannot read ().

A query service MUST NOT execute SPARQL Update operations (see ).

SPARQL 1.2 forward compatibility

SPARQL 1.2 [[SPARQL12-PROTOCOL]] [[SPARQL12-QUERY]] is, at the protocol layer, a backward-compatible superset of SPARQL 1.1: the same three bindings and the same core media types, adding an optional version media-type parameter and RDF-1.2-aware payloads [[RDF12-CONCEPTS]].

A query service MAY additionally implement the SPARQL 1.2 Protocol query operation, including accepting the version media-type parameter and producing RDF 1.2 constructs (such as triple terms) in results. A query service MUST NOT require any SPARQL 1.2 feature: every capability required for conformance to this specification is expressible in SPARQL 1.1.

Caching

Query results are a function of the requester. A query service MUST prevent a response computed for one requester from being served to a different requester by a shared cache.

In practice this means emitting Cache-Control: private (or no-store) on query responses, together with an appropriate Vary header (e.g. Vary: Authorization, Accept) [[RFC9110]].

Pod Content to RDF Dataset Mapping

A SPARQL query is evaluated against an RDF dataset: one default graph and zero or more named graphs [[SPARQL11-QUERY]] [[RDF11-CONCEPTS]]. This section defines how a storage's content determines that dataset; defines how it is restricted per request.

Resource graphs: one named graph per RDF source

Each RDF source in the storage contributes exactly one named graph to the queried dataset:

Containers are RDF sources; a container's resource graph comprises its representation as served on GET, including server-managed containment triples. Auxiliary resources that are RDF sources (for example WAC ACL resources) likewise contribute resource graphs, subject to their own read-access rules ().

Resources that are not RDF sources (binary/non-RDF resources) MUST NOT contribute a graph to the dataset.

Because the graph name is the resource IRI, the set of named graph names is exactly the set of (RDF source) resource IRIs — which is why enumerating graph names is equivalent to listing resources, and why every disclosure rule in this specification is enforced at the graph-name layer.

The default graph is empty

The standing default graph of the queried dataset MUST be empty. A query that does not supply a dataset description (no FROM/FROM NAMED, no default-graph-uri/named-graph-uri) and does not invoke union default graph mode is therefore evaluated against a dataset whose default graph contains no triples: a bare basic graph pattern such as { ?s ?p ?o } yields zero solutions.

All resource content is exposed through named graphs. Cross-resource querying is explicit: via GRAPH patterns (), via a per-query dataset description (), or via the opt-in union default graph mode ().

Concrete syntaxes and the flattening rule

A Turtle document [[TURTLE]] serializes a single RDF graph; for an RDF source represented in Turtle (or any other graph-denoting syntax, such as N-Triples or RDFa), the resource graph is simply that graph.

A JSON-LD document, by contrast, serializes an RDF dataset [[JSON-LD11]] [[JSON-LD11-API]]: a node object carrying both @id and @graph denotes an inner named graph, so a single resource's representation may deserialize to default-graph triples plus named graphs. This breaks the one-resource-one-graph bijection of unless resolved. This specification resolves it by flattening:

When an RDF source's representation deserializes to an RDF dataset, the resource graph MUST be the RDF graph comprising every triple asserted in any graph of that dataset — the default graph and all inner named graphs — with blank node identity preserved as in the deserialized dataset. The inner graph names are discarded as graph names; triples whose subject or object is an inner graph name are retained as ordinary triples.

A server MUST NOT expose an inner named graph name as a named graph of the queried dataset, and MUST NOT contribute triples from one resource's representation to any graph other than that resource's own resource graph.

Promoting inner graph names to first-class SPARQL named graphs would let the content of one resource choose arbitrary graph names — including names equal to other resources' IRIs. Two resources declaring the same inner @id would commingle under one graph name that the access control system decides as a unit, and a hostile document could inject triples into the graph name of a resource its author cannot write. It would also break the invariant that every named graph corresponds to exactly one resource and hence exactly one access control verdict (). Flattening is collision-free and keeps that invariant airtight.

Flattening affects only what a SPARQL query can see. The stored representation is untouched: a GET on the resource returns the original document, inner graphs and all. What is lost to queries is only the association between an inner graph name and its triples — a structure that is rare in practice in Pod data. A compatible, opt-in fidelity mode that preserves this association as data is sketched in .

Querying across graphs

This section is a consequence of SPARQL 1.1 semantics [[SPARQL11-QUERY]] applied to the dataset of this specification; it is stated normatively because implementations MUST preserve these outcomes exactly (they are load-bearing for ).

SPARQL has no FROM * or FROM ?g: FROM and FROM NAMED take fixed IRIs. The "query over all graphs" capability is the GRAPH ?g pattern, whose graph variable, on a query service, ranges over exactly the named graphs of the authorized dataset:

Blank nodes are scoped to a single resource's representation: distinct resources never share blank nodes, so cross-graph joins can only join on IRIs and literals. Worked examples, including the intra- versus cross-graph contrast on a concrete dataset, are given in .

Roadmap: a named-graph fidelity mode (reification)

This section is informative. It names, but does not specify, a future compatible extension.

A future version of this specification is expected to define an opt-in fidelity mode that preserves inner-named-graph structure as data inside the resource graph, using RDF 1.2 triple terms (RDF-star) [[RDF12-CONCEPTS]]: each triple of an inner named graph would additionally be described by an annotation associating it with the inner graph name, along the lines of:

GRAPH <https://alice.pod.example/notes/meeting> {
  <#decision1> schema:text "Adopt the proposal" .
  << <#decision1> schema:text "Adopt the proposal" >>
      solid-sparql:inGraph <#minutes> .   # predicate name illustrative
}

This design keeps every property that makes flattening safe — no additional SPARQL named graphs, no cross-resource name collisions, one access-control verdict per resource — while making the inner-graph association queryable by clients that need it. It was chosen over promoting inner graphs to namespaced first-class graphs because it needs no name-rewriting or provenance table. It is deferred from this version because RDF-star querying is a SPARQL 1.2 feature, and this specification deliberately requires only SPARQL 1.1 (): pulling reification into the core would smuggle a normative RDF 1.2 dependency into a specification whose protocol layer refuses one. When defined, the fidelity mode will be an additive, per-query or per-service opt-in; documents already stored will need no migration, since flattening never alters stored representations. Any such future mode must preserve the prohibition of raw inner-graph-name promotion.

Authentication

A request to the query service is an ordinary HTTP request to the Solid server. The server MUST process authentication for a query-service request through the Solid Protocol authentication layer it uses for ordinary resource requests [[SOLID-PROTOCOL]]. This specification does not define or mandate any particular authentication mechanism.

A query service MAY refuse service categorically to classes of requester (for example, requiring authentication for all queries, or disabling the endpoint entirely), using an ordinary 401/403 response. Such refusal MUST be uniform: it MUST NOT depend on the query text, on the dataset description, or on the existence, contents, or access rules of any resource — otherwise the refusal itself becomes a disclosure oracle ().

Authorization: the Authorized Dataset

Read visibility (GET-equivalence)

Query visibility is governed by GET-equivalence: a named graph is part of a requester's authorized dataset if and only if the corresponding resource is readable by that requester — that is, if and only if the storage's access control system would authorize a GET on that resource for that requester.

Only readability governs visibility. No other access mode (write, append, control) grants or denies query visibility.

Constructing the authorized dataset

For each query request, the query service MUST construct the authorized dataset:

Restriction MUST be applied at the graph-name layer, before query evaluation: the query MUST be evaluated over exactly the authorized dataset (as modified by an explicit dataset description per and ). In particular:

The authorization decisions used to construct the dataset MUST reflect the storage's access control state as evaluated for this request; a server MAY use caches or indexes to compute the readable set, provided the observable result equals a fresh per-request evaluation.

FROM, FROM NAMED, and protocol dataset parameters

A request MAY describe a query dataset via FROM/FROM NAMED clauses in the query, or via default-graph-uri/named-graph-uri protocol parameters (protocol parameters taking precedence, per [[SPARQL11-PROTOCOL]]). Every IRI so referenced is resolved against the authorized dataset:

A query service MUST NOT respond with an error, a distinct status code, a distinct error message, or any other distinguishable signal because a dataset description references an unreadable or non-existent graph. The response MUST be indistinguishable from the response to the same query referencing a graph that does not exist ().

Per SPARQL 1.1 dataset semantics, a query whose request supplies a dataset description is evaluated over exactly the described dataset (as resolved above): its default graph is the merge of the resolved default-graph IRIs' graphs, and its named graphs are the resolved named-graph IRIs' graphs. A query with no dataset description (and no union default graph mode request) is evaluated over the standing authorized dataset.

GRAPH <iri> with a fixed IRI that is not a named graph of the query's dataset simply yields zero solutions in SPARQL; no special server behaviour is needed or permitted there.

Union default graph mode (opt-in)

A query service MAY implement union default graph mode. In this mode, the default graph of a single query's dataset is the RDF merge of all named graphs of the requester's authorized dataset, so bare basic graph patterns join across everything the requester can read.

Because blank nodes are scoped per resource (), the named graphs of the authorized dataset share no blank nodes, and their RDF merge coincides with their union.

Requesting the mode

Union default graph mode MUST be requested explicitly, per query, by the requester. This specification reserves the IRI http://www.w3.org/ns/solid/sparql#union-default-graph (solid-sparql:union-default-graph) for that purpose. A service implementing the mode MUST honour the reserved IRI in both of the following positions:

When the reserved IRI appears among a query's default-graph IRIs, the default graph of that query's dataset is the merge of (a) all named graphs of the authorized dataset and (b) the resolved graphs of any other default-graph IRIs supplied alongside it. Since every readable graph in (b) is already a member of (a), the result equals the union in (a).

The reserved IRI affects only the default graph:

Never a standing default

A query service MUST NOT apply union default graph semantics to a query that has not explicitly requested it: the standing default graph remains empty (). Consequently a service MUST NOT describe itself with sd:UnionDefaultGraph (), and MUST NOT offer a configuration in which the union is the default dataset presented to queries that did not opt in.

Behaviour on services not implementing the mode

On a query service that does not implement union default graph mode, the reserved IRI is simply an IRI that names no graph in the storage, and the rules of apply: it resolves to nothing, without error. A client that depends on the mode SHOULD first confirm the service advertises solid-sparql:UnionDefaultGraphOptIn in its service description (), since a request sent to a non-implementing service will succeed with an empty default graph rather than fail.

The reserved IRI is in the w3.org namespace and therefore cannot collide with any resource IRI inside a storage.

Security analysis

The union's content is, by construction, exactly the union of graphs already exposed to the same requester via GRAPH ?g; the mode therefore adds no new disclosure oracle. Authorization is still enforced at the graph-name layer before evaluation — the union is assembled from the authorized dataset, never from the raw store — and the reserved IRI behaves like any other dataset-description target: absent if not requested, absent if unsupported, with the same absence semantics as an unreadable graph, so its handling can never leak. The mode is per-query and opt-in precisely so that the empty standing default of — which makes accidental whole-store queries impossible — is never weakened by configuration.

Existence Non-Disclosure

A query service MUST NOT disclose, by any observable behaviour, the existence, non-existence, metadata, or contents of any resource outside the requester's authorized dataset. Unreadable resources and non-existent resources MUST be observationally equivalent through the query interface.

Invariants

Specifically, for every query request:

  1. Bindings. No solution, no triple of a CONSTRUCT or DESCRIBE result, and no ASK answer may be derived from a graph outside the authorized dataset.
  2. Enumeration. GRAPH ?g MUST NOT produce a binding for, and no result may otherwise reveal, the name of any graph outside the authorized dataset.
  3. Aggregates. Aggregate values (COUNT, SUM, GROUP_CONCAT, …) MUST be computed over solutions from the authorized dataset only; a count may never differ because of unreadable content.
  4. Status and errors. The response status code, headers, error document, and any error message MUST NOT differ between (a) a query referencing an unreadable graph and (b) the same query referencing a non-existent graph. There is no 403-versus-404 distinction inside the dataset: dataset references that cannot be honoured are silently absent (), never errors.
  5. Resource limits. Limit behaviour () MUST be a function of the query and the authorized dataset only; evaluation over the authorized dataset guarantees this structurally, since unreadable graphs never participate.
  6. Timing. A query service SHOULD ensure that observable differences in processing time do not disclose the existence or contents of resources outside the authorized dataset.
  7. Ancillary surfaces. The service description () and the discovery advertisement () MUST NOT reveal graphs the requester cannot read.

Structural enforcement

Invariants 1–5 are satisfied structurally — not by output scrubbing — when the implementation restricts the dataset at the graph-name layer before evaluation, as requires: an engine that never touches unreadable graphs cannot leak them through bindings, counts, errors, limits, or (to first order) timing. This is why post-filtering is non-conforming even when its final output is identical. The residual timing channel addressed by invariant 6 concerns shared infrastructure (for example, index structures whose access cost varies with whole-store size); see .

Result Formats and Content Negotiation

Result serialization follows the SPARQL 1.1 family of result formats, negotiated via HTTP content negotiation [[RFC9110]].

Service Description

On a GET request to the query service IRI with no query parameters, the service SHOULD return a SPARQL 1.1 Service Description [[SPARQL11-SERVICE-DESCRIPTION]] describing the endpoint's capabilities: supported language(s), result formats, features, and (optionally) resource limits.

Because graph names are resource IRIs (), a graph inventory is a resource listing. Accordingly:

@prefix sd: <http://www.w3.org/ns/sparql-service-description#> .
@prefix solid-sparql: <http://www.w3.org/ns/solid/sparql#> .

<https://alice.pod.example/sparql> a sd:Service ;
    sd:endpoint <https://alice.pod.example/sparql> ;
    sd:supportedLanguage sd:SPARQL11Query ;
    sd:resultFormat
      <http://www.w3.org/ns/formats/SPARQL_Results_JSON> ,
      <http://www.w3.org/ns/formats/SPARQL_Results_XML> ,
      <http://www.w3.org/ns/formats/SPARQL_Results_CSV> ,
      <http://www.w3.org/ns/formats/SPARQL_Results_TSV> ,
      <http://www.w3.org/ns/formats/Turtle> ,
      <http://www.w3.org/ns/formats/JSON-LD> ;
    sd:feature solid-sparql:UnionDefaultGraphOptIn .
# No graph inventory; no sd:UnionDefaultGraph.

Security and Privacy Considerations

Query cost and denial of service

SPARQL evaluation cost is unbounded in general: property paths, large joins, and OPTIONAL-heavy queries can consume arbitrary CPU, memory, and I/O. A query service MAY impose resource limits — including evaluation timeouts, complexity or cardinality bounds, result-size caps, and per-requester rate limits. When a limit causes a query to be aborted, the service SHOULD respond with a 5xx status (for example, 503 Service Unavailable, with Retry-After where appropriate) or with a documented error response; it MUST honour the non-disclosure invariants in doing so (, invariant 5).

The read-oracle class

The central threat this specification defends against is the read oracle: any channel through which a query's observable outcome varies with data the requester cannot read. The class includes result bindings, ASK answers, aggregate counts, graph enumeration, dataset-clause errors, status-code differences (403 versus 404), resource-limit behaviour, and evaluation timing. The architecture closes the class structurally: authorization is applied to graph names before evaluation, so unreadable data never enters the computation whose outcome the attacker observes ().

Residual channels are those shared between requests at the infrastructure layer — cache occupancy, index sizes, storage latency correlated with whole-store contents. These are qualitatively the same channels present in any multi-tenant HTTP server and are mitigated with the usual tools (constant-time-ish paths where cheap, noise, rate limiting); invariant 6 of makes their mitigation a SHOULD.

Federated query (SERVICE)

The SPARQL 1.1 SERVICE keyword instructs the server to contact remote endpoints, creating a server-side request-forgery (SSRF) and confused-deputy surface. A query service MAY reject queries containing SERVICE, and SHOULD reject them by default. A service that does evaluate SERVICE patterns MUST NOT forward the requester's credentials (or any server credential) to the remote endpoint, and SHOULD apply standard SSRF protections (deny-by-default network egress, address-family and DNS validation) to the outbound request.

Privacy

SPARQL Update (Out of Scope)

This specification defines a read-only interface. A query service MUST NOT execute SPARQL Update operations [[SPARQL11-PROTOCOL]]. A request that is recognizably an update request MUST be refused without side effects: a service SHOULD respond 415 Unsupported Media Type to a direct POST of application/sparql-update, and 400 Bad Request to a form request carrying an update parameter.

Future work: an access-controlled update mapping

A future specification may define an update operation on the same substrate. The expected mode mapping, recorded here so the design space is visible, is per target graph: WHERE patterns require Read on every graph they consult; INSERT requires Append on each target graph; DELETE requires Write on each target graph. The principal new hazard is the DELETE/WHERE read oracle — an update whose effect (which triples got deleted, or whether anything changed) reveals the outcome of a WHERE evaluation the requester could not have performed as a query — which must be closed with the same before-evaluation dataset restriction used here, applied to the WHERE clause. Update is deferred until that analysis is complete.

Conformance Test Scenarios

The scenarios below restate the normative requirements of this specification in testable form; the referenced sections are authoritative. Except where stated, assume the example storage of : requester R can read /profile/card and /contacts/bob but not /private/journal, and /no-such-resource does not exist.

  1. Protocol bindings. The same query submitted via GET, URL-encoded POST, and direct POST returns the same results. ()
  2. Empty standing default graph. SELECT * WHERE { ?s ?p ?o } with no dataset description returns zero solutions, whatever the storage contains. ()
  3. Graph-name mapping. Every binding of SELECT DISTINCT ?g WHERE { GRAPH ?g { ?s ?p ?o } } is the IRI of an RDF source readable by R. (, )
  4. No enumeration of unreadable graphs. The bindings of scenario 3 exclude </private/journal>. ()
  5. Unreadable ≡ non-existent (status). SELECT * FROM </private/journal> WHERE { ?s ?p ?o } and SELECT * FROM </no-such-resource> WHERE { ?s ?p ?o } both return 200 with zero solutions and indistinguishable response structure — no error, no 403/404. (, )
  6. Unreadable ≡ non-existent (ASK). ASK { GRAPH </private/journal> { ?s ?p ?o } } and ASK { GRAPH </no-such-resource> { ?s ?p ?o } } both answer false. ()
  7. Aggregate non-disclosure. SELECT (COUNT(*) AS ?n) WHERE { GRAPH ?g { ?s ?p ?o } } yields the same ?n whether or not /private/journal exists. ()
  8. GET-equivalence for auxiliaries. With WAC governing the storage: an agent holding acl:Read but not acl:Control on /x sees graph </x> but not graph </x.acl>. ()
  9. Flattening. For the JSON-LD resource of , all triples (default-graph and inner-graph) are matched inside GRAPH </notes/meeting>, and ASK { GRAPH </notes/meeting#minutes> { ?s ?p ?o } } answers false. ()
  10. No raw preservation. No inner named graph name from any resource representation ever appears as a ?g binding or is matchable as a GRAPH name. ()
  11. Intra- versus cross-graph joins. On the example storage, the intra-graph query of returns zero solutions and the cross-graph query returns exactly one. ()
  12. Union mode is opt-in only. On a service advertising solid-sparql:UnionDefaultGraphOptIn: scenario 2 still returns zero solutions, while the same bare-BGP query with FROM <http://www.w3.org/ns/solid/sparql#union-default-graph> (or the equivalent default-graph-uri parameter) returns solutions from all readable graphs. ()
  13. Union IRI is inert elsewhere. FROM NAMED <http://www.w3.org/ns/solid/sparql#union-default-graph> adds no named graph, and ?g never binds to the reserved IRI. ()
  14. Service description hygiene. The service description contains no named-graph inventory (or a per-requester-filtered one), and never sd:UnionDefaultGraph. ()
  15. Update refusal. A direct POST of application/sparql-update is refused (e.g. 415) with no side effects; a form POST with an update parameter is refused (e.g. 400). ()
  16. Cache privacy. Query responses carry cache directives preventing shared caches from serving them to a different requester. ()

Worked Examples

Example storage

The storage https://alice.pod.example/ contains three RDF sources. The requester R is authorized to read /profile/card and /contacts/bob, but not /private/journal.

# Graph <https://alice.pod.example/profile/card>   (readable by R)
GRAPH <https://alice.pod.example/profile/card> {
  <https://alice.pod.example/profile/card#me>
      foaf:name  "Alice" ;
      foaf:knows <https://bob.pod.example/profile/card#me> .
}

# Graph <https://alice.pod.example/contacts/bob>   (readable by R)
GRAPH <https://alice.pod.example/contacts/bob> {
  <https://bob.pod.example/profile/card#me> foaf:name "Bob" .
}

# https://alice.pod.example/private/journal exists but is NOT readable by R:
# it contributes NO graph to R's authorized dataset.

The empty default graph, and enumerating readable graphs

SELECT ?s ?p ?o WHERE { ?s ?p ?o }
# → no solutions: the standing default graph is empty.
SELECT DISTINCT ?g WHERE { GRAPH ?g { ?s ?p ?o } }
# → ?g = <https://alice.pod.example/profile/card>
#   ?g = <https://alice.pod.example/contacts/bob>
# /private/journal is absent — indistinguishable from not existing.

A readable resource whose graph is empty (for example, an empty Turtle document) produces no binding here — ordinary SPARQL semantics, since GRAPH ?g requires a matching triple. That is not a disclosure issue; it is the same behaviour for every requester who can read the resource.

Intra-graph versus cross-graph joins

Alice's foaf:knows triple lives in /profile/card, but Bob's name lives in /contacts/bob. A join written inside a single GRAPH block requires both triples in the same graph, and therefore fails:

PREFIX foaf: <http://xmlns.com/foaf/0.1/>
SELECT ?f ?n WHERE {
  GRAPH ?g { ?p foaf:knows ?f . ?f foaf:name ?n }
}
# → no solutions: no single graph contains both triples.

Separate GRAPH blocks with distinct graph variables join across graphs on the shared variable ?f:

PREFIX foaf: <http://xmlns.com/foaf/0.1/>
SELECT ?f ?n WHERE {
  GRAPH ?g1 { ?p foaf:knows ?f }
  GRAPH ?g2 { ?f foaf:name ?n }
}
# → ?f = <https://bob.pod.example/profile/card#me>, ?n = "Bob"
#   (?g1 = <…/profile/card>, ?g2 = <…/contacts/bob>)

Alternatively, merge chosen graphs into the query's default graph with FROM, so bare patterns join across them:

PREFIX foaf: <http://xmlns.com/foaf/0.1/>
SELECT ?f ?n
FROM <https://alice.pod.example/profile/card>
FROM <https://alice.pod.example/contacts/bob>
WHERE { ?p foaf:knows ?f . ?f foaf:name ?n }
# → ?f = <https://bob.pod.example/profile/card#me>, ?n = "Bob"

An unauthorized graph is absent

SELECT ?s ?p ?o
FROM <https://alice.pod.example/private/journal>
WHERE { ?s ?p ?o }
# → 200 OK, zero solutions.
# Identical (status, shape, error-free) to:
#   SELECT ?s ?p ?o FROM <https://alice.pod.example/no-such-resource> WHERE { ?s ?p ?o }
ASK { GRAPH <https://alice.pod.example/private/journal> { ?s ?p ?o } }
# → false — exactly as for a graph that does not exist.

A JSON-LD resource with inner named graphs, under the flattening rule

The resource https://alice.pod.example/notes/meeting is stored as JSON-LD and uses an inner named graph:

{
  "@context": {
    "@vocab": "https://schema.org/",
    "dct": "http://purl.org/dc/terms/"
  },
  "@graph": [
    {
      "@id": "https://alice.pod.example/notes/meeting",
      "dct:title": "Meeting notes"
    },
    {
      "@id": "https://alice.pod.example/notes/meeting#minutes",
      "@graph": [
        {
          "@id": "https://alice.pod.example/notes/meeting#decision1",
          "text": "Adopt the proposal"
        }
      ]
    }
  ]
}

The representation deserializes to a dataset: one default-graph triple plus an inner named graph <…#minutes>. Flattening () collapses it into the single resource graph:

GRAPH <https://alice.pod.example/notes/meeting> {
  <https://alice.pod.example/notes/meeting>
      <http://purl.org/dc/terms/title> "Meeting notes" .
  <https://alice.pod.example/notes/meeting#decision1>
      <https://schema.org/text> "Adopt the proposal" .
}
ASK { GRAPH <https://alice.pod.example/notes/meeting#minutes> { ?s ?p ?o } }
# → false: inner graph names are never named graphs of the dataset.

SELECT ?text WHERE {
  GRAPH <https://alice.pod.example/notes/meeting> {
    ?d <https://schema.org/text> ?text
  }
}
# → ?text = "Adopt the proposal" — the inner graph's triples are in the resource graph.

Contrast the forbidden raw-preservation behaviour (): had <…#minutes> become a first-class graph, a hostile document at /evil declaring an inner @id of https://alice.pod.example/profile/card could have injected triples under the profile's graph name.

Union default graph mode (opt-in)

On a service advertising solid-sparql:UnionDefaultGraphOptIn:

PREFIX foaf: <http://xmlns.com/foaf/0.1/>
SELECT ?f ?n
FROM <http://www.w3.org/ns/solid/sparql#union-default-graph>
WHERE { ?p foaf:knows ?f . ?f foaf:name ?n }
# → ?f = <https://bob.pod.example/profile/card#me>, ?n = "Bob"
# The default graph for THIS query is the merge of all graphs R can read;
# a bare BGP now joins across resources.
GET /sparql?query=SELECT%20%3Ff%20%3Fn%20WHERE%20%7B%20%3Fp%20foaf%3Aknows%20%3Ff%20.%20%3Ff%20foaf%3Aname%20%3Fn%20%7D&default-graph-uri=http%3A%2F%2Fwww.w3.org%2Fns%2Fsolid%2Fsparql%23union-default-graph HTTP/1.1
Host: alice.pod.example
Accept: application/sparql-results+json

The same query without the reserved IRI still returns zero solutions — the standing default graph remains empty, always.

Acknowledgements

This specification builds on the work of the W3C Solid Community Group and on the SPARQL and RDF specifications of the W3C. The dataset-restriction-before-evaluation architecture and its existence-non-disclosure invariants were developed and validated in the solid-server-rs implementation.