Cypher Queries
All queries below run in the BloodHound CE Raw Query panel (or directly in the Neo4j browser if you are querying Neo4j directly).
User hunting
All Salesforce users
MATCH (m:SFUser) RETURN mAll relationships for a specific user (up to 6 hops)
MATCH (u:SFUser)WHERE u.name = "PETER WIENER"MATCH p = (u)-[*1..6]->(n)RETURN DISTINCT pAll system permissions held by a specific user
MATCH (u:SFUser {Username: "username@example.com"})-[:AssignedProfile|AssignedPermissionSet]->(ps)-[perm]->(org:SFOrganization)WHERE ps:SFProfile OR ps:SFPermissionSetRETURN u.name AS UserName, ps.name AS PermissionSetOrProfile, type(perm) AS SystemPermissionSystem permission queries
Shortest path to org compromise (Tier 0 permissions)
MATCH p=(u:SFUser)-[:AssignedProfile|AssignedPermissionSet|AssignedPermissionSetGroup|HasPermissionSet|IncludesPermissionSet*1..5]->(ps)-[r:ModifyAllData|ManageSharing|ManageProfilesPermissionsets|CustomizeApplication|AuthorApex|ManageUsers|ManageRoles]->(org:SFOrganization)WHERE (ps:SFProfile OR ps:SFPermissionSet) AND u <> orgRETURN pLIMIT 1000All users with a specific high-risk permission (swap ModifyAllData as needed)
MATCH path = (u:SFUser)-[:AssignedProfile|AssignedPermissionSet]->(ps)-[:ModifyAllData]->(org:SFOrganization)WHERE ps:SFProfile OR ps:SFPermissionSetRETURN pathCount how many users have each system permission (useful for risk prioritisation)
MATCH (u:SFUser)-[:AssignedProfile|AssignedPermissionSet]->(ps)-[perm]->(org:SFOrganization)WHERE ps:SFProfile OR ps:SFPermissionSetRETURN type(perm) AS Permission, COUNT(DISTINCT u) AS UserCountORDER BY UserCount DESCAvailable system permission edge types:
ModifyAllData · ViewAllData · ViewSetup · ManageUsers · ManageRoles · ManageSharing · ManageProfilesPermissionsets · ManageTranslation · CustomizeApplication · AuthorApex · ApiEnabled · EditTask · EditEvent
Object permission queries
All custom objects (potential sensitive data)
MATCH (obj:SFSObject)WHERE obj.IsCustom = TrueRETURN obj.name, obj.Label, obj.InternalSharingModelUsers with access to crown jewel objects
MATCH path = (u:SFUser)-[:AssignedProfile|AssignedPermissionSet|AssignedPermissionSetGroup|HasPermissionSet|IncludesPermissionSet*1..5]->(ps)-[r:CanCreate|CanRead|CanEdit|CanDelete|CanViewAll|CanModifyAll]->(obj:SFSObject)WHERE u.name = "PETER WIENER" AND obj.name = "SECRETDATA__C"AND (ps:SFPermissionSet OR ps:SFProfile)RETURN pathShortest path from any user to custom SObjects
MATCH p=(u:SFUser)-[:AssignedProfile|AssignedPermissionSet|AssignedPermissionSetGroup|HasPermissionSet|IncludesPermissionSet|CanCreate|CanRead|CanEdit|CanDelete|CanViewAll|CanModifyAll*1..10]->(f:SFSObject)WHERE f.name ENDS WITH '__C' AND u <> fRETURN pLIMIT 1000All shortest paths to custom objects
MATCH p=allShortestPaths((u:SFUser)-[:AssignedProfile|AssignedPermissionSet|AssignedPermissionSetGroup|HasPermissionSet|IncludesPermissionSet|CanCreate|CanRead|CanEdit|CanDelete|CanViewAll|CanModifyAll*1..10]->(f:SFSObject))WHERE f.name ENDS WITH '__C' AND u <> fRETURN pLIMIT 1000All users who can delete a specific object
MATCH (u:SFUser)-[:AssignedProfile|AssignedPermissionSet|AssignedPermissionSetGroup|HasPermissionSet|IncludesPermissionSet*1..5]->(p)-[:CanDelete]->(obj:SFSObject {name: "SECRETDATA__C"})WHERE (p:SFPermissionSet OR p:SFProfile)RETURN DISTINCT u.name AS User, p.name AS GrantedBy, obj.Label AS ObjectUsers with ModifyAll on custom objects (bypass sharing)
MATCH (u:SFUser)-[:AssignedProfile|AssignedPermissionSet|AssignedPermissionSetGroup|HasPermissionSet|IncludesPermissionSet*1..5]->(p)-[:CanModifyAll]->(obj:SFSObject)WHERE obj.IsCustom = TrueAND (p:SFPermissionSet OR p:SFProfile)RETURN DISTINCT u.name AS User, obj.name AS CustomObject, obj.LabelORDER BY u.name, obj.nameUsers with suspicious permission combo: Create + Delete + ModifyAll on same object
MATCH (u:SFUser)-[:AssignedProfile|AssignedPermissionSet|AssignedPermissionSetGroup|HasPermissionSet|IncludesPermissionSet*1..5]->(p1)-[:CanCreate]->(obj:SFSObject), (u)-[:AssignedProfile|AssignedPermissionSet|AssignedPermissionSetGroup|HasPermissionSet|IncludesPermissionSet*1..5]->(p2)-[:CanDelete]->(obj), (u)-[:AssignedProfile|AssignedPermissionSet|AssignedPermissionSetGroup|HasPermissionSet|IncludesPermissionSet*1..5]->(p3)-[:CanModifyAll]->(obj)WHERE (p1:SFPermissionSet OR p1:SFProfile)AND (p2:SFPermissionSet OR p2:SFProfile)AND (p3:SFPermissionSet OR p3:SFProfile)RETURN DISTINCT u.name, obj.name, obj.LabelField permission (FLS) queries
Shortest path from a specific user to a specific field
MATCH p=shortestPath((u:SFUser)-[:AssignedProfile|AssignedPermissionSet|AssignedPermissionSetGroup|HasPermissionSet|IncludesPermissionSet|CanCreate|CanRead|CanEdit|CanDelete|CanViewAll|CanModifyAll|IsVisible|ReadOnly|Contains*1..10]->(f:SFField))WHERE u.name = "PETER WIENER" AND f.name = "SECRETDATA__C.HIGHLYSENSITIVEFIELD__C" AND u <> fRETURN pLIMIT 1000Shortest path to crown jewel fields (from any user)
MATCH p=shortestPath((u:SFUser)-[:AssignedProfile|AssignedPermissionSet|AssignedPermissionSetGroup|HasPermissionSet|IncludesPermissionSet|CanCreate|CanRead|CanEdit|CanDelete|CanViewAll|CanModifyAll|IsVisible|ReadOnly|Contains*1..10]->(f:SFField))WHERE f.name = "SECRETDATA__C.HIGHLYSENSITIVEFIELD__C" AND u <> fRETURN pLIMIT 1000All users with access to any custom fields
MATCH p=(u:SFUser)-[:AssignedProfile|AssignedPermissionSet|AssignedPermissionSetGroup|HasPermissionSet|IncludesPermissionSet|CanCreate|CanRead|CanEdit|CanDelete|CanViewAll|CanModifyAll|IsVisible|ReadOnly|Contains*1..10]->(f:SFField)WHERE f.name ENDS WITH '__C' AND u <> fRETURN pLIMIT 1000Role hierarchy queries
All users and their role assignments
MATCH (u:SFUser)-[h:HasRole]->(r:SFRole)RETURN u, h, rRole hierarchy tree (up to 5 levels)
MATCH path = (child:SFRole)-[:InheritsRole*1..5]->(ancestor:SFRole)WHERE NOT (ancestor)-[:InheritsRole]->()RETURN pathPortal role users
MATCH (u:SFUser)-[:HasRole]->(r:SFRole)WHERE r.IsPortalRole = TrueRETURN u, rTransitive object access via role hierarchy
These queries surface the scenario where a subordinate user holds an explicit object permission (e.g. CanRead on SECRETDATA__C), and a senior user above them in the role hierarchy therefore gains implicit visibility to records on that same object — without holding any explicit permission to that object themselves.
All transitive access paths — senior users, their role chain, subordinates, and the objects subordinates have permissions on
MATCH path = (senior:SFUser)-[:HasRole]->(seniorRole:SFRole)-[:InheritsRole*1..5]->(juniorRole:SFRole)<-[:HasRole]-(junior:SFUser)-[:AssignedProfile|AssignedPermissionSet]->(ps)-[:CanRead|CanCreate|CanEdit|CanDelete|CanViewAll|CanModifyAll]->(obj:SFSObject)WHERE senior <> junior AND (ps:SFProfile OR ps:SFPermissionSet) AND NOT obj.InternalSharingModel IN ["Public Read/Write", "ReadWrite"]RETURN pathLIMIT 200Scope to a specific senior user — all subordinates and objects that senior can view via role hierarchy
MATCH path = (senior:SFUser)-[:HasRole]->(seniorRole:SFRole)-[:InheritsRole*1..5]->(juniorRole:SFRole)<-[:HasRole]-(junior:SFUser)-[:AssignedProfile|AssignedPermissionSet]->(ps)-[:CanRead|CanCreate|CanEdit|CanDelete|CanViewAll|CanModifyAll]->(obj:SFSObject)WHERE senior.name = "TONY STARK" AND (ps:SFProfile OR ps:SFPermissionSet)RETURN pathLIMIT 200Scope to a specific senior/junior pair — confirm one senior inherits access to a specific subordinate’s objects
MATCH path = (senior:SFUser)-[:HasRole]->(seniorRole:SFRole)-[:InheritsRole*1..5]->(juniorRole:SFRole)<-[:HasRole]-(junior:SFUser)-[:AssignedProfile|AssignedPermissionSet]->(ps)-[:CanRead|CanCreate|CanEdit|CanDelete|CanViewAll|CanModifyAll]->(obj:SFSObject)WHERE senior.name = "SENIOR USER NAME" AND junior.name = "JUNIOR USER NAME" AND (ps:SFProfile OR ps:SFPermissionSet)RETURN pathLIMIT 200Effective access profile
The complete set of permissions a single user holds — combining all explicit grants (object CRUD, field-level security, system permissions, connected app authorization) reached through any assignment chain up to 4 degrees, plus implicit record visibility gained through the role hierarchy. Together these two queries constitute a full effective-access audit for a user.
System and app permissions only (high-signal, no object/field noise)
Use this for a clean view of what the user can do administratively. Excludes object CRUD and field-level edges, which can produce hundreds of nodes and obscure the signal in the BloodHound UI.
MATCH path = (u:SFUser)-[:AssignedProfile|AssignedPermissionSet|AssignedPermissionSetGroup|HasPermissionSet|IncludesPermissionSet*1..4]->(ps)-[access:ModifyAllData|ViewAllData|ViewSetup|ManageUsers|ManageRoles|ManageSharing|ManageProfilesPermissionsets|AuthorApex|CustomizeApplication|ApiEnabled|EditTask|EditEvent|ManageTranslation|CanAuthorize]->(target)WHERE u.name = "PETER WIENER" AND (ps:SFProfile OR ps:SFPermissionSet OR ps:SFPermissionSetGroup)RETURN pathLIMIT 500Full-fidelity — system permissions plus object CRUD and field-level security
Adds object CRUD (CanCreate through CanModifyAll) and field visibility (IsVisible, ReadOnly) on top of the system permissions above. Expect a dense graph for users with broad profiles — reduce LIMIT or add an AND target:SFSObject / AND target:SFField filter if needed.
MATCH path = (u:SFUser)-[:AssignedProfile|AssignedPermissionSet|AssignedPermissionSetGroup|HasPermissionSet|IncludesPermissionSet*1..4]->(ps)-[access:CanCreate|CanRead|CanEdit|CanDelete|CanViewAll|CanModifyAll|IsVisible|ReadOnly|ModifyAllData|ViewAllData|ViewSetup|ManageUsers|ManageRoles|ManageSharing|ManageProfilesPermissionsets|AuthorApex|CustomizeApplication|ApiEnabled|EditTask|EditEvent|ManageTranslation|CanAuthorize]->(target)WHERE u.name = "PETER WIENER" AND (ps:SFProfile OR ps:SFPermissionSet OR ps:SFPermissionSetGroup)RETURN pathLIMIT 500Group & queue queries
All Public Groups
MATCH (g:SFGroup) RETURN gPublic Group membership (including nested groups)
MATCH (g:SFGroup)-[h:HasMember]->(m)RETURN g, h, mUsers in a specific Public Group (direct and via nested groups, up to 3 hops)
MATCH path = (g:SFGroup {name: 'KaiberSecInternalUsers'})-[:HasMember*1..3]->(u:SFUser)RETURN pathAll Queues
MATCH (q:SFQueue) RETURN qQueue members
MATCH (q:SFQueue)-[h:HasMember]->(m)RETURN q, h, mConnected App queries
All Connected Apps
MATCH (app:SFConnectedApp) RETURN appConnected Apps and their creators
MATCH p = (app:SFConnectedApp)-[:CreatedBy]->(u:SFUser)RETURN pWhich admin created the most apps?
MATCH (app:SFConnectedApp)-[:CreatedBy]->(u:SFUser)RETURN u.name AS Admin, count(app) AS AppCountORDER BY AppCount DESCWhich Profiles/PermissionSets can authorise which Connected Apps?
MATCH p = (ps)-[:CanAuthorize]->(app:SFConnectedApp)RETURN pComplete attack path: user → Connected App authorisation
MATCH path = (u:SFUser)-[:AssignedProfile|AssignedPermissionSet*1..2]->(ps)-[:CanAuthorize]->(app:SFConnectedApp)RETURN pathConnected Apps that allow self-authorisation (security risk)
MATCH (app:SFConnectedApp)WHERE app.AdminApprovedUsersOnly = FalseRETURN app.name, app.AdminApprovedUsersOnly, app.CreatedDateSharing model queries
Objects with most restrictive sharing (Private)
MATCH (obj:SFSObject)WHERE obj.InternalSharingModel = "Private"RETURN objORDER BY obj.nameLIMIT 50Custom objects with public access (potential data leak)
MATCH (obj:SFSObject)WHERE obj.IsCustom = True AND obj.InternalSharingModel IN ["Public Read/Write", "ReadWrite", "Public Read Only", "Read"]RETURN objSharing model mismatch (internal vs external)
MATCH (obj:SFSObject)WHERE obj.InternalSharingModel <> obj.ExternalSharingModelRETURN objLIMIT 50Generic counts
MATCH (m:SFUser) RETURN count(m) AS UsersMATCH (m:SFPermissionSet) RETURN count(m) AS PermissionSetsMATCH (m:SFProfile) RETURN count(m) AS ProfilesAll nodes and relationships (expensive — limit appropriately)
MATCH (n)OPTIONAL MATCH (n)-[r]->(m)RETURN n, r, mLIMIT 500