Skip to content

Conversation

@specter-flq
Copy link
Contributor

@specter-flq specter-flq commented Dec 19, 2025

Description

PR to merge v8.4.2 back to main

Motivation and Context

Resolves BED-6953

How Has This Been Tested?

Screenshots (optional):

Types of changes

  • Chore (a change that does not modify the application functionality)
  • Database Migrations

Checklist:

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced graph visualization with improved visual indicators for specific node types (tier zero and owned objects), dynamically enabled through feature controls.
    • New ACL relationship filtering capability for ingestion workflows, excluding post-processed relationships from ingest operations.
  • Chores

    • Updated Go language runtime version and upgraded multiple core dependencies for improved compatibility and security.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 19, 2025

Walkthrough

This PR introduces a feature-flag-controlled glyph rendering system for graph nodes and applies dependency updates. The tier_management_engine feature flag gates new glyph rendering logic in the Explore view, enabling tier-zero and owned-object glyph variants. Schema changes add filtered ingest-specific ACL relationships. Go dependencies are updated to newer patch and minor versions.

Changes

Cohort / File(s) Summary
Feature flag integration
cmd/ui/src/views/Explore/GraphView.tsx
Added useFeatureFlag hook call for tier_management_engine; feature flag state (pzFeatureFlagEnabled) passed to graph options with dependency tracking.
Glyph rendering system
cmd/ui/src/views/Explore/utils.ts
Introduced getNodeGlyphs function to compute per-node glyph placements based on feature flag state; added GLYPHS constants (TIER_ZERO, OWNED_OBJECT variants); extended GraphOptions type with pzFeatureFlagEnabled field; refactored initGraphNodes to use new glyph path.
Glyph rendering tests
cmd/ui/src/views/Explore/utils.test.ts
Added test coverage for getNodeGlyphs with feature flag enabled/disabled scenarios; extended existing graph initialization tests with pzFeatureFlagEnabled parameter.
Dependency updates
go.mod
Updated Go version to 1.24.11 and bumped multiple dependencies (golang.org/x packages, cuelang.org/go, RoaringBitmap, etc.).
Schema configuration
packages/cue/bh/ad/ad.cue, packages/cue/bh/bh.cue
Added IngestACLRelationships field to ActiveDirectory schema, computed as ACLRelationships filtered to exclude PostProcessedRelationships.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

user interface

Suggested reviewers

  • superlinkx
  • urangel

Poem

A hop through glyph-laden fields of display,
Where tier-zero gems catch the feature flag's ray,
And owned-object skulls wear their crown with delight,
The rabbit rejoices—new rendering takes flight! 🐰✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.46% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies this as a release merge of v8.4.2 back to main with the associated ticket BED-6953.
Description check ✅ Passed The description includes all major required sections: clear description of purpose, motivation with ticket link, change types, and completed checklist items confirming testing and documentation updates.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch stage/v8.4.2

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.

@specter-flq specter-flq changed the title chore: Merge v8.4.2 release to main BED-6953 chore: Merge stage/v8.4.2 release to main BED-6953 Dec 19, 2025
@specter-flq specter-flq self-assigned this Dec 19, 2025
@specter-flq specter-flq added the release Release Fixups/Merge backs label Dec 19, 2025
@specter-flq specter-flq marked this pull request as ready for review December 19, 2025 16:45
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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/javascript/bh-shared-ui/src/views/Explore/ContextMenu/ContextMenuPrivilegeZonesEnabled.tsx (1)

52-72: Use explicit undefined check for autoCertify parameter.

Line 64 uses a truthy check (autoCertify ?) to conditionally include the auto_certify field. This would incorrectly exclude the value if autoCertify is 0, which is likely the value for AssetGroupTagSelectorAutoCertifyDisabled. Use an explicit check for undefined instead.

🔎 Proposed fix
-                ...(autoCertify ? { auto_certify: autoCertify } : {}),
+                ...(autoCertify !== undefined ? { auto_certify: autoCertify } : {}),
🧹 Nitpick comments (2)
cmd/ui/src/views/Explore/utils.test.ts (2)

156-156: Minor typo in test description.

The test description has a typo: "tier_managent_engine" should be "tier_management_engine" (missing 'e' in management).

🔎 Proposed fix
-    it('determines glyphs with the tier_managent_engine feature flag disabled', () => {
+    it('determines glyphs with the tier_management_engine feature flag disabled', () => {

204-204: Minor typo in test description.

Same typo as Line 156: "tier_managent_engine" should be "tier_management_engine".

🔎 Proposed fix
-    it('determines glyphs with the tier_managent_engine feature flag enabled', () => {
+    it('determines glyphs with the tier_management_engine feature flag enabled', () => {
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3f0b41b and 1abebc5.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (41)
  • cmd/api/src/analysis/ad/ad_integration_test.go (0 hunks)
  • cmd/api/src/analysis/ad/adcs_integration_test.go (0 hunks)
  • cmd/api/src/analysis/ad/ntlm_integration_test.go (0 hunks)
  • cmd/api/src/analysis/analysis_integration_test.go (0 hunks)
  • cmd/api/src/analysis/hybrid/hybrid_integration_test.go (0 hunks)
  • cmd/api/src/analysis/membership_integration_test.go (0 hunks)
  • cmd/api/src/analysis/post_integration_test.go (0 hunks)
  • cmd/api/src/api/tools/pg_test.go (0 hunks)
  • cmd/api/src/daemons/datapipe/agt_test.go (0 hunks)
  • cmd/api/src/database/analysisrequest_test.go (0 hunks)
  • cmd/api/src/database/audit_test.go (0 hunks)
  • cmd/api/src/database/auth_test.go (0 hunks)
  • cmd/api/src/database/datapipestatus_integration_test.go (0 hunks)
  • cmd/api/src/database/ingest_job_test.go (0 hunks)
  • cmd/api/src/database/migration/agi_integration_test.go (0 hunks)
  • cmd/api/src/database/migration/asset_group_constraints_test.go (0 hunks)
  • cmd/api/src/database/migration/migration_integration_test.go (0 hunks)
  • cmd/api/src/database/oidc_providers_test.go (0 hunks)
  • cmd/api/src/database/parameters_test.go (0 hunks)
  • cmd/api/src/database/saved_queries_permissions_integration_test.go (0 hunks)
  • cmd/api/src/database/sourcekinds_integration_test.go (0 hunks)
  • cmd/api/src/database/sso_providers_test.go (0 hunks)
  • cmd/api/src/model/appcfg/parameter_integration_test.go (0 hunks)
  • cmd/api/src/queries/graph_integration_test.go (0 hunks)
  • cmd/api/src/services/graphify/ingest_integration_test.go (0 hunks)
  • cmd/api/src/services/graphify/ingestrelationships_integration_test.go (0 hunks)
  • cmd/ui/src/views/Explore/GraphView.tsx (3 hunks)
  • cmd/ui/src/views/Explore/utils.test.ts (5 hunks)
  • cmd/ui/src/views/Explore/utils.ts (4 hunks)
  • dockerfiles/bloodhound.Dockerfile (2 hunks)
  • go.mod (8 hunks)
  • go.work.template (1 hunks)
  • packages/cue/bh/ad/ad.cue (2 hunks)
  • packages/cue/bh/bh.cue (2 hunks)
  • packages/go/analysis/ad/ad_integration_test.go (0 hunks)
  • packages/go/analysis/post_integration_test.go (0 hunks)
  • packages/go/graphschema/ad/ad.go (1 hunks)
  • packages/go/schemagen/generator/golang.go (1 hunks)
  • packages/go/schemagen/model/schema.go (1 hunks)
  • packages/javascript/bh-shared-ui/src/views/Explore/ContextMenu/ContextMenuPrivilegeZonesEnabled.tsx (5 hunks)
  • tools/docker-compose/api.Dockerfile (1 hunks)
💤 Files with no reviewable changes (28)
  • cmd/api/src/analysis/membership_integration_test.go
  • cmd/api/src/database/sso_providers_test.go
  • cmd/api/src/analysis/post_integration_test.go
  • cmd/api/src/services/graphify/ingest_integration_test.go
  • cmd/api/src/database/saved_queries_permissions_integration_test.go
  • packages/go/analysis/ad/ad_integration_test.go
  • cmd/api/src/queries/graph_integration_test.go
  • cmd/api/src/database/datapipestatus_integration_test.go
  • cmd/api/src/database/ingest_job_test.go
  • cmd/api/src/analysis/analysis_integration_test.go
  • cmd/api/src/database/auth_test.go
  • cmd/api/src/database/analysisrequest_test.go
  • cmd/api/src/database/sourcekinds_integration_test.go
  • cmd/api/src/daemons/datapipe/agt_test.go
  • cmd/api/src/database/audit_test.go
  • cmd/api/src/database/oidc_providers_test.go
  • packages/go/analysis/post_integration_test.go
  • cmd/api/src/api/tools/pg_test.go
  • cmd/api/src/analysis/ad/ad_integration_test.go
  • cmd/api/src/analysis/ad/ntlm_integration_test.go
  • cmd/api/src/services/graphify/ingestrelationships_integration_test.go
  • cmd/api/src/model/appcfg/parameter_integration_test.go
  • cmd/api/src/database/migration/agi_integration_test.go
  • cmd/api/src/database/migration/migration_integration_test.go
  • cmd/api/src/database/migration/asset_group_constraints_test.go
  • cmd/api/src/analysis/hybrid/hybrid_integration_test.go
  • cmd/api/src/database/parameters_test.go
  • cmd/api/src/analysis/ad/adcs_integration_test.go
🧰 Additional context used
🧠 Learnings (12)
📓 Common learnings
Learnt from: superlinkx
Repo: SpecterOps/BloodHound PR: 1629
File: go.mod:18-18
Timestamp: 2025-06-30T22:58:54.834Z
Learning: Go 1.24 supports patch versions in the go directive of go.mod files. Syntax like "go 1.24.4" is valid and specifies the minimum Go version required to build the module, including the patch level.
📚 Learning: 2025-08-28T18:42:47.612Z
Learnt from: benwaples
Repo: SpecterOps/BloodHound PR: 1829
File: packages/javascript/bh-shared-ui/src/components/Navigation/MainNav.tsx:41-56
Timestamp: 2025-08-28T18:42:47.612Z
Learning: The BaseSVG component in packages/javascript/bh-shared-ui/src/components/AppIcon/Icons/utils.tsx already provides accessibility through VisuallyHidden labels, eliminating the need for additional accessible names on wrapper elements.

Applied to files:

  • cmd/ui/src/views/Explore/utils.ts
📚 Learning: 2025-08-11T23:51:32.709Z
Learnt from: benwaples
Repo: SpecterOps/BloodHound PR: 1749
File: packages/javascript/bh-shared-ui/src/hooks/useExploreSelectedItem.tsx:40-46
Timestamp: 2025-08-11T23:51:32.709Z
Learning: The useGraphItem hook in packages/javascript/bh-shared-ui/src/hooks/useGraphItem.tsx already has an enabled field set to !!itemId, which prevents the query from executing when itemId is falsy, providing built-in null-safety.

Applied to files:

  • cmd/ui/src/views/Explore/GraphView.tsx
📚 Learning: 2025-08-28T19:26:03.304Z
Learnt from: benwaples
Repo: SpecterOps/BloodHound PR: 1829
File: packages/javascript/bh-shared-ui/src/views/ZoneManagement/ZoneAnalysisIcon.tsx:26-26
Timestamp: 2025-08-28T19:26:03.304Z
Learning: In packages/javascript/bh-shared-ui/src/hooks/, useZonePathParams is exported through the useZoneParams barrel (useZoneParams/index.ts exports it via wildcard from useZonePathParams.tsx), and usePrivilegeZoneAnalysis is exported through useConfiguration.ts. Both are available via the main hooks barrel import.

Applied to files:

  • cmd/ui/src/views/Explore/GraphView.tsx
  • packages/javascript/bh-shared-ui/src/views/Explore/ContextMenu/ContextMenuPrivilegeZonesEnabled.tsx
📚 Learning: 2025-09-08T19:22:49.284Z
Learnt from: jvacca-specterops
Repo: SpecterOps/BloodHound PR: 1823
File: packages/javascript/bh-shared-ui/src/views/Explore/ExploreSearch/SavedQueries/TagToZoneLabelDialog.tsx:34-35
Timestamp: 2025-09-08T19:22:49.284Z
Learning: In BloodHound's TagToZoneLabelDialog component (packages/javascript/bh-shared-ui/src/views/Explore/ExploreSearch/SavedQueries/TagToZoneLabelDialog.tsx), importing AssetGroupTag type from 'js-client-library' to type tag shapes is incorrect - this type should not be used for typing tags in this context.

Applied to files:

  • packages/javascript/bh-shared-ui/src/views/Explore/ContextMenu/ContextMenuPrivilegeZonesEnabled.tsx
📚 Learning: 2025-08-25T20:12:35.629Z
Learnt from: mistahj67
Repo: SpecterOps/BloodHound PR: 1803
File: packages/javascript/bh-shared-ui/src/views/ZoneManagement/Summary/SummaryCard.tsx:24-24
Timestamp: 2025-08-25T20:12:35.629Z
Learning: The useHighestPrivilegeTagId hook is available through the hooks barrel export in packages/javascript/bh-shared-ui/src/hooks/index.ts via the wildcard export `export * from './useAssetGroupTags'`. The import `import { useHighestPrivilegeTagId } from '../../../hooks'` works correctly and doesn't cause build failures.

Applied to files:

  • packages/javascript/bh-shared-ui/src/views/Explore/ContextMenu/ContextMenuPrivilegeZonesEnabled.tsx
📚 Learning: 2025-09-08T19:38:54.755Z
Learnt from: jvacca-specterops
Repo: SpecterOps/BloodHound PR: 1823
File: packages/javascript/bh-shared-ui/src/views/Explore/ExploreSearch/SavedQueries/ConfirmUpdateQueryDialog.tsx:27-40
Timestamp: 2025-09-08T19:38:54.755Z
Learning: In BloodHound's ConfirmUpdateQueryDialog component (packages/javascript/bh-shared-ui/src/views/Explore/ExploreSearch/SavedQueries/ConfirmUpdateQueryDialog.tsx), the nested Dialog structure with DialogTitle and DialogActions inside DialogContent (rather than as direct siblings of Dialog) is intentional design and should not be changed to follow conventional MUI patterns.

Applied to files:

  • packages/javascript/bh-shared-ui/src/views/Explore/ContextMenu/ContextMenuPrivilegeZonesEnabled.tsx
📚 Learning: 2025-09-24T16:36:12.870Z
Learnt from: dcairnsspecterops
Repo: SpecterOps/BloodHound PR: 1923
File: packages/javascript/bh-shared-ui/src/components/FileUploadDialog/FileUploadDialog.tsx:55-58
Timestamp: 2025-09-24T16:36:12.870Z
Learning: In BloodHound's FileUploadDialog component (packages/javascript/bh-shared-ui/src/components/FileUploadDialog/FileUploadDialog.tsx), the useOnClickOutside hook with dialogRef on the Dialog root is specifically intended to detect navbar clicks, not backdrop clicks. Backdrop clicks are already handled by MUI Dialog's built-in onClose behavior, while the hook handles navbar interactions to allow closing the dialog when users interact with navigation elements.

Applied to files:

  • packages/javascript/bh-shared-ui/src/views/Explore/ContextMenu/ContextMenuPrivilegeZonesEnabled.tsx
📚 Learning: 2025-06-06T23:12:14.181Z
Learnt from: elikmiller
Repo: SpecterOps/BloodHound PR: 1563
File: packages/go/graphschema/azure/azure.go:24-24
Timestamp: 2025-06-06T23:12:14.181Z
Learning: In BloodHound, files in packages/go/graphschema/*/`*.go` are generated from CUE schemas. When `just prepare-for-codereview` is run, it triggers code generation that may automatically add import aliases or other formatting changes. These changes are legitimate outputs of the generation process, not manual edits that would be overwritten.

Applied to files:

  • packages/go/schemagen/generator/golang.go
  • packages/cue/bh/ad/ad.cue
  • dockerfiles/bloodhound.Dockerfile
  • go.mod
📚 Learning: 2025-06-30T22:58:54.834Z
Learnt from: superlinkx
Repo: SpecterOps/BloodHound PR: 1629
File: go.mod:18-18
Timestamp: 2025-06-30T22:58:54.834Z
Learning: Go 1.24 supports patch versions in the go directive of go.mod files. Syntax like "go 1.24.4" is valid and specifies the minimum Go version required to build the module, including the patch level.

Applied to files:

  • go.work.template
  • go.mod
📚 Learning: 2025-08-26T18:13:36.643Z
Learnt from: superlinkx
Repo: SpecterOps/BloodHound PR: 1820
File: .github/workflows/audit-release.yml:0-0
Timestamp: 2025-08-26T18:13:36.643Z
Learning: The go tool command supports custom/third-party binaries as of Go 1.24, allowing invocation of custom CLI tools like "go tool stbernard" rather than being limited to only built-in Go toolchain binaries.

Applied to files:

  • go.work.template
📚 Learning: 2025-08-28T16:43:43.961Z
Learnt from: mvlipka
Repo: SpecterOps/BloodHound PR: 1784
File: packages/go/openapi/doc/openapi.json:18008-18029
Timestamp: 2025-08-28T16:43:43.961Z
Learning: In SpecterOps/BloodHound, packages/go/openapi/doc/openapi.json is generated from YAML under packages/go/openapi/src/schemas; edits must be made to the YAML and then the spec regenerated.

Applied to files:

  • go.mod
🧬 Code graph analysis (4)
cmd/ui/src/views/Explore/utils.test.ts (1)
cmd/ui/src/views/Explore/utils.ts (1)
  • getNodeGlyphs (117-161)
packages/go/schemagen/model/schema.go (1)
packages/go/graphschema/ad/ad.go (1)
  • IngestACLRelationships (1155-1157)
packages/javascript/bh-shared-ui/src/views/Explore/ContextMenu/ContextMenuPrivilegeZonesEnabled.tsx (2)
packages/javascript/bh-shared-ui/src/hooks/useGraphItem.tsx (1)
  • NodeResponse (28-32)
packages/javascript/js-client-library/src/types.ts (2)
  • AssetGroupTagSelectorAutoCertifyType (175-178)
  • AssetGroupTagSelectorAutoCertifySeedsOnly (172-172)
packages/go/schemagen/generator/golang.go (2)
packages/go/graphschema/ad/ad.go (1)
  • IngestACLRelationships (1155-1157)
packages/go/schemagen/csgen/models.go (1)
  • Symbol (23-23)
🔇 Additional comments (26)
packages/javascript/bh-shared-ui/src/views/Explore/ContextMenu/ContextMenuPrivilegeZonesEnabled.tsx (3)

19-23: LGTM: Imports are correctly sourced.

The imports for AssetGroupTagSelectorAutoCertifyType and related constants from js-client-library are appropriate and align with their usage in the mutation function.


106-122: LGTM: Clean higher-order function pattern.

The makeHandleAddNode function provides a clean abstraction that captures the autoCertify context and returns a handler with the appropriate configuration. The loading state check and dialog lifecycle management are correctly implemented.


163-163: LGTM: Appropriate auto-certification strategies applied.

The usage correctly applies different auto-certification strategies:

  • Tier Zero assets use AssetGroupTagSelectorAutoCertifySeedsOnly to auto-certify seed nodes
  • Owned assets omit the parameter, allowing the backend to apply default behavior

Also applies to: 188-188

cmd/ui/src/views/Explore/utils.ts (5)

17-32: LGTM! Imports are well-organized.

The new imports for glyph rendering (FontAwesome icons, glyph utilities, and types) are appropriate and properly used throughout the file.


65-65: LGTM! Type definition is appropriate.

The pzFeatureFlagEnabled: boolean | undefined type correctly handles cases where the feature flag may not be loaded or available.


102-115: LGTM! GLYPHS constant is well-structured.

The glyph definitions provide appropriate light/dark variants for both tier zero and owned object icons, ensuring proper visibility across themes.


117-161: LGTM! Feature flag logic is well-implemented.

The getNodeGlyphs function cleanly separates the new feature-flag-driven path from the legacy behavior, with appropriate glyph positioning and styling for both modes.


185-189: LGTM! Refactoring improves code organization.

The extraction of glyph logic into getNodeGlyphs improves maintainability and testability. The conditional application of glyphs when the array is non-empty is appropriate.

cmd/ui/src/views/Explore/GraphView.tsx (3)

39-39: LGTM! Import is correct.

The useFeatureFlag hook import is properly placed with other bh-shared-ui imports.


83-83: LGTM! Feature flag retrieval is correct.

The hook correctly fetches the tier_management_engine feature flag with appropriate naming.


96-105: LGTM! Graph options integration is correct.

The feature flag is properly integrated into graphOptions with appropriate optional chaining and dependency tracking. The memo will correctly recompute when the feature flag state changes.

cmd/ui/src/views/Explore/utils.test.ts (4)

19-20: LGTM! Test imports are appropriate.

The imports for GlyphLocation and getNodeGlyphs support the new test suite.


28-41: LGTM! Test data properly supports both feature flag paths.

The updated test nodes correctly include both the legacy flags (isTierZero, isOwnedObject) and the new kinds-based tags (Tag_Tier_Zero, Tag_Owned), enabling comprehensive testing of both code paths.


87-119: LGTM! Tests updated to include feature flag.

The existing initGraph tests correctly pass the new pzFeatureFlagEnabled option, maintaining test validity while the new glyph-specific behavior is properly tested in the dedicated getNodeGlyphs suite.


123-251: LGTM! Comprehensive test coverage for glyph behavior.

The test suite thoroughly validates both feature flag states, verifying glyph positioning, styling, and image selection for both tier zero and owned object nodes. Excellent test coverage!

go.work.template (1)

1-1: LGTM! Go toolchain version bump is consistent with the release.

The update from 1.24.9 to 1.24.11 aligns with similar changes across go.mod and Dockerfiles.

tools/docker-compose/api.Dockerfile (1)

64-64: LGTM! Docker base image update aligns with Go toolchain bump.

The update to golang:1.24.11-alpine3.22 is consistent with the Go version updates in go.mod and go.work.template.

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

1155-1157: LGTM! New IngestACLRelationships function provides an ingest-focused subset.

The function returns a curated subset of ACL relationships, excluding WriteOwner, Owns, SyncLAPSPassword, and DCSync from the full ACLRelationships set. This is generated code from CUE schemas and follows the established pattern.

go.mod (2)

18-18: LGTM! Go version update is valid.

The bump to 1.24.11 is consistent with the toolchain updates across the repository. Based on learnings, Go 1.24 supports patch versions in the go directive.


23-23: The specified RoaringBitmap version v2.14.4 does not appear to exist.

The RoaringBitmap/roaring repository shows v2.4.5 as the latest release as of early February. Version numbering jumps from v2.4.x directly to much lower versions in the public releases. The claim about updating from v2.6.0 to v2.14.4 cannot be verified as v2.14.4 does not appear in any official releases, GitHub tags, or pkg.go.dev documentation.

Confirm the correct version number and re-evaluate the dependency update accordingly.

dockerfiles/bloodhound.Dockerfile (1)

75-75: LGTM! Docker base images updated consistently.

Both build stages (ldflag-builder and api-builder) now use golang:1.24.11-alpine3.22, aligning with the Go toolchain updates across the repository.

Also applies to: 97-97

packages/go/schemagen/model/schema.go (1)

67-67: LGTM! New field supports IngestACLRelationships schema.

The IngestACLRelationships field is properly positioned and typed, consistent with the existing ACLRelationships field. This drives the code generation for the new IngestACLRelationships API.

packages/cue/bh/bh.cue (1)

53-53: LGTM! CUE schema properly defines IngestACLRelationships.

The field is correctly typed as [...types.#Kind] and properly wired to ad.IngestACLRelationships, following the same pattern as ACLRelationships.

Also applies to: 88-88

packages/go/schemagen/generator/golang.go (1)

397-405: LGTM! Codegen for IngestACLRelationships follows established pattern.

The function generation mirrors the ACLRelationships codegen structure and properly iterates over adSchema.IngestACLRelationships to emit relationship symbols. The placement between ACLRelationships and PathfindingRelationships is logical.

packages/cue/bh/ad/ad.cue (2)

29-29: LGTM! Clean schema export addition.

The new IngestACLRelationships export follows the established pattern and is properly placed alongside the related ACLRelationships declaration.


1842-1843: LGTM! Correct list comprehension logic.

The computed definition properly filters ACLRelationships to exclude post-processed relationships, creating a clean separation between ingest-time and post-processing ACL relationships. The CUE list comprehension syntax is correct and the logic aligns with the schema's design.


require (
cuelang.org/go v0.13.2
cuelang.org/go v0.15.1
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

🧩 Analysis chain

🏁 Script executed:

# First, let's understand the repo structure and find CUE-related processes
echo "=== Looking for CUE-related build/generation files ==="
fd -t f -e justfile -e Makefile | head -20
echo ""
echo "=== Checking justfile for CUE or schema generation commands ==="
if [ -f justfile ]; then
  cat justfile | head -100
fi

Repository: SpecterOps/BloodHound

Length of output: 3597


🌐 Web query:

cuelang.org/go v0.15.1 breaking changes release notes

💡 Result:

Summary — cuelang.org/go v0.15.1 (release v0.15.1)

  • No new breaking changes introduced in v0.15.1. The release is a bugfix/minor update (evaluator fixes, LSP and cmd/cue fixes, a subsume bugfix, and renaming an internal build tag to avoid conflicts). (github.com)

Context (for breaking changes you may care about)

  • Breaking/large changes were introduced earlier in v0.15.0 (language experiments like explicitopen, removal of evalv2, etc.); those are not new to v0.15.1. (github.com)

If you want, I can paste the full changelog entries or link specific commits/tests from the release.

Citations:


🏁 Script executed:

# Check for generated schema files and see if they were modified in this change
echo "=== Looking for CUE-generated schema files ==="
fd -t f 'azure|schema' packages/go/graphschema/ 2>/dev/null | head -20
echo ""
echo "=== Checking if azure.go exists and its size ==="
if [ -f "packages/go/graphschema/azure/azure.go" ]; then
  wc -l packages/go/graphschema/azure/azure.go
  echo ""
  echo "=== First 50 lines of azure.go to verify it's generated ==="
  head -50 packages/go/graphschema/azure/azure.go
fi
echo ""
echo "=== Checking for CUE source files ==="
fd -t f -e 'cue' packages/go/graphschema/ 2>/dev/null | head -20

Repository: SpecterOps/BloodHound

Length of output: 2887


🏁 Script executed:

# Look for any indication of code generation success
echo "=== Checking for stbernard tool and generation-related files ==="
fd -t f 'stbernard|generate' | grep -E '\.(go|mod|yaml|yml)$' | head -20
echo ""
echo "=== Checking packages/go directory structure ==="
fd -t d . packages/go/ | head -20

Repository: SpecterOps/BloodHound

Length of output: 883


Verify compatibility with cuelang.org/go v0.15.0 breaking changes.

The update includes v0.15.0, which introduced breaking changes (language experiments like explicitopen, and removal of evalv2). Confirm these changes don't impact the CUE schema generation pipeline. Note that v0.15.1 itself is a bugfix release with no new breaking changes.

🤖 Prompt for AI Agents
In go.mod around line 21, the repo now references cuelang.org/go v0.15.1 but
v0.15.0 introduced breaking changes (explicitopen, removal of evalv2); verify
our CUE schema generation pipeline isn't affected by searching for any
imports/usages of evalv2 or APIs changed in v0.15.0, run a full build and
unit/integration tests (go test ./...), run go vet and try generating the CUE
schemas end-to-end, and if you find breakages either update the code to use the
new API equivalents (replace evalv2 usage with the new eval API and adapt
explicit-open behavior) or pin go.mod to the last compatible pre-0.15.x version;
after changes, run go mod tidy and commit updated go.sum.

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: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
cmd/api/src/api/v2/saved_queries_permissions_test.go (1)

363-363: Fix typo in test name.

The test name contains "priavte" which should be "private".

🔎 Proposed fix
-	t.Run("Admin (non-admin owned), shared query set to priavte error", func(t *testing.T) {
+	t.Run("Admin (non-admin owned), shared query set to private error", func(t *testing.T) {
cmd/api/src/queries/graph_internal_test.go (1)

249-251: Test assertions verify input extraction rather than output correctness.

The assertions compare expectedName, expectedObjectId, and expectedDistinguishedName against string literals, which only verifies that Get().String() works on the input properties. They should instead verify that the actual output contains the expected values, as done in Test_filterNodesToSearchResult_default.

🔎 Proposed fix
-	require.Equal(t, expectedName, "this is a name")
-	require.Equal(t, expectedObjectId, "object id")
-	require.Equal(t, expectedDistinguishedName, "ze most distinguished")
+	require.Equal(t, expectedName, actual[0].Name)
+	require.Equal(t, expectedObjectId, actual[0].ObjectID)
+	require.Equal(t, expectedDistinguishedName, actual[0].DistinguishedName)
cmd/api/src/bootstrap/server.go (1)

75-128: Critical bug: password digest computed before password is generated.

The secretDigest is computed on line 80 using cfg.DefaultAdmin.Password, which may be empty at that point. However, defaultAdminFunction() is called later (lines 114-118) to populate the password. This means the stored digest will be for an empty string, not the generated password.

The password generation must happen before the digest is computed:

🔎 Proposed fix
 func CreateDefaultAdmin(ctx context.Context, cfg config.Configuration, db database.Database, defaultAdminFunction func() (config.DefaultAdminConfiguration, error)) error {
 	var secretDigester = cfg.Crypto.Argon2.NewDigester()
+	var needsLog = false
+
+	if cfg.DefaultAdmin.Password == "" {
+		needsLog = true
+		if admin, err := defaultAdminFunction(); err != nil {
+			return fmt.Errorf("error in setup initializing auth secret: %w", err)
+		} else {
+			cfg.DefaultAdmin = admin
+		}
+	}

 	if roles, err := db.GetAllRoles(ctx, "", model.SQLFilter{}); err != nil {
 		return fmt.Errorf("error while attempting to fetch user roles: %w", err)
 	} else if secretDigest, err := secretDigester.Digest(cfg.DefaultAdmin.Password); err != nil {
 		return fmt.Errorf("error while attempting to digest secret for user: %w", err)
 	} else if adminRole, found := roles.FindByName(auth.RoleAdministrator); !found {
 		return fmt.Errorf("unable to find admin role")
 	} else {
 		// ... existing user lookup/delete logic ...

 		var (
 			adminUser = model.User{
 				// ...
 			}

 			authSecret = model.AuthSecret{
 				Digest:       secretDigest.String(),
 				DigestMethod: secretDigester.Method(),
 			}
-			needsLog = false
 		)
-
-		if cfg.DefaultAdmin.Password == "" {
-			needsLog = true
-			if admin, err := defaultAdminFunction(); err != nil {
-				return fmt.Errorf("error in setup initalizing auth secret: %w", err)
-			} else {
-				cfg.DefaultAdmin = admin
-			}
-		}
cmd/api/src/api/v2/assetgrouptags.go (1)

1188-1244: Fix compile-time bug in fuzzy history filter loop

assetGroupTagHistoryImplementation currently uses:

for range len(queryableColumns) {
    sqlFilter.Params = append(sqlFilter.Params, fuzzyQueryParams)
}

You can’t range over an int in Go; this will not compile.

Change the loop to iterate over the slice itself so the fuzzy params are appended once per queryable column:

Proposed fix
-			sqlFilter.SQLString += querySQL
-			for range len(queryableColumns) {
-				sqlFilter.Params = append(sqlFilter.Params, fuzzyQueryParams)
-			}
+			sqlFilter.SQLString += querySQL
+			for range queryableColumns {
+				sqlFilter.Params = append(sqlFilter.Params, fuzzyQueryParams)
+			}
cmd/api/src/api/v2/assetgrouptags_test.go (1)

1056-1089: UpdateAssetGroupTagSelector “DatabaseError” test is missing a PrepareCypherQuery expectation

In the DatabaseError case for UpdateAssetGroupTagSelector, the request body includes Cypher seeds:

Seeds: []model.SelectorSeed{
    {Type: model.SelectorTypeCypher, Value: "this should be a string of cypher"},
},

The handler’s validateSelectorSeeds will call s.GraphQuery.PrepareCypherQuery for Cypher‑typed seeds, but this test’s Setup only configures UpdateAssetGroupTagSelector, GetAssetGroupTag, and GetAssetGroupTagSelectorBySelectorId expectations. With gomock’s default strictness, the call to PrepareCypherQuery will be an unexpected method call and cause the test to fail.

Add a PrepareCypherQuery expectation similar to your other tests:

Suggested adjustment
 			{
 				Name: "DatabaseError",
@@
 				},
 				Setup: func() {
-					mockDB.EXPECT().UpdateAssetGroupTagSelector(
+					mockGraphDb.EXPECT().
+						PrepareCypherQuery(gomock.Any(), int64(queries.DefaultQueryFitnessLowerBoundSelector)).
+						Return(queries.PreparedQuery{}, nil).Times(1)
+
+					mockDB.EXPECT().UpdateAssetGroupTagSelector(
 						gomock.Any(),
 						uuid.Nil.String(),
 						"[email protected]",
@@
 					).
 						Return(model.AssetGroupTagSelector{}, errors.New("failure")).Times(1)
🧹 Nitpick comments (16)
cmd/ui/src/views/Administration/Administration.test.tsx (2)

72-78: LGTM! Consider reducing redundant queries.

The test correctly validates both the link's presence and its href attribute using the dynamic route-based aria-label pattern (consistent with the AppLink component's fallback behavior per retrieved learnings).

For slightly improved efficiency, you could store the element reference once:

Optional refactor to reduce DOM queries
-        expect(
-            screen.getByRole('link', { name: `Navigate to ${ROUTE_ADMINISTRATION_DATA_QUALITY}` })
-        ).toBeInTheDocument();
-        expect(screen.getByRole('link', { name: `Navigate to ${ROUTE_ADMINISTRATION_DATA_QUALITY}` })).toHaveAttribute(
+        const dataQualityLink = screen.getByRole('link', { name: `Navigate to ${ROUTE_ADMINISTRATION_DATA_QUALITY}` });
+        expect(dataQualityLink).toBeInTheDocument();
+        expect(dataQualityLink).toHaveAttribute(
             'href',
             ROUTE_ADMINISTRATION_DATA_QUALITY
         );

95-100: LGTM! Async handling is appropriate, but consider reusing the element reference.

The use of findByRole (async) correctly handles the SSO configuration link appearing asynchronously (likely after the features API response). However, line 99 re-queries the DOM for the same element.

Optional refactor to reuse element reference
-        expect(
-            await screen.findByRole('link', { name: `Navigate to ${ROUTE_ADMINISTRATION_SSO_CONFIGURATION}` })
-        ).toBeInTheDocument();
-        expect(
-            screen.getByRole('link', { name: `Navigate to ${ROUTE_ADMINISTRATION_SSO_CONFIGURATION}` })
-        ).toHaveAttribute('href', ROUTE_ADMINISTRATION_SSO_CONFIGURATION);
+        const ssoLink = await screen.findByRole('link', { name: `Navigate to ${ROUTE_ADMINISTRATION_SSO_CONFIGURATION}` });
+        expect(ssoLink).toBeInTheDocument();
+        expect(ssoLink).toHaveAttribute('href', ROUTE_ADMINISTRATION_SSO_CONFIGURATION);
cmd/ui/src/views/Explore/GraphItemInformationPanel.tsx (1)

27-29: Consider simplifying the type annotation.

The type annotation HTMLProps<HTMLElement>['className'] is verbose and could be simplified to just string. The HTMLProps import would then be unnecessary.

🔎 Proposed simplification
-import { HTMLProps } from 'react';
-
-const defaultClasses: HTMLProps<HTMLElement>['className'] = 'bottom-0 top-0 py-4 absolute right-4';
+const defaultClasses = 'bottom-0 top-0 py-4 absolute right-4';
cmd/api/src/api/v2/graphschema.go (1)

68-68: Minor: Use camelCase for variable names per Go conventions.

The variable schema_name_filter uses snake_case, but Go idiom prefers camelCase for local variables.

🔎 Suggested fix
-		for i, schema_name_filter := range queryFilters["schema_names"] {
-			schema_name_filter.SetOperator = model.FilterOr
-			translatedFilters["schema.name"][i] = schema_name_filter
+		for i, schemaNameFilter := range queryFilters["schema_names"] {
+			schemaNameFilter.SetOperator = model.FilterOr
+			translatedFilters["schema.name"][i] = schemaNameFilter
cmd/api/src/database/helper.go (1)

179-186: Add a default case to handle unexpected sort directions.

The switch covers AscendingSortDirection, DescendingSortDirection, and InvalidSortDirection, but lacks a default case. If a new SortDirection enum value is added in the future, it would silently produce an empty dirString, resulting in malformed SQL (e.g., "column_name ").

🔎 Suggested fix
 		switch item.Direction {
 		case model.AscendingSortDirection:
 			dirString = "ASC"
 		case model.DescendingSortDirection:
 			dirString = "DESC"
 		case model.InvalidSortDirection:
 			return "", ErrInvalidSortDirection
+		default:
+			return "", ErrInvalidSortDirection
 		}
cmd/api/src/queries/graph_integration_test.go (2)

157-161: Reflection access may panic if field doesn't exist.

Using reflect.ValueOf(expectedUser).FieldByName(testCase.matchUserField) could return a zero Value if the field doesn't exist, and calling .String() on it would panic. Consider adding a validity check.

🔎 Proposed safer reflection access
 			if testCase.shouldMatchUser {
 				expectedUser := results[0]
 				value := reflect.ValueOf(expectedUser)
-				require.Equal(t, value.FieldByName(testCase.matchUserField).String(), testCase.queryString)
+				field := value.FieldByName(testCase.matchUserField)
+				require.True(t, field.IsValid(), "field %s not found", testCase.matchUserField)
+				require.Equal(t, field.String(), testCase.queryString)
 			}

253-261: Map access without nil/existence check.

Accessing actualResult.Properties.Map[testCase.matchUserField] without checking if the key exists could return nil, which may cause unexpected behavior in the equality assertion.

🔎 Proposed safer map access
 			if testCase.shouldMatchUser {
 				var actualResult *graph.Node
 				for _, node := range results {
 					actualResult = node
 					break
 				}
-				actualValue := actualResult.Properties.Map[testCase.matchUserField]
-				require.Equal(t, actualValue, testCase.searchValue)
+				require.NotNil(t, actualResult, "expected at least one result")
+				actualValue, ok := actualResult.Properties.Map[testCase.matchUserField]
+				require.True(t, ok, "field %s not found in properties", testCase.matchUserField)
+				require.Equal(t, actualValue, testCase.searchValue)
 			}
cmd/api/src/api/v2/search.go (1)

47-58: Consider early return pattern for cleaner control flow.

The nested if-else structure with the user check could be simplified using early returns, which is more idiomatic Go.

🔎 Proposed refactor for cleaner control flow
-	if user, isUser := auth.GetUserFromAuthCtx(bhCtx.FromRequest(request).AuthCtx); !isUser {
+	user, isUser := auth.GetUserFromAuthCtx(bhCtx.FromRequest(request).AuthCtx)
+	if !isUser {
 		api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusBadRequest, "no associated user found with request", request), response)
 		return
-	} else {
-		// ETAC feature flag
-		if etacFlag, err := s.DB.GetFlagByKey(request.Context(), appcfg.FeatureETAC); err != nil {
-			api.HandleDatabaseError(request, response, err)
-			return
-		} else if etacFlag.Enabled && !user.AllEnvironments {
-			etacAllowedList = ExtractEnvironmentIDsFromUser(&user)
-		}
 	}
+
+	// ETAC feature flag
+	if etacFlag, err := s.DB.GetFlagByKey(request.Context(), appcfg.FeatureETAC); err != nil {
+		api.HandleDatabaseError(request, response, err)
+		return
+	} else if etacFlag.Enabled && !user.AllEnvironments {
+		etacAllowedList = ExtractEnvironmentIDsFromUser(&user)
+	}
cmd/api/src/queries/queries_integration_test.go (2)

145-149: Close error is discarded.

GraphDB.Close may return an error that is currently ignored. Consider logging or checking the error, especially in test teardown where lingering issues could affect subsequent tests.

🔎 Proposed fix
 func teardownIntegrationTestSuite(t *testing.T, suite *IntegrationTestSuite) {
 	t.Helper()
 
-	suite.GraphDB.Close(suite.Context)
+	if err := suite.GraphDB.Close(suite.Context); err != nil {
+		t.Logf("warning: failed to close graph database: %v", err)
+	}
 }

39-46: Acknowledged code duplication with a TODO note.

The NOTE comment indicates this setup logic is duplicated from graphify_integration_test(). Consider extracting shared test utilities to a common location (e.g., test/integration/utils) to reduce maintenance burden as more integration tests are added.

cmd/api/src/database/graphschema_integration_test.go (2)

132-134: Consider adding t.Parallel() for test isolation.

Unlike other test functions in this file, TestDatabase_GetGraphSchemaExtensions doesn't call t.Parallel(). If this is intentional due to shared state concerns, consider adding a comment. Otherwise, adding t.Parallel() would be consistent with other tests.


1283-1305: Inconsistent expected data initialization.

The second SchemaEnvironment in the expected result has explicit Basic struct with dates that will be zeroed out during comparison anyway (lines 1319-1322). This creates unnecessary noise and inconsistency with the first item. Consider simplifying:

🔎 Suggested simplification
 				res: []model.SchemaEnvironment{
 					{
 						Serial: model.Serial{
 							ID: 1,
 						},
 						SchemaExtensionId: 1,
 						EnvironmentKindId: 1,
 						SourceKindId:      1,
 					},
 					{
 						Serial: model.Serial{
 							ID: 2,
-							Basic: model.Basic{
-								CreatedAt: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC),
-								UpdatedAt: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC),
-								DeletedAt: sql.NullTime{Time: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), Valid: false},
-							},
 						},
 						SchemaExtensionId: 1,
 						EnvironmentKindId: 2,
 						SourceKindId:      2,
 					},
 				},
cmd/api/src/api/v2/search_test.go (1)

37-217: Clarify ETAC flag handling in “No User” SearchHandler test

The SearchHandler tests comprehensively exercise OpenGraphSearch + ETAC interactions, but the case Name: "Fail -- ETAC Feature Flag On, No User" never configures mockDB.GetFlagByKey for appcfg.FeatureETAC, whereas all other ETAC‑specific cases do.

If SearchHandler determines ETAC enablement via GetFlagByKey, this test either:

  • won’t actually exercise the “ETAC on” branch, or
  • will panic on an unexpected mock call.

Consider either:

  • adding an explicit GetFlagByKey(..., appcfg.FeatureETAC) expectation returning Enabled: true, or
  • renaming the case to avoid implying the feature flag is on if the handler doesn’t consult ETAC in this path.
cmd/api/src/api/v2/assetgrouptags_test.go (1)

3247-3317: Align tag types in “success – query by name type label” SearchAssetGroupTags test

In the “success – query by name type label” test, myTags is initialized with Type: model.AssetGroupTagTypeTier:

myTags := model.AssetGroupTags{{Name: "test label", Type: model.AssetGroupTagTypeTier}}
myKinds := graph.Kinds{myTags[0].ToKind()}

but the mocked GetAssetGroupTags return value uses Type: model.AssetGroupTagTypeLabel. That mismatch doesn’t currently break the test (since ToKind likely derives the graph kind from the name), but it makes the intent harder to follow.

For clarity and to avoid future surprises if ToKind starts depending on tag type, consider making myTags’ type consistent with the mocked DB tag (i.e., AssetGroupTagTypeLabel in both places).

cmd/api/src/database/migration/migrations/v8.5.0.sql (1)

92-102: Consider reordering PRIMARY KEY clause for consistency.

The PRIMARY KEY (id) clause is placed after the created_at/updated_at columns, which differs from other tables in this migration where PRIMARY KEY comes earlier (e.g., lines 39, 86, 97). While syntactically valid, consistent ordering improves readability.

That said, the migration is idempotent and correctly structured with appropriate foreign keys, indexes, and constraints.

cmd/api/src/database/graphschema.go (1)

58-58: Consider using a constant or error type instead of string matching.

The DuplicateKeyValueErrorString constant is used for detecting PostgreSQL unique constraint violations by checking if the error message contains this substring. While this works, it's fragile against database error message changes.

Consider using a more robust approach like checking the PostgreSQL error code (e.g., 23505 for unique_violation) if available through the database driver.

Comment on lines 78 to 87
run: |
#!/usr/bin/env bash
set -euo pipefail
shopt -s extglob
# Trim the title in case there is whitespace surrounding the issue
PR_TITLE="${{ github.event.pull_request.title }}"
PR_TITLE="${PR_TITLE##*( )}"
TRIMMED_TITLE="${PR_TITLE%%*( )}"
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

Fix script injection vulnerability.

The PR title is directly interpolated into the bash script on line 85, which creates a script injection risk. User-controlled input should be passed through an environment variable instead.

🔎 Proposed fix
       - name: Check Issue Reference Exists
         id: "ref-check"
         env:
+          PR_TITLE: ${{ github.event.pull_request.title }}
           JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }}
           JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }}
           JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}
         run: |
           #!/usr/bin/env bash
 
           set -euo pipefail
           shopt -s extglob
 
           # Trim the title in case there is whitespace surrounding the issue
-          PR_TITLE="${{ github.event.pull_request.title }}"
+          # PR_TITLE is now set via the environment variable above
           PR_TITLE="${PR_TITLE##*( )}"
           TRIMMED_TITLE="${PR_TITLE%%*( )}"
📝 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
run: |
#!/usr/bin/env bash
set -euo pipefail
shopt -s extglob
# Trim the title in case there is whitespace surrounding the issue
PR_TITLE="${{ github.event.pull_request.title }}"
PR_TITLE="${PR_TITLE##*( )}"
TRIMMED_TITLE="${PR_TITLE%%*( )}"
run: |
#!/usr/bin/env bash
set -euo pipefail
shopt -s extglob
# Trim the title in case there is whitespace surrounding the issue
# PR_TITLE is now set via the environment variable above
PR_TITLE="${PR_TITLE##*( )}"
TRIMMED_TITLE="${PR_TITLE%%*( )}"
🧰 Tools
🪛 actionlint (1.7.9)

78-78: "github.event.pull_request.title" is potentially untrusted. avoid using it directly in inline scripts. instead, pass it through an environment variable. see https://docs.github.com/en/actions/reference/security/secure-use#good-practices-for-mitigating-script-injection-attacks for more details

(expression)

🤖 Prompt for AI Agents
.github/workflows/reusable.conventional-commits.yml around lines 78-87:
currently the workflow interpolates github.event.pull_request.title directly
into the bash run script which creates a script-injection risk; instead, expose
the PR title via the step's env (e.g., set PR_TITLE: ${{
github.event.pull_request.title }}) and then in the run block reference the
value from the environment ($PR_TITLE) with proper double-quotes and parameter
expansion for trimming (do not inject the raw string into the script text);
ensure all expansions are quoted to prevent word-splitting and command
injection.

Comment on lines 1161 to 1185
t.Run("Invalid JSON Payload.", func(t *testing.T) {

// Create the Invalid Request Payload
payload := []string{
`{"Public": false}`,
`Invalid JSON`,
}

// Create the HTTP Request
request, err := http.NewRequestWithContext(createContextWithOwnerId(userId), "PUT", fmt.Sprintf(endpoint, savedQueryId), must.MarshalJSONReader(payload))
require.Nil(t, err)

// Set the Request Headers
request.Header.Set(headers.ContentType.String(), mediatypes.ApplicationJson.String())

router := mux.NewRouter()
router.HandleFunc("/api/v2/saved-queries/{saved_query_id}/permissions", resources.ShareSavedQueries).Methods("PUT")

response := httptest.NewRecorder()

router.ServeHTTP(response, request)
require.Equal(t, http.StatusBadRequest, response.Code)
require.Contains(t, response.Body.String(), "could not decode limited payload request into value")

})
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 the invalid JSON test construction.

The payload is constructed as a []string, which will marshal to a valid JSON array ["...", "..."] rather than invalid JSON. To properly test invalid JSON handling, you should pass actual malformed JSON.

🔎 Proposed fix for testing invalid JSON
-	// Create the Invalid Request Payload
-	payload := []string{
-		`{"Public": false}`,
-		`Invalid JSON`,
-	}
-
-	// Create the HTTP Request
-	request, err := http.NewRequestWithContext(createContextWithOwnerId(userId), "PUT", fmt.Sprintf(endpoint, savedQueryId), must.MarshalJSONReader(payload))
+	// Create the HTTP Request with invalid JSON body
+	request, err := http.NewRequestWithContext(createContextWithOwnerId(userId), "PUT", fmt.Sprintf(endpoint, savedQueryId), strings.NewReader(`{invalid json`))
 	require.Nil(t, err)
🤖 Prompt for AI Agents
In cmd/api/src/api/v2/saved_queries_permissions_test.go around lines 1161-1185,
the test builds payload as a []string which marshals into a valid JSON array;
replace that with an actual malformed JSON payload (e.g. a []byte or io.Reader
containing invalid JSON like `{"Public": false` or `Invalid JSON`) and pass that
reader into http.NewRequestWithContext so the request body is syntactically
invalid; keep the Content-Type header as application/json and assert the same
BadRequest and error substring.

Comment on lines 427 to 462
func (s *BloodhoundDB) GetGraphSchemaEdgeKindsWithSchemaName(ctx context.Context, edgeKindFilters model.Filters, sort model.Sort, skip, limit int) (model.GraphSchemaEdgeKindsWithNamedSchema, int, error) {
var (
schemaEdgeKinds = model.GraphSchemaEdgeKindsWithNamedSchema{}
totalRowCount int
)

if filterAndPagination, err := parseFiltersAndPagination(edgeKindFilters, sort, skip, limit); err != nil {
return schemaEdgeKinds, 0, err
} else {
sqlStr := fmt.Sprintf(`SELECT edge.id, edge.name, edge.description, edge.is_traversable, schema.name as schema_name
FROM %s edge JOIN %s schema ON edge.schema_extension_id = schema.id %s %s %s`,
model.GraphSchemaEdgeKind{}.TableName(),
model.GraphSchemaExtension{}.TableName(),
filterAndPagination.WhereClause,
filterAndPagination.OrderSql,
filterAndPagination.SkipLimit)

if result := s.db.WithContext(ctx).Raw(sqlStr, filterAndPagination.Filter.params...).Scan(&schemaEdgeKinds); result.Error != nil {
return nil, 0, CheckError(result)
} else {
if limit > 0 || skip > 0 {
countSqlStr := fmt.Sprintf(`SELECT COUNT(*) FROM %s edge JOIN %s schema ON edge.schema_extension_id = schema.id %s`,
model.GraphSchemaEdgeKind{}.TableName(),
model.GraphSchemaExtension{}.TableName(),
filterAndPagination.WhereClause)
if countResult := s.db.WithContext(ctx).Raw(countSqlStr, filterAndPagination.Filter.params...).Scan(&totalRowCount); countResult.Error != nil {
return model.GraphSchemaEdgeKindsWithNamedSchema{}, 0, CheckError(countResult)
}
} else {
totalRowCount = len(schemaEdgeKinds)
}
}
return schemaEdgeKinds, totalRowCount, nil

}
}
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find parseFiltersAndPagination function
rg -n "parseFiltersAndPagination|buildSQLFilter" --type go cmd/api/src/database/ -A 30 | head -150

Repository: SpecterOps/BloodHound

Length of output: 12185


🏁 Script executed:

#!/bin/bash
# Find parseFiltersAndPagination function definition
rg -n "func.*parseFiltersAndPagination" --type go cmd/api/src/database/ -A 50

Repository: SpecterOps/BloodHound

Length of output: 2323


🏁 Script executed:

#!/bin/bash
# Get more details about buildSQLFilter to see how it generates column references
rg -n "func buildSQLFilter" --type go cmd/api/src/database/helper.go -A 80 | head -120

Repository: SpecterOps/BloodHound

Length of output: 2733


🏁 Script executed:

#!/bin/bash
# Get the complete buildSQLFilter function
rg -n "func buildSQLFilter" --type go cmd/api/src/database/helper.go -A 120 | tail -70

Repository: SpecterOps/BloodHound

Length of output: 2443


Filter column references require table alias qualification in JOIN queries.

The GetGraphSchemaEdgeKindsWithSchemaName method uses table aliases (edge, schema) in its JOIN. However, the buildSQLFilter function generates unqualified column identifiers (e.g., id instead of edge.id). This causes SQL ambiguity errors when filtering on columns that exist in both tables. Filters applied to this query must either use table-prefixed column names (e.g., edge.id) or only target columns unique to a single table to avoid ambiguous column reference errors.

Comment on lines 110 to 116
entries := strings.Split(config.Database.Connection, " ")

environmentMap := make(map[string]string)
for _, entry := range entries {
parts := strings.Split(entry, "=")
environmentMap[parts[0]] = parts[1]
}
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

Potential panic on malformed connection string entries.

If an entry in the connection string doesn't contain an = character, strings.Split(entry, "=") will return a single-element slice, causing a panic when accessing parts[1] on line 115.

🔎 Proposed defensive fix
 	for _, entry := range entries {
 		parts := strings.Split(entry, "=")
+		if len(parts) != 2 {
+			continue
+		}
 		environmentMap[parts[0]] = parts[1]
 	}
📝 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
entries := strings.Split(config.Database.Connection, " ")
environmentMap := make(map[string]string)
for _, entry := range entries {
parts := strings.Split(entry, "=")
environmentMap[parts[0]] = parts[1]
}
entries := strings.Split(config.Database.Connection, " ")
environmentMap := make(map[string]string)
for _, entry := range entries {
parts := strings.Split(entry, "=")
if len(parts) != 2 {
continue
}
environmentMap[parts[0]] = parts[1]
}
🤖 Prompt for AI Agents
In cmd/api/src/queries/queries_integration_test.go around lines 110 to 116, the
loop that splits connection string entries assumes every entry contains '=' and
directly accesses parts[1], which can panic for malformed entries; update the
loop to defensively handle entries without '=', e.g., skip or log malformed
entries, use strings.SplitN(entry, "=", 2) and check that the resulting slice
has length 2 before assigning environmentMap[key]=value, and trim spaces from
key/value to avoid empty keys.

Comment on lines 142 to 150
{/*
`inert` is a native property that tells screen readers to
disregard this non-interactive, presentational button.
It is unavailable in React 18 (enabled in v19), so this spread
workaround applies the property without triggering type errors
*/}
<div ref={(node) => node && node.setAttribute('inert', '')}>
<Switch checked={darkMode} />
</div>
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

Reconsider the accessibility pattern for the Dark Mode switch.

Marking the Switch as inert while it remains visually interactive creates a potential accessibility mismatch:

  • Sighted users see a clickable Switch component
  • Screen reader users are told the element should be disregarded
  • The parent menu item IS interactive (via functionHandler), but the visual Switch suggests direct interaction

This divergence between visual and assistive technology experiences may confuse users. Consider these alternatives:

  1. Use aria-hidden instead of inert to hide the decorative Switch from screen readers while keeping the parent menu item's label and handler accessible
  2. Make the Switch truly non-interactive by removing pointer events with CSS (pointer-events: none)
  3. Document clearly that the entire menu item (not the Switch) is the interactive element
🔎 Proposed fix using aria-hidden
                <>
                    {'Dark Mode'}
-                   {/* 
-                       `inert` is a native property that tells screen readers to 
-                       disregard this non-interactive, presentational button. 
-                       It is unavailable in React 18 (enabled in v19), so this spread
-                       workaround applies the property without triggering type errors
-                   */}
-                   <div ref={(node) => node && node.setAttribute('inert', '')}>
+                   <div aria-hidden="true" style={{ pointerEvents: 'none' }}>
                        <Switch checked={darkMode} />
                    </div>
                </>

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In cmd/ui/src/components/MainNav/MainNavData.tsx around lines 142-150 the code
sets a DOM attribute 'inert' on the container to hide the visual Switch from
assistive tech which creates a mismatch between visual affordance and
screen-reader behavior; remove the inert setAttribute call and instead mark the
Switch as decorative by adding aria-hidden="true" on the Switch element, make
the Switch non-interactive with CSS (pointer-events: none) so users cannot
interact with it directly, and ensure the parent menu item remains the
accessible control (has role="button"/tabindex and an accessible label/handler)
so screen readers and keyboard users can operate dark mode via the parent;
update any related tests or documentation to note that the entire menu item is
the interactive target.


const tagGlyphs = { Tag_Tier_Zero: 'gem', owned: 'Tag_Owned', ownedGlyph: 'skull' };

it('determines glyphs with the tier_managent_engine feature flag disabled', () => {
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

Typo in test description: "tier_managent_engine" should be "tier_management_engine".

The test descriptions contain a typo - "managent" should be "management".

🔎 Proposed fix
-    it('determines glyphs with the tier_managent_engine feature flag disabled', () => {
+    it('determines glyphs with the tier_management_engine feature flag disabled', () => {

And similarly for line 190:

-    it('determines glyphs with the tier_managent_engine feature flag enabled', () => {
+    it('determines glyphs with the tier_management_engine feature flag enabled', () => {

Also applies to: 190-190

🤖 Prompt for AI Agents
In cmd/ui/src/views/Explore/utils.test.ts around lines 142 and 190, two test
descriptions contain a typo ("tier_managent_engine") — update both occurrences
to "tier_management_engine" so the test names are spelled correctly; simply
correct the string literals in the it(...) descriptions at those lines.

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: 1

🧹 Nitpick comments (2)
cmd/api/src/bootstrap/server.go (1)

61-61: Consider consistent parameter naming.

The parameter is named defaultAdminFunc in MigrateDB (line 61) but defaultAdminFunction in CreateDefaultAdmin (line 75). While functionally identical, consistent naming improves readability.

🔎 Proposed fix for consistency
-func CreateDefaultAdmin(ctx context.Context, cfg config.Configuration, db database.Database, defaultAdminFunction func() (config.DefaultAdminConfiguration, error)) error {
+func CreateDefaultAdmin(ctx context.Context, cfg config.Configuration, db database.Database, defaultAdminFunc func() (config.DefaultAdminConfiguration, error)) error {
 	var (
 		secretDigester = cfg.Crypto.Argon2.NewDigester()
 		needsLog       = false
 	)
 
 	if cfg.DefaultAdmin.Password == "" {
 		needsLog = true
-		if admin, err := defaultAdminFunction(); err != nil {
+		if admin, err := defaultAdminFunc(); err != nil {
 			return fmt.Errorf("error in setup initializing auth secret: %w", err)

Also applies to: 75-75

cmd/api/src/api/v2/assetgrouptags.go (1)

110-118: Minor optimization: Remove redundant map initialization.

Line 110 pre-allocates assetGroupTagCountsMap, but line 118 immediately overwrites it with the result from GetAssetGroupTagSelectorCounts. Consider removing line 110 or changing line 118 to avoid the redundant allocation.

🔎 Proposed optimization
-		assetGroupTagCountsMap = make(model.AssetGroupTagCountsMap, len(tags))
+		var assetGroupTagCountsMap model.AssetGroupTagCountsMap
 	)

 	if paramIncludeCounts {
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between beb6929 and f4bbe44.

📒 Files selected for processing (4)
  • cmd/api/src/api/v2/assetgrouptags.go
  • cmd/api/src/api/v2/assetgrouptags_test.go
  • cmd/api/src/bootstrap/server.go
  • cmd/api/src/model/search.go
🧰 Additional context used
🧠 Learnings (8)
📚 Learning: 2025-07-09T00:36:54.112Z
Learnt from: mistahj67
Repo: SpecterOps/BloodHound PR: 1648
File: cmd/api/src/api/v2/assetgrouptags.go:763-766
Timestamp: 2025-07-09T00:36:54.112Z
Learning: In cmd/api/src/api/v2/assetgrouptags.go, the SearchAssetGroupTags method intentionally fetches all asset group tags and selectors without database-level filtering because it needs to build a complete `kinds` array from all relevant tags for the graph query filter. This allows members to be searched across all tags of the requested type while still filtering the returned tags/selectors by name match.

Applied to files:

  • cmd/api/src/api/v2/assetgrouptags.go
  • cmd/api/src/api/v2/assetgrouptags_test.go
📚 Learning: 2025-11-05T21:13:08.542Z
Learnt from: ktstrader
Repo: SpecterOps/BloodHound PR: 2020
File: cmd/api/src/queries/graph.go:198-203
Timestamp: 2025-11-05T21:13:08.542Z
Learning: In cmd/api/src/queries/graph.go, when ETAC filtering is enabled in GetAssetGroupComboNode, an empty etacAllowedList (when user.AllEnvironments is false and user.EnvironmentTargetedAccessControl is empty) is intentional and should not be guarded against. The empty list will cause query.In() to filter out all nodes, which is the correct security behavior—users with no environment assignments should see no results.

Applied to files:

  • cmd/api/src/api/v2/assetgrouptags.go
  • cmd/api/src/api/v2/assetgrouptags_test.go
📚 Learning: 2025-09-08T19:22:49.284Z
Learnt from: jvacca-specterops
Repo: SpecterOps/BloodHound PR: 1823
File: packages/javascript/bh-shared-ui/src/views/Explore/ExploreSearch/SavedQueries/TagToZoneLabelDialog.tsx:34-35
Timestamp: 2025-09-08T19:22:49.284Z
Learning: In BloodHound's TagToZoneLabelDialog component (packages/javascript/bh-shared-ui/src/views/Explore/ExploreSearch/SavedQueries/TagToZoneLabelDialog.tsx), importing AssetGroupTag type from 'js-client-library' to type tag shapes is incorrect - this type should not be used for typing tags in this context.

Applied to files:

  • cmd/api/src/api/v2/assetgrouptags.go
📚 Learning: 2025-07-22T20:30:34.839Z
Learnt from: LawsonWillard
Repo: SpecterOps/BloodHound PR: 1700
File: cmd/api/src/api/v2/saved_queries_test.go:3182-3182
Timestamp: 2025-07-22T20:30:34.839Z
Learning: In Go table-driven tests in cmd/api/src/api/v2/saved_queries_test.go, subtest parallelization with t.Parallel() is acceptable when tests are self-contained, each creating their own mock controller (gomock.NewController(t)) and having isolated mock expectations without shared state between subtests.

Applied to files:

  • cmd/api/src/api/v2/assetgrouptags_test.go
📚 Learning: 2025-06-06T23:12:14.181Z
Learnt from: elikmiller
Repo: SpecterOps/BloodHound PR: 1563
File: packages/go/graphschema/azure/azure.go:24-24
Timestamp: 2025-06-06T23:12:14.181Z
Learning: In BloodHound, files in packages/go/graphschema/*/`*.go` are generated from CUE schemas. When `just prepare-for-codereview` is run, it triggers code generation that may automatically add import aliases or other formatting changes. These changes are legitimate outputs of the generation process, not manual edits that would be overwritten.

Applied to files:

  • cmd/api/src/api/v2/assetgrouptags_test.go
📚 Learning: 2025-08-28T16:43:43.961Z
Learnt from: mvlipka
Repo: SpecterOps/BloodHound PR: 1784
File: packages/go/openapi/doc/openapi.json:18008-18029
Timestamp: 2025-08-28T16:43:43.961Z
Learning: In SpecterOps/BloodHound, packages/go/openapi/doc/openapi.json is generated from YAML under packages/go/openapi/src/schemas; edits must be made to the YAML and then the spec regenerated.

Applied to files:

  • cmd/api/src/api/v2/assetgrouptags_test.go
📚 Learning: 2025-06-17T22:37:36.389Z
Learnt from: LawsonWillard
Repo: SpecterOps/BloodHound PR: 1595
File: cmd/api/src/api/v2/saved_queries_test.go:2594-2594
Timestamp: 2025-06-17T22:37:36.389Z
Learning: In Go table-driven tests, there's a distinction between main test function parallelism and subtest parallelism. Main test functions can safely use t.Parallel() for performance benefits, but individual subtests within table-driven tests may need to run sequentially to avoid race conditions with mocks, deferred functions, or shared resources.

Applied to files:

  • cmd/api/src/api/v2/assetgrouptags_test.go
📚 Learning: 2025-05-27T16:58:33.295Z
Learnt from: superlinkx
Repo: SpecterOps/BloodHound PR: 1503
File: cmd/api/src/services/job/jobs_test.go:19-143
Timestamp: 2025-05-27T16:58:33.295Z
Learning: Tests in cmd/api/src/services/job/jobs_test.go have been found to be flaky in the past and are due for rewrite. They should be skipped with t.Skip() until they can be properly rewritten.

Applied to files:

  • cmd/api/src/api/v2/assetgrouptags_test.go
🧬 Code graph analysis (3)
cmd/api/src/model/search.go (2)
packages/go/schemagen/csgen/models.go (1)
  • Type (84-84)
packages/go/graphschema/common/common.go (3)
  • Name (53-53)
  • ObjectID (52-52)
  • Collected (57-57)
cmd/api/src/api/v2/assetgrouptags.go (3)
cmd/api/src/model/assetgrouptags.go (3)
  • AssetGroupTagCounts (93-99)
  • AssetGroupTagCountsMap (101-101)
  • AssetGroupSelectorNode (288-300)
cmd/api/src/api/filters.go (1)
  • GetValidFilterPredicatesAsStrings (47-57)
packages/go/graphschema/common/common.go (2)
  • Name (53-53)
  • ObjectID (52-52)
cmd/api/src/api/v2/assetgrouptags_test.go (6)
cmd/api/src/model/filter.go (1)
  • SQLFilter (98-101)
packages/go/graphschema/common/common.go (4)
  • Name (53-53)
  • Description (55-55)
  • ObjectID (52-52)
  • PrimaryKind (70-70)
cmd/api/src/model/assetgrouptags.go (8)
  • AssetGroupTags (121-121)
  • AssetGroupTag (103-119)
  • AssetGroupTag (123-125)
  • AssetGroupTagCountsMap (101-101)
  • AssetGroupTagSelector (225-241)
  • AssetGroupTagSelector (243-245)
  • SelectorSeed (202-206)
  • SelectorSeed (208-210)
cmd/api/src/queries/graph.go (1)
  • DefaultQueryFitnessLowerBoundSelector (67-67)
packages/go/graphschema/ad/ad.go (2)
  • User (29-29)
  • Entity (28-28)
cmd/api/src/database/db.go (1)
  • ErrNotFound (41-41)
🔇 Additional comments (15)
cmd/api/src/bootstrap/server.go (1)

130-130: Typo fix applied correctly.

The typo "initalizing" → "initializing" has been properly fixed as noted in the previous review feedback.

cmd/api/src/model/search.go (1)

43-43: The field follows established codebase patterns and doesn't introduce new inconsistencies. The JSON naming uses snake_case (hygiene_attack_paths), which is the prevalent convention across the model files (e.g., user_id, exposure_percent, asset_group_tag_id). The impactValue field on line 42 is actually the outlier using camelCase, not the new field introducing inconsistency. Additionally, the int64 JavaScript precision concern is already documented with a comment on line 43, which appropriately warns UI consumers about the potential truncation when values exceed JavaScript's Number.MAX_SAFE_INTEGER.

Likely an incorrect or invalid review comment.

cmd/api/src/api/v2/assetgrouptags.go (7)

828-834: LGTM: ValidFilters method correctly defines filterable fields.

The filter definitions align with the AssetGroupMember struct fields and are used consistently in downstream translation logic.


947-983: Filter translation logic is correct.

The function properly translates API filter names to graph properties and operators. The NotEquals handling via query.Not wrapper is correct.


1056-1059: Verify empty environment filter handling is intentional.

The code adds environment filtering without checking if environmentIds is empty. Based on learnings, an empty list may be intentional for ETAC filtering (filtering out all nodes when user has no environment access). Confirm this behavior is correct for this endpoint.

Based on learnings, empty environment lists may be intentional security behavior.


1194-1202: Asset group tag ID filtering added correctly.

The logic correctly filters by tag ID when provided. Note that if a tag ID is specified but doesn't match the requested tag_type, it will be filtered out in the subsequent loop (line 1208), resulting in an empty response. This may be intentional but could be clarified with documentation.


64-64: Type refactoring to model package is correct.

The change to reference *model.AssetGroupTagCounts aligns with the broader refactoring that centralizes type definitions in the model package. All usages are updated consistently throughout the file.


900-914: No changes needed to filter parsing.

The omission of IsStringData is intentional. Unlike GetAssetGroupTags and GetAssetGroupMembersBySelector, which build SQL filters via BuildSQLFilter() and need IsStringData for proper value conversion, this code passes filters directly to buildAssetGroupMembersByTagGraphDbFilters, which constructs graph criteria without an intermediate SQL step. IsStringData is not required for graph query criteria.


1052-1064: SQL filter concatenation is safe and properly parameterized.

The BuildSQLFilter() method uses a pgsql.Expression builder and format.SyntaxNode() to generate SQL, which properly handles parameterization. The subsequent string concatenation at lines 1052-1064 maintains this safety by using "?" placeholders with values passed through the Params array, following the standard parameterized query pattern.

cmd/api/src/api/v2/assetgrouptags_test.go (6)

85-239: LGTM: Mock expectations correctly updated to use concrete types.

The change from gomock.Any() to model.SQLFilter{} provides more precise test expectations. The AssetGroupTagCountsMap structure correctly reflects the new count fields (Selectors, CustomSelectors, DefaultSelectors, DisabledSelectors), and the CountNodesByKind expectations properly use ToKind() for each tag.


2340-2448: Excellent test coverage for new filtering functionality.

The test cases comprehensively cover filter validation and translation:

  • Invalid predicate and column errors are tested
  • primary_kind filtering with Equals operator
  • name filtering with ApproximatelyEquals (translated to StringContains)
  • object_id filtering with NotEquals (wrapped in query.Not)

All expectations correctly match the production code's filter translation logic.


2946-3058: Test coverage for node_primary_kind filtering is comprehensive.

The two test cases properly verify:

  1. Field name translation from API primary_kind to database node_primary_kind
  2. Conditional certification filtering when RequireCertify is enabled

The SQL filter expectations match the production code's translation and filtering logic.


3571-3756: Excellent test coverage for new asset_group_tag_id filtering.

The two new test cases comprehensively verify:

  1. Filtering by asset_group_tag_id with tier type (lines 3571-3654)
  2. Filtering by asset_group_tag_id with label type (lines 3656-3756)

Both tests correctly expect the SQL filter "id = ?" to be passed to GetAssetGroupTags and verify the corresponding selectors and members are filtered appropriately. The test data and expectations align with the production code changes.


51-51: Import addition is correct.

The graphschema/common import is necessary for common.Name and common.ObjectID constants used in the new filter translation test expectations.


478-482: Mock expectations correctly use explicit fitness value.

The change from gomock.Any() to int64(-3) (which equals queries.DefaultQueryFitnessLowerBoundSelector) makes the test more precise and verifies the correct fitness value is passed.

Comment on lines 81 to 88
if cfg.DefaultAdmin.Password == "" {
needsLog = true
if admin, err := defaultAdminFunction(); err != nil {
return fmt.Errorf("error in setup initializing auth secret: %w", err)
} else {
cfg.DefaultAdmin = admin
}
}
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

Clarify error message context.

The error message at line 84 states "error in setup initializing auth secret", but at this point the code is generating default admin configuration, not initializing an auth secret. The auth secret initialization happens later at line 129.

🔎 Proposed fix for clarity
 	if cfg.DefaultAdmin.Password == "" {
 		needsLog = true
 		if admin, err := defaultAdminFunction(); err != nil {
-			return fmt.Errorf("error in setup initializing auth secret: %w", err)
+			return fmt.Errorf("error generating default admin configuration: %w", err)
 		} else {
 			cfg.DefaultAdmin = admin
 		}
 	}
🤖 Prompt for AI Agents
In cmd/api/src/bootstrap/server.go around lines 81 to 88, the error return uses
the misleading message "error in setup initializing auth secret" while this
block is generating a default admin; update the error string to accurately
reflect the operation (e.g., "error creating default admin") and keep the
existing error wrapping (%w) so callers receive the original error details.

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: 0

♻️ Duplicate comments (1)
cmd/ui/src/views/Explore/utils.test.ts (1)

142-142: Fix typo in test descriptions.

The test descriptions contain a typo: "tier_managent_engine" should be "tier_management_engine" to match the actual feature flag key.

🔎 Proposed fix
-    it('determines glyphs with the tier_managent_engine feature flag disabled', () => {
+    it('determines glyphs with the tier_management_engine feature flag disabled', () => {

And for line 190:

-    it('determines glyphs with the tier_managent_engine feature flag enabled', () => {
+    it('determines glyphs with the tier_management_engine feature flag enabled', () => {

Also applies to: 190-190

🧹 Nitpick comments (1)
cmd/ui/src/views/Explore/utils.ts (1)

117-161: LGTM! Glyph generation logic is clean and well-structured.

The getNodeGlyphs function properly encapsulates the glyph rendering logic with two distinct paths:

  • When pzFeatureFlagEnabled is true: uses tagGlyphs from node kinds
  • When false: uses legacy isTierZero and isOwnedObject boolean flags

Both paths correctly apply themed colors and glyph locations.

💡 Optional ergonomics improvement

Consider making the standardGlyphs parameter optional with GLYPHS as the default value to simplify call sites:

 export const getNodeGlyphs = (
     node: GraphNode,
     options: GraphOptions & { themedOptions: ThemedOptions },
-    standardGlyphs: typeof GLYPHS
+    standardGlyphs: typeof GLYPHS = GLYPHS
 ) => {

This would allow callers to omit the third argument in most cases while still supporting test scenarios that need to inject mock glyphs.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f4bbe44 and 767cf6e.

📒 Files selected for processing (3)
  • cmd/ui/src/views/Explore/GraphView.tsx
  • cmd/ui/src/views/Explore/utils.test.ts
  • cmd/ui/src/views/Explore/utils.ts
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-08-11T23:51:32.709Z
Learnt from: benwaples
Repo: SpecterOps/BloodHound PR: 1749
File: packages/javascript/bh-shared-ui/src/hooks/useExploreSelectedItem.tsx:40-46
Timestamp: 2025-08-11T23:51:32.709Z
Learning: The useGraphItem hook in packages/javascript/bh-shared-ui/src/hooks/useGraphItem.tsx already has an enabled field set to !!itemId, which prevents the query from executing when itemId is falsy, providing built-in null-safety.

Applied to files:

  • cmd/ui/src/views/Explore/GraphView.tsx
📚 Learning: 2025-07-17T16:28:00.770Z
Learnt from: TheNando
Repo: SpecterOps/BloodHound PR: 1693
File: cmd/ui/src/views/Explore/GraphView.tsx:155-159
Timestamp: 2025-07-17T16:28:00.770Z
Learning: In the BloodHound codebase GraphView.tsx, the context menu toggle logic that switches between null and new coordinates (closing on different item clicks) is intentional, expected behavior as of the current implementation, though this may change in the future.

Applied to files:

  • cmd/ui/src/views/Explore/GraphView.tsx
📚 Learning: 2025-08-28T18:42:47.612Z
Learnt from: benwaples
Repo: SpecterOps/BloodHound PR: 1829
File: packages/javascript/bh-shared-ui/src/components/Navigation/MainNav.tsx:41-56
Timestamp: 2025-08-28T18:42:47.612Z
Learning: The BaseSVG component in packages/javascript/bh-shared-ui/src/components/AppIcon/Icons/utils.tsx already provides accessibility through VisuallyHidden labels, eliminating the need for additional accessible names on wrapper elements.

Applied to files:

  • cmd/ui/src/views/Explore/utils.ts
📚 Learning: 2025-08-28T18:42:47.612Z
Learnt from: benwaples
Repo: SpecterOps/BloodHound PR: 1829
File: packages/javascript/bh-shared-ui/src/components/Navigation/MainNav.tsx:41-56
Timestamp: 2025-08-28T18:42:47.612Z
Learning: The BaseSVG component in BloodHound's bh-shared-ui package automatically includes VisuallyHidden labels with accessible names using the pattern "app-icon-{name}", making additional aria-label or title attributes unnecessary on parent elements.

Applied to files:

  • cmd/ui/src/views/Explore/utils.ts
📚 Learning: 2025-09-08T19:22:49.284Z
Learnt from: jvacca-specterops
Repo: SpecterOps/BloodHound PR: 1823
File: packages/javascript/bh-shared-ui/src/views/Explore/ExploreSearch/SavedQueries/TagToZoneLabelDialog.tsx:34-35
Timestamp: 2025-09-08T19:22:49.284Z
Learning: In BloodHound's TagToZoneLabelDialog component (packages/javascript/bh-shared-ui/src/views/Explore/ExploreSearch/SavedQueries/TagToZoneLabelDialog.tsx), importing AssetGroupTag type from 'js-client-library' to type tag shapes is incorrect - this type should not be used for typing tags in this context.

Applied to files:

  • cmd/ui/src/views/Explore/utils.ts
  • cmd/ui/src/views/Explore/utils.test.ts
🧬 Code graph analysis (2)
cmd/ui/src/views/Explore/GraphView.tsx (1)
packages/javascript/bh-shared-ui/src/hooks/useFeatureFlags.tsx (1)
  • useFeatureFlag (59-61)
cmd/ui/src/views/Explore/utils.test.ts (1)
cmd/ui/src/views/Explore/utils.ts (1)
  • getNodeGlyphs (117-161)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Build BloodHound Container Image / Build and Package Container
  • GitHub Check: run-analysis
  • GitHub Check: run-go-unit-tests
  • GitHub Check: build-ui
  • GitHub Check: run-tests
🔇 Additional comments (7)
cmd/ui/src/views/Explore/utils.ts (4)

17-26: LGTM! Imports are appropriate for the glyph rendering feature.

The new imports for FontAwesome icons, glyph utilities from bh-shared-ui, and Glyph types are correctly added to support the feature-flagged glyph rendering system.

Also applies to: 30-31


65-65: LGTM! Feature flag field added to GraphOptions.

The pzFeatureFlagEnabled field correctly uses boolean | undefined to handle cases where the feature flag hasn't loaded yet.


102-115: LGTM! Standard glyph constants are well-defined.

The GLYPHS constant provides light and dark mode variants for tier-zero and owned-object glyphs using FontAwesome icons with appropriate scaling.


185-189: LGTM! Clean refactor that improves testability.

The inline glyph logic has been successfully extracted into the getNodeGlyphs function, making the code more modular and easier to test.

cmd/ui/src/views/Explore/GraphView.tsx (1)

38-38: LGTM! Feature flag integration is correctly implemented.

The feature flag is properly retrieved using useFeatureFlag('tier_management_engine'), passed into graphOptions with optional chaining to handle loading states, and included in the dependency array to ensure graph re-rendering when the flag changes.

Also applies to: 83-83, 103-103, 105-105

cmd/ui/src/views/Explore/utils.test.ts (2)

18-20: LGTM! Test data properly extended for glyph testing.

The test data now includes kinds arrays with Tag_Tier_Zero and Tag_Owned, along with corresponding isTierZero and isOwnedObject flags. The pzFeatureFlagEnabled variable is correctly passed to initGraph calls.

Also applies to: 28-42, 76-76, 89-89, 104-104


116-237: LGTM! Comprehensive test coverage for glyph rendering.

The test suite thoroughly validates both feature flag paths:

  • When disabled: verifies legacy isTierZero/isOwnedObject behavior
  • When enabled: verifies tag-based glyph selection

Each test correctly checks glyph location, themed colors, and image selection.

@elikmiller elikmiller merged commit 8d1035d into main Dec 24, 2025
23 of 24 checks passed
@elikmiller elikmiller deleted the stage/v8.4.2 branch December 24, 2025 00:35
@github-actions github-actions bot locked and limited conversation to collaborators Dec 24, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

release Release Fixups/Merge backs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants