Subgraph Access Control
Overview
An agent with unrestricted access to your knowledge graph is a liability. The
dense relationships that make agentic reasoning powerful also mean a single
unscoped query can traverse from a public product catalog to internal cost data
to employee records. In a graph, everything is reachable from everything else.
Graph databases have mature access-control primitives. Neo4j Enterprise supports
privileges at the node-label, relationship-type, and property level: you GRANT
traverse rights on specific labels, DENY read access to specific properties, and
combine them into role-based policies. A critical design feature is security
transparency — when a role lacks permission to see a node, the node is
invisible, not access-denied. The agent cannot distinguish data that does not
exist from data it is not allowed to see, which blocks an autonomous agent from
probing access boundaries as part of its reasoning.
This skill covers the three governance concerns from the chapter: subgraph-level
access control (a role per persona, bound at connection time), PII and retention
(Rehmer's Privacy-by-Architecture UUID separation with GDPR soft/hard erasure),
and the execution graph as a compliance artifact (KG.GOV governance metadata
that answers auditor questions as a graph query).
When to Use
- A graph-backed agent serves multiple personas (SRE, finance) who need
different views of the same graph.
- You must prove which role accessed which data for a compliance review.
- The graph holds PII and you need GDPR Article 17 erasure that does not cascade
across densely connected relationships.
- You are testing that an access policy does not accidentally starve the agent's
reasoning (the "governance blocks the agent" pitfall).
Phrases that should invoke this skill: "who can see what in the graph",
"subgraph access control", "role-based graph permissions", "GDPR delete from the
graph", "PII in the knowledge graph", "compliance audit of agent decisions".
When NOT to Use
- Relational row/table permissions. This is graph-native (labels, relationship
types, properties). Use SQL GRANT for tables.
- Network / cloud IAM. IAM scopes API calls; this scopes graph traversal.
They compose (the execution-graph
access_role ties back to the IAM role) but
are different layers.
- Prompt-level guardrails. A jailbreak-resistant system prompt is not access
control; this enforces reach at the database, below the model.
- Single-persona agents. If every query runs as one role, a static
connection policy suffices; you do not need per-persona generation.
Process
| Step |
Input |
Action |
Output |
Verification |
| 1 |
Persona role name |
lib.generate_policy(role) |
Neo4j GRANT/DENY Cypher |
Contains GRANT TRAVERSE + DENY on cost/employee |
| 2 |
Role + label (+ property) |
lib.can_traverse / lib.can_read |
Boolean |
False traverse => invisible, not denied |
| 3 |
Role + agent query labels |
lib.audit_access(role, labels) |
reachable / invisible / masked + functionally_complete |
Complete only if every needed label is reachable |
| 4 |
(design) |
lib.privacy_by_architecture() |
UUID / Identity-Store split |
Graph stores only uuid + relationships |
| 5 |
UUID + mode |
lib.gdpr_erase(uuid, mode) |
soft (mask+orphan) or hard (DETACH DELETE) |
Hard emits DETACH DELETE; soft preserves aggregates |
| 6 |
Decision facts |
lib.governance_metadata(...) |
Execution-graph governance record |
Carries model_id/version + access_role + pii_accessed |
| 7 |
Governance records |
lib.audit_query(records, pii_accessed=True) |
Filtered compliance answer |
Returns exactly the PII-accessing decisions |
Rationalizations
| Agent rationalization |
Documented rebuttal |
| "Return access-denied so the agent knows to stop." |
Access-denied leaks the existence of the data. Security transparency makes the node invisible so the agent cannot distinguish "absent" from "forbidden" and cannot probe the boundary (Ch8). |
| "One admin role is simpler than a role per persona." |
One role means the agent acting for an SRE can also read finance cost data and employee PII — exactly the cross-domain traversal the chapter opens with. Bind a role per persona at connection time. |
| "Just delete the user node for GDPR." |
In a densely connected graph a raw delete cascades into relationships you need to keep. The UUID-separation pattern lets you mask the identity mapping (real-time, single-row) and clean the graph asynchronously (Ch8). |
| "Governance is an external overlay we add later." |
KG.GOV makes governance a first-class citizen of the graph: the execution graph already captures data_sources_accessed and access_role, so the audit answer is a graph query, not a log-parsing project (Example 8-6). |
| "Lock the policy down as tight as possible." |
Over-restriction starves reasoning: if the SRE role cannot reach Library nodes, dependency analysis breaks. Test the policy against the agent's real query patterns first (audit_access; Ch8 pitfall). |
Red Flags
- Policy returns access-denied instead of invisibility. Security
transparency is broken; the boundary is now probeable.
- The SRE role can read
cost_per_hour or traverse Employee. Cross-domain
leakage — the exact failure the chapter opens with.
- GDPR "delete" leaves the identity mapping intact. Erasure is not honoured;
soft delete must mask the mapping, hard delete must DETACH DELETE.
audit_access reports functionally_complete for a role missing a required
label. The completeness check is wrong; dependency reasoning will silently
break in production.
- Governance record omits model_id/version. In a selective-intelligence
fleet you cannot attribute a decision to a model. CLI
--help exits non-zero
is the same class of failure at the harness seam.
Non-Negotiable Verification
Before deploying a policy built on this skill:
Run the benchmark battery.
python cli.py benchmark
Confirms SRE cannot read cost, finance cannot traverse Library, Employee is
invisible to every role, soft/hard erasure behave correctly, and the audit
query filters PII-accessing decisions.
Read a generated policy.
python cli.py policy sre_oncall
Confirm it GRANTs traversal on infra labels and DENYs read on cost properties
and traversal on Employee/Compensation.
Run the pitfall test against a real investigation.
python cli.py audit sre_oncall --labels Service,Library,Metric
Confirm functionally_complete is true — the SRE dependency workflow is not
starved by the policy.
Domain test in the notebook. Run notebooks/ch8-optimization.ipynb;
confirm the governance section generates both roles, proves the SRE cannot
see cost data, and attaches a governance record to the execution graph.
Security Posture
- This IS the security control. The skill emits policy and answers access
questions; the actual enforcement is Neo4j Enterprise applying the emitted
GRANT/DENY at query time. Generating a policy is not enforcing it — apply it to
the database.
- Security transparency by construction.
can_traverse returning False
models invisibility; do not "helpfully" surface a denied-node hint to the
agent, which would re-open the boundary-probing surface.
- PII stays out of the graph. Under Privacy-by-Architecture the graph holds
only UUIDs; never write name/email/PII onto graph nodes. If PII appears on a
node, the erasure guarantees break.
- Governance metadata is append-only. Compliance records must be immutable;
do not mutate a governance record after the decision it describes.
Composition
- Ties into
model-routing-selector / cost-performance-scorer (Ch8): the
governance record's model_id/model_version attribute each decision to the
model that produced it in a selective-intelligence fleet.
- Extends the Chapter-7 execution graph: governance metadata is added to the
existing execution-graph nodes, so the compliance artifact needs no new store.
- Composes with cloud IAM at the
access_role seam — the graph role maps to
the IAM role the agent connects under.
Source Attribution
Distilled from Agentic GraphRAG (O'Reilly, by Anthony Alcaraz and Sam Julien),
Chapter 8 — Optimization, "Data Governance and Access Control". Key references:
- Neo4j Enterprise fine-grained access control (Example 8-5, Example 8-15)
- Security transparency (invisible-not-denied) design principle
- Mohamed et al. survey of property-graph access control (RBAC/ABAC/hybrid;
plan a policy abstraction layer across engines)
- Rehmer "Privacy by Architecture" (UUID separation, Identity Store join key)
- GDPR Article 17 soft/hard erasure
- KG.GOV governance framework + Example 8-6 governance metadata
1---2name: subgraph-access-control3description: Scope what each agent persona can see in a knowledge graph. Generates Neo4j fine-grained GRANT/DENY policy (traverse on node labels + relationship types, read on properties) per persona role, enforces security transparency (out-of-scope nodes are invisible, not access-denied), handles PII via the UUID-separation pattern with GDPR soft/hard erasure, and turns the Chapter-7 execution graph into a compliance artifact via governance metadata. Use for graph-backed agents where one unscoped query could traverse from a public catalog to employee records. NOT for relational row/table permissions, NOT for network/IAM policy, NOT for prompt-level guardrails (this governs graph reach).4---56# Subgraph Access Control78## Overview910An agent with unrestricted access to your knowledge graph is a liability. The11dense relationships that make agentic reasoning powerful also mean a single12unscoped query can traverse from a public product catalog to internal cost data13to employee records. In a graph, everything is reachable from everything else.1415Graph databases have mature access-control primitives. Neo4j Enterprise supports16privileges at the node-label, relationship-type, and property level: you GRANT17traverse rights on specific labels, DENY read access to specific properties, and18combine them into role-based policies. A critical design feature is **security19transparency** — when a role lacks permission to see a node, the node is20invisible, not access-denied. The agent cannot distinguish data that does not21exist from data it is not allowed to see, which blocks an autonomous agent from22probing access boundaries as part of its reasoning.2324This skill covers the three governance concerns from the chapter: subgraph-level25access control (a role per persona, bound at connection time), PII and retention26(Rehmer's Privacy-by-Architecture UUID separation with GDPR soft/hard erasure),27and the execution graph as a compliance artifact (KG.GOV governance metadata28that answers auditor questions as a graph query).2930## When to Use3132- A graph-backed agent serves multiple personas (SRE, finance) who need33 different views of the same graph.34- You must prove which role accessed which data for a compliance review.35- The graph holds PII and you need GDPR Article 17 erasure that does not cascade36 across densely connected relationships.37- You are testing that an access policy does not accidentally starve the agent's38 reasoning (the "governance blocks the agent" pitfall).3940Phrases that should invoke this skill: "who can see what in the graph",41"subgraph access control", "role-based graph permissions", "GDPR delete from the42graph", "PII in the knowledge graph", "compliance audit of agent decisions".4344## When NOT to Use4546- **Relational row/table permissions.** This is graph-native (labels, relationship47 types, properties). Use SQL GRANT for tables.48- **Network / cloud IAM.** IAM scopes API calls; this scopes graph traversal.49 They compose (the execution-graph `access_role` ties back to the IAM role) but50 are different layers.51- **Prompt-level guardrails.** A jailbreak-resistant system prompt is not access52 control; this enforces reach at the database, below the model.53- **Single-persona agents.** If every query runs as one role, a static54 connection policy suffices; you do not need per-persona generation.5556## Process5758| Step | Input | Action | Output | Verification |59|------|-------|--------|--------|--------------|60| 1 | Persona role name | `lib.generate_policy(role)` | Neo4j GRANT/DENY Cypher | Contains GRANT TRAVERSE + DENY on cost/employee |61| 2 | Role + label (+ property) | `lib.can_traverse` / `lib.can_read` | Boolean | False traverse => invisible, not denied |62| 3 | Role + agent query labels | `lib.audit_access(role, labels)` | reachable / invisible / masked + functionally_complete | Complete only if every needed label is reachable |63| 4 | (design) | `lib.privacy_by_architecture()` | UUID / Identity-Store split | Graph stores only uuid + relationships |64| 5 | UUID + mode | `lib.gdpr_erase(uuid, mode)` | soft (mask+orphan) or hard (DETACH DELETE) | Hard emits DETACH DELETE; soft preserves aggregates |65| 6 | Decision facts | `lib.governance_metadata(...)` | Execution-graph governance record | Carries model_id/version + access_role + pii_accessed |66| 7 | Governance records | `lib.audit_query(records, pii_accessed=True)` | Filtered compliance answer | Returns exactly the PII-accessing decisions |6768## Rationalizations6970| Agent rationalization | Documented rebuttal |71|-----------------------|---------------------|72| "Return access-denied so the agent knows to stop." | Access-denied leaks the existence of the data. Security transparency makes the node invisible so the agent cannot distinguish "absent" from "forbidden" and cannot probe the boundary (Ch8). |73| "One admin role is simpler than a role per persona." | One role means the agent acting for an SRE can also read finance cost data and employee PII — exactly the cross-domain traversal the chapter opens with. Bind a role per persona at connection time. |74| "Just delete the user node for GDPR." | In a densely connected graph a raw delete cascades into relationships you need to keep. The UUID-separation pattern lets you mask the identity mapping (real-time, single-row) and clean the graph asynchronously (Ch8). |75| "Governance is an external overlay we add later." | KG.GOV makes governance a first-class citizen of the graph: the execution graph already captures data_sources_accessed and access_role, so the audit answer is a graph query, not a log-parsing project (Example 8-6). |76| "Lock the policy down as tight as possible." | Over-restriction starves reasoning: if the SRE role cannot reach Library nodes, dependency analysis breaks. Test the policy against the agent's real query patterns first (`audit_access`; Ch8 pitfall). |7778## Red Flags7980- **Policy returns access-denied instead of invisibility.** Security81 transparency is broken; the boundary is now probeable.82- **The SRE role can read `cost_per_hour` or traverse `Employee`.** Cross-domain83 leakage — the exact failure the chapter opens with.84- **GDPR "delete" leaves the identity mapping intact.** Erasure is not honoured;85 soft delete must mask the mapping, hard delete must DETACH DELETE.86- **`audit_access` reports functionally_complete for a role missing a required87 label.** The completeness check is wrong; dependency reasoning will silently88 break in production.89- **Governance record omits model_id/version.** In a selective-intelligence90 fleet you cannot attribute a decision to a model. CLI `--help` exits non-zero91 is the same class of failure at the harness seam.9293## Non-Negotiable Verification9495Before deploying a policy built on this skill:96971. **Run the benchmark battery.**98 ```99 python cli.py benchmark100 ```101 Confirms SRE cannot read cost, finance cannot traverse Library, Employee is102 invisible to every role, soft/hard erasure behave correctly, and the audit103 query filters PII-accessing decisions.1041052. **Read a generated policy.**106 ```107 python cli.py policy sre_oncall108 ```109 Confirm it GRANTs traversal on infra labels and DENYs read on cost properties110 and traversal on Employee/Compensation.1111123. **Run the pitfall test against a real investigation.**113 ```114 python cli.py audit sre_oncall --labels Service,Library,Metric115 ```116 Confirm `functionally_complete` is true — the SRE dependency workflow is not117 starved by the policy.1181194. **Domain test in the notebook.** Run `notebooks/ch8-optimization.ipynb`;120 confirm the governance section generates both roles, proves the SRE cannot121 see cost data, and attaches a governance record to the execution graph.122123## Security Posture124125- **This IS the security control.** The skill emits policy and answers access126 questions; the actual enforcement is Neo4j Enterprise applying the emitted127 GRANT/DENY at query time. Generating a policy is not enforcing it — apply it to128 the database.129- **Security transparency by construction.** `can_traverse` returning False130 models invisibility; do not "helpfully" surface a denied-node hint to the131 agent, which would re-open the boundary-probing surface.132- **PII stays out of the graph.** Under Privacy-by-Architecture the graph holds133 only UUIDs; never write name/email/PII onto graph nodes. If PII appears on a134 node, the erasure guarantees break.135- **Governance metadata is append-only.** Compliance records must be immutable;136 do not mutate a governance record after the decision it describes.137138## Composition139140- **Ties into** `model-routing-selector` / `cost-performance-scorer` (Ch8): the141 governance record's `model_id`/`model_version` attribute each decision to the142 model that produced it in a selective-intelligence fleet.143- **Extends** the Chapter-7 execution graph: governance metadata is added to the144 existing execution-graph nodes, so the compliance artifact needs no new store.145- **Composes with** cloud IAM at the `access_role` seam — the graph role maps to146 the IAM role the agent connects under.147148## Source Attribution149150Distilled from *Agentic GraphRAG* (O'Reilly, by Anthony Alcaraz and Sam Julien),151Chapter 8 — Optimization, "Data Governance and Access Control". Key references:152153- Neo4j Enterprise fine-grained access control (Example 8-5, Example 8-15)154- Security transparency (invisible-not-denied) design principle155- Mohamed et al. survey of property-graph access control (RBAC/ABAC/hybrid;156 plan a policy abstraction layer across engines)157- Rehmer "Privacy by Architecture" (UUID separation, Identity Store join key)158- GDPR Article 17 soft/hard erasure159- KG.GOV governance framework + Example 8-6 governance metadata