Skip to content

Commit 591db41

Browse files
namedgraphclaude
andcommitted
Exempt proxy requests from local ACL checks in AuthorizationFilter
Document-centric ACL (acl:accessTo <document>) is semantically wrong for the proxy, which is a global transport function. Requiring acl:Write on a local document to forward a DELETE to a remote target would be a security anti-pattern — the target endpoint enforces its own access control, and SSRF protection via URLValidator is the appropriate security layer. Extends the existing mapped-URI bypass to cover all methods and all URIs when ?uri= is present. Adds a regression test that verifies an agent with acl:Append only on /sparql (not on the root URL) can POST via proxy. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 4085431 commit 591db41

File tree

2 files changed

+49
-3
lines changed

2 files changed

+49
-3
lines changed
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
initialize_dataset "$END_USER_BASE_URL" "$TMP_END_USER_DATASET" "$END_USER_ENDPOINT_URL"
5+
initialize_dataset "$ADMIN_BASE_URL" "$TMP_ADMIN_DATASET" "$ADMIN_ENDPOINT_URL"
6+
purge_cache "$END_USER_VARNISH_SERVICE"
7+
purge_cache "$ADMIN_VARNISH_SERVICE"
8+
purge_cache "$FRONTEND_VARNISH_SERVICE"
9+
10+
# grant the agent acl:Append on the SPARQL endpoint only (not on the root URL)
11+
12+
create-authorization.sh \
13+
-f "$OWNER_CERT_FILE" \
14+
-p "$OWNER_CERT_PWD" \
15+
-b "$ADMIN_BASE_URL" \
16+
--label "Append authorization for SPARQL endpoint" \
17+
--agent "$AGENT_URI" \
18+
--to "${END_USER_BASE_URL}sparql" \
19+
--append
20+
21+
purge_cache "$END_USER_VARNISH_SERVICE"
22+
purge_cache "$ADMIN_VARNISH_SERVICE"
23+
purge_cache "$FRONTEND_VARNISH_SERVICE"
24+
25+
# execute SPARQL query using LDH as a proxy
26+
# agent has acl:Append on /sparql but NOT on the root URL —
27+
# currently returns 403 because AuthorizationFilter checks ACL on the root proxy URL
28+
29+
http_code=$(curl -k -s -o /dev/null -w "%{http_code}" \
30+
-X POST \
31+
-E "$AGENT_CERT_FILE":"$AGENT_CERT_PWD" \
32+
-H 'Content-Type: application/sparql-query' \
33+
-H 'Accept: application/sparql-results+xml' \
34+
--url-query "uri=${END_USER_BASE_URL}sparql" \
35+
--data 'SELECT (COUNT(*) AS ?count) WHERE { ?s ?p ?o }' \
36+
"$END_USER_BASE_URL")
37+
38+
if [ "$http_code" -ne 200 ]; then
39+
echo "Expected HTTP 200, got: $http_code"
40+
exit 1
41+
fi

src/main/java/com/atomgraph/linkeddatahub/server/filter/request/AuthorizationFilter.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,16 @@ public void filter(ContainerRequestContext request) throws IOException
106106
if (request == null) throw new IllegalArgumentException("ContainerRequestContext cannot be null");
107107
if (log.isDebugEnabled()) log.debug("Authorizing request URI: {}", request.getUriInfo().getRequestUri());
108108

109-
// allow proxied URIs that are mapped to local files
110-
if (request.getMethod().equals(HttpMethod.GET) && request.getUriInfo().getQueryParameters().containsKey(AC.uri.getLocalName()))
109+
// allow proxied URIs - ACL is enforced by the target endpoint
110+
// SSRF protection is handled separately in ProxiedGraph via URLValidator
111+
// LDH's document-centric ACL (acl:accessTo <document>) is not meaningful for the proxy,
112+
// which is a global transport function, not a document. Requiring acl:Write on a local
113+
// document just to forward a DELETE to a remote target would be a security anti-pattern.
114+
if (request.getUriInfo().getQueryParameters().containsKey(AC.uri.getLocalName()))
111115
{
112116
String proxiedURI = request.getUriInfo().getQueryParameters().getFirst(AC.uri.getLocalName());
113-
if (getSystem().getDataManager().isMapped(proxiedURI)) return;
117+
if (getSystem().getDataManager().isMapped(proxiedURI)) return; // mapped local file
118+
return; // external URI — skip ACL, target enforces its own access control
114119
}
115120

116121
Resource accessMode = ACCESS_MODES.get(request.getMethod());

0 commit comments

Comments
 (0)