DOCS-17: Opengraph extension tutorial - #351
Conversation
WalkthroughAdds a complete OpenGraph extension tutorial. It covers schema, environments, node styling, principals, relationships, Entity Panel content, Privilege Zones, findings, remediation, and troubleshooting. It also updates related guidance and documentation navigation. ChangesOpenGraph extension documentation
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/opengraph/developer/extension-example.mdx`:
- Around line 107-130: Add an empty edges array to the graph object in both
copyable payload examples, including the additional payload referenced by the
review, while preserving their existing nodes data and overall structure.
- Around line 680-706: Add tutorial steps before the pathfinding instructions to
install the relationship schema and upload the conforming relationship payload,
ensuring the extension schema is installed before payload upload. In the later
validation section covering the updated finding definition, add the schema
upsert step for the schema containing relationship_findings before checking the
resulting finding.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 273e729e-4818-4aee-969b-207a8157aab1
⛔ Files ignored due to path filters (5)
docs/images/opengraph/extensions/develop/define-relationships.pngis excluded by!**/*.pngdocs/images/opengraph/extensions/develop/display-kind.pngis excluded by!**/*.pngdocs/images/opengraph/extensions/develop/environment-selector.pngis excluded by!**/*.pngdocs/images/opengraph/extensions/develop/findings-and-remediation.pngis excluded by!**/*.pngdocs/images/opengraph/extensions/develop/full-remediation-plan.pngis excluded by!**/*.png
📒 Files selected for processing (3)
docs/docs.jsondocs/opengraph/developer/extension-example.mdxdocs/opengraph/developer/graph-definition.mdx
fa30b17 to
0707a1d
Compare
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/opengraph/developer/extension-example.mdx`:
- Around line 352-390: Correct the [!code ++:N] highlight markers in
docs/opengraph/developer/extension-example.mdx: at lines 352, 361, 370, and 379
use counts 9, 9, 9, and 12; at line 596 use 22 and at line 706 use 46. Update
only these marker spans so each covers exactly its intended added lines.
- Line 1059: Update the sentence in the relationship findings documentation so
the plural subject uses the plural verb form, changing “it does require” to
“they do require” while preserving the rest of the meaning.
- Around line 1022-1026: Restore the `info` object, including its `abuse`
section, on the `EX_HasSession` definition in the extension example, matching
the previously established content and preserving all existing fields.
- Around line 1073-1089: Update the five references in the finding details and
remediation-plan lists to use the actual anchors defined in graph-definition.mdx
for display name, short description, short remediation, long description, and
long remediation; remove links only where no corresponding target heading
exists, while preserving the visible field names.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 799bfdf3-4abc-4f8d-916b-058dff158796
⛔ Files ignored due to path filters (8)
docs/images/opengraph/extensions/develop/data-quality.pngis excluded by!**/*.pngdocs/images/opengraph/extensions/develop/define-custom-content.pngis excluded by!**/*.pngdocs/images/opengraph/extensions/develop/define-relationships.pngis excluded by!**/*.pngdocs/images/opengraph/extensions/develop/display-kind.pngis excluded by!**/*.pngdocs/images/opengraph/extensions/develop/environment-selector.pngis excluded by!**/*.pngdocs/images/opengraph/extensions/develop/extension-management.pngis excluded by!**/*.pngdocs/images/opengraph/extensions/develop/findings-and-remediation.pngis excluded by!**/*.pngdocs/images/opengraph/extensions/develop/full-remediation-plan.pngis excluded by!**/*.png
📒 Files selected for processing (3)
docs/docs.jsondocs/opengraph/developer/extension-example.mdxdocs/opengraph/developer/graph-definition.mdx
🚧 Files skipped from review as they are similar to previous changes (2)
- docs/docs.json
- docs/opengraph/developer/graph-definition.mdx
| { // [!code ++:11] | ||
| "id": "example-group-b", | ||
| "kinds": ["EX_Group"], | ||
| "properties": { | ||
| "name": "group-b", | ||
| "displayname": "Group B", | ||
| "environmentid": "example-domain-1" | ||
| } | ||
| }, | ||
| { // [!code ++:11] | ||
| "id": "example-user-c", | ||
| "kinds": ["EX_User"], | ||
| "properties": { | ||
| "name": "user-c", | ||
| "displayname": "User C", | ||
| "environmentid": "example-domain-1" | ||
| } | ||
| }, | ||
| { // [!code ++:11] | ||
| "id": "example-server-d", | ||
| "kinds": ["EX_Computer"], | ||
| "properties": { | ||
| "name": "server-d", | ||
| "displayname": "Server D", | ||
| "environmentid": "example-domain-1" | ||
| } | ||
| }, | ||
| { // [!code ++:14] | ||
| "id": "example-domain-admin", | ||
| "kinds": [ | ||
| "EX_DomainAdmin", | ||
| "EX_HighValue" | ||
| ], | ||
| "properties": { | ||
| "name": "domain-admin", | ||
| "displayname": "Domain Admin", | ||
| "environmentid": "example-domain-1" | ||
| } | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Several [!code ++:N] markers declare the wrong line span. In both code blocks the declared count exceeds the number of added lines by two, so Shiki extends each highlight past the intended object or array and into the following object or the closing brackets. Recount each block and set N to the exact number of highlighted lines.
docs/opengraph/developer/extension-example.mdx#L352-L390: change the marker at line 352 to++:9, line 361 to++:9, line 370 to++:9, and line 379 to++:12.docs/opengraph/developer/extension-example.mdx#L596-L617: change the marker at line 596 to++:22and the marker at line 706 to++:46.
📍 Affects 1 file
docs/opengraph/developer/extension-example.mdx#L352-L390(this comment)docs/opengraph/developer/extension-example.mdx#L596-L617
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/opengraph/developer/extension-example.mdx` around lines 352 - 390,
Correct the [!code ++:N] highlight markers in
docs/opengraph/developer/extension-example.mdx: at lines 352, 361, 370, and 379
use counts 9, 9, 9, and 12; at line 596 use 22 and at line 706 use 46. Update
only these marker spans so each covers exactly its intended added lines.
| { | ||
| "name": "EX_HasSession", | ||
| "description": "Session exposure where control of the source computer can lead to control of the target identity.", | ||
| "is_traversable": true | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Restore the info object on EX_HasSession.
The previous section adds an info object with an abuse section to EX_HasSession at lines 857-865. This iteration drops it. A reader who follows the tutorial in order and uploads v1.0.6 replaces the installed definition and loses the custom Abuse Entity Panel content. Every other section carries forward all prior fields, so this iteration should too.
🐛 Proposed fix
{
"name": "EX_HasSession",
"description": "Session exposure where control of the source computer can lead to control of the target identity.",
- "is_traversable": true
+ "is_traversable": true,
+ "info": {
+ "abuse": {
+ "title": "Abuse",
+ "position": 1,
+ "markdown": {
+ "content": "An attacker who controls the source computer can hijack this session to act as the target identity, including a Tier Zero identity such as Domain Admin."
+ }
+ }
+ }
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| { | |
| "name": "EX_HasSession", | |
| "description": "Session exposure where control of the source computer can lead to control of the target identity.", | |
| "is_traversable": true | |
| } | |
| { | |
| "name": "EX_HasSession", | |
| "description": "Session exposure where control of the source computer can lead to control of the target identity.", | |
| "is_traversable": true, | |
| "info": { | |
| "abuse": { | |
| "title": "Abuse", | |
| "position": 1, | |
| "markdown": { | |
| "content": "An attacker who controls the source computer can hijack this session to act as the target identity, including a Tier Zero identity such as Domain Admin." | |
| } | |
| } | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/opengraph/developer/extension-example.mdx` around lines 1022 - 1026,
Restore the `info` object, including its `abuse` section, on the `EX_HasSession`
definition in the extension example, matching the previously established content
and preserving all existing fields.
| <Tab title="Example payload"> | ||
| Use the same payload from the traversable relationship example and the same Tier Zero zone configuration from the Privilege Zone example. | ||
|
|
||
| Relationship findings do not require a separate payload object, but it does require manually running analysis after uploading the example definition for the first time. |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Fix the subject/verb agreement.
The subject is plural, so "it does require" is incorrect.
✏️ Proposed fix
- Relationship findings do not require a separate payload object, but it does require manually running analysis after uploading the example definition for the first time.
+ Relationship findings do not require a separate payload object, but they do require you to run analysis manually after you upload the example definition for the first time.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Relationship findings do not require a separate payload object, but it does require manually running analysis after uploading the example definition for the first time. | |
| Relationship findings do not require a separate payload object, but they do require you to run analysis manually after you upload the example definition for the first time. |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/opengraph/developer/extension-example.mdx` at line 1059, Update the
sentence in the relationship findings documentation so the plural subject uses
the plural verb form, changing “it does require” to “they do require” while
preserving the rest of the meaning.
| The finding details panel shows text from the following fields: | ||
|
|
||
| - [`relationship_findings.display_name`](/opengraph/developer/graph-definition#param-display-name-2) | ||
| - [`remediation.short_description`](/opengraph/developer/graph-definition#param-remediation-short-description) | ||
| - [`remediation.short_remediation`](/opengraph/developer/graph-definition#param-remediation-short-remediation) | ||
|
|
||
| <Frame> | ||
| <img | ||
| src="/images/opengraph/extensions/develop/findings-and-remediation.png" | ||
| alt="An example of how a finding appears in the BloodHound Enterprise UI for a custom extension." | ||
| /> | ||
| </Frame> | ||
|
|
||
| To see the full remediation plan, click **View/Export Full Remediation Plan** in the finding details panel. The remediation plan includes text from the following fields: | ||
|
|
||
| - [`remediation.long_description`](/opengraph/developer/graph-definition#param-remediation-long-description) | ||
| - [`remediation.long_remediation`](/opengraph/developer/graph-definition#param-remediation-long-remediation) |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Extract internal links from the tutorial and check that each target file and anchor exists.
set -uo pipefail
TUTORIAL=$(fd -t f 'extension-example.mdx' | head -n 1)
slug() {
# Mintlify-style slug: lowercase, strip punctuation except underscores/hyphens, spaces -> hyphens
printf '%s' "$1" \
| sed -E 's/`//g; s/\*\*//g' \
| tr '[:upper:]' '[:lower:]' \
| sed -E 's/[^a-z0-9_ -]//g; s/ +/-/g'
}
rg -oN --no-filename '\]\((/[^)#]*)(#[^)]+)?\)' -r '$1|$2' "$TUTORIAL" | sort -u | while IFS='|' read -r path anchor; do
target=$(fd -t f -p "${path#/}.mdx" . | head -n 1)
if [ -z "$target" ]; then
echo "MISSING FILE $path"
continue
fi
[ -z "$anchor" ] && { echo "OK $path"; continue; }
want="${anchor#\#}"
# collect heading slugs, appending -2, -3 ... for duplicates
found=$(rg -oN --no-filename '^#{2,6} +(.+)$' -r '$1' "$target" | while IFS= read -r h; do slug "$h"; done \
| awk '{c[$0]++; if (c[$0]==1) print $0; else print $0 "-" c[$0]}' \
| rg -x -- "$want")
# also accept explicit anchors/ids declared in the file (e.g. ParamField/expandable anchors)
if [ -z "$found" ] && ! rg -qF -- "$want" "$target"; then
echo "MISSING ANCHOR $path$anchor"
else
echo "OK $path$anchor"
fi
doneRepository: SpecterOps/bloodhound-docs
Length of output: 2568
Fix five broken anchor links in the documentation reference.
The snippet references five anchors in /opengraph/developer/graph-definition that do not exist in the target file:
#param-display-name-2(line 1076)#param-remediation-short-description(line 1077)#param-remediation-short-remediation(line 1078)#param-remediation-long-description(line 1085)#param-remediation-long-remediation(line 1086)
Update these links to point to the correct anchors in graph-definition.mdx, or remove the links if the target headings do not exist.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/opengraph/developer/extension-example.mdx` around lines 1073 - 1089,
Update the five references in the finding details and remediation-plan lists to
use the actual anchors defined in graph-definition.mdx for display name, short
description, short remediation, long description, and long remediation; remove
links only where no corresponding target heading exists, while preserving the
visible field names.
craigw-SO
left a comment
There was a problem hiding this comment.
Per our convo, pushing this review as-is. Realized we needed a major structural reorg and framing shift. Sending review with the things I had already identified, and going to wait for the next iteration with the changes we discussed.
| Determines whether to use the `icon` and `color` definitions for nodes of this kind in the graph. | ||
| Determines whether this node kind can control a node's visual representation in the graph. | ||
|
|
||
| BloodHound uses display kinds to decide which `icon` and `color` to show for matching nodes. Set this field to `true` when this kind should represent the node's primary visual identity in BloodHound. Set it to `false` for secondary, descriptive, or non-representational kinds that can appear on a node but should not define its icon or color. |
There was a problem hiding this comment.
I like how we describe the false case here. Should we add that it might represent a subset or sub-category? Or do we feel that is already covered by secondary, descriptive, or non-representational?
| /> | ||
|
|
||
| Environments are platform-specific groupings of nodes that BloodHound Enterprise uses to scope findings and metrics. Each environment definition specifies which node kinds represent <Tooltip tip="An entity in a system that can authenticate, be assigned permissions, and serve as a basis for risk metric calculations.">principals</Tooltip> within each environment. | ||
| Environments are platform-specific groupings of nodes that BloodHound uses to organize environment-specific data and UI selection. In BloodHound Enterprise, these same definitions also scope findings and metrics. Each environment definition specifies which node kinds represent <Tooltip tip="An entity in a system that can authenticate, be assigned permissions, and serve as a basis for risk metric calculations.">principals</Tooltip> within each environment. |
There was a problem hiding this comment.
Something about the wording of the opening sounds strange to me
Environments ... organize environment-specific data ...
This is the definition I came up with for the OG glossary:
The top-level organizing unit or scoped context that groups related graph data for analysis within a platform, analogous to constructs like an Active Directory domain, Azure tenant, enterprise, organization, or similar platform-specific boundary.
The key being that an environment is itself a node in the graph, not exactly a grouping. But other nodes are aligned to it, and we use that for grouping/aggregating purposes.
TBD, I'm not sure how well this model is going to extend to other systems.
| Represents which node type within the extension is considered an environment for organizational and analytics purposes. Must match a node kind defined in the `node_kinds` array. | ||
|
|
||
| For findings and metrics in BloodHound Enterprise, the [`graph.nodes.properties.environmentid`](/opengraph/developer/nodes#param-properties-environmentid) field for applicable nodes in the data payload must match this field. | ||
| BloodHound uses this definition to organize platform-specific environments in the UI. For findings and metrics in BloodHound Enterprise, the [`graph.nodes.properties.environmentid`](/opengraph/developer/nodes#param-properties-environmentid) field for applicable nodes in the data payload must match this field. |
There was a problem hiding this comment.
The wording here is confusing.
BloodHound uses this [evironment_kind] definition to organize platform-specific environments
It just seems circular and ambiguous.
Is this what we're trying to say?
BloodHound uses environment nodes with this kind to organize and aggregate a platform's data and analytics.
For this:
For findings and metrics in BloodHound Enterprise, the
graph.nodes.properties.environmentidfield for applicable nodes in the data payload must match this field.
What are we trying to say here? That nodes must have the environment node's object ID as their environment ID in order to be aggregated and accounted for? must match this field is confusing because it sounds like we're saying graph.nodes.properties.environmentid must match environment_kind, which doesn't make sense.
| @@ -0,0 +1,1122 @@ | |||
| --- | |||
| title: Build a sample OpenGraph extension | |||
| description: Use this tutorial to learn how BloodHound renders, scopes, traverses, and reports on custom graph data. | |||
There was a problem hiding this comment.
What do we mean by "scopes"?
I think this might be more accurate:
... how BloodHound ingests, models, renders, navigates, and analyzes custom graph data.
| alt="Applies to BloodHound Enterprise and Community Edition" | ||
| /> | ||
|
|
||
| An OpenGraph extension definition schema tells BloodHound how to display, scope, and analyze custom graph data. In this tutorial, you'll build a small sample extension that models an identity control path: |
There was a problem hiding this comment.
I feel like we should open with a reference to what an extension includes, but I don't know if we actually define that elsewhere? We have the Graph Definition page, but we don't actually define that an extension is all the components necessary to extend BloodHound to model other platforms.
Explaining the components is kind of important to explain how the ext def schema and the PZ rules interrelate, in particular.
| User A -> Group B -> User C -> Server D -> Domain Admin | ||
| ``` | ||
|
|
||
| In this model, every identity or asset is a node. Every group membership, permission, and session is an edge that connects two nodes. Because BloodHound evaluates traversable edges transitively, a standard user can still control a critical identity through a chain of relationships. This is a structural failure of the least privilege principle. |
There was a problem hiding this comment.
It's not clear what we mean by "transitively".
| @@ -0,0 +1,1122 @@ | |||
| --- | |||
| title: Build a sample OpenGraph extension | |||
There was a problem hiding this comment.
I think we should make it more clear that this is a tutorial. The navbar just says "Example extension", which makes me thing "something I can look at/refer to" and not "go here to learn build process".
Wondering if we should have page title be
Tutorial: Build a Sample OpenGraph Extension
I think we want Title Case here, too.
| - Styles nodes with display kinds | ||
| - Identifies principal kinds for pathfinding and <Tooltip tip="Exposure and impact">metrics calculation</Tooltip> | ||
| - Makes relationships traversable for attack path analysis | ||
| - Adds custom Entity Panel content to a relationship kind |
| By the end of this tutorial, you'll have a sample extension definition schema and matching payload that: | ||
|
|
||
| - Defines extension metadata with the `schema` object | ||
| - Defines an environment to establish analysis scope | ||
| - Styles nodes with display kinds | ||
| - Identifies principal kinds for pathfinding and <Tooltip tip="Exposure and impact">metrics calculation</Tooltip> | ||
| - Makes relationships traversable for attack path analysis | ||
| - Adds custom Entity Panel content to a relationship kind | ||
| - Adds a Privilege Zone target for finding evaluation | ||
| - Defines relationship findings with context and remediation guidance in BloodHound Enterprise |
There was a problem hiding this comment.
Rather than the results of what this tutorial builds, I think we should speak to the learning outcomes we want for them.
For example: instead of
Defines an environment
something more like
Understand how
Environmentsorganize graph model contents and metrics
| - Adds a Privilege Zone target for finding evaluation | ||
| - Defines relationship findings with context and remediation guidance in BloodHound Enterprise | ||
|
|
||
| ## Define the schema object |
There was a problem hiding this comment.
I think the order we need is:
- Model the system (outside of BH), use
Graph Theorypage to understand how - Setup the schema framework (including
schemaobject, but also create the other top-level objects) - Define the
graph schema(define nodes and edges, according to model) - Define extension organization (
environments,principals) - Define representations (custom icons, Entity Panels)
- Define risk boundaries (PZ rules, findings)
TBH, I'm not sure if we should even mention defining custom findings. While I think our enterprise customers could technically do it, it's not a capability that we're ready to support for them.
Summary
This pull request (PR) was originally inspired by internal feedback on the extension definition schema reference published for the OpenGraph MVP launch (see DOCS-17).
Feedback tl;dr
Show how the schema influences BloodHound UI behavior
Pivot
While addressing that feedback, it became clear that simply adding screenshots of schema-driven behavior would be insufficient. Not only would it clutter the reference, but it would be a missed opportunity to move beyond reference content and into procedural content similar to the failed pre-MVP launch attempt for generic graphs (#155).
Out of scope
Extension developer guidance and examples for creating the following for custom extensions:
Summary by CodeRabbit
Documentation