From neo4j-skills
Manages Neo4j security programmatically via Cypher on system DB: user/role lifecycle (CREATE/ALTER/DROP), RBAC/ABAC privileges (GRANT/DENY/REVOKE), property/subgraph access, SHOW PRIVILEGES, LDAP/OIDC config.
npx claudepluginhub neo4j-contrib/neo4j-skillsThis skill is limited to using the following tools:
- Creating, altering, suspending, or dropping users
Guides Neo4j CLI tools for database admin (neo4j-admin backups/restores/imports), query execution/scripting (cypher-shell), cloud provisioning (aura-cli), and AI agent setup (neo4j-mcp/cli).
Models authorization as a graph of relationships for inherited permissions following resource hierarchies like org-team-project-document. Prevents IDOR by checking user-resource links.
Implements Supabase RBAC using JWT app_metadata.role claims, RLS policies with auth.jwt(), organization-scoped access, and per-role API key scoping for admin/member/viewer hierarchies.
Share bugs, ideas, or general feedback.
SHOW PRIVILEGES)neo4j-cypher-skillneo4j-cli-tools-skillneo4j-driver-*-skillBefore executing ANY of the following, show the planned command and wait for explicit confirmation:
CREATE USER / ALTER USER / DROP USERCREATE ROLE / DROP ROLEGRANT / DENY / REVOKE (any privilege)CREATE AUTH RULE / DROP AUTH RULENever auto-execute privilege changes. Show exact Cypher, annotate impact, get "yes".
All security Cypher runs against the system database:
// Neo4j auto-routes CREATE/ALTER/SHOW USER|ROLE|PRIVILEGE to system
// If using cypher-shell: cypher-shell -d system
// If using driver: use database="system"
CREATE USER alice SET PASSWORD 'secret' CHANGE NOT REQUIRED;
// CHANGE REQUIRED (default): forces password change on first login
// CHANGE NOT REQUIRED: password valid immediately
// SET STATUS ACTIVE (default) | SUSPENDED
CREATE USER $username SET PASSWORD $password CHANGE NOT REQUIRED;
ALTER USER alice SET PASSWORD $newPw CHANGE NOT REQUIRED;
ALTER USER alice SET STATUS SUSPENDED; // lock account
ALTER USER alice SET STATUS ACTIVE; // unlock
ALTER USER alice SET HOME DATABASE mydb; // default db on connect
ALTER USER alice IF EXISTS SET PASSWORD $pw; // safe if missing
SHOW USERS YIELD username, roles, passwordChangeRequired, suspended, homeDatabase
WHERE suspended = false
RETURN username, roles ORDER BY username;
DROP USER alice IF EXISTS;
CREATE ROLE analyst;
CREATE ROLE analyst IF NOT EXISTS;
DROP ROLE analyst IF EXISTS;
GRANT ROLE analyst TO alice;
GRANT ROLE analyst, writer TO alice, bob; // bulk
REVOKE ROLE analyst FROM alice;
SHOW ROLES YIELD role, member ORDER BY role;
SHOW ROLE analyst PRIVILEGES AS COMMANDS; // returns runnable GRANT commands
SHOW POPULATED ROLES YIELD role; // only roles with members
| Goal | Command |
|---|---|
| Allow db connection | GRANT ACCESS ON DATABASE mydb TO analyst |
| Read all graph data | GRANT MATCH {*} ON GRAPH mydb ELEMENTS * TO analyst |
| Read specific label | GRANT MATCH {*} ON GRAPH mydb NODES Person TO analyst |
| Read specific rel type | GRANT MATCH {*} ON GRAPH mydb RELATIONSHIPS KNOWS TO analyst |
| Read one property | GRANT READ {email} ON GRAPH mydb NODES Person TO analyst |
| Traverse but hide properties | GRANT TRAVERSE ON GRAPH mydb NODES Person TO analyst |
| Write (create/set) | GRANT WRITE ON GRAPH mydb TO writer |
| Create nodes only | GRANT CREATE ON GRAPH mydb NODES Person TO writer |
| Delete nodes only | GRANT DELETE ON GRAPH mydb NODES Person TO writer |
| Execute procedure | GRANT EXECUTE PROCEDURE apoc.* TO analyst |
| Execute function | GRANT EXECUTE USER DEFINED FUNCTION apoc.* TO analyst |
| All on one db | GRANT ALL ON DATABASE mydb TO dba |
| Full DBMS admin | GRANT ALL ON DBMS TO dba |
| Manage users | GRANT USER MANAGEMENT ON DBMS TO secadmin |
| Manage roles | GRANT ROLE MANAGEMENT ON DBMS TO secadmin |
| Schema changes | GRANT CREATE ELEMENT TYPES ON DATABASE mydb TO schemaadmin |
// Analyst can read Person but NOT the ssn property
GRANT MATCH {*} ON GRAPH mydb NODES Person TO analyst;
DENY READ {ssn} ON GRAPH mydb NODES Person TO analyst;
REVOKE GRANT READ {email} ON GRAPH mydb NODES Person FROM analyst;
REVOKE DENY READ {ssn} ON GRAPH mydb NODES Person FROM analyst;
REVOKE MATCH {*} ON GRAPH mydb NODES Person FROM analyst; // removes both grant+deny
CREATE ROLE analyst IF NOT EXISTS;
GRANT ACCESS ON DATABASE mydb TO analyst;
GRANT MATCH {*} ON GRAPH mydb ELEMENTS * TO analyst;
GRANT EXECUTE PROCEDURE apoc.* TO analyst;
CREATE ROLE writer IF NOT EXISTS;
GRANT ACCESS ON DATABASE mydb TO writer;
GRANT MATCH {*} ON GRAPH mydb ELEMENTS * TO writer;
GRANT WRITE ON GRAPH mydb TO writer;
CREATE ROLE limited_reader IF NOT EXISTS;
GRANT ACCESS ON DATABASE mydb TO limited_reader;
GRANT TRAVERSE ON GRAPH mydb ELEMENTS * TO limited_reader; // can traverse
GRANT MATCH {*} ON GRAPH mydb NODES Person TO limited_reader; // Person props visible
GRANT MATCH {*} ON GRAPH mydb NODES Company TO limited_reader; // Company props visible
// Other labels: traversable but properties invisible
CREATE ROLE dba IF NOT EXISTS;
GRANT ALL ON DBMS TO dba;
GRANT ALL ON DATABASE * TO dba;
Restrict read access to individual properties:
// Grant read on all Person props, then deny sensitive ones
GRANT MATCH {*} ON GRAPH mydb NODES Person TO analyst;
DENY READ {ssn, dateOfBirth} ON GRAPH mydb NODES Person TO analyst;
Property-based pattern matching (sub-graph access):
// Only see Person nodes where classification = 'public'
GRANT MATCH {*} ON GRAPH mydb
FOR (n:Person) WHERE n.classification = 'public'
TO analyst;
// Block access to classified nodes
DENY MATCH {*} ON GRAPH mydb
FOR (n) WHERE n.classification <> 'UNCLASSIFIED'
TO regularUsers;
Constraints:
FOR pattern applies to read privileges only — not writeTRAVERSE rules cost more than READABAC grants roles dynamically from JWT/OIDC claims rather than explicit GRANT ROLE ... TO user.
# neo4j.conf
dbms.security.abac.authorization_providers=<oidc-provider-alias>
CREATE AUTH RULE salesRule
SET CONDITION abac.oidc.user_attribute('department') = 'sales';
GRANT ROLE analyst TO AUTH RULE salesRule;
CREATE OR REPLACE AUTH RULE seniorRule
SET CONDITION abac.oidc.user_attribute('department') = 'engineering'
AND abac.oidc.user_attribute('level') >= 5;
GRANT ROLE senior_engineer TO AUTH RULE seniorRule;
SHOW AUTH RULES YIELD ruleName, condition, roles;
ALTER AUTH RULE salesRule SET ENABLED false; // disable without dropping
RENAME AUTH RULE salesRule TO salesDeptRule;
DROP AUTH RULE salesDeptRule;
REVOKE ROLE analyst FROM AUTH RULE salesRule;
Notes:
// All privileges in the system
SHOW PRIVILEGES YIELD *;
// Privileges for a specific user (as runnable commands)
SHOW USER alice PRIVILEGES AS COMMANDS;
// Privileges for a specific role
SHOW ROLE analyst PRIVILEGES YIELD privilege, action, resource, graph, segment;
// Find who has access to a database
SHOW PRIVILEGES YIELD *
WHERE graph = 'mydb'
RETURN role, action, resource, segment ORDER BY role;
// Find all DENY rules
SHOW PRIVILEGES YIELD *
WHERE access = 'DENIED'
RETURN role, action, resource, segment;
| Role | Scope |
|---|---|
admin | Full DBMS + all databases |
architect | Schema changes + write on all databases |
publisher | Write on all databases |
editor | Write excluding schema changes |
reader | Read-only on all databases |
public | All users implicitly; default home database access |
Assign built-in roles: GRANT ROLE reader TO alice;
dbms.security.auth_enabled=true
dbms.security.auth_max_failed_attempts=3 # lockout threshold
dbms.security.auth_provider=ldap
dbms.security.ldap.host=ldap://ldap.example.com
dbms.security.ldap.authentication.mechanism=simple
dbms.security.ldap.authentication.user_dn_template=uid={0},ou=users,dc=example,dc=com
dbms.security.ldap.authorization.group_membership_attributes=memberOf
dbms.security.ldap.authorization.group_to_role_mapping=\
"cn=analysts,ou=groups,dc=example,dc=com" = analyst;\
"cn=admins,ou=groups,dc=example,dc=com" = admin
dbms.security.oidc.<alias>.display_name=Okta
dbms.security.oidc.<alias>.auth_flow=pkce
dbms.security.oidc.<alias>.well_known_discovery_uri=https://example.okta.com/.well-known/openid-configuration
dbms.security.oidc.<alias>.audience=neo4j
dbms.security.oidc.<alias>.claims.username=email
dbms.security.oidc.<alias>.claims.groups=groups
dbms.security.oidc.<alias>.authorization.group_to_role_mapping=\
"neo4j-analysts" = analyst;\
"neo4j-admins" = admin
Config changes require server restart. Roles referenced in mappings must exist in Neo4j (native or created via Cypher).
CREATE ROLE ... IF NOT EXISTSSHOW ROLE ... PRIVILEGES AS COMMANDS to verifyGRANT ROLE ... TO ...SHOW USER ... PRIVILEGES AS COMMANDSFull privilege syntax → references/privilege-reference.md