Skip to content

Conversation

@ncha-syn
Copy link

@ncha-syn ncha-syn commented Oct 24, 2025

Description

This pull request add the ability to BloodHound-CE to support more edges related to local privileges via access tokens.

I have also made changes to SharpHound and BloodHound.py to support this feature.

Motivation and Context

Resolves #1998.

Why is this change required? What problem does it solve?

This allows BloodHound-CE users to quickly figure out where users have high impact local privilege on computers to better anticipate local privilege escalation.

How Has This Been Tested?

Please describe in detail how you tested your changes.
Include details of your testing environment, and the tests you ran to
see how your change affects other areas of the code, etc.

The changes have been tested manually on a lab environment. I did not change much of the code and successfully run the tests.

Screenshots (optional):

image

Types of changes

  • New feature (non-breaking change which adds functionality)

Checklist:

Summary by CodeRabbit

  • New Features
    • Added support for tracking additional Active Directory user rights and system privileges on computers.
    • Introduced new search queries for identifying computers with local privileges and interactive logon capabilities.
    • Enhanced privilege escalation analysis to detect additional privilege-based attack paths.
@github-actions
Copy link

github-actions bot commented Oct 24, 2025

CLA Assistant Lite bot All contributors have signed the CLA ✍️ ✅

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 24, 2025

Walkthrough

This PR extends the system to collect and process multiple local user privileges on machines, expanding beyond the previously supported SeRemoteInteractiveLogonRight. It adds 10 new privilege types across data models, parsing logic, schema definitions, and UI components, enabling detection of local privilege escalation vectors through user rights.

Changes

Cohort / File(s) Change Summary
Data models and type definitions
packages/go/ein/incoming_models.go, packages/go/ein/models.go
Added 11 new user-right constants (UserRightInteractiveLogon, UserRightAssignPrimaryTokenPrivilege, UserRightBackupPrivilege, UserRightCreateTokenPrivilege, UserRightDebugPrivilege, UserRightImpersonatePrivilege, UserRightLoadDriverPrivilege, UserRightManageVolumePrivilege, UserRightRestorePrivilege, UserRightTakeOwnershipPrivilege, UserRightTcbPrivilege); introduced GPOUserRights, Privilege types; extended OU struct with GPOUserRights field; created ParsedUserRightsData structure
Data conversion and parsing
cmd/api/src/services/graphify/convertors.go, packages/go/ein/ad.go
Expanded convertComputerData to handle multiple privilege cases via switch statement; extended convertOUData to parse GPOUserRights; added ParseGPOUserRights() function to translate GPO user rights into nodes and relationships with ACL-style edge mapping
Schema definitions
packages/cue/bh/ad/ad.cue, packages/go/graphschema/ad/ad.go
Added 11 new Kind declarations (InteractiveLogonRight and 10 privilege kinds); updated RelationshipKinds list to include DebugPrivilege and CreateTokenPrivilege
UI and TypeScript schema
packages/javascript/bh-shared-ui/src/graphSchema.ts, packages/javascript/bh-shared-ui/src/commonSearchesAGT.ts
Extended ActiveDirectoryRelationshipKind enum with 11 new members; added display mappings for all new kinds; added Local Privileges section to common searches with four new queries for privilege escalation analysis

Sequence Diagram

sequenceDiagram
    participant OU as OU with GPO Data
    participant Parse as ParseGPOUserRights()
    participant Map as Privilege→Kind Mapping
    participant Out as Nodes & Relationships

    OU->>Parse: GPOUserRights (members per privilege)
    Parse->>Parse: Filter privileges with members
    Parse->>Map: Map privilege names to graph kinds
    Map->>Parse: Kind mapping (e.g., DebugPrivilege)
    Parse->>Out: Create computer node (marked HasURA)
    Parse->>Out: Emit ACL-style relationships<br/>(member→computer with kind)
    Out-->>Out: Ingestible data
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

The changes are systematic additions across multiple layers with coherent patterns (new types, new schema kinds, new parsing logic, new UI elements), but heterogeneous in scope. The parsing logic in convertors.go and the ParseGPOUserRights function introduce moderate logic density. The repetitive nature of 11 similar privilege kind declarations reduces review complexity compared to purely diverse changes.

Possibly related PRs

  • #1717 — Modifies the same convertors.go file and GPO processing logic; related refactoring of container parsing and relationship postprocessing.

Suggested labels

enhancement, user interface

Suggested reviewers

  • urangel
  • wes-mil

Poem

🐰 Privileges now multiply,
Local rights across the sky,
Ten new kinds the hunters find,
Escalation paths outlined,
User rights—no more unseen!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description check ❓ Inconclusive PR description provides context and resolves issue, but the author has not completed the required checklist items, particularly test practices and documentation updates. Author should verify and mark checklist items complete: confirm all new/existing tests pass, update code comments (GoDocs/JSDocs) as needed, and confirm contributing prerequisites are met.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title "Add local privilege collection" directly summarizes the main change in the changeset. The pull request fundamentally adds support for collecting additional local privileges beyond the previously supported RemoteInteractiveLogonRight, which is exactly what the title conveys. The title is concise, specific, and clearly communicates the primary change from a developer's perspective without being vague or generic.
Out of Scope Changes Check ✅ Passed All changes in the pull request are directly related to implementing the feature described in issue #1998 for collecting local user privileges. The additions to Go data models and constants, the new ParseGPOUserRights parsing function, the schema definitions in both Cue and Go, and the UI enhancements including the new common searches are all necessary and cohesive components of the privilege collection feature. No unrelated refactoring, utility changes, or out-of-scope modifications are present.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ncha-syn
Copy link
Author

I have read the CLA Document and I hereby sign the CLA

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (3)
packages/go/ein/ad.go (2)

856-863: Set HasURA only when at least one edge is emitted for the computer

Currently a Computer node with HasURA=true is created for every AffectedComputer, even when no members exist for any mapped right. Gate HasURA on actual edge emission to avoid noisy nodes.

Apply:

 func ParseGPOUserRights(privileges GPOUserRights) ParsedUserRightsData {
   parsedData := ParsedUserRightsData{}
@@
-  for _, computer := range privileges.AffectedComputers {
-    computerNode := IngestibleNode{
-      ObjectID:    computer.ObjectIdentifier,
-      PropertyMap: map[string]any{ad.HasURA.String(): true},
-      Labels:      []graph.Kind{ad.Computer},
-    }
-    parsedData.Nodes = append(parsedData.Nodes, computerNode)
-
-    for _, privilege := range privilegesToProcess {
-      edgeType := privilegeMap[privilege.Name]
-
-      for _, member := range privilege.Members {
-        parsedData.Relationships = append(parsedData.Relationships, NewIngestibleRelationship(
+  for _, computer := range privileges.AffectedComputers {
+    hasAny := false
+    for _, privilege := range privilegesToProcess {
+      edgeType := privilegeMap[privilege.Name]
+      for _, member := range privilege.Members {
+        parsedData.Relationships = append(parsedData.Relationships, NewIngestibleRelationship(
           IngestibleEndpoint{
             Value: member.ObjectIdentifier,
             Kind:  member.Kind(),
           },
           IngestibleEndpoint{
             Value: computer.ObjectIdentifier,
             Kind:  ad.Computer,
           },
           IngestibleRel{
             RelProps: map[string]any{
               ad.IsACL.String():    false,
             },
             RelType: edgeType,
           },
         ))
+        hasAny = true
       }
     }
+    if hasAny {
+      parsedData.Nodes = append(parsedData.Nodes, IngestibleNode{
+        ObjectID:    computer.ObjectIdentifier,
+        PropertyMap: map[string]any{ad.HasURA.String(): true},
+        Labels:      []graph.Kind{ad.Computer},
+      })
+    }
   }

Also applies to: 864-885


844-855: Optional: determinism for tests

Consider sorting privilegesToProcess (by Name) and each privilege.Members (by ObjectIdentifier) to make outputs deterministic, which stabilizes tests and diffs.

cmd/api/src/services/graphify/convertors.go (1)

110-123: Set HasURA once after the loop; remove per-case duplication

Reduce repetition and minor map churn by toggling a local hasURA flag in cases and assigning the property once after processing all user rights.

Apply:

@@
-  for _, userRight := range computer.UserRights {
+  hasURA := false
+  for _, userRight := range computer.UserRights {
@@
-    case ein.UserRightInteractiveLogon:
-      converted.RelProps = append(converted.RelProps, ein.ParseUserRightData(userRight, computer, ad.InteractiveLogonRight)...)
-      baseNodeProp.PropertyMap[ad.HasURA.String()] = true
+    case ein.UserRightInteractiveLogon:
+      converted.RelProps = append(converted.RelProps, ein.ParseUserRightData(userRight, computer, ad.InteractiveLogonRight)...)
+      hasURA = true
@@
-    case ein.UserRightRemoteInteractiveLogon:
-      converted.RelProps = append(converted.RelProps, ein.ParseUserRightData(userRight, computer, ad.RemoteInteractiveLogonRight)...)
-      baseNodeProp.PropertyMap[ad.HasURA.String()] = true
+    case ein.UserRightRemoteInteractiveLogon:
+      converted.RelProps = append(converted.RelProps, ein.ParseUserRightData(userRight, computer, ad.RemoteInteractiveLogonRight)...)
+      hasURA = true
@@
-    case ein.UserRightAssignPrimaryTokenPrivilege:
+    case ein.UserRightAssignPrimaryTokenPrivilege:
       converted.RelProps = append(converted.RelProps, ein.ParseUserRightData(userRight, computer, ad.AssignPrimaryTokenPrivilege)...)
-      baseNodeProp.PropertyMap[ad.HasURA.String()] = true
+      hasURA = true
@@
-    case ein.UserRightBackupPrivilege:
+    case ein.UserRightBackupPrivilege:
       converted.RelProps = append(converted.RelProps, ein.ParseUserRightData(userRight, computer, ad.BackupPrivilege)...)
-      baseNodeProp.PropertyMap[ad.HasURA.String()] = true
+      hasURA = true
@@
-    case ein.UserRightCreateTokenPrivilege:
+    case ein.UserRightCreateTokenPrivilege:
       converted.RelProps = append(converted.RelProps, ein.ParseUserRightData(userRight, computer, ad.CreateTokenPrivilege)...)
-      baseNodeProp.PropertyMap[ad.HasURA.String()] = true
+      hasURA = true
@@
-    case ein.UserRightDebugPrivilege:
+    case ein.UserRightDebugPrivilege:
       converted.RelProps = append(converted.RelProps, ein.ParseUserRightData(userRight, computer, ad.DebugPrivilege)...)
-      baseNodeProp.PropertyMap[ad.HasURA.String()] = true
+      hasURA = true
@@
-    case ein.UserRightImpersonatePrivilege:
+    case ein.UserRightImpersonatePrivilege:
       converted.RelProps = append(converted.RelProps, ein.ParseUserRightData(userRight, computer, ad.ImpersonatePrivilege)...)
-      baseNodeProp.PropertyMap[ad.HasURA.String()] = true
+      hasURA = true
@@
-    case ein.UserRightLoadDriverPrivilege:
+    case ein.UserRightLoadDriverPrivilege:
       converted.RelProps = append(converted.RelProps, ein.ParseUserRightData(userRight, computer, ad.LoadDriverPrivilege)...)
-      baseNodeProp.PropertyMap[ad.HasURA.String()] = true
+      hasURA = true
@@
-    case ein.UserRightManageVolumePrivilege:
+    case ein.UserRightManageVolumePrivilege:
       converted.RelProps = append(converted.RelProps, ein.ParseUserRightData(userRight, computer, ad.ManageVolumePrivilege)...)
-      baseNodeProp.PropertyMap[ad.HasURA.String()] = true
+      hasURA = true
@@
-    case ein.UserRightRestorePrivilege:
+    case ein.UserRightRestorePrivilege:
       converted.RelProps = append(converted.RelProps, ein.ParseUserRightData(userRight, computer, ad.RestorePrivilege)...)
-      baseNodeProp.PropertyMap[ad.HasURA.String()] = true
+      hasURA = true
@@
-    case ein.UserRightTakeOwnershipPrivilege:
+    case ein.UserRightTakeOwnershipPrivilege:
       converted.RelProps = append(converted.RelProps, ein.ParseUserRightData(userRight, computer, ad.TakeOwnershipPrivilege)...)
-      baseNodeProp.PropertyMap[ad.HasURA.String()] = true
+      hasURA = true
@@
-    case ein.UserRightTcbPrivilege:
+    case ein.UserRightTcbPrivilege:
       converted.RelProps = append(converted.RelProps, ein.ParseUserRightData(userRight, computer, ad.TcbPrivilege)...)
-      baseNodeProp.PropertyMap[ad.HasURA.String()] = true
+      hasURA = true
     }
   }
+  if hasURA {
+    baseNodeProp.PropertyMap[ad.HasURA.String()] = true
+  }

Also applies to: 124-137, 138-152

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 77f06ef and c002fe5.

📒 Files selected for processing (8)
  • cmd/api/src/services/graphify/convertors.go (2 hunks)
  • packages/cue/bh/ad/ad.cue (2 hunks)
  • packages/go/ein/ad.go (1 hunks)
  • packages/go/ein/incoming_models.go (2 hunks)
  • packages/go/ein/models.go (1 hunks)
  • packages/go/graphschema/ad/ad.go (2 hunks)
  • packages/javascript/bh-shared-ui/src/commonSearchesAGT.ts (1 hunks)
  • packages/javascript/bh-shared-ui/src/graphSchema.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
packages/go/ein/ad.go (3)
packages/go/ein/incoming_models.go (15)
  • GPOUserRights (370-373)
  • UserRightInteractiveLogon (31-31)
  • UserRightRemoteInteractiveLogon (32-32)
  • UserRightAssignPrimaryTokenPrivilege (33-33)
  • UserRightBackupPrivilege (34-34)
  • UserRightCreateTokenPrivilege (35-35)
  • UserRightDebugPrivilege (36-36)
  • UserRightImpersonatePrivilege (37-37)
  • UserRightLoadDriverPrivilege (38-38)
  • UserRightManageVolumePrivilege (39-39)
  • UserRightRestorePrivilege (40-40)
  • UserRightTakeOwnershipPrivilege (41-41)
  • UserRightTcbPrivilege (42-42)
  • Privilege (375-378)
  • Computer (340-360)
packages/go/ein/models.go (5)
  • ParsedUserRightsData (92-95)
  • IngestibleNode (77-81)
  • NewIngestibleRelationship (22-33)
  • IngestibleEndpoint (45-49)
  • IngestibleRel (51-54)
packages/go/graphschema/ad/ad.go (17)
  • InteractiveLogonRight (82-82)
  • RemoteInteractiveLogonRight (83-83)
  • AssignPrimaryTokenPrivilege (84-84)
  • BackupPrivilege (85-85)
  • CreateTokenPrivilege (86-86)
  • DebugPrivilege (87-87)
  • ImpersonatePrivilege (88-88)
  • LoadDriverPrivilege (89-89)
  • ManageVolumePrivilege (90-90)
  • RestorePrivilege (91-91)
  • TakeOwnershipPrivilege (92-92)
  • TcbPrivilege (93-93)
  • HasURA (185-185)
  • Computer (30-30)
  • Nodes (1157-1159)
  • Relationships (1160-1162)
  • IsACL (169-169)
packages/go/ein/models.go (2)
packages/go/graphschema/ad/ad.go (2)
  • Relationships (1160-1162)
  • Nodes (1157-1159)
packages/go/graphschema/common/common.go (2)
  • Relationships (36-38)
  • Nodes (33-35)
cmd/api/src/services/graphify/convertors.go (3)
packages/go/ein/incoming_models.go (14)
  • Privilege (375-378)
  • UserRightInteractiveLogon (31-31)
  • UserRightRemoteInteractiveLogon (32-32)
  • UserRightAssignPrimaryTokenPrivilege (33-33)
  • UserRightBackupPrivilege (34-34)
  • UserRightCreateTokenPrivilege (35-35)
  • UserRightDebugPrivilege (36-36)
  • UserRightImpersonatePrivilege (37-37)
  • UserRightLoadDriverPrivilege (38-38)
  • UserRightManageVolumePrivilege (39-39)
  • UserRightRestorePrivilege (40-40)
  • UserRightTakeOwnershipPrivilege (41-41)
  • UserRightTcbPrivilege (42-42)
  • GPOUserRights (370-373)
packages/go/ein/ad.go (2)
  • ParseUserRightData (1292-1313)
  • ParseGPOUserRights (826-889)
packages/go/graphschema/ad/ad.go (15)
  • InteractiveLogonRight (82-82)
  • HasURA (185-185)
  • RemoteInteractiveLogonRight (83-83)
  • AssignPrimaryTokenPrivilege (84-84)
  • BackupPrivilege (85-85)
  • CreateTokenPrivilege (86-86)
  • DebugPrivilege (87-87)
  • ImpersonatePrivilege (88-88)
  • LoadDriverPrivilege (89-89)
  • ManageVolumePrivilege (90-90)
  • RestorePrivilege (91-91)
  • TakeOwnershipPrivilege (92-92)
  • TcbPrivilege (93-93)
  • Relationships (1160-1162)
  • Nodes (1157-1159)
🔇 Additional comments (6)
packages/go/ein/models.go (1)

92-95: Struct addition looks correct

ParsedUserRightsData mirrors ParsedLocalGroupData and aligns with downstream usage. No issues.

packages/go/ein/incoming_models.go (2)

25-43: Constants set is consistent with Windows user-right names

The constants map cleanly to the Se* right names used in collectors and parsers. Looks good.


370-379: Struct additions LGTM

GPOUserRights and Privilege shapes are minimal and sufficient. Adding OU.GPOUserRights integrates with convertors.

Also applies to: 385-386

cmd/api/src/services/graphify/convertors.go (1)

229-232: OU GPO user-rights ingestion wired correctly

Appending ParseGPOUserRights results into converted data for OUs looks good.

Please confirm the collector populates OU.GPOUserRights for your test data so this path is exercised.

packages/javascript/bh-shared-ui/src/commonSearchesAGT.ts (1)

567-592: Mirror these queries in commonSearchesAGI.ts

Header notes there are two files. Ensure the same “Local Privileges” section is added to commonSearchesAGI.ts to keep AGT/AGI parity.

packages/go/graphschema/ad/ad.go (1)

82-93: New privilege kinds added — ensure they’re actually exposed as relationships.

Constants are fine, but most are not present in Relationships(), so they won’t surface in the API until wired via CUE RelationshipKinds and codegen. Confirm intended set and regenerate Go.

Comment on lines +1469 to +1527
InteractiveLogonRight: types.#Kind & {
symbol: "InteractiveLogonRight"
schema: "active_directory"
}

RemoteInteractiveLogonRight: types.#Kind & {
symbol: "RemoteInteractiveLogonRight"
schema: "active_directory"
}

AssignPrimaryTokenPrivilege: types.#Kind & {
symbol: "AssignPrimaryTokenPrivilege"
schema: "active_directory"
}

BackupPrivilege: types.#Kind & {
symbol: "BackupPrivilege"
schema: "active_directory"
}

CreateTokenPrivilege: types.#Kind & {
symbol: "CreateTokenPrivilege"
schema: "active_directory"
}

DebugPrivilege: types.#Kind & {
symbol: "DebugPrivilege"
schema: "active_directory"
}

ImpersonatePrivilege: types.#Kind & {
symbol: "ImpersonatePrivilege"
schema: "active_directory"
}

LoadDriverPrivilege: types.#Kind & {
symbol: "LoadDriverPrivilege"
schema: "active_directory"
}

ManageVolumePrivilege: types.#Kind & {
symbol: "ManageVolumePrivilege"
schema: "active_directory"
}

RestorePrivilege: types.#Kind & {
symbol: "RestorePrivilege"
schema: "active_directory"
}

TakeOwnershipPrivilege: types.#Kind & {
symbol: "TakeOwnershipPrivilege"
schema: "active_directory"
}

TcbPrivilege: types.#Kind & {
symbol: "TcbPrivilege"
schema: "active_directory"
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

New privilege kinds defined but not wired into RelationshipKinds.

Without adding these kinds to RelationshipKinds, they won’t appear as edges anywhere. Wire them in and regenerate Go.

🤖 Prompt for AI Agents
In packages/cue/bh/ad/ad.cue around lines 1469 to 1527, several new privilege
kinds (InteractiveLogonRight, RemoteInteractiveLogonRight,
AssignPrimaryTokenPrivilege, BackupPrivilege, CreateTokenPrivilege,
DebugPrivilege, ImpersonatePrivilege, LoadDriverPrivilege,
ManageVolumePrivilege, RestorePrivilege, TakeOwnershipPrivilege, TcbPrivilege)
are defined but not added to RelationshipKinds; update the RelationshipKinds
definition to include these new kinds so they are exposed as edges, then run the
code generation step to regenerate the Go artifacts.
Comment on lines +1815 to 1817
DebugPrivilege,
CreateTokenPrivilege,
SyncLAPSPassword,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

��� Analysis chain

Add missing local-privilege relationships to RelationshipKinds (and then regenerate).

Currently only DebugPrivilege and CreateTokenPrivilege are listed. Add the rest (and optionally InteractiveLogonRight if desired). This keeps CUE → Go → TS in sync and enables ingestion/UI.

Apply this diff:

--- a/packages/cue/bh/ad/ad.cue
+++ b/packages/cue/bh/ad/ad.cue
@@
   MemberOfLocalGroup,
-  RemoteInteractiveLogonRight,
-  DebugPrivilege,
-  CreateTokenPrivilege,
+  RemoteInteractiveLogonRight,
+  InteractiveLogonRight,           // if you want local interactive explicitly modeled
+  AssignPrimaryTokenPrivilege,
+  BackupPrivilege,
+  CreateTokenPrivilege,
+  DebugPrivilege,
+  ImpersonatePrivilege,
+  LoadDriverPrivilege,
+  ManageVolumePrivilege,
+  RestorePrivilege,
+  TakeOwnershipPrivilege,
+  TcbPrivilege,
   SyncLAPSPassword,

Then regenerate the Go schema so packages/go/graphschema/ad/ad.go:Relationships() reflects this set.


I'll now generate verification shell scripts to examine the current state of the CUE file and validate the review comment.


🏁 Script executed:

#!/bin/bash
# Examine the CUE file around the specified lines
echo "=== Checking CUE file at packages/cue/bh/ad/ad.cue ==="
if [ -f "packages/cue/bh/ad/ad.cue" ]; then
  echo "File exists. Showing lines 1800-1830:"
  sed -n '1800,1830p' "packages/cue/bh/ad/ad.cue" | cat -n
else
  echo "CUE file not found at expected path"
fi

echo ""
echo "=== Searching for RelationshipKinds in CUE file ==="
grep -n "RelationshipKinds" "packages/cue/bh/ad/ad.cue" | head -20

echo ""
echo "=== Checking for Go schema file ==="
if [ -f "packages/go/graphschema/ad/ad.go" ]; then
  echo "Go file exists. Checking Relationships() function:"
  grep -n "Relationships()" "packages/go/graphschema/ad/ad.go" -A 30 | head -40
else
  echo "Go schema file not found at expected path"
  find . -name "ad.go" -path "*/graphschema/*" 2>/dev/null
fi

echo ""
echo "=== Searching for privilege definitions in CUE ==="
rg -n "(Privilege|Right)" "packages/cue/bh/ad/ad.cue" --type cue | grep -E "(Debug|CreateToken|SyncLAPS|Interactive|Assign|Backup|Impersonate|LoadDriver|ManageVolume|Restore|TakeOwnership|Tcb)" | head -20

Length of output: 7344


Let me search for more context about these privileges and any generation scripts:


🏁 Script executed:

#!/bin/bash
# Search for documentation or context about these privileges
echo "=== Searching for privilege definitions/comments in CUE file ==="
grep -n -B 5 -A 5 "Privilege\|LogonRight" "packages/cue/bh/ad/ad.cue" | grep -E "(type|#|comment|//" | head -30

echo ""
echo "=== Looking for generation scripts or comments about regeneration ==="
find . -type f \( -name "*.sh" -o -name "*.go" -o -name "*.py" -o -name "*.ts" -o -name "*.cue" \) | xargs grep -l "schema.*gen\|cue.*go\|generate" 2>/dev/null | head -10

echo ""
echo "=== Checking if there's a generate directive or comments in the Go file ==="
head -50 "packages/go/graphschema/ad/ad.go" | grep -E "//go:generate|generated|code gen"

echo ""
echo "=== Searching for any TypeScript schema definitions ==="
find . -path "*/graphschema/*" -name "*.ts" 2>/dev/null | head -5

echo ""
echo "=== Looking for additional context in CUE file around RelationshipKinds ==="
sed -n '1770,1900p' "packages/cue/bh/ad/ad.cue" | head -100

Length of output: 2431


Let me verify if these privilege names are referenced elsewhere and check for other schema files:


🏁 Script executed:

#!/bin/bash
# Search for any references to the 9 missing privileges
echo "=== Searching for references to missing privileges ==="
for priv in "InteractiveLogonRight" "AssignPrimaryTokenPrivilege" "BackupPrivilege" "ImpersonatePrivilege" "LoadDriverPrivilege" "ManageVolumePrivilege" "RestorePrivilege" "TakeOwnershipPrivilege" "TcbPrivilege"; do
  echo "Searching for: $priv"
  rg -n "$priv" --type-list 2>/dev/null | head -1 || echo "  (no matches)"
done

echo ""
echo "=== Checking for TypeScript or OpenAPI schema files ==="
find . -path "*/schema*" -type f \( -name "*.ts" -o -name "*.json" -o -name "*.yaml" -o -name "*.yml" \) 2>/dev/null | grep -v node_modules | head -20

echo ""
echo "=== Checking for any documentation about these privileges ==="
find . -type f \( -name "*.md" -o -name "*.txt" -o -name "*.rst" \) | xargs grep -l "Privilege\|LogonRight" 2>/dev/null | head -5

echo ""
echo "=== Verifying the exact diff location in CUE file ==="
sed -n '1810,1825p' "packages/cue/bh/ad/ad.cue" | cat -n

Length of output: 2347


Add the 9 missing local-privilege relationships to RelationshipKinds in packages/cue/bh/ad/ad.cue (lines 1815–1817 region), then regenerate the Go schema.

Currently only DebugPrivilege and CreateTokenPrivilege are included. The following privileges are missing and should be added in alphabetical order:

  • AssignPrimaryTokenPrivilege
  • BackupPrivilege
  • ImpersonatePrivilege
  • InteractiveLogonRight
  • LoadDriverPrivilege
  • ManageVolumePrivilege
  • RestorePrivilege
  • TakeOwnershipPrivilege
  • TcbPrivilege

After adding these to the CUE file, regenerate packages/go/graphschema/ad/ad.go so the Relationships() function reflects the complete set. This ensures CUE → Go → TS consistency for ingestion and UI.

Comment on lines +829 to +842
privilegeMap := map[string]graph.Kind{
UserRightInteractiveLogon: ad.InteractiveLogonRight,
UserRightRemoteInteractiveLogon: ad.RemoteInteractiveLogonRight,
UserRightAssignPrimaryTokenPrivilege: ad.AssignPrimaryTokenPrivilege,
UserRightBackupPrivilege: ad.BackupPrivilege,
UserRightCreateTokenPrivilege: ad.CreateTokenPrivilege,
UserRightDebugPrivilege: ad.DebugPrivilege,
UserRightImpersonatePrivilege: ad.ImpersonatePrivilege,
UserRightLoadDriverPrivilege: ad.LoadDriverPrivilege,
UserRightManageVolumePrivilege: ad.ManageVolumePrivilege,
UserRightRestorePrivilege: ad.RestorePrivilege,
UserRightTakeOwnershipPrivilege: ad.TakeOwnershipPrivilege,
UserRightTcbPrivilege: ad.TcbPrivilege,
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Confirm schema coverage for all mapped edge kinds

Ensure every mapped edge kind here is declared and exported in packages/go/graphschema/ad/ad.go and included in Relationships(). If you want these edges traversed in shortest-path queries, also ensure they’re in the ActiveDirectoryPathfindingEdges set.

Run:


🏁 Script executed:

#!/bin/bash
# Verify new privilege kinds are listed in graphschema and used by UI pathfinding

set -euo pipefail

echo "Checking graphschema/ad/ad.go Relationships() includes all privilege edges..."
edges=(
  InteractiveLogonRight
  RemoteInteractiveLogonRight
  AssignPrimaryTokenPrivilege
  BackupPrivilege
  CreateTokenPrivilege
  DebugPrivilege
  ImpersonatePrivilege
  LoadDriverPrivilege
  ManageVolumePrivilege
  RestorePrivilege
  TakeOwnershipPrivilege
  TcbPrivilege
)
block="$(rg -nPU '(?s)func\s+Relationships\(\)\s*\[\]graph\.Kind\s*\{.*?return\s*\[\]graph\.Kind\{.*?\}\s*\}' packages/go/graphschema/ad/ad.go -o | sed -E 's/^:[0-9]+://' )"
missing=()
for e in "${edges[@]}"; do
  grep -q "$e" <<<"$block" || missing+=("$e")
done
if ((${#missing[@]})); then
  echo "Missing in Relationships(): ${missing[*]}"
  exit 1
else
  echo "All edges present in Relationships()."
fi

echo "Checking UI pathfinding edges (graphSchema.ts) include new privilege edges (if intended)..."
ui_file="$(fd -t f graphSchema.ts packages/javascript | head -1 || true)"
if [[ -n "$ui_file" ]]; then
  ui_block="$(rg -nPU '(?s)ActiveDirectoryPathfindingEdges\s*\(\)\s*{.*?return\s*\[.*?\];\s*}' "$ui_file" -o | sed -E 's/^:[0-9]+://')"
  ui_missing=()
  for e in "${edges[@]}"; do
    grep -q "$e" <<<"$ui_block" || ui_missing+=("$e")
  done
  if ((${#ui_missing[@]})); then
    echo "Missing from ActiveDirectoryPathfindingEdges(): ${ui_missing[*]}"
    exit 2
  else
    echo "All edges present in ActiveDirectoryPathfindingEdges()."
  fi
else
  echo "UI graphSchema.ts not found; skip."
fi

Length of output: 286



🏁 Script executed:

#!/bin/bash
# Get the actual state of Relationships() in graphschema to show what's currently there
echo "Current content of Relationships() function:"
rg -nPU '(?s)func\s+Relationships\(\)\s*\[\]graph\.Kind\s*\{.*?return\s*\[\]graph\.Kind\{(.*?)\}\s*\}' packages/go/graphschema/ad/ad.go -o | head -50

Length of output: 1462


🏁 Script executed:

#!/bin/bash
# Double-check the exact kind definitions in ad.go to see what's exported
echo "Checking what edge kinds are actually defined in graphschema/ad/ad.go..."
rg -nP '(InteractiveLogonRight|RemoteInteractiveLogonRight|AssignPrimaryTokenPrivilege|BackupPrivilege|CreateTokenPrivilege|DebugPrivilege|ImpersonatePrivilege|LoadDriverPrivilege|ManageVolumePrivilege|RestorePrivilege|TakeOwnershipPrivilege|TcbPrivilege)' packages/go/graphschema/ad/ad.go

Length of output: 2374


🏁 Script executed:

#!/bin/bash
# Find where these kinds should be defined/added
echo "Finding const or var declarations in ad.go:"
fd -t f ad.go packages/go/graphschema | head -5 | xargs -I {} sh -c 'echo "File: {}"; cat {}'

Length of output: 53601


🏁 Script executed:

#!/bin/bash
# Check if types are defined by looking for their declaration patterns
echo "Searching for type definitions of the privilege kinds..."
rg -n "const\s+\(|var\s+\(" packages/go/graphschema/ad/ad.go | head -20

Length of output: 77


Add missing privilege edges to Relationships() in packages/go/graphschema/ad/ad.go

10 privilege edges are defined (lines 82–93) but missing from the Relationships() function (line 1160–1162): InteractiveLogonRight, AssignPrimaryTokenPrivilege, BackupPrivilege, CreateTokenPrivilege, ImpersonatePrivilege, LoadDriverPrivilege, ManageVolumePrivilege, RestorePrivilege, TakeOwnershipPrivilege, TcbPrivilege. Add all 12 privilege edges to Relationships() and evaluate whether they should also be added to PathfindingRelationships() depending on whether shortest-path queries should traverse them.

}
func Relationships() []graph.Kind {
return []graph.Kind{Owns, GenericAll, GenericWrite, WriteOwner, WriteDACL, MemberOf, ForceChangePassword, AllExtendedRights, AddMember, HasSession, Contains, GPLink, AllowedToDelegate, CoerceToTGT, GetChanges, GetChangesAll, GetChangesInFilteredSet, CrossForestTrust, SameForestTrust, SpoofSIDHistory, AbuseTGTDelegation, AllowedToAct, AdminTo, CanPSRemote, CanRDP, ExecuteDCOM, HasSIDHistory, AddSelf, DCSync, ReadLAPSPassword, ReadGMSAPassword, DumpSMSAPassword, SQLAdmin, AddAllowedToAct, WriteSPN, AddKeyCredentialLink, LocalToComputer, MemberOfLocalGroup, RemoteInteractiveLogonRight, SyncLAPSPassword, WriteAccountRestrictions, WriteGPLink, RootCAFor, DCFor, PublishedTo, ManageCertificates, ManageCA, DelegatedEnrollmentAgent, Enroll, HostsCAService, WritePKIEnrollmentFlag, WritePKINameFlag, NTAuthStoreFor, TrustedForNTAuth, EnterpriseCAFor, IssuedSignedBy, GoldenCert, EnrollOnBehalfOf, OIDGroupLink, ExtendedByPolicy, ADCSESC1, ADCSESC3, ADCSESC4, ADCSESC6a, ADCSESC6b, ADCSESC9a, ADCSESC9b, ADCSESC10a, ADCSESC10b, ADCSESC13, SyncedToEntraUser, CoerceAndRelayNTLMToSMB, CoerceAndRelayNTLMToADCS, WriteOwnerLimitedRights, WriteOwnerRaw, OwnsLimitedRights, OwnsRaw, ClaimSpecialIdentity, CoerceAndRelayNTLMToLDAP, CoerceAndRelayNTLMToLDAPS, ContainsIdentity, PropagatesACEsTo, GPOAppliesTo, CanApplyGPO, HasTrustKeys, ProtectAdminGroups}
return []graph.Kind{Owns, GenericAll, GenericWrite, WriteOwner, WriteDACL, MemberOf, ForceChangePassword, AllExtendedRights, AddMember, HasSession, Contains, GPLink, AllowedToDelegate, CoerceToTGT, GetChanges, GetChangesAll, GetChangesInFilteredSet, CrossForestTrust, SameForestTrust, SpoofSIDHistory, AbuseTGTDelegation, AllowedToAct, AdminTo, CanPSRemote, CanRDP, ExecuteDCOM, HasSIDHistory, AddSelf, DCSync, ReadLAPSPassword, ReadGMSAPassword, DumpSMSAPassword, SQLAdmin, AddAllowedToAct, WriteSPN, AddKeyCredentialLink, LocalToComputer, MemberOfLocalGroup, RemoteInteractiveLogonRight, DebugPrivilege, SyncLAPSPassword, WriteAccountRestrictions, WriteGPLink, RootCAFor, DCFor, PublishedTo, ManageCertificates, ManageCA, DelegatedEnrollmentAgent, Enroll, HostsCAService, WritePKIEnrollmentFlag, WritePKINameFlag, NTAuthStoreFor, TrustedForNTAuth, EnterpriseCAFor, IssuedSignedBy, GoldenCert, EnrollOnBehalfOf, OIDGroupLink, ExtendedByPolicy, ADCSESC1, ADCSESC3, ADCSESC4, ADCSESC6a, ADCSESC6b, ADCSESC9a, ADCSESC9b, ADCSESC10a, ADCSESC10b, ADCSESC13, SyncedToEntraUser, CoerceAndRelayNTLMToSMB, CoerceAndRelayNTLMToADCS, WriteOwnerLimitedRights, WriteOwnerRaw, OwnsLimitedRights, OwnsRaw, ClaimSpecialIdentity, CoerceAndRelayNTLMToLDAP, CoerceAndRelayNTLMToLDAPS, ContainsIdentity, PropagatesACEsTo, GPOAppliesTo, CanApplyGPO, HasTrustKeys, ProtectAdminGroups}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Schema mismatch: CreateTokenPrivilege missing from Relationships(); others not exposed.

CUE adds CreateTokenPrivilege (and DebugPrivilege) to RelationshipKinds, but this Go list only includes DebugPrivilege. This will drop CreateTokenPrivilege edges at runtime.

  • Do not hand-edit this file; add the missing relationships in CUE and regenerate.

Apply in packages/cue/bh/ad/ad.cue (see dedicated comment) and then regenerate to sync this function.

Run to compare TS vs Go vs CUE relationship sets:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "TS ActiveDirectoryRelationshipKind values:"
rg -nU 'export enum ActiveDirectoryRelationshipKind\s*{(?s).*?^}' packages/javascript/bh-shared-ui/src/graphSchema.ts | sed -n '/ActiveDirectoryRelationshipKind {/,/}/{p}' | rg "^\s*[A-Za-z0-9_]+ =" -n | sed 's/.*ActiveDirectoryRelationshipKind\.//g;s/\s*=.*//g' | sort -u

echo
echo "Go ad.Relationships() values:"
rg -nU 'func Relationships\(\).*?return \[\]graph\.Kind{(?s).*?}' packages/go/graphschema/ad/ad.go | sed -n '/return \[\]graph.Kind{/,/}/{p}' | tr -d ' ' | tr ',' '\n' | rg '^[A-Za-z0-9]+' | sed 's/}//g' | sort -u

echo
echo "CUE RelationshipKinds values:"
sed -n '/RelationshipKinds:\s*\[/,/]/p' packages/cue/bh/ad/ad.cue | rg '^\s*[A-Za-z0-9]+' -n | sed 's/^[[:space:]]\+//g;s/,//g' | sort -u

echo
echo "Diff (TS - Go):"
comm -23 <(rg -nU 'export enum ActiveDirectoryRelationshipKind\s*{(?s).*?^}' packages/javascript/bh-shared-ui/src/graphSchema.ts | sed -n '/ActiveDirectoryRelationshipKind {/,/}/{p}' | rg "^\s*[A-Za-z0-9_]+ =" -n | sed 's/.*ActiveDirectoryRelationshipKind\.//g;s/\s*=.*//g' | sort -u) \
          <(rg -nU 'func Relationships\(\).*?return \[\]graph\.Kind{(?s).*?}' packages/go/graphschema/ad/ad.go | sed -n '/return \[\]graph.Kind{/,/}/{p}' | tr -d ' ' | tr ',' '\n' | rg '^[A-Za-z0-9]+' | sed 's/}//g' | sort -u)

echo
echo "Diff (CUE - Go):"
comm -23 <(sed -n '/RelationshipKinds:\s*\[/,/]/p' packages/cue/bh/ad/ad.cue | rg '^\s*[A-Za-z0-9]+' -n | sed 's/^[[:space:]]\+//g;s/,//g' | sort -u) \
          <(rg -nU 'func Relationships\(\).*?return \[\]graph\.Kind{(?s).*?}' packages/go/graphschema/ad/ad.go | sed -n '/return \[\]graph.Kind{/,/}/{p}' | tr -d ' ' | tr ',' '\n' | rg '^[A-Za-z0-9]+' | sed 's/}//g' | sort -u)

Length of output: 228


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== TS ActiveDirectoryRelationshipKind values ==="
rg 'export enum ActiveDirectoryRelationshipKind' -A 200 packages/javascript/bh-shared-ui/src/graphSchema.ts | grep -E '^\s+[A-Za-z]+\s*=' | sed 's/.*=//;s/[,;].*//' | tr -d ' ' | sort -u

echo
echo "=== Go ad.Relationships() values ==="
rg 'func Relationships\(\)' -A 5 packages/go/graphschema/ad/ad.go | grep 'return \[\]' -A 1 | tr ',' '\n' | tr -d ' {}[]' | grep -E '^[A-Za-z]' | sort -u

echo
echo "=== CUE RelationshipKinds values ==="
rg 'RelationshipKinds:' -A 200 packages/cue/bh/ad/ad.cue | sed -n '/RelationshipKinds:/,/]/p' | grep -E '^\s+[A-Za-z_]+' | sed 's/^[[:space:]]*//;s/[,].*//' | sort -u

echo
echo "=== Relationships in TS but NOT in Go ==="
comm -23 <(rg 'export enum ActiveDirectoryRelationshipKind' -A 200 packages/javascript/bh-shared-ui/src/graphSchema.ts | grep -E '^\s+[A-Za-z]+\s*=' | sed 's/.*=//;s/[,;].*//' | tr -d ' ' | sort -u) \
          <(rg 'func Relationships\(\)' -A 5 packages/go/graphschema/ad/ad.go | grep 'return \[\]' -A 1 | tr ',' '\n' | tr -d ' {}[]' | grep -E '^[A-Za-z]' | sort -u)

echo
echo "=== Relationships in CUE but NOT in Go ==="
comm -23 <(rg 'RelationshipKinds:' -A 200 packages/cue/bh/ad/ad.cue | sed -n '/RelationshipKinds:/,/]/p' | grep -E '^\s+[A-Za-z_]+' | sed 's/^[[:space:]]*//;s/[,].*//' | sort -u) \
          <(rg 'func Relationships\(\)' -A 5 packages/go/graphschema/ad/ad.go | grep 'return \[\]' -A 1 | tr ',' '\n' | tr -d ' {}[]' | grep -E '^[A-Za-z]' | sort -u)

Length of output: 4282


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Extract and clean the lists for easy comparison
echo "=== CRITICAL FINDING: Relationships in CUE but NOT in Go ==="
cat > /tmp/cue_list.txt << 'EOF'
ADCSESC1
ADCSESC10a
ADCSESC10b
ADCSESC13
ADCSESC3
ADCSESC4
ADCSESC6a
ADCSESC6b
ADCSESC9a
ADCSESC9b
AbuseTGTDelegation
AddAllowedToAct
AddKeyCredentialLink
AddMember
AddSelf
AdminTo
AllExtendedRights
AllowedToAct
AllowedToDelegate
CanApplyGPO
CanPSRemote
CanRDP
ClaimSpecialIdentity
CoerceAndRelayNTLMToADCS
CoerceAndRelayNTLMToLDAP
CoerceAndRelayNTLMToLDAPS
CoerceAndRelayNTLMToSMB
CoerceToTGT
Contains
ContainsIdentity
CreateTokenPrivilege
CrossForestTrust
DCFor
DCSync
DebugPrivilege
DelegatedEnrollmentAgent
DumpSMSAPassword
Enroll
EnrollOnBehalfOf
EnterpriseCAFor
ExecuteDCOM
ExtendedByPolicy
ForceChangePassword
GPLink
GPOAppliesTo
GenericAll
GenericWrite
GetChanges
GetChangesAll
GetChangesInFilteredSet
GoldenCert
HasSIDHistory
HasSession
HasTrustKeys
HostsCAService
IssuedSignedBy
LocalToComputer
ManageCA
ManageCertificates
MemberOf
MemberOfLocalGroup
NTAuthStoreFor
OIDGroupLink
Owns
OwnsLimitedRights
OwnsRaw
PropagatesACEsTo
ProtectAdminGroups
PublishedTo
ReadGMSAPassword
ReadLAPSPassword
RemoteInteractiveLogonRight
RootCAFor
SQLAdmin
SameForestTrust
SpoofSIDHistory
SyncLAPSPassword
SyncedToEntraUser
TrustedForNTAuth
WriteAccountRestrictions
WriteDACL
WriteGPLink
WriteOwner
WriteOwnerLimitedRights
WriteOwnerRaw
WritePKIEnrollmentFlag
WritePKINameFlag
WriteSPN
EOF

cat > /tmp/go_list.txt << 'EOF'
ADCSESC1
ADCSESC10a
ADCSESC10b
ADCSESC13
ADCSESC3
ADCSESC4
ADCSESC6a
ADCSESC6b
ADCSESC9a
ADCSESC9b
AbuseTGTDelegation
AddAllowedToAct
AddKeyCredentialLink
AddMember
AddSelf
AdminTo
AllExtendedRights
AllowedToAct
AllowedToDelegate
CanApplyGPO
CanPSRemote
CanRDP
ClaimSpecialIdentity
CoerceAndRelayNTLMToADCS
CoerceAndRelayNTLMToLDAP
CoerceAndRelayNTLMToLDAPS
CoerceAndRelayNTLMToSMB
CoerceToTGT
Contains
ContainsIdentity
CrossForestTrust
DCFor
DCSync
DebugPrivilege
DelegatedEnrollmentAgent
DumpSMSAPassword
Enroll
EnrollOnBehalfOf
EnterpriseCAFor
ExecuteDCOM
ExtendedByPolicy
ForceChangePassword
GPLink
GPOAppliesTo
GenericAll
GenericWrite
GetChanges
GetChangesAll
GetChangesInFilteredSet
GoldenCert
HasSIDHistory
HasSession
HasTrustKeys
HostsCAService
IssuedSignedBy
LocalToComputer
ManageCA
ManageCertificates
MemberOf
MemberOfLocalGroup
NTAuthStoreFor
OIDGroupLink
Owns
OwnsLimitedRights
OwnsRaw
PropagatesACEsTo
ProtectAdminGroups
PublishedTo
ReadGMSAPassword
ReadLAPSPassword
RemoteInteractiveLogonRight
RootCAFor
SQLAdmin
SameForestTrust
SpoofSIDHistory
SyncLAPSPassword
SyncedToEntraUser
TrustedForNTAuth
WriteAccountRestrictions
WriteDACL
WriteGPLink
WriteOwner
WriteOwnerLimitedRights
WriteOwnerRaw
WritePKIEnrollmentFlag
WritePKINameFlag
WriteSPN
EOF

grep -Fvxf /tmp/go_list.txt /tmp/cue_list.txt

echo
echo "=== Relationships in TS but NOT in Go (sample check) ==="
grep -i "CreateTokenPrivilege\|AssignPrimaryTokenPrivilege\|BackupPrivilege" << 'TSLIST'
AbuseTGTDelegation
AddAllowedToAct
AddKeyCredentialLink
AddMember
AddSelf
AdminTo
AllExtendedRights
AllowedToAct
AllowedToDelegate
AssignPrimaryTokenPrivilege
BackupPrivilege
CanApplyGPO
CanPSRemote
CanRDP
ClaimSpecialIdentity
CoerceAndRelayNTLMToADCS
CoerceAndRelayNTLMToLDAP
CoerceAndRelayNTLMToLDAPS
CoerceAndRelayNTLMToSMB
CoerceToTGT
Contains
ContainsIdentity
CreateTokenPrivilege
CrossForestTrust
DCFor
DCSync
DebugPrivilege
DelegatedEnrollmentAgent
DumpSMSAPassword
Enroll
EnrollOnBehalfOf
EnterpriseCAFor
ExecuteDCOM
ExtendedByPolicy
ForceChangePassword
GPLink
GPOAppliesTo
GenericAll
GenericWrite
GetChanges
GetChangesAll
GetChangesInFilteredSet
GoldenCert
HasSIDHistory
HasSession
HasTrustKeys
HostsCAService
ImpersonatePrivilege
InteractiveLogonRight
IssuedSignedBy
LoadDriverPrivilege
LocalToComputer
ManageCA
ManageCertificates
ManageVolumePrivilege
MemberOf
MemberOfLocalGroup
NTAuthStoreFor
OIDGroupLink
Owns
OwnsLimitedRights
OwnsRaw
PropagatesACEsTo
ProtectAdminGroups
PublishedTo
ReadGMSAPassword
ReadLAPSPassword
RemoteInteractiveLogonRight
RestorePrivilege
RootCAFor
SQLAdmin
SameForestTrust
SpoofSIDHistory
SyncLAPSPassword
SyncedToEntraUser
TakeOwnershipPrivilege
TcbPrivilege
TrustedForNTAuth
WriteAccountRestrictions
WriteDacl
WriteGPLink
WriteOwner
WriteOwnerLimitedRights
WriteOwnerRaw
WritePKIEnrollmentFlag
WritePKINameFlag
WriteSPN
TSLIST

Length of output: 204


Confirmed: CreateTokenPrivilege missing from Go Relationships(); needs regeneration from CUE.

CUE includes CreateTokenPrivilege in RelationshipKinds, but the Go Relationships() function at line 1161 does not. This will drop CreateTokenPrivilege edges at runtime. Regenerate this file from the CUE source to sync.

🤖 Prompt for AI Agents
packages/go/graphschema/ad/ad.go around line 1161: the Relationships() return
slice is missing CreateTokenPrivilege (present in the CUE RelationshipKinds) so
runtime edges are dropped; regenerate this Go file from the CUE source (or
re-run the codegen that emits RelationshipKinds) so the returned []graph.Kind
includes CreateTokenPrivilege, then run tests/build, verify the generated list
contains CreateTokenPrivilege, and commit the regenerated file.
Comment on lines +587 to +590
name: 'Computers where local privilege escalation is possible through users\'s privileges',
description: '',
query: `MATCH p = (a)-[:AssignPrimaryTokenPrivilege|BackupPrivilege|CreateTokenPrivilege|DebugPrivilege|ImpersonatePrivilege|LoadDriverPrivilege|ManageVolumePrivilege|RestorePrivilege|TakeOwnershipPrivilege|TcbPrivilege]->(c:Computer)\nRETURN p LIMIT 500`,
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix possessive typo in query name

Use “users’ privileges” (plural possessive) instead of “users’s”.

Apply:

-                name: 'Computers where local privilege escalation is possible through users\'s privileges',
+                name: 'Computers where local privilege escalation is possible through users\' privileges',
📝 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.

Suggested change
name: 'Computers where local privilege escalation is possible through users\'s privileges',
description: '',
query: `MATCH p = (a)-[:AssignPrimaryTokenPrivilege|BackupPrivilege|CreateTokenPrivilege|DebugPrivilege|ImpersonatePrivilege|LoadDriverPrivilege|ManageVolumePrivilege|RestorePrivilege|TakeOwnershipPrivilege|TcbPrivilege]->(c:Computer)\nRETURN p LIMIT 500`,
},
name: 'Computers where local privilege escalation is possible through users\' privileges',
description: '',
query: `MATCH p = (a)-[:AssignPrimaryTokenPrivilege|BackupPrivilege|CreateTokenPrivilege|DebugPrivilege|ImpersonatePrivilege|LoadDriverPrivilege|ManageVolumePrivilege|RestorePrivilege|TakeOwnershipPrivilege|TcbPrivilege]->(c:Computer)\nRETURN p LIMIT 500`,
},
🤖 Prompt for AI Agents
In packages/javascript/bh-shared-ui/src/commonSearchesAGT.ts around lines 587 to
590, the query name contains a possessive typo "users's privileges"; change it
to the correct plural possessive "users' privileges" so the string becomes
"Computers where local privilege escalation is possible through users'
privileges".
Comment on lines +111 to 123
InteractiveLogonRight = 'InteractiveLogonRight',
RemoteInteractiveLogonRight = 'RemoteInteractiveLogonRight',
AssignPrimaryTokenPrivilege = 'AssignPrimaryTokenPrivilege',
BackupPrivilege = 'BackupPrivilege',
CreateTokenPrivilege = 'CreateTokenPrivilege',
DebugPrivilege = 'DebugPrivilege',
ImpersonatePrivilege = 'ImpersonatePrivilege',
LoadDriverPrivilege = 'LoadDriverPrivilege',
ManageVolumePrivilege = 'ManageVolumePrivilege',
RestorePrivilege = 'RestorePrivilege',
TakeOwnershipPrivilege = 'TakeOwnershipPrivilege',
TcbPrivilege = 'TcbPrivilege',
SyncLAPSPassword = 'SyncLAPSPassword',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

UI-backend parity: UI lists 11 new local-privilege edges, backend exposes 1–2.

TS adds 11 kinds and display cases, but Go Relationships() currently includes RemoteInteractiveLogonRight and DebugPrivilege only (CreateTokenPrivilege also intended in CUE). This mismatch can surface edges the API never returns.

  • Either narrow the TS enum to the currently supported set, or finish wiring all intended kinds in CUE RelationshipKinds and regenerate Go.
  • After backend alignment, consider whether these edges should be included in pathfinding arrays if they must influence routes.

I can generate a follow-up PR to align both sides once you confirm the desired set.

Also applies to: 249-272


🏁 Script executed:

#!/bin/bash
# Examine full context of graphSchema.ts around the reported lines
echo "=== graphSchema.ts lines 100-130 ==="
find . -name "graphSchema.ts" -type f | head -1 | xargs sed -n '100,130p'

echo -e "\n=== graphSchema.ts lines 240-280 ==="
find . -name "graphSchema.ts" -type f | head -1 | xargs sed -n '240,280p'

Length of output: 3525


🏁 Script executed:

#!/bin/bash
# Find Go files containing Relationships function
echo "=== Searching for Go Relationships() function ==="
rg -n "func.*Relationships" --type=go -A 20

# Find CUE files with RelationshipKinds
echo -e "\n=== Searching for RelationshipKinds in CUE files ==="
rg -n "RelationshipKinds" --type-not=ts --type-not=js -B 2 -A 10

Length of output: 145935


🏁 Script executed:

#!/bin/bash
# Count privilege-related enum values in graphSchema.ts
echo "=== Counting InteractiveLogonRight through SyncLAPSPassword in TS ==="
find . -name "graphSchema.ts" -type f | head -1 | xargs grep -n "Privilege\|LogonRight\|SyncLAPSPassword" | head -20

Length of output: 1214


UI-backend relationship kind mismatch: TS enum includes 10 kinds not exposed by Go Relationships().

TS enum lines 111-123 adds 13 privilege/logon-right kinds. Go Relationships() in packages/go/graphschema/ad/ad.go:1160 exposes only 3 of them: RemoteInteractiveLogonRight, DebugPrivilege, and SyncLAPSPassword. Missing from backend: InteractiveLogonRight, AssignPrimaryTokenPrivilege, BackupPrivilege, CreateTokenPrivilege, ImpersonatePrivilege, LoadDriverPrivilege, ManageVolumePrivilege, RestorePrivilege, TakeOwnershipPrivilege, TcbPrivilege.

Frontend will advertise edges that the API cannot return, causing display or search failures.

Action required:

  • Either remove the 10 unsupported kinds from the TS enum and display mapping, or
  • Complete the CUE RelationshipKinds definition (packages/cue/bh/ad/ad.cue:1775) to include all 13 kinds and regenerate Go code.

Align both sides before merging to prevent runtime "unknown edge" issues.

🤖 Prompt for AI Agents
In packages/javascript/bh-shared-ui/src/graphSchema.ts around lines 111-123 the
TypeScript enum adds 13 privilege/logon-right kinds but the Go backend
(packages/go/graphschema/ad/ad.go:1160) only exposes 3, causing the frontend to
advertise edges the API cannot return; fix by either removing the 10 unsupported
kinds from this TS enum and any display/mapping that references them
(InteractiveLogonRight, AssignPrimaryTokenPrivilege, BackupPrivilege,
CreateTokenPrivilege, ImpersonatePrivilege, LoadDriverPrivilege,
ManageVolumePrivilege, RestorePrivilege, TakeOwnershipPrivilege, TcbPrivilege)
or by adding all 13 kinds into the CUE RelationshipKinds definition at
packages/cue/bh/ad/ad.cue:1775 and regenerating the Go code so
packages/go/graphschema/ad/ad.go includes all new kinds (then run codegen/build
to propagate changes); ensure the final change keeps TS enum and Go
Relationships() in exact sync before merging.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

2 participants