Environment
- Apache AGE 1.7.0 (
apache/age:release_PG18_1.7.0) — crashes
- Apache AGE 1.8.0 (current master,
apache/age:dev_snapshot_PG18) — still crashes
- PostgreSQL 18.1, official
apache/age image (age in shared_preload_libraries)
Summary
When a non-superuser role that is subject to FORCE ROW LEVEL SECURITY with a policy on an
edge label table runs MATCH (n) DETACH DELETE n where n has at least one connected edge, the
backend crashes with SIGSEGV (signal 11) ("server closed the connection unexpectedly"). The
server restarts and recovers, but the operation cannot complete.
RLS enforcement for Cypher DELETE was added at the executor level in #2309; this appears to be a gap
in the DETACH DELETE implicit-edge-cleanup path when an edge-label RLS policy is in force.
Minimal reproduction
-- === as a SUPERUSER ===
LOAD 'age';
SET search_path = ag_catalog, "$user", public;
SELECT create_graph('g');
SELECT create_vlabel('g', 'V');
SELECT create_elabel('g', 'E');
CREATE ROLE rls_user LOGIN PASSWORD 'pw';
GRANT USAGE ON SCHEMA ag_catalog, g TO rls_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON g."V", g."E", g."_ag_label_vertex", g."_ag_label_edge" TO rls_user;
GRANT USAGE ON ALL SEQUENCES IN SCHEMA g TO rls_user;
ALTER ROLE rls_user SET search_path = "$user", public, ag_catalog;
-- an ordinary agtype-keyed policy, fixed to tid='x' for the repro
ALTER TABLE g."V" ENABLE ROW LEVEL SECURITY; ALTER TABLE g."V" FORCE ROW LEVEL SECURITY;
ALTER TABLE g."E" ENABLE ROW LEVEL SECURITY; ALTER TABLE g."E" FORCE ROW LEVEL SECURITY;
CREATE POLICY p ON g."V" FOR ALL TO rls_user
USING (ag_catalog.agtype_access_operator(properties, '"tid"'::agtype) = '"x"'::agtype)
WITH CHECK (ag_catalog.agtype_access_operator(properties, '"tid"'::agtype) = '"x"'::agtype);
CREATE POLICY p ON g."E" FOR ALL TO rls_user
USING (ag_catalog.agtype_access_operator(properties, '"tid"'::agtype) = '"x"'::agtype)
WITH CHECK (ag_catalog.agtype_access_operator(properties, '"tid"'::agtype) = '"x"'::agtype);
-- === reconnect as rls_user ===
-- \c "dbname=... user=rls_user password=pw"
SET search_path = "$user", public, ag_catalog;
SELECT * FROM cypher('g', $$ CREATE (:V {tid:'x'})-[:E {tid:'x'}]->(:V {tid:'x'}) $$) AS (v agtype);
SELECT * FROM cypher('g', $$ MATCH (n) DETACH DELETE n $$) AS (v agtype); -- <-- SIGSEGV here
Observed
server closed the connection unexpectedly
This probably means the server terminated abnormally before or while processing the request.
connection to server was lost
Server log: LOG: client backend (PID …) was terminated by signal 11: Segmentation fault.
Expected
DETACH DELETE deletes the vertex and its connected edges that are visible to the role under RLS,
without crashing (as it does for a superuser / a role that bypasses RLS).
Narrowing (what works vs. crashes, same non-superuser role, same FORCE RLS)
- ✅ edge
CREATE (WITH CHECK enforced correctly)
- ✅ edge
SET / property UPDATE
- ✅ edge-only delete:
MATCH ()-[r:E]->() DELETE r
- ✅ node-only
DETACH DELETE (vertex with no connected edge)
- ✅ two-step:
MATCH ()-[r]->() DELETE r then MATCH (n) DELETE n
- ❌
DETACH DELETE of a vertex WITH a connected edge → SIGSEGV (the only failing path)
- ✅ the same
DETACH DELETE as a superuser (RLS bypass) — so it is specific to RLS-bound roles.
Disabling the RLS policy on the edge label (g."E") alone avoids the crash (the vertex-label
policy is not the trigger), which points at the edge-policy evaluation during DETACH's implicit edge
deletion.
Impact
Any multi-tenant AGE deployment that isolates tenants via RLS on the graph label tables cannot run
DETACH DELETE (e.g. a projection rebuild) under the tenant-bound runtime role.
Environment
apache/age:release_PG18_1.7.0) — crashesapache/age:dev_snapshot_PG18) — still crashesapache/ageimage (age inshared_preload_libraries)Summary
When a non-superuser role that is subject to FORCE ROW LEVEL SECURITY with a policy on an
edge label table runs
MATCH (n) DETACH DELETE nwherenhas at least one connected edge, thebackend crashes with SIGSEGV (signal 11) ("server closed the connection unexpectedly"). The
server restarts and recovers, but the operation cannot complete.
RLS enforcement for Cypher DELETE was added at the executor level in #2309; this appears to be a gap
in the
DETACH DELETEimplicit-edge-cleanup path when an edge-label RLS policy is in force.Minimal reproduction
Observed
Server log:
LOG: client backend (PID …) was terminated by signal 11: Segmentation fault.Expected
DETACH DELETEdeletes the vertex and its connected edges that are visible to the role under RLS,without crashing (as it does for a superuser / a role that bypasses RLS).
Narrowing (what works vs. crashes, same non-superuser role, same FORCE RLS)
CREATE(WITH CHECK enforced correctly)SET/ propertyUPDATEMATCH ()-[r:E]->() DELETE rDETACH DELETE(vertex with no connected edge)MATCH ()-[r]->() DELETE rthenMATCH (n) DELETE nDETACH DELETEof a vertex WITH a connected edge → SIGSEGV (the only failing path)DETACH DELETEas a superuser (RLS bypass) — so it is specific to RLS-bound roles.Disabling the RLS policy on the edge label (
g."E") alone avoids the crash (the vertex-labelpolicy is not the trigger), which points at the edge-policy evaluation during DETACH's implicit edge
deletion.
Impact
Any multi-tenant AGE deployment that isolates tenants via RLS on the graph label tables cannot run
DETACH DELETE(e.g. a projection rebuild) under the tenant-bound runtime role.