Skip to content

BloodHound CE

BloodHound CE (Community Edition) is the primary analysis surface for SFHound graphs. SFHound emits a BloodHound OpenGraph v2-compatible JSON file that BloodHound CE ingests natively.


Prerequisites

  • BloodHound CE running (Docker Compose or standalone)
  • SFHound collector has produced an output JSON in ./opengraph_output/

Loading data

Run the collector with --auto-ingest:

Terminal window
python sfhound.py --auto-ingest

What happens:

  1. Validates the JSON against the OpenGraph schema.
  2. Checks for stuck/active jobs — aborts if any exist.
  3. Creates a BloodHound file-upload job, uploads the graph, and starts ingestion.
  4. Polls until ingestion completes (prints status every 15 s).
  5. Prints completed task details including any errors or warnings.

All credentials can be passed via config.yaml or command line:

Terminal window
python sfhound.py --auto-ingest \
--bh-url http://127.0.0.1:8080 \
--bh-username admin \
--bh-password YOUR_BLOODHOUND_PASSWORD

Register custom icons

SFHound ships custom BloodHound node icons for all Salesforce node types. Register them once after BloodHound is running:

Terminal window
python examples/post_custom_icons.py

This POSTs SVG icon definitions for SFUser, SFProfile, SFPermissionSet, SFPermissionSetGroup, SFRole, SFGroup, SFQueue, SFConnectedApp, SFSObject, SFField, and SFOrganization to the BloodHound API.


Configure Tier Zero rules

System-level compromise

Add this Cypher rule in BloodHound CE → Configuration → Tier Zero → Add Cypher Rule:

MATCH (u:SFUser)-[:AssignedProfile|AssignedPermissionSet]->(ps)-[:ModifyAllData|ManageUsers|ManageProfilesPermissionsets|AuthorApex|CustomizeApplication|ManageSharing]->(:SFOrganization)
WHERE ps:SFProfile OR ps:SFPermissionSet
RETURN DISTINCT u;

Crown-jewel object access

MATCH (u:SFUser)-[:AssignedProfile|AssignedPermissionSet|AssignedPermissionSetGroup|HasPermissionSet|IncludesPermissionSet*1..5]->(ps)-[:CanCreate|CanRead|CanEdit|CanDelete|CanViewAll|CanModifyAll]->(obj:SFSObject)
WHERE obj.name IN ["SECRETDATA__C", "SENSITIVEDATA__C", "HIDDENDATA__C"]
AND (ps:SFPermissionSet OR ps:SFProfile)
RETURN DISTINCT u;

Crown-jewel field access

MATCH (u:SFUser)-[:AssignedProfile|AssignedPermissionSet|AssignedPermissionSetGroup|HasPermissionSet|IncludesPermissionSet|CanCreate|CanRead|CanEdit|CanDelete|CanViewAll|CanModifyAll|IsVisible|ReadOnly|Contains*1..10]->(f:SFField)
WHERE f.name IN ["SECRETDATA__C.HIGHLYSENSITIVEFIELD__C","SECRETDATA__C.OTHERSENSITIVEFIELD__C"]
RETURN DISTINCT u
LIMIT 1000;

Browsing the graph

Useful starting points

  • Search for SFOrganization to see all system permissions entering the org node.
  • Search for a specific user by Username or name to explore their assignment chain.
  • Use the Pathfinding tab to find the shortest path between any two nodes.

Node properties panel

Click any node or edge to view its properties. Named edges (system permissions, CRUD, FLS, assignment) display:

PropertyWhat you see
GeneralPlain-language description of the permission
AbuseInfoHow an attacker exploits this edge
RemediationInfoSteps to restrict access + SOQL queries
OPSECLogging gaps an attacker could exploit
ReferencesMITRE ATT&CK mappings + Salesforce docs

BloodHound CE Docker Compose quickstart

If you don’t already have BloodHound CE running:

Terminal window
curl -L https://ghst.ly/getbhce | docker compose -f - up

Default credentials: admin / check the container logs for the initial password on first run.

BloodHound CE is available at http://127.0.0.1:8080.


Troubleshooting

IssueCauseFix
Auto-ingest aborts: active job detectedA previous ingestion is stuckClear it in Administration → File Ingest
Nodes appear but edges are missingPartial extraction from limited userRe-run with a more privileged user; see Quickstart
Custom icons not showingpost_custom_icons.py not run, or run before BloodHound was readyRe-run the script after BloodHound is fully started
400 Bad Request on uploadJSON does not validate against OpenGraph schemaCheck SFHound version; run with --debug