Quickstart
This guide gets you from zero to a loaded BloodHound graph in three steps.
Prerequisites
| Requirement | Version |
|---|---|
| Python | 3.8 or higher |
| pip | Any recent version |
| RAM | 2 GB minimum; 4 GB+ for large orgs |
| OS | Windows 10/11, Ubuntu 20.04+, macOS |
| BloodHound CE | Running locally or remotely (for auto-ingest) |
Step 1 — Install SFHound
git clone https://github.com/Khadinxc/sfhound.gitcd sfhound/sf-opengraphpip install -r requirements.txtStep 2 — Create a Salesforce Connected App
SFHound authenticates via the JWT Bearer OAuth flow. You need a Connected App with a certificate uploaded.
2a — Generate the JWT certificate
openssl genrsa -out salesforce_jwt.key 2048openssl req -new -x509 -key salesforce_jwt.key -out salesforce_jwt.crt -days 365Keep salesforce_jwt.key secret — it is your private key.
2b — Create the Connected App in Salesforce
- Navigate to Setup → App Manager → New Connected App.
- Enable OAuth Settings and set the callback URL to
https://login.salesforce.com/services/oauth2/callback. - Enable Use digital signatures and upload
salesforce_jwt.crt. - Add OAuth scopes: api and refresh_token, offline_access.
- Save and wait 2–10 minutes for the changes to propagate.
2c — Pre-authorise your integration user
- Navigate to Setup → Connected Apps → Manage Connected Apps → sfhound → Edit Policies.
- Set Permitted Users to
Admin approved users are pre-authorized. - Under Manage Profiles or Manage Permission Sets, add the Profile or Permission Set assigned to your integration user.
Step 3 — Assign minimum permissions
Assign the System Administrator profile, or a custom profile with:
| Permission | Purpose |
|---|---|
| API Enabled | Required for REST/Tooling API access |
| View Setup and Configuration | Query Profiles, PermissionSets, Roles, ConnectedApps |
| View All Data | Query Users, Groups, all record data |
This produces a complete graph with all nodes and edges populated.
Minimum permissions for a partial extraction:
| Permission | Purpose |
|---|---|
| API Enabled | Required |
| View Setup and Configuration | Required |
| Read on User, Group, PermissionSetAssignment, GroupMember | Minimum data access |
The graph will be incomplete but still useful for analysing visible attack paths.
To create a custom profile:
- Setup → Profiles → Clone “Standard User”
- Enable
API EnabledandView Setup and Configuration - Grant Read on:
User,Group,PermissionSetAssignment,GroupMember - Assign to your integration user
Step 4 — Configure config.yaml
cp config.yaml.example config.yamlEdit config.yaml with your values:
salesforce: client_id: "YOUR_CONNECTED_APP_CONSUMER_KEY" username: "your.integration.user@example.com" private_key_path: "./salesforce_jwt.key" login_url: "https://login.salesforce.com" # Use https://test.salesforce.com for sandboxes api_version: "v56.0"
# Optional: BloodHound CE auto-ingestbloodhound: url: "http://127.0.0.1:8080" username: "admin" password: "YOUR_BLOODHOUND_PASSWORD" auto-ingest: false
env: output_path: "./opengraph_output"Step 5 — Run the collector
cd sf-opengraphpython sfhound.pyThe collector produces a BloodHound-compatible JSON file in ./opengraph_output/.
Auto-ingest (recommended)
Pass --auto-ingest to extract and upload in a single command:
python sfhound.py --auto-ingestWhat happens:
- Validates the exported JSON against the OpenGraph schema.
- Checks for stuck/active jobs in BloodHound and aborts if any exist.
- Creates a file-upload job, uploads the graph, and signals ingestion start.
- Polls until ingestion completes, printing status every 15 seconds.
- Prints completed task details including any errors or warnings.
All credentials on the command line
python sfhound.py --auto-ingest \ --client-id YOUR_CLIENT_ID \ --username user@example.com \ --private-key ./salesforce_jwt.key \ --bh-url http://127.0.0.1:8080 \ --bh-username admin \ --bh-password YOUR_BLOODHOUND_PASSWORDStep 6 — Register custom icons (optional)
SFHound ships custom BloodHound node icons for Salesforce node types. Register them once:
python examples/post_custom_icons.pyStep 7 — Set up Tier Zero Privilege Zones
BloodHound CE lets you define Tier Zero cypher rules to automatically tag high-value principals. Set these up once after ingestion.
System-level permissions (org compromise)
Tag any user with a direct path to a system permission capable of compromising the org:
MATCH (u:SFUser)-[:AssignedProfile|AssignedPermissionSet]->(ps)-[:ModifyAllData|ManageUsers|ManageProfilesPermissionsets|AuthorApex|CustomizeApplication|ManageSharing]->(:SFOrganization)WHERE ps:SFProfile OR ps:SFPermissionSetRETURN DISTINCT u;Access to highest-value objects
Tag users who can read or modify your most sensitive SObjects. Extend obj.name IN [...] with your own object API names:
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"] AND (ps:SFPermissionSet OR ps:SFProfile)RETURN DISTINCT u;Access to highest-value fields
Tag users with visibility into specific sensitive fields:
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","SENSITIVEDATA__C.HIGHLYSENSITIVEFIELD__C"]RETURN DISTINCT uLIMIT 1000;In BloodHound CE, navigate to Settings → Tier Zero and add each query as a Cypher rule type.
Step 8 — Explore the graph
Open BloodHound CE and try your first query:
-- All users and their direct permission set assignmentsMATCH (u:SFUser)-[:AssignedProfile|AssignedPermissionSet]->(ps)RETURN u, psLIMIT 100Continue to the Cypher Queries reference for a full library of analysis queries.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
JWT authentication failed | Clock skew or cert mismatch | Ensure your system clock is accurate; re-upload salesforce_jwt.crt |
INVALID_SESSION_ID during collection | Token expired mid-run | Re-run; check if IP restrictions are blocking the runner |
Empty opengraph_output/ | Permission error on output path | Check that the output directory is writable |
BloodHound reports dangling edges | Partial extraction from limited user | Expected; use a privileged user for a complete graph |
Auto-ingest aborts with active job | A previous import is still running | Wait for it to finish or clear it in the BloodHound UI |