BloodHound CE via Decepticon's bhce_* Tools
Decepticon ships a sidecar BloodHound Community Edition v9.2.2 stack
(see docs/adr/0005-bloodhound-via-bhce-rest-client.md). Three
@tool wrappers expose it to the agent:
| tool |
what it does |
bhce_status |
Confirm BHCE is healthy and our HMAC token authenticates. Always call this first when an AD task starts. |
bhce_cypher |
Run any Cypher query against BHCE's graph. Mutations are off by default. |
bhce_ingest_zip |
Push a SharpHound .zip into BHCE — 3-step file-upload flow + polling until BHCE finishes parse + ESC* analysis. |
Use these instead of the legacy bh_ingest_zip / dcsync_check /
delegation_audit / gpo_audit / adcs_audit family — those are
the in-house port and are being retired per ADR-0005.
Why we use BHCE rather than our own ingest
BHCE's PostProcessedRelationships Go pipeline emits every edge a
red-team operator expects: ADCSESC1-13, GoldenCert, DCSync,
TrustedForNTAuth, IssuedSignedBy, CoerceAndRelayNTLMTo*,
HasSIDHistory, HasTrustKeys, SyncLAPSPassword, … The list is in
graphschema/ad/ad.go::PostProcessedRelationships() in the BHCE
source. We deliberately do not re-implement these in our
codebase; the agent leans on BHCE's analyzer instead.
The Decepticon KGStore still owns web, cloud, and smart-contract
findings, and stays canonical for cross-domain chain planning.
BHCE is the AD layer.
End-to-end loop the agent should follow
Health check — bhce_status(). Verify version.data.server_version matches the deployed v9.2.2 and self.data.principal_name is non-empty. If the diagnostic mentions BHCE_URL / BHCE_TOKEN_*, the sidecar is offline or the token has been revoked — stop and report.
Ingest — for every SharpHound collection drop:
bhce_ingest_zip(path="/abs/path/to/20260605_lab.zip")
Expected result envelope: {job_id, terminal_status, last_payload, elapsed_seconds}. terminal_status must be one of
Complete, PartiallyComplete, Failed, Cancelled. Anything else
(an error field, missing terminal_status) means BHCE never closed
the job — surface the error to the operator rather than continuing
with stale data.
Walk the graph with bhce_cypher. Some battle-tested starting
queries (BHCE node labels — User, Computer, Group, Domain,
GPO, OU, CertTemplate, EnterpriseCA, RootCA, AIACA,
NTAuthStore, IssuancePolicy):
All Domain Admins (sanity check that ingest landed):
MATCH (g:Group)
WHERE g.objectid ENDS WITH '-512'
MATCH (n)-[:MemberOf*1..]->(g)
RETURN n.name, labels(n)
Shortest path from a foothold to a Tier-Zero asset:
MATCH p = shortestPath(
(n {objectid: $foothold_sid})-[*1..15]->(t {system_tags: 'admin_tier_0'})
)
RETURN p
ADCS escalation paths (BHCE post-processes ESC* edges, so the
agent never has to derive them):
MATCH p = (u {objectid: $foothold_sid})
-[:MemberOf|ADCSESC1|ADCSESC3|ADCSESC4|ADCSESC6a|ADCSESC6b|
ADCSESC9a|ADCSESC9b|ADCSESC10a|ADCSESC10b|ADCSESC13|
Enroll|AutoEnroll|GenericAll*1..10]->
(t:Domain)
RETURN p LIMIT 10
GoldenCert opportunities (CA + NTAuthStore + Domain triangle):
MATCH (ca:EnterpriseCA)-[:TrustedForNTAuth]->(:NTAuthStore)
MATCH (ca)-[:GoldenCert]->(d:Domain)
RETURN ca.name, d.name
DCSync candidates:
MATCH (n)-[:DCSync]->(d:Domain)
RETURN n.name, labels(n), d.name
Cross-domain plays — when an AD path terminates and the chain
needs to cross into the Decepticon KGStore (web exploitation, cloud
pivots, smart-contract findings), hand the BHCE finding off to the
chain planner. Do not try to write web/cloud nodes into BHCE; BHCE
is AD-only.
Common failure modes
bhce_status returns a BHCE_URL diagnostic — the sidecar
isn't running (docker compose up -d bhce-neo4j bhce) or the
agent's environment is missing BHCE_TOKEN_ID / BHCE_TOKEN_KEY.
bhce_cypher returns 401 signature digest mismatch — clock
skew. BHCE enforces ±1 hour (cmd/api/src/api/auth.go:276-296).
Check the container time before opening a wider investigation.
bhce_ingest_zip reports terminal_status: Failed — the ZIP
is corrupt or BHCE rejected an unknown JSON schema version
(meta.version). Look in last_payload for the BHCE-side reason.
- An expected ADCS edge is missing after ingest — BHCE only
derives ESC* / GoldenCert / DCSync at analysis time, which runs
after ingest closes. Wait for
terminal_status (the tool already
polls until terminal) rather than re-running cypher in a busy loop.
Where authoritative information lives
- BHCE source code:
github.com/SpecterOps/BloodHound (current
release v9.2.2, 2026-06-01). This is the single source of truth
for every edge, post-process algorithm, and Cypher passthrough
guard. When the upstream changes the schema, the agent's queries
here have to follow.
- Official BHCE REST API spec: shipped at
packages/go/openapi/src/openapi.yaml in the BHCE repo, also
reachable at runtime via GET /api/v2/spec on the sidecar.
- SpecterOps documentation site:
https://bloodhound.specterops.io — official methodology, AD / Azure
/ ADCS guides, and the canonical attack-path query library.
Consult it directly for novel attack patterns; we deliberately
avoid vendoring it here so the agent always reads the latest.
- ADR:
docs/adr/0005-bloodhound-via-bhce-rest-client.md — why
the agent talks to a sidecar BHCE instead of using the in-house
port that PRs #560..#578 built.
1---2name: bloodhound-bhce3description: Operate BloodHound Community Edition v9.2.2 via Decepticon's bhce_* tools — health check, Cypher passthrough, SharpHound ZIP ingest. Replaces the in-house ingest + ESC* post-process pipeline per ADR-0005.4---56# BloodHound CE via Decepticon's `bhce_*` Tools78Decepticon ships a sidecar BloodHound Community Edition v9.2.2 stack9(see `docs/adr/0005-bloodhound-via-bhce-rest-client.md`). Three10`@tool` wrappers expose it to the agent:1112| tool | what it does |13|---|---|14| `bhce_status` | Confirm BHCE is healthy and our HMAC token authenticates. Always call this first when an AD task starts. |15| `bhce_cypher` | Run any Cypher query against BHCE's graph. Mutations are off by default. |16| `bhce_ingest_zip` | Push a SharpHound `.zip` into BHCE — 3-step `file-upload` flow + polling until BHCE finishes parse + ESC* analysis. |1718Use these instead of the legacy `bh_ingest_zip` / `dcsync_check` /19`delegation_audit` / `gpo_audit` / `adcs_audit` family — those are20the in-house port and are being retired per ADR-0005.2122## Why we use BHCE rather than our own ingest2324BHCE's `PostProcessedRelationships` Go pipeline emits every edge a25red-team operator expects: `ADCSESC1-13`, `GoldenCert`, `DCSync`,26`TrustedForNTAuth`, `IssuedSignedBy`, `CoerceAndRelayNTLMTo*`,27`HasSIDHistory`, `HasTrustKeys`, `SyncLAPSPassword`, … The list is in28`graphschema/ad/ad.go::PostProcessedRelationships()` in the BHCE29source. We deliberately do **not** re-implement these in our30codebase; the agent leans on BHCE's analyzer instead.3132The Decepticon KGStore still owns web, cloud, and smart-contract33findings, and stays canonical for cross-domain chain planning.34BHCE is the AD layer.3536## End-to-end loop the agent should follow37381. **Health check** — `bhce_status()`. Verify `version.data.server_version` matches the deployed v9.2.2 and `self.data.principal_name` is non-empty. If the diagnostic mentions `BHCE_URL` / `BHCE_TOKEN_*`, the sidecar is offline or the token has been revoked — stop and report.39402. **Ingest** — for every SharpHound collection drop:41 ```42 bhce_ingest_zip(path="/abs/path/to/20260605_lab.zip")43 ```44 Expected result envelope: `{job_id, terminal_status, last_payload, elapsed_seconds}`. `terminal_status` must be one of45 `Complete`, `PartiallyComplete`, `Failed`, `Cancelled`. Anything else46 (an `error` field, missing terminal_status) means BHCE never closed47 the job — surface the error to the operator rather than continuing48 with stale data.49503. **Walk the graph** with `bhce_cypher`. Some battle-tested starting51 queries (BHCE node labels — `User`, `Computer`, `Group`, `Domain`,52 `GPO`, `OU`, `CertTemplate`, `EnterpriseCA`, `RootCA`, `AIACA`,53 `NTAuthStore`, `IssuancePolicy`):5455 - **All Domain Admins** (sanity check that ingest landed):56 ```cypher57 MATCH (g:Group)58 WHERE g.objectid ENDS WITH '-512'59 MATCH (n)-[:MemberOf*1..]->(g)60 RETURN n.name, labels(n)61 ```6263 - **Shortest path from a foothold to a Tier-Zero asset**:64 ```cypher65 MATCH p = shortestPath(66 (n {objectid: $foothold_sid})-[*1..15]->(t {system_tags: 'admin_tier_0'})67 )68 RETURN p69 ```7071 - **ADCS escalation paths** (BHCE post-processes ESC* edges, so the72 agent never has to derive them):73 ```cypher74 MATCH p = (u {objectid: $foothold_sid})75 -[:MemberOf|ADCSESC1|ADCSESC3|ADCSESC4|ADCSESC6a|ADCSESC6b|76 ADCSESC9a|ADCSESC9b|ADCSESC10a|ADCSESC10b|ADCSESC13|77 Enroll|AutoEnroll|GenericAll*1..10]->78 (t:Domain)79 RETURN p LIMIT 1080 ```8182 - **GoldenCert opportunities** (CA + NTAuthStore + Domain triangle):83 ```cypher84 MATCH (ca:EnterpriseCA)-[:TrustedForNTAuth]->(:NTAuthStore)85 MATCH (ca)-[:GoldenCert]->(d:Domain)86 RETURN ca.name, d.name87 ```8889 - **DCSync candidates**:90 ```cypher91 MATCH (n)-[:DCSync]->(d:Domain)92 RETURN n.name, labels(n), d.name93 ```94954. **Cross-domain plays** — when an AD path terminates and the chain96 needs to cross into the Decepticon KGStore (web exploitation, cloud97 pivots, smart-contract findings), hand the BHCE finding off to the98 chain planner. Do not try to write web/cloud nodes into BHCE; BHCE99 is AD-only.100101## Common failure modes102103- **`bhce_status` returns a `BHCE_URL` diagnostic** — the sidecar104 isn't running (`docker compose up -d bhce-neo4j bhce`) or the105 agent's environment is missing `BHCE_TOKEN_ID` / `BHCE_TOKEN_KEY`.106- **`bhce_cypher` returns 401 `signature digest mismatch`** — clock107 skew. BHCE enforces ±1 hour (`cmd/api/src/api/auth.go:276-296`).108 Check the container time before opening a wider investigation.109- **`bhce_ingest_zip` reports `terminal_status: Failed`** — the ZIP110 is corrupt or BHCE rejected an unknown JSON schema version111 (`meta.version`). Look in `last_payload` for the BHCE-side reason.112- **An expected ADCS edge is missing after ingest** — BHCE only113 derives ESC* / GoldenCert / DCSync at analysis time, which runs114 after ingest closes. Wait for `terminal_status` (the tool already115 polls until terminal) rather than re-running cypher in a busy loop.116117## Where authoritative information lives118119- **BHCE source code**: `github.com/SpecterOps/BloodHound` (current120 release v9.2.2, 2026-06-01). This is the single source of truth121 for every edge, post-process algorithm, and Cypher passthrough122 guard. When the upstream changes the schema, the agent's queries123 here have to follow.124- **Official BHCE REST API spec**: shipped at125 `packages/go/openapi/src/openapi.yaml` in the BHCE repo, also126 reachable at runtime via `GET /api/v2/spec` on the sidecar.127- **SpecterOps documentation site**:128 https://bloodhound.specterops.io — official methodology, AD / Azure129 / ADCS guides, and the canonical attack-path query library.130 Consult it directly for novel attack patterns; we deliberately131 avoid vendoring it here so the agent always reads the latest.132- **ADR**: `docs/adr/0005-bloodhound-via-bhce-rest-client.md` — why133 the agent talks to a sidecar BHCE instead of using the in-house134 port that PRs #560..#578 built.