Skip to content

Quickstart

This guide gets you from zero to a loaded BloodHound graph in three steps.

Prerequisites

RequirementVersion
Python3.8 or higher
pipAny recent version
RAM2 GB minimum; 4 GB+ for large orgs
OSWindows 10/11, Ubuntu 20.04+, macOS
BloodHound CERunning locally or remotely (for auto-ingest)

Step 1 — Install SFHound

Terminal window
git clone https://github.com/Khadinxc/sfhound.git
cd sfhound/sf-opengraph
pip install -r requirements.txt

Step 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

Terminal window
openssl genrsa -out salesforce_jwt.key 2048
openssl req -new -x509 -key salesforce_jwt.key -out salesforce_jwt.crt -days 365

Keep salesforce_jwt.key secret — it is your private key.

2b — Create the Connected App in Salesforce

  1. Navigate to Setup → App Manager → New Connected App.
  2. Enable OAuth Settings and set the callback URL to https://login.salesforce.com/services/oauth2/callback.
  3. Enable Use digital signatures and upload salesforce_jwt.crt.
  4. Add OAuth scopes: api and refresh_token, offline_access.
  5. Save and wait 2–10 minutes for the changes to propagate.

2c — Pre-authorise your integration user

  1. Navigate to Setup → Connected Apps → Manage Connected Apps → sfhound → Edit Policies.
  2. Set Permitted Users to Admin approved users are pre-authorized.
  3. 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:

PermissionPurpose
API EnabledRequired for REST/Tooling API access
View Setup and ConfigurationQuery Profiles, PermissionSets, Roles, ConnectedApps
View All DataQuery Users, Groups, all record data

This produces a complete graph with all nodes and edges populated.


Step 4 — Configure config.yaml

Terminal window
cp config.yaml.example config.yaml

Edit 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-ingest
bloodhound:
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

Terminal window
cd sf-opengraph
python sfhound.py

The collector produces a BloodHound-compatible JSON file in ./opengraph_output/.

Pass --auto-ingest to extract and upload in a single command:

Terminal window
python sfhound.py --auto-ingest

What happens:

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

All credentials on the command line

Terminal window
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_PASSWORD

Step 6 — Register custom icons (optional)

SFHound ships custom BloodHound node icons for Salesforce node types. Register them once:

Terminal window
python examples/post_custom_icons.py

Step 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:SFPermissionSet
RETURN 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 u
LIMIT 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 assignments
MATCH (u:SFUser)-[:AssignedProfile|AssignedPermissionSet]->(ps)
RETURN u, ps
LIMIT 100

Continue to the Cypher Queries reference for a full library of analysis queries.


Troubleshooting

SymptomLikely causeFix
JWT authentication failedClock skew or cert mismatchEnsure your system clock is accurate; re-upload salesforce_jwt.crt
INVALID_SESSION_ID during collectionToken expired mid-runRe-run; check if IP restrictions are blocking the runner
Empty opengraph_output/Permission error on output pathCheck that the output directory is writable
BloodHound reports dangling edgesPartial extraction from limited userExpected; use a privileged user for a complete graph
Auto-ingest aborts with active jobA previous import is still runningWait for it to finish or clear it in the BloodHound UI