neo4j-security-skill
SkillSecurityProgrammatic security management in Neo4j — RBAC/ABAC, user lifecycle (CREATE/ALTER/DROP USER),
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the neo4j-security-skill skill
What this skill tells your AI
The instructions your AI receives, as published by neo4j-contrib/neo4j-skills in neo4j-security-skill/SKILL.md and read by ahel’s review.
When to Use
- Creating, altering, suspending, or dropping users
- Creating roles, granting/revoking role membership
- Granting/denying/revoking graph, database, or DBMS privileges
- Inspecting current privileges (
SHOW PRIVILEGES) - Implementing property-level access control (read/write per property)
- Setting up ABAC rules against OIDC claims or native user tags
- Referencing LDAP/SSO auth provider configuration
When NOT to Use
- Writing Cypher queries against application data →
neo4j-cypher-skill - Cluster ops, backups, server config →
neo4j-cli-tools-skill - Driver connection setup →
neo4j-driver-*-skill
MCP Write Gate — MANDATORY
Before 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 RULE
Never auto-execute privilege changes. Show exact Cypher, annotate impact, get "yes".
Execution Context
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"
1. User Management
Create user
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
Parameterised password (preferred in scripts)
CREATE USER $username SET PASSWORD $password CHANGE NOT REQUIRED;
Alter user
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
User tags [2026.06+, Enterprise]
Tags are arbitrary strings on the native user object, readable in ABAC rules via abac.native.user_tags().
CREATE USER jake SET PASSWORD $pw SET TAGS 'finance', 'auditor';
ALTER USER jake REMOVE TAG 'auditor' ADD TAG 'on-call'; // REMOVE before ADD before SET
ALTER USER jake SET TAGS 'finance', 'auditor'; // replaces all existing tags
ALTER USER jake REMOVE ALL TAGS;
ALTER USERS jake, alice ADD TAGS 'pii-access'; // bulk tag edit
SET/ADD/REMOVE TAG[S] requires SET USER METADATA; reading tag values requires SHOW USER METADATA:
GRANT SET USER METADATA ON DBMS TO userAdmin;
GRANT SHOW USER METADATA ON DBMS TO auditor;
GRANT USER METADATA MANAGEMENT ON DBMS TO userManager; // both of the above
Show users
SHOW USERS YIELD user, roles, passwordChangeRequired, suspended, home
WHERE suspended = false
RETURN user, roles ORDER BY user;
// tags column returns null without SHOW USER METADATA [2026.06+]
SHOW USERS YIELD user, roles, tags;
Drop user
DROP USER alice IF EXISTS;
2. Role Management
Create / drop role
CREATE ROLE analyst;
CREATE ROLE analyst IF NOT EXISTS;
DROP ROLE analyst IF EXISTS;
Assign / remove roles
GRANT ROLE analyst TO alice;
GRANT ROLE analyst, writer TO alice, bob; // bulk
REVOKE ROLE analyst FROM alice;
Inspect roles
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
3. Privilege Decision Table
| 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 user tags [2026.06] | GRANT USER METADATA 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 |
DENY overrides GRANT
// 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 removes a specific grant or deny
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
4. Common Role Patterns
Read-only analyst
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;
Write role (no admin)
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;
Read-only on specific labels only
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
DBA role (full admin)
CREATE ROLE dba IF NOT EXISTS;
GRANT ALL ON DBMS TO dba;
GRANT ALL ON DATABASE * TO dba;
5. Property-Level Access Control (Enterprise)
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:
FORpattern applies to read privileges only — not write- Each property-based privilege restricted by a single property
- Performance overhead scales with number of rules;
TRAVERSErules cost more thanREAD - Ensure the property used for rules cannot be modified by the restricted role
6. ABAC — Attribute-Based Access Control (Enterprise)
ABAC grants roles dynamically from OIDC/JWT claims or native user tags rather than explicit GRANT ROLE ... TO user.
Prerequisites
# neo4j.conf — providers must also appear in dbms.security.authorization_providers
dbms.security.abac.authorization_providers=<oidc-provider-alias>,native # native [2026.06+]
Unlisted provider → its accessor function (abac.oidc.user_attribute(), abac.native.user_tags()) unavailable and rules referencing it fail (no silent default).
Create auth rule
CREATE AUTH RULE salesRule
SET CONDITION abac.oidc.user_attribute('department') = 'sales';
GRANT ROLE analyst TO AUTH RULE salesRule;
Compound conditions
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;
Manage auth rules
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;
Native users [2026.06+]: tag native DB users (see User tags) and match tags in rules via abac.native.user_tags() — no OIDC required:
CREATE AUTH RULE nativeSalesRule
SET CONDITION 'sales' IN abac.native.user_tags();
GRANT ROLE analyst TO AUTH RULE nativeSalesRule;
// require several tags at once
CREATE AUTH RULE financeAuditRule
SET CONDITION all(tag IN ['finance', 'auditor'] WHERE tag IN abac.native.user_tags());
// combine tags with OIDC claims and temporal conditions
CREATE AUTH RULE onCallRule
SET CONDITION 'on-call' IN abac.native.user_tags()
AND abac.oidc.user_attribute('department') = 'engineering'
AND time.transaction('UTC').hour >= 9;
❌ Never condition on tag absence: NOT ('restricted' IN abac.native.user_tags()) — any user created without tags satisfies it, escalating privileges.
✅ Require presence of a tag: 'unrestricted' IN abac.native.user_tags().
Notes:
- Missing claims evaluate to NULL → rule condition false → role not granted
- Rules apply immediately to existing sessions when claims are already loaded
- OIDC claims via
abac.oidc.user_attribute(); native user tags viaabac.native.user_tags()[2026.06+]. LDAP has no accessor function — tag the LDAP user's native user object instead - User-defined functions rejected in PBAC property-rule predicates [2026.06+]
- Infinigraph (sharded property databases) supports PBAC
READonly, granted on the virtual database [2026.07+, not on Aura] — see references/privilege-reference.md
7. SHOW PRIVILEGES Patterns
// 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;
8. Built-in Roles (do not drop)
| 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;
9. Auth Provider Config Reference (operational — not Cypher)
Native (default)
dbms.security.auth_enabled=true
dbms.security.auth_max_failed_attempts=3 # lockout threshold
LDAP
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
OIDC / SSO (Okta, Auth0, Entra ID)
dbms.security.oidc.<alias>.display_name=Okta
dbms.security.oidc.<alias>.auth_flow=pkce # `implicit` deprecated 2026.06, removal planned — keep 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).
Checklist — New Role Setup
- Determine required operations: read / write / admin
- Identify target database(s) and graph scope (all labels vs specific)
- Identify any properties that must be hidden (→ DENY READ)
- Create role:
CREATE ROLE ... IF NOT EXISTS - Grant ACCESS on database
- Grant MATCH / TRAVERSE / WRITE as needed
- Apply DENY for restricted properties
- Run
SHOW ROLE ... PRIVILEGES AS COMMANDSto verify - Assign to users:
GRANT ROLE ... TO ... - Test with
SHOW USER ... PRIVILEGES AS COMMANDS
Full privilege syntax → references/privilege-reference.md
Signals
- GitHub stars
- 110
- Forks
- 38
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
neo4j-security-skill- Source
- github.com/neo4j-contrib/neo4j-skills