Chapter 15 · Security: Authentication, RBAC, Privileges, TLS, Secrets, and Least Privilege
Threat-Model a Graph Application Including Query Injection, Over-Privileged Procedures, Data Exfiltration, and Admin Paths
Threat-model AtlasMart across Cypher construction, tenant traversal, credentials, TLS, procedures, backups and administrative recovery, then turn defenses into negative tests.
Learning outcomes
A secure AtlasMart deployment can still fail through composition: a parameterized endpoint is safe from value injection but lets the caller choose arbitrary labels; an APOC HTTP procedure has unrestricted network access; a backup directory is readable by a web-service account; a Community DB credential bypasses application tenant checks; or an admin recovery step temporarily disables authentication while the server remains remotely reachable. Threat modeling connects these paths before an attacker does.
Build an asset/trust-boundary/threat/control matrix for graph application, DBMS, plugins, network and admin paths.
Distinguish parameterized value safety from dynamic-label/type/query-shape authorization.
Assess procedure/plugin, file/URL, backup and unrestricted/boosted execution as data-exfiltration paths.
Run safe negative tests for cross-tenant access, injection-like input, bad TLS, bad auth and forbidden admin behavior.
Produce a production security acceptance checklist with explicit edition/tier gaps and incident-response ownership.
The continuity lab remains Neo4j Community
2026.07.1, database neo4j, explicit
CYPHER 25 where language behavior matters,
container atlasmart-neo4j, loopback Bolt
bolt://127.0.0.1:7687, synthetic credential
neo4j/atlasmart-course-2026,
official Python driver neo4j 6.3.0, and no
mandatory plugin. Neo4j 5.26.30 is the LTS
comparison line. For TLS changes, this chapter deliberately
uses a second disposable container
atlasmart-neo4j-secure so earlier labs are not
disrupted.
Current Community Edition supports native users but has no roles; every Community user has implied administrator privileges. Fine-grained RBAC, built-in/custom roles, graph/database/procedure privileges, external auth-provider integration, security/query logs and related enterprise authorization controls are Enterprise/Aura-tier features. Mandatory Community exercises therefore prove authentication, parameterization, TLS, credential rotation and application-layer authorization, while RBAC denial examples are explicitly marked licensed/deterministic rather than presented as Community output.
All credentials, certificates and attacks in this chapter are synthetic and disposable. Never paste production passwords/private keys into source control, do not disable certificate verification to make a production connection work, and do not broaden procedure/plugin privileges merely to get a demo running.
Reproducible AtlasMart setup
$env:NEO4J_URI='bolt://127.0.0.1:7687'$env:NEO4J_USER='neo4j'$env:NEO4J_PASSWORD='atlasmart-course-2026'$env:NEO4J_DATABASE='neo4j'py -3 -m venv .venv.\.venv\Scripts\Activate.ps1python -m pip install --upgrade pippython -m pip install neo4j==6.3.0
CYPHER 25CREATE CONSTRAINT customer_id IF NOT EXISTSFOR (c:Customer) REQUIRE c.customerId IS UNIQUE;CREATE CONSTRAINT product_id IF NOT EXISTSFOR (p:Product) REQUIRE p.productId IS UNIQUE;CREATE CONSTRAINT order_id IF NOT EXISTSFOR (o:Order) REQUIRE o.orderId IS UNIQUE;MERGE (c:Customer {customerId:'C-1001'})SET c.name='Mina Rahimi', c.tier='GOLD', c.tenantId='TENANT-A'MERGE (p:Product {productId:'P-1001'})SET p.name='Trail Camera', p.category='Cameras', p.price=129.90MERGE (o:Order {orderId:'O-5001'})SET o.status='PAID', o.tenantId='TENANT-A', o.orderedAt=datetime('2026-09-08T16:30:00Z')MERGE (c)-[:PLACED]->(o)MERGE (o)-[r:CONTAINS]->(p)SET r.quantity=1, r.unitPrice=129.90;
These are course-only credentials and synthetic records. The
fixture deliberately includes tenantId because
authorization mistakes often appear when a graph traversal
crosses a tenant boundary even though authentication succeeded.
1. Define assets and trust boundaries
| Asset/boundary | Threat | Control/evidence |
|---|---|---|
| Customer/order graph | cross-tenant traversal/PII exfiltration | DB RBAC where available + mandatory tenant predicate/subject authorization + negative tests |
| Cypher text | injection or arbitrary query-shape control | parameters for values; allowlisted identifiers/query templates |
| Bolt/HTTPS | credential/data interception | TLS + full trust verification + network segmentation |
| DB credential | credential theft | scoped identity, secret manager, rotation, no source/log exposure |
| APOC/custom plugin | file/network/boosted privilege abuse | install minimum extensions; allowlist; no broad unrestricted/boosted access |
| backup/data directory | offline full-database theft | filesystem/object-store ACLs, encryption, separate ops identity |
| admin/recovery path | auth disabled or admin token abused | local/network isolation, break-glass logging, two-person/runbook controls |
2. Query injection: parameters solve values, not arbitrary syntax
def search(label, text, driver): # WRONG: label and text both become query syntax. query = f"MATCH (n:{label}) WHERE n.name CONTAINS '{text}' RETURN n" return driver.execute_query(query, database_="neo4j")
ALLOWED_LABELS = {"Product", "Category"}def search(label, text, driver): if label not in ALLOWED_LABELS: raise ValueError("unsupported search domain") query = f""" CYPHER 25 MATCH (n:{label}) WHERE n.name CONTAINS $text RETURN n.name AS name ORDER BY name LIMIT 50 """ return driver.execute_query(query, text=text, database_="neo4j")
Cypher parameters are the primary defense for values. When identifiers or query structure cannot be parameterized, do not “escape whatever the user sent”; choose from server-owned query templates/allowlists.
3. Tenant boundary must be before or inside graph expansion
CYPHER 25MATCH (o:Order {orderId:$orderId})-[:CONTAINS]->(p:Product)RETURN o.orderId, o.status, collect(p.productId) AS products;
CYPHER 25MATCH (o:Order {orderId:$orderId, tenantId:$tenantId})-[:CONTAINS]->(p:Product)RETURN o.orderId, o.status, collect(p.productId) AS products;
Application authorization must establish the caller’s tenant/subject from trusted identity context, not accept a tenant ID and blindly trust it. In Enterprise, graph/property-based privileges may add another DB-enforced layer; in Community the app predicate is critical because the DB user remains an implied admin.
4. Procedures/plugins can become exfiltration or privilege bridges
| Extension path | Risk | Guardrail |
|---|---|---|
| APOC file import/export | read/write host/container files | disabled unless required; constrained import/export directory; least filesystem ACL |
| APOC HTTP/JSON | server-side request to attacker/internal endpoint | explicit URL/network policy; do not enable casually |
| custom Java procedure | arbitrary code/resource access depending implementation | code review, dependency provenance, compatibility testing |
dbms.security.procedures.unrestricted
|
procedure may bypass normal sandbox restrictions |
minimal glob list; never broad * without
threat model
|
| EXECUTE BOOSTED | Enterprise privilege escalation inside procedure | grant to narrowly reviewed procedures/roles only |
docker exec atlasmart-neo4j sh -lc ' grep -E "^[[:space:]]*dbms.security.procedures.(allowlist|unrestricted)" "$NEO4J_HOME/conf/neo4j.conf" || true ls -la /plugins 2>/dev/null || true'
5. Safe negative-test matrix
| Test | Expected signal | Reset/blast radius |
|---|---|---|
| wrong password | authentication denied | none |
| plain Bolt to TLS-required lab | TLS/connection failure | none |
| wrong hostname with trusted certificate | certificate verification failure | none |
| cross-tenant order ID | zero rows/application 404/403 by policy | none |
injection string in $text |
treated as data, not syntax | none |
| forbidden Enterprise delete | authorization denied | none; licensed RBAC test |
| unapproved procedure | execute/allowlist denied | no config broadening to make test pass |
| break-glass recovery drill | network remains localhost-only while auth disabled | isolated disposable DBMS only |
6. Threat-model one request end to end
Internet/client -> API gateway / TLS / authentication -> application subject + tenant authorization -> fixed repository method + parameterized Cypher -> driver TLS + service credential -> Neo4j authentication -> Neo4j RBAC (Enterprise/Aura-capable tier) OR documented Community gap -> graph traversal/property access -> procedure/plugin/file/network boundary if invoked -> bounded projection -> audit/correlation evidence
Every arrow is a trust boundary with an owner. If a control is absent—such as Community DB RBAC—write the gap down and decide whether application controls plus deployment isolation are sufficient for the risk, or whether the tier must change.
7. Security acceptance checklist
| Area | Acceptance evidence |
|---|---|
| Identity | no application admin credentials; break-glass owner and rotation documented |
| RBAC/tier | licensed deployment has explicit grants/denies; Community gap acknowledged, not mislabeled |
| Injection | values parameterized; dynamic identifiers/query templates allowlisted; regression tests include adversarial strings |
| Tenant isolation | trusted identity→tenant mapping and cross-tenant negative tests |
| Transport | remote Bolt/HTTPS encrypted with verified CA/hostname; no production ignore flags |
| Secrets | no real secret in source/image/log; tested rotation/revocation |
| Extensions | plugins inventoried/pinned; unrestricted/boosted/file/network capabilities minimal |
| Admin/backup | host, backup, certificate/private-key and recovery paths access-controlled |
| Evidence | security/query/app/audit evidence available to tier is protected and reviewed |
| Incident response | credential/procedure/data-exfiltration tabletop can be executed without improvisation |
8. Cleanup of course-only identities
DROP USER atlasmart_app IF EXISTS;// Enterprise-only cleanup if you created the optional RBAC lab objects:// DROP USER atlasmart_reader_user IF EXISTS;// DROP USER atlasmart_service IF EXISTS;// DROP ROLE atlasmart_reader IF EXISTS;// DROP ROLE atlasmart_order_writer IF EXISTS;// DROP ROLE atlasmart_visualizer IF EXISTS;
Production judgment
| Review area | Decision evidence |
|---|---|
| Identity/authentication | native or external identity source, MFA/SSO upstream where available, password/token lifecycle, lockout and break-glass process |
| Authorization | least-privilege database/graph/procedure grants; explicit DENY review; Community control gap documented |
| Transport | encrypted remote Bolt/HTTPS, trusted CA and hostname validation; self-signed only for isolated testing |
| Secrets | out of source/logs/images; rotated without code changes; incident revocation path tested |
| Application layer | parameterized Cypher, tenant/subject authorization before graph expansion, bounded result/data-export paths |
| Extensions/admin | procedure allowlist/unrestricted/boosted review, plugin provenance, backup/admin filesystem and host access |
| Evidence | auth/TLS denial tests, security/query logs where licensed, driver/server correlation and configuration review |
| Recovery | credential compromise playbook, backup encryption/access, break-glass scope, rollback/reconciliation and post-incident validation |
Check your understanding
- Does parameterizing $text make an arbitrary user-provided label safe?
- Why are procedures part of the threat model?
- What is the strongest Community authorization gap to remember?
- Why protect backups like the live database?
- What makes a security acceptance test useful?
Review the answers
1. No. Parameters protect values; dynamic identifiers/query shape need fixed templates or strict allowlists.
2. They can access graph, files, network or elevated privileges beyond ordinary query intent depending configuration and implementation.
3. All Community users have implied administrator privileges; the DBMS cannot enforce Enterprise-style least-privilege roles.
4. A readable backup can disclose the entire graph without going through Bolt authentication/RBAC.
5. It contains observable positive and negative behavior, explicit ownership, edition/tier assumptions and a safe reset path.
Summary and next step
Neo4j security is layered: identity, authorization, verified transport, secret lifecycle, safe query construction, extension governance, filesystem/admin boundaries and evidence. Chapter 16 can now introduce clustering and availability without treating replication as a security control or assuming a highly available cluster automatically has a least-privilege architecture.
Authoritative references
- Current Neo4j versions — Current server and 5.26 LTS release snapshot.
- Security checklist — Official baseline for deployment, transport, extensions, backup and filesystem security.
- Manage users — Native users, password handling, Community/Enterprise user-model distinctions.
- Role-based access control — Enterprise RBAC model and GRANT/DENY/REVOKE semantics.
- Read privileges — TRAVERSE, READ and MATCH graph privilege semantics.
- Write privileges — CREATE, DELETE, SET, MERGE and WRITE privilege semantics.
- SSL framework — Bolt/HTTPS TLS policies, certificate files, TLS levels and URI schemes.
- Procedure/function privileges — Execute and boosted-execution privilege boundaries.
- Python driver advanced connections — TLS/trust and rotating authentication token support in the maintained Python driver.
- Security log / logging — Log availability and Enterprise security/query-log boundaries.
- Password/user recovery — Safe auth-disable recovery sequence with network isolation.
- APOC security guidelines — APOC procedure security, file/network and unrestricted configuration considerations.