-
Notifications
You must be signed in to change notification settings - Fork 0
OpenGraph Plugins
ADPathfinder treats OpenGraph plugin data as supplementary to a SharpHound/BloodHound baseline. Import plugin ZIPs with the baseline, or import them after the database already contains BloodHound data.
The full data contract is maintained in docs/OPENGRAPH.md. This page covers what contributors usually need when adding checks for plugin data.
Most plugin support does not require importer changes.
- Make sure the plugin emits valid OpenGraph JSON with a top-level
graphobject. - Add a datasource gate if checks should be skipped when that plugin data is absent.
- Add checks that query the plugin labels and relationships.
- Add Neo4j fixture tests for the new checks.
Use a datasource gate when several checks share a collector or when missing collector data should be reported as a clean skip instead of empty findings.
Use this quick guide to decide what to change:
| Need | Change |
|---|---|
| Query plugin labels or relationships | Add a datasource gate, a check with requires=[...], and a fixture test |
| Warn when a collector omits a relationship that checks need | Add OPENGRAPH_REQUIREMENTS to the check |
| Deduplicate collector files, enrich existing AD principals, or reserve labels | Add a CollectorManifest under modules/collectors/
|
| Define the JSON shape a collector should emit | Update docs/OPENGRAPH.md, not this quick-start page |
from checks.core import DataSource, datasource
@datasource("example_platform")
class ExamplePlatformAvailability(DataSource):
def available(self):
rows = self.query(
"MATCH (n:Example_PluginNode) RETURN count(n) AS c",
name="example_platform_availability",
)
return rows[0]["c"] > 0 if rows else FalseDatasource files in datasources/ are imported automatically at startup.
from checks.core import Check, check
@check(
risk="Medium",
category="Example Platform Exposure",
entity="computer",
data=[],
requires=["example_platform"],
section="example_platform",
)
class ExamplePlatformExposureCheck(Check):
def execute(self):
rows = self.query("""
MATCH (c:Computer)-[:Example_PluginEdge]->(target)
RETURN c.objectid AS sid, target.name AS target
""", name="example_platform_exposure")
return {
row["sid"]: self.finding(f"Plugin edge to {row['target']}")
for row in rows
if row.get("sid")
}Use data=[] for checks that perform their own Cypher lookup. Apply domain filtering inside the query when the result could otherwise cross domain boundaries.
The importer has generic OpenGraph handling for nodes, edges, endpoint matching, property filtering, and endpoint stubs. Valid plugin labels and relationships can be imported without adding collector-specific branches to BloodhoundImporter.py.
Built-in collector manifests cover policy that cannot be expressed by generic OpenGraph alone:
- SharpHound AD companion data enriches existing
User,Computer, andGroupnodes instead of creating replacement AD principals. - MSSQLHound data is anchored on
MSSQL_Serverand uses manifest-owned identity rules so duplicate server views can be suppressed by host, port, or instance.
New collector manifests are optional. Add one under modules/collectors/ only when a plugin needs importer policy such as exact identity dedupe, companion enrichment of existing AD principals, reserved labels, or custom owned label sets. Do not add collector-specific branches to BloodhoundImporter.py.
Some checks depend on relationships that may be absent from otherwise valid OpenGraph data. For example, MSSQL checks that need to map SQL servers back to AD computers require:
Computer-[:MSSQL_HostFor]->MSSQL_Server
When a check depends on a relationship like this, add an OPENGRAPH_REQUIREMENTS tuple to the check class. The importer evaluates these requirements after import and prints a warning when imported nodes are missing relationships that checks need.
from modules.opengraph_contracts import mssql_host_mapping_requirement
class ExampleMSSQLCheck(Check):
OPENGRAPH_REQUIREMENTS = (
mssql_host_mapping_requirement("example_mssql_check"),
)Relationship derivation should come from explicit collector data. Do not infer high-impact platform relationships from hostnames alone.
If a plugin emits AD principals, prefer SID-based objectid values and relationships that connect to existing SharpHound User, Computer, or Group nodes. If duplicate AD principal nodes are created with plugin-only labels, checks may query the right relationship type but still miss attack paths because the data sits on a separate node.
For generic plugin nodes, use collector-specific labels such as Jenkins_Server or Example_PluginNode. Do not use broad AD labels such as Base, User, Computer, or Group as plugin source kinds; those labels are reserved for the SharpHound baseline and AD companion handling.