Skip to content

feat: add decoy object tagging (#2912)#2932

Open
MickeyDB wants to merge 1 commit into
SpecterOps:mainfrom
MickeyDB:codex/decoy-asset-group
Open

feat: add decoy object tagging (#2912)#2932
MickeyDB wants to merge 1 commit into
SpecterOps:mainfrom
MickeyDB:codex/decoy-asset-group

Conversation

@MickeyDB

@MickeyDB MickeyDB commented Jun 26, 2026

Copy link
Copy Markdown

Description

Adds Decoy object tagging to BloodHound CE.

This change introduces a default Decoy asset group tag, exposes decoy state through graph API responses, and adds UI support for marking and unmarking objects as Decoy from Explore/entity views. Decoy objects get their own graph label/glyph behavior and are treated similarly to Owned objects where that model already exists.

Inspired by: Honeypot detection
Notes: mbaedev.notion.site

The implementation includes:

  • A database migration for the default Decoy asset group tag
  • Backend support for the Decoy tag type and Tag_Decoy graph kind
  • tiering.IsDecoy() and UnifiedNode.isDecoyObject support
  • UI controls for Decoy marking/unmarking through AGT selectors
  • Decoy display support in Explore, entity info, search results, and graph glyph handling
  • Protection against renaming/deleting the default Decoy tag
  • Inclusion of Decoy objects in label-style asset group tag search
  • Exclusion of Decoy nodes from predefined queries
  • OpenAPI updates for the new isDecoyObject graph node field

This also includes a small selector seed filtering fix: selector seed value filtering now works as expected. The Decoy banner depends on that behavior to find existing object-id selectors, so it is included here intentionally rather than as accidental scope creep.

Motivation and Context

Resolves #2912

BloodHound currently has strong support for marking high-value or controlled objects, but there is no equivalent way to mark deception assets such as decoy users, computers, or other intentionally placed objects.

That creates two practical problems:

  • Decoy assets can appear as normal nodes in attack path workflows
  • Users have no built-in way to distinguish deception objects from ordinary assets in the graph UI

This feature gives operators a way to mark those objects explicitly. The goal is not to hide decoys from the graph entirely, but to make them visible as Decoy objects and avoid treating them as normal targets in predefined queries.

How Has This Been Tested?

Tested locally from on branch codex/decoy-asset-group, rebased onto current upstream/main.

The required repository prep command completed successfully:

SB_YARN_CMD=<SNIPPED>/BloodHound/.yarn/releases/yarn-4.13.0.cjs just prepare-for-codereview

Additional coverage added/updated includes:

  • Go tests for Decoy tag behavior, selector filtering, search inclusion, and unified graph response fields
  • tiering.IsDecoy() unit coverage
  • UI tests for potential Decoy banner behavior
  • Explore/context menu tests for Decoy actions and glyph behavior
  • Search/query tests updated for isDecoyObject

I also manually validated the feature in a local Docker dev stack by marking a user as Decoy through the AGT selector flow and confirming the node received the Tag_Decoy label in Neo4j.

Screenshots:

image image

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Database Migrations

Checklist:

Summary by CodeRabbit

  • New Features

    • Added Decoy support across asset group tags (including search, selection, tagging) and graph visualization.
    • Decoy objects now expose an isDecoyObject signal for glyphs, entity details, and the potential-decoy banner/toggle.
    • Updated UI context menus and OpenAPI schema to recognize decoy tag type 4.
  • Bug Fixes

    • Corrected decoy-aware filtering/matching behavior and ensured forbidden operations apply to default decoy tags.
    • Updated graph/Cypher search results to exclude decoy-tagged nodes where appropriate.
  • Documentation / Tests

    • Expanded OpenAPI and API/UI test coverage for decoy scenarios.

@github-actions

github-actions Bot commented Jun 26, 2026

Copy link
Copy Markdown

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

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6250b6e0-11d9-4142-a5c8-1809a8cc624d

📥 Commits

Reviewing files that changed from the base of the PR and between 78b3fc6 and 78923d7.

📒 Files selected for processing (41)
  • cmd/api/src/api/v2/assetgrouptags.go
  • cmd/api/src/api/v2/assetgrouptags_test.go
  • cmd/api/src/api/v2/cypherquery_test.go
  • cmd/api/src/api/v2/etac.go
  • cmd/api/src/database/assetgrouptags.go
  • cmd/api/src/database/assetgrouptags_test.go
  • cmd/api/src/database/migration/agi_integration_test.go
  • cmd/api/src/database/migration/migrations/20260618120000_v9_add_decoy_asset_group.sql
  • cmd/api/src/model/assetgrouptags.go
  • cmd/api/src/model/unified_graph.go
  • cmd/api/src/model/unified_graph_test.go
  • cmd/ui/src/ducks/assetgroups/reducer.ts
  • cmd/ui/src/views/Explore/ContextMenu/AssetGroupMenuItem.tsx
  • cmd/ui/src/views/Explore/ContextMenu/ContextMenu.tsx
  • cmd/ui/src/views/Explore/utils.test.ts
  • cmd/ui/src/views/Explore/utils.ts
  • packages/go/analysis/agt.go
  • packages/go/analysis/metrics.go
  • packages/go/analysis/tiering/tiering.go
  • packages/go/analysis/tiering/tiering_test.go
  • packages/go/graphschema/ad/const.go
  • packages/go/openapi/doc/openapi.json
  • packages/go/openapi/src/paths/asset-isolation.asset-group-tags.search.yaml
  • packages/go/openapi/src/schemas/model.unified-graph.node.yaml
  • packages/javascript/bh-shared-ui/src/commonSearchesAGI.ts
  • packages/javascript/bh-shared-ui/src/commonSearchesAGT.ts
  • packages/javascript/bh-shared-ui/src/components/EntityInfo/EntityObjectInformation.tsx
  • packages/javascript/bh-shared-ui/src/components/EntityInfo/PotentialDecoyBanner.test.tsx
  • packages/javascript/bh-shared-ui/src/components/EntityInfo/PotentialDecoyBanner.tsx
  • packages/javascript/bh-shared-ui/src/constants.ts
  • packages/javascript/bh-shared-ui/src/hooks/useAssetGroupTags/useAssetGroupTags.test.tsx
  • packages/javascript/bh-shared-ui/src/hooks/useAssetGroupTags/useAssetGroupTags.tsx
  • packages/javascript/bh-shared-ui/src/hooks/useAssetGroupTags/useTagGlyphs.ts
  • packages/javascript/bh-shared-ui/src/hooks/useExploreGraph/queries/utils.ts
  • packages/javascript/bh-shared-ui/src/hooks/useGraphItem.tsx
  • packages/javascript/bh-shared-ui/src/mocks/factories/explore.ts
  • packages/javascript/bh-shared-ui/src/utils/entityInfoDisplay.ts
  • packages/javascript/bh-shared-ui/src/views/Explore/ContextMenu/ContextMenuPrivilegeZonesEnabled.test.tsx
  • packages/javascript/bh-shared-ui/src/views/Explore/ContextMenu/ContextMenuPrivilegeZonesEnabled.tsx
  • packages/javascript/bh-shared-ui/src/views/Explore/ExploreSearch/SavedQueries/TagToZoneLabelDialog.tsx
  • packages/javascript/js-client-library/src/types.ts
🚧 Files skipped from review as they are similar to previous changes (38)
  • packages/javascript/bh-shared-ui/src/hooks/useAssetGroupTags/useAssetGroupTags.test.tsx
  • packages/javascript/bh-shared-ui/src/components/EntityInfo/EntityObjectInformation.tsx
  • packages/go/analysis/metrics.go
  • packages/go/analysis/tiering/tiering_test.go
  • cmd/api/src/model/unified_graph.go
  • packages/javascript/bh-shared-ui/src/utils/entityInfoDisplay.ts
  • packages/javascript/bh-shared-ui/src/hooks/useAssetGroupTags/useTagGlyphs.ts
  • cmd/api/src/api/v2/etac.go
  • cmd/api/src/database/migration/migrations/20260618120000_v9_add_decoy_asset_group.sql
  • cmd/ui/src/ducks/assetgroups/reducer.ts
  • cmd/api/src/model/unified_graph_test.go
  • packages/go/graphschema/ad/const.go
  • packages/javascript/bh-shared-ui/src/hooks/useGraphItem.tsx
  • packages/go/openapi/src/paths/asset-isolation.asset-group-tags.search.yaml
  • cmd/ui/src/views/Explore/utils.ts
  • packages/javascript/bh-shared-ui/src/constants.ts
  • cmd/api/src/database/assetgrouptags_test.go
  • packages/javascript/bh-shared-ui/src/mocks/factories/explore.ts
  • cmd/ui/src/views/Explore/ContextMenu/ContextMenu.tsx
  • packages/go/analysis/tiering/tiering.go
  • cmd/api/src/database/assetgrouptags.go
  • packages/go/analysis/agt.go
  • packages/go/openapi/doc/openapi.json
  • packages/javascript/bh-shared-ui/src/components/EntityInfo/PotentialDecoyBanner.test.tsx
  • packages/javascript/bh-shared-ui/src/views/Explore/ContextMenu/ContextMenuPrivilegeZonesEnabled.test.tsx
  • cmd/api/src/model/assetgrouptags.go
  • packages/javascript/bh-shared-ui/src/components/EntityInfo/PotentialDecoyBanner.tsx
  • cmd/ui/src/views/Explore/ContextMenu/AssetGroupMenuItem.tsx
  • cmd/ui/src/views/Explore/utils.test.ts
  • packages/javascript/js-client-library/src/types.ts
  • packages/javascript/bh-shared-ui/src/hooks/useAssetGroupTags/useAssetGroupTags.tsx
  • cmd/api/src/api/v2/assetgrouptags.go
  • cmd/api/src/api/v2/assetgrouptags_test.go
  • packages/javascript/bh-shared-ui/src/views/Explore/ExploreSearch/SavedQueries/TagToZoneLabelDialog.tsx
  • cmd/api/src/api/v2/cypherquery_test.go
  • packages/javascript/bh-shared-ui/src/commonSearchesAGI.ts
  • packages/javascript/bh-shared-ui/src/commonSearchesAGT.ts
  • packages/javascript/bh-shared-ui/src/hooks/useExploreGraph/queries/utils.ts

Walkthrough

Adds Decoy as a first-class tag and object state across backend APIs, database, graph metadata, saved searches, and Explore UI. It also updates OpenAPI schemas, analysis metrics, tests, and entity-detail rendering.

Changes

Decoy object support

Layer / File(s) Summary
Core decoy types and analysis
packages/go/graphschema/ad/const.go, packages/go/analysis/tiering/*, packages/go/analysis/agt.go, packages/go/analysis/metrics.go, cmd/api/src/model/assetgrouptags.go, packages/javascript/js-client-library/src/types.ts
Decoy tag constants, asset-group types, graph detection, tagging, metrics, and client types are added.
Asset-group tag lifecycle
cmd/api/src/api/v2/assetgrouptags.go, cmd/api/src/database/assetgrouptags.go, cmd/api/src/database/migration/*, cmd/api/src/*/assetgrouptags_test.go, packages/go/openapi/*
The Decoy asset group is migrated, selectable, searchable, and protected from deletion or default-tag renaming.
Graph metadata propagation
cmd/api/src/model/unified_graph.go, cmd/api/src/api/v2/etac.go, packages/go/openapi/*, packages/javascript/bh-shared-ui/src/hooks/*, packages/javascript/bh-shared-ui/src/mocks/*
Graph responses and frontend models expose isDecoyObject, including ETAC placeholders and serialized response tests.
Saved search exclusions
packages/javascript/bh-shared-ui/src/commonSearchesAGI.ts, packages/javascript/bh-shared-ui/src/commonSearchesAGT.ts
Predefined attack-path and related searches exclude decoy nodes across AD, Azure, Kerberos, ADCS, hygiene, and NTLM relay queries.
Explore decoy controls and visuals
packages/javascript/bh-shared-ui/src/components/EntityInfo/*, packages/javascript/bh-shared-ui/src/views/Explore/*, cmd/ui/src/views/Explore/*, cmd/ui/src/ducks/assetgroups/reducer.ts
Entity details, context menus, tag filtering, selector operations, and node glyphs support marking and displaying decoys.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Suggested labels: api

Poem

I’m a little rabbit with a mask in the night,
A decoy tag makes hidden trails bright.
Paths hop away from my clever disguise,
While glyphs and banners help spot my surprise.
Snip, snap—selectors mark where I lie!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the PR's main change.
Description check ✅ Passed The description follows the template and includes the required sections, issue reference, testing, and checklist.
Linked Issues check ✅ Passed The changes implement decoy tagging, UI/API support, legacy path handling, and query exclusions requested by #2912.
Out of Scope Changes check ✅ Passed No clear unrelated changes stand out beyond the requested decoy-tagging feature and its supporting fixes.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot added api A pull request containing changes affecting the API code. enhancement New feature or request go Pull requests that update go code javascript Pull requests that update javascript code user interface A pull request containing changes affecting the UI code. labels Jun 26, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 8

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)

49-62: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Gate asset-group actions on a real node selection.

objectIdSelectorPayload is built before confirming selectedItemQuery.data is a node, and it falls back to ''. For an edge/undefined selection, isOwnedObject/isDecoyObject return false, so these menu items still render and can post a selector with an empty objectId seed.

Suggested fix
-    const node = selectedItemQuery.data ? (selectedItemQuery.data as NodeResponse) : undefined;
+    const node = selectedItemQuery.data && isNode(selectedItemQuery.data) ? selectedItemQuery.data : undefined;
@@
-            <AssetGroupMenuItem
-                addNodePayload={tierZeroPayload}
-                isCurrentMemberFn={isTierZero}
-                removeNodePathFn={(tag: AssetGroupTag) => tagDetailsLink(tag.id, 'zones')}
-                showConfirmationOnAdd
-                tagIdentifierFn={getIsTierZeroTag}
-            />
-
-            <AssetGroupMenuItem
-                addNodePayload={objectIdSelectorPayload}
-                isCurrentMemberFn={isOwnedObject}
-                removeNodePathFn={(tag: AssetGroupTag) => tagDetailsLink(tag.id, 'labels')}
-                tagIdentifierFn={getIsOwnedTag}
-            />
-
-            <AssetGroupMenuItem
-                addNodePayload={objectIdSelectorPayload}
-                isCurrentMemberFn={isDecoyObject}
-                removeNodePathFn={(tag: AssetGroupTag) => tagDetailsLink(tag.id, 'labels')}
-                tagIdentifierFn={getIsDecoyTag}
-            />
+            {node && (
+                <>
+                    <AssetGroupMenuItem
+                        addNodePayload={tierZeroPayload}
+                        isCurrentMemberFn={isTierZero}
+                        removeNodePathFn={(tag: AssetGroupTag) => tagDetailsLink(tag.id, 'zones')}
+                        showConfirmationOnAdd
+                        tagIdentifierFn={getIsTierZeroTag}
+                    />
+
+                    <AssetGroupMenuItem
+                        addNodePayload={objectIdSelectorPayload}
+                        isCurrentMemberFn={isOwnedObject}
+                        removeNodePathFn={(tag: AssetGroupTag) => tagDetailsLink(tag.id, 'labels')}
+                        tagIdentifierFn={getIsOwnedTag}
+                    />
+
+                    <AssetGroupMenuItem
+                        addNodePayload={objectIdSelectorPayload}
+                        isCurrentMemberFn={isDecoyObject}
+                        removeNodePathFn={(tag: AssetGroupTag) => tagDetailsLink(tag.id, 'labels')}
+                        tagIdentifierFn={getIsDecoyTag}
+                    />
+                </>
+            )}

Also applies to: 111-123

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/javascript/bh-shared-ui/src/views/Explore/ContextMenu/ContextMenuPrivilegeZonesEnabled.tsx`
around lines 49 - 62, Gate the asset-group actions in
ContextMenuPrivilegeZonesEnabled on a confirmed NodeResponse selection instead
of building objectIdSelectorPayload with empty fallbacks; only create/use the
selector payload when selectedItemQuery.data is a real node, and otherwise hide
or disable those menu items. Update the logic around objectIdSelectorPayload,
isOwnedObject, and isDecoyObject so edge/undefined selections cannot satisfy the
conditions or render actions that would submit an empty objectId seed.
🧹 Nitpick comments (2)
cmd/api/src/database/migration/agi_integration_test.go (1)

31-37: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the required default groups instead of the total count.

This already had to change from 2 to 3 in this PR. Checking for the specific seeded groups/tags will make the migration test resilient to future default-group additions that are unrelated to this behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/api/src/database/migration/agi_integration_test.go` around lines 31 - 37,
The migration test is asserting only the total number of asset groups in
SetupDB, which is too brittle. Update the AGI integration test to verify the
specific seeded default groups/tags returned by GetAllAssetGroups instead of
comparing len(assetGroups) to a fixed count. Use the existing setup in
agi_integration_test.go and the GetAllAssetGroups call to assert the required
default groups are present, so future unrelated default-group additions do not
break the test.
cmd/ui/src/ducks/assetgroups/reducer.ts (1)

76-78: 📐 Maintainability & Code Quality | 🔵 Trivial

Align the selector return type with its actual runtime behavior.

The implementation find(...)?.id can return undefined when the 'decoy' asset group is missing. The current signature : number is incorrect under strict TypeScript mode and misrepresents the contract. Update the return type to number | undefined to reflect that the value may be absent.

Suggested fix
-export const selectDecoyAssetGroupId = (state: AppState): number => {
+export const selectDecoyAssetGroupId = (state: AppState): number | undefined => {
     return state.assetgroups.assetGroups.find((assetGroup) => assetGroup.tag === 'decoy')?.id;
 };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/ui/src/ducks/assetgroups/reducer.ts` around lines 76 - 78, The selector
selectDecoyAssetGroupId currently returns assetGroup.id via find(...)?.id, so it
can be undefined when no decoy asset group exists. Update the return type on
selectDecoyAssetGroupId to number | undefined so the AppState selector contract
matches the actual runtime behavior under strict TypeScript.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@cmd/api/src/database/migration/migrations/20260618120000_v9_add_decoy_asset_group.sql`:
- Around line 18-31: The rollback in this migration is not symmetric with the
seed logic because `Up` can reuse existing `asset_groups`, `kind`, and
`asset_group_tags` rows via `ON CONFLICT`, while `Down` unconditionally deletes
by decoy identifiers. Update the migration so `Down` only removes rows this
migration actually created, or change the `Up` path to avoid attaching to
pre-existing records; use the existing `asset_groups`, `kind`, and
`asset_group_tags` insert/delete statements as the targets for the fix.

In `@cmd/ui/src/views/Explore/ContextMenu/AssetGroupMenuItem.tsx`:
- Around line 59-71: The asset group members query is running without a
guaranteed real objectId, which can build an undefined object_id filter and
expose stale Add/Remove actions. Update the useQuery call in AssetGroupMenuItem
to only enable the members lookup when assetGroupId is set and
selectedItemQuery.data is a NodeResponse with a valid objectId, and make the
query key/fetcher depend on that resolved objectId. Apply the same guard to the
second members query in this menu so non-node selections and unresolved data do
not render member actions prematurely.

In `@packages/go/analysis/tiering/tiering_test.go`:
- Line 17: Update the test package declaration in tiering_test.go from the
internal package to the external test package so it exercises the public API
through imports. Since this test only covers exported symbols like IsDecoy and
KindTagDecoy, change the package name to the code package plus _test to match
the repo’s _test.go rule and preserve import-surface validation.

In `@packages/javascript/bh-shared-ui/src/commonSearchesAGI.ts`:
- Around line 133-135: The privilege ranking query in commonSearchesAGI still
counts decoy Computer nodes because the second MATCH in the Kerberoastable users
search does not apply excludeDecoyNodesAGI. Update the query for this search so
the Computer traversal is filtered the same way as decoy users, using the
existing decoy-exclusion helper/symbols around the u-to-c path before COUNT(c)
is computed. This keeps adminCount from being inflated by decoy endpoints while
preserving the rest of the search logic.

In `@packages/javascript/bh-shared-ui/src/commonSearchesAGT.ts`:
- Around line 133-135: The AGT ranking search still counts Computer targets
reached through decoy-tagged endpoints, so it is not fully aligned with the
decoy-exclusion behavior. Update the query in commonSearchesAGT’s saved search
entry (the one named “Kerberoastable users with most admin privileges”) to
exclude TAG_DECOY_AGT-tagged Computer nodes as well as decoy users, and make
sure the MATCH/WHERE logic only contributes non-decoy computers to adminCount.

In
`@packages/javascript/bh-shared-ui/src/components/EntityInfo/PotentialDecoyBanner.tsx`:
- Around line 255-256: Update the marked/unmarked decoy copy in
PotentialDecoyBanner so it is object-neutral for non-user nodes: keep the
heuristic warning user-specific, but replace the hard-coded “User marked as
decoy” / “User unmarked as decoy” style strings used in the switch, banner, and
notifications with generic decoy wording. Adjust the relevant logic in
PotentialDecoyBanner where the checked-state message and success toast key are
chosen so computers/groups already marked as decoys display correct text.
- Around line 163-166: The membership lookup is using a double-prefixed
object_id query value, which breaks the backend filter format; update
PotentialDecoyBanner’s listAssetGroupMembers call so params.object_id is built
as eq:${objectId} instead of object_id=eq:${objectId}. Also apply the same fix
in AssetGroupMenuItem where the same parameter pattern is used, keeping the
query value consistent with what the API expects.

In `@packages/javascript/bh-shared-ui/src/hooks/useExploreGraph/queries/utils.ts`:
- Line 79: The `isDecoyObject` derivation in the `useExploreGraph` query utils
only checks `system_tags`, so AGT decoys marked via `kinds`/`TAG_DECOY_AGT` are
missed. Update the logic that builds the node shape in this utils module to
treat either legacy `system_tags` or the AGT decoy kind/tag as decoy state when
setting `isDecoyObject`. Keep the existing `DECOY_OBJECT_TAG` check, but also
inspect the node’s `kinds` data so transformed nodes preserve the correct
boolean for downstream consumers.

---

Outside diff comments:
In
`@packages/javascript/bh-shared-ui/src/views/Explore/ContextMenu/ContextMenuPrivilegeZonesEnabled.tsx`:
- Around line 49-62: Gate the asset-group actions in
ContextMenuPrivilegeZonesEnabled on a confirmed NodeResponse selection instead
of building objectIdSelectorPayload with empty fallbacks; only create/use the
selector payload when selectedItemQuery.data is a real node, and otherwise hide
or disable those menu items. Update the logic around objectIdSelectorPayload,
isOwnedObject, and isDecoyObject so edge/undefined selections cannot satisfy the
conditions or render actions that would submit an empty objectId seed.

---

Nitpick comments:
In `@cmd/api/src/database/migration/agi_integration_test.go`:
- Around line 31-37: The migration test is asserting only the total number of
asset groups in SetupDB, which is too brittle. Update the AGI integration test
to verify the specific seeded default groups/tags returned by GetAllAssetGroups
instead of comparing len(assetGroups) to a fixed count. Use the existing setup
in agi_integration_test.go and the GetAllAssetGroups call to assert the required
default groups are present, so future unrelated default-group additions do not
break the test.

In `@cmd/ui/src/ducks/assetgroups/reducer.ts`:
- Around line 76-78: The selector selectDecoyAssetGroupId currently returns
assetGroup.id via find(...)?.id, so it can be undefined when no decoy asset
group exists. Update the return type on selectDecoyAssetGroupId to number |
undefined so the AppState selector contract matches the actual runtime behavior
under strict TypeScript.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: e7dffb9d-b806-4d01-8a6f-130bf634a31b

📥 Commits

Reviewing files that changed from the base of the PR and between da123dd and d176a1e.

📒 Files selected for processing (41)
  • cmd/api/src/api/v2/assetgrouptags.go
  • cmd/api/src/api/v2/assetgrouptags_test.go
  • cmd/api/src/api/v2/cypherquery_test.go
  • cmd/api/src/api/v2/etac.go
  • cmd/api/src/database/assetgrouptags.go
  • cmd/api/src/database/assetgrouptags_test.go
  • cmd/api/src/database/migration/agi_integration_test.go
  • cmd/api/src/database/migration/migrations/20260618120000_v9_add_decoy_asset_group.sql
  • cmd/api/src/model/assetgrouptags.go
  • cmd/api/src/model/unified_graph.go
  • cmd/api/src/model/unified_graph_test.go
  • cmd/ui/src/ducks/assetgroups/reducer.ts
  • cmd/ui/src/views/Explore/ContextMenu/AssetGroupMenuItem.tsx
  • cmd/ui/src/views/Explore/ContextMenu/ContextMenu.tsx
  • cmd/ui/src/views/Explore/utils.test.ts
  • cmd/ui/src/views/Explore/utils.ts
  • packages/go/analysis/agt.go
  • packages/go/analysis/metrics.go
  • packages/go/analysis/tiering/tiering.go
  • packages/go/analysis/tiering/tiering_test.go
  • packages/go/graphschema/ad/const.go
  • packages/go/openapi/doc/openapi.json
  • packages/go/openapi/src/paths/asset-isolation.asset-group-tags.search.yaml
  • packages/go/openapi/src/schemas/model.unified-graph.node.yaml
  • packages/javascript/bh-shared-ui/src/commonSearchesAGI.ts
  • packages/javascript/bh-shared-ui/src/commonSearchesAGT.ts
  • packages/javascript/bh-shared-ui/src/components/EntityInfo/EntityObjectInformation.tsx
  • packages/javascript/bh-shared-ui/src/components/EntityInfo/PotentialDecoyBanner.test.tsx
  • packages/javascript/bh-shared-ui/src/components/EntityInfo/PotentialDecoyBanner.tsx
  • packages/javascript/bh-shared-ui/src/constants.ts
  • packages/javascript/bh-shared-ui/src/hooks/useAssetGroupTags/useAssetGroupTags.test.tsx
  • packages/javascript/bh-shared-ui/src/hooks/useAssetGroupTags/useAssetGroupTags.tsx
  • packages/javascript/bh-shared-ui/src/hooks/useAssetGroupTags/useTagGlyphs.ts
  • packages/javascript/bh-shared-ui/src/hooks/useExploreGraph/queries/utils.ts
  • packages/javascript/bh-shared-ui/src/hooks/useGraphItem.tsx
  • packages/javascript/bh-shared-ui/src/mocks/factories/explore.ts
  • packages/javascript/bh-shared-ui/src/utils/entityInfoDisplay.ts
  • packages/javascript/bh-shared-ui/src/views/Explore/ContextMenu/ContextMenuPrivilegeZonesEnabled.test.tsx
  • packages/javascript/bh-shared-ui/src/views/Explore/ContextMenu/ContextMenuPrivilegeZonesEnabled.tsx
  • packages/javascript/bh-shared-ui/src/views/Explore/ExploreSearch/SavedQueries/TagToZoneLabelDialog.tsx
  • packages/javascript/js-client-library/src/types.ts

Comment thread cmd/ui/src/views/Explore/ContextMenu/AssetGroupMenuItem.tsx
Comment thread packages/go/analysis/tiering/tiering_test.go Outdated
Comment thread packages/javascript/bh-shared-ui/src/commonSearchesAGI.ts Outdated
Comment thread packages/javascript/bh-shared-ui/src/commonSearchesAGT.ts Outdated
Comment thread packages/javascript/bh-shared-ui/src/hooks/useExploreGraph/queries/utils.ts Outdated
@MickeyDB

Copy link
Copy Markdown
Author

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

@MickeyDB
MickeyDB force-pushed the codex/decoy-asset-group branch from d176a1e to 78b3fc6 Compare June 26, 2026 18:39
@MickeyDB

Copy link
Copy Markdown
Author

Addressed coderabbit feedback

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/api/src/database/migration/agi_integration_test.go`:
- Around line 36-44: The asset group seeding test in agi_integration_test.go
only checks membership with slices.ContainsFunc, so it can miss duplicate or
extra default rows. Add an explicit assertion on the total length of assetGroups
in the same test before or alongside the existing checks, so the migration
validation in TestAgiIntegration still enforces the exact seeded set. Use the
existing assetGroups variable and the current ContainsFunc assertions as the
location to update.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: feeaac4b-d419-4d73-97cd-c0ae877a1069

📥 Commits

Reviewing files that changed from the base of the PR and between d176a1e and 78b3fc6.

📒 Files selected for processing (41)
  • cmd/api/src/api/v2/assetgrouptags.go
  • cmd/api/src/api/v2/assetgrouptags_test.go
  • cmd/api/src/api/v2/cypherquery_test.go
  • cmd/api/src/api/v2/etac.go
  • cmd/api/src/database/assetgrouptags.go
  • cmd/api/src/database/assetgrouptags_test.go
  • cmd/api/src/database/migration/agi_integration_test.go
  • cmd/api/src/database/migration/migrations/20260618120000_v9_add_decoy_asset_group.sql
  • cmd/api/src/model/assetgrouptags.go
  • cmd/api/src/model/unified_graph.go
  • cmd/api/src/model/unified_graph_test.go
  • cmd/ui/src/ducks/assetgroups/reducer.ts
  • cmd/ui/src/views/Explore/ContextMenu/AssetGroupMenuItem.tsx
  • cmd/ui/src/views/Explore/ContextMenu/ContextMenu.tsx
  • cmd/ui/src/views/Explore/utils.test.ts
  • cmd/ui/src/views/Explore/utils.ts
  • packages/go/analysis/agt.go
  • packages/go/analysis/metrics.go
  • packages/go/analysis/tiering/tiering.go
  • packages/go/analysis/tiering/tiering_test.go
  • packages/go/graphschema/ad/const.go
  • packages/go/openapi/doc/openapi.json
  • packages/go/openapi/src/paths/asset-isolation.asset-group-tags.search.yaml
  • packages/go/openapi/src/schemas/model.unified-graph.node.yaml
  • packages/javascript/bh-shared-ui/src/commonSearchesAGI.ts
  • packages/javascript/bh-shared-ui/src/commonSearchesAGT.ts
  • packages/javascript/bh-shared-ui/src/components/EntityInfo/EntityObjectInformation.tsx
  • packages/javascript/bh-shared-ui/src/components/EntityInfo/PotentialDecoyBanner.test.tsx
  • packages/javascript/bh-shared-ui/src/components/EntityInfo/PotentialDecoyBanner.tsx
  • packages/javascript/bh-shared-ui/src/constants.ts
  • packages/javascript/bh-shared-ui/src/hooks/useAssetGroupTags/useAssetGroupTags.test.tsx
  • packages/javascript/bh-shared-ui/src/hooks/useAssetGroupTags/useAssetGroupTags.tsx
  • packages/javascript/bh-shared-ui/src/hooks/useAssetGroupTags/useTagGlyphs.ts
  • packages/javascript/bh-shared-ui/src/hooks/useExploreGraph/queries/utils.ts
  • packages/javascript/bh-shared-ui/src/hooks/useGraphItem.tsx
  • packages/javascript/bh-shared-ui/src/mocks/factories/explore.ts
  • packages/javascript/bh-shared-ui/src/utils/entityInfoDisplay.ts
  • packages/javascript/bh-shared-ui/src/views/Explore/ContextMenu/ContextMenuPrivilegeZonesEnabled.test.tsx
  • packages/javascript/bh-shared-ui/src/views/Explore/ContextMenu/ContextMenuPrivilegeZonesEnabled.tsx
  • packages/javascript/bh-shared-ui/src/views/Explore/ExploreSearch/SavedQueries/TagToZoneLabelDialog.tsx
  • packages/javascript/js-client-library/src/types.ts
✅ Files skipped from review due to trivial changes (4)
  • cmd/api/src/api/v2/etac.go
  • cmd/api/src/model/unified_graph.go
  • packages/go/openapi/src/schemas/model.unified-graph.node.yaml
  • packages/javascript/bh-shared-ui/src/utils/entityInfoDisplay.ts
🚧 Files skipped from review as they are similar to previous changes (36)
  • packages/go/graphschema/ad/const.go
  • packages/go/openapi/src/paths/asset-isolation.asset-group-tags.search.yaml
  • cmd/ui/src/ducks/assetgroups/reducer.ts
  • packages/javascript/bh-shared-ui/src/hooks/useAssetGroupTags/useTagGlyphs.ts
  • packages/go/analysis/tiering/tiering_test.go
  • packages/go/analysis/metrics.go
  • packages/javascript/bh-shared-ui/src/components/EntityInfo/EntityObjectInformation.tsx
  • packages/go/analysis/agt.go
  • packages/javascript/bh-shared-ui/src/hooks/useAssetGroupTags/useAssetGroupTags.test.tsx
  • packages/javascript/bh-shared-ui/src/views/Explore/ContextMenu/ContextMenuPrivilegeZonesEnabled.test.tsx
  • cmd/ui/src/views/Explore/ContextMenu/ContextMenu.tsx
  • packages/javascript/bh-shared-ui/src/constants.ts
  • packages/javascript/bh-shared-ui/src/views/Explore/ExploreSearch/SavedQueries/TagToZoneLabelDialog.tsx
  • packages/go/analysis/tiering/tiering.go
  • packages/javascript/bh-shared-ui/src/hooks/useExploreGraph/queries/utils.ts
  • cmd/ui/src/views/Explore/ContextMenu/AssetGroupMenuItem.tsx
  • packages/javascript/bh-shared-ui/src/views/Explore/ContextMenu/ContextMenuPrivilegeZonesEnabled.tsx
  • packages/javascript/bh-shared-ui/src/hooks/useGraphItem.tsx
  • cmd/api/src/model/assetgrouptags.go
  • cmd/api/src/database/migration/migrations/20260618120000_v9_add_decoy_asset_group.sql
  • packages/javascript/js-client-library/src/types.ts
  • cmd/ui/src/views/Explore/utils.ts
  • packages/javascript/bh-shared-ui/src/components/EntityInfo/PotentialDecoyBanner.test.tsx
  • cmd/api/src/model/unified_graph_test.go
  • cmd/api/src/database/assetgrouptags.go
  • cmd/api/src/database/assetgrouptags_test.go
  • cmd/api/src/api/v2/cypherquery_test.go
  • cmd/ui/src/views/Explore/utils.test.ts
  • packages/javascript/bh-shared-ui/src/components/EntityInfo/PotentialDecoyBanner.tsx
  • packages/go/openapi/doc/openapi.json
  • packages/javascript/bh-shared-ui/src/hooks/useAssetGroupTags/useAssetGroupTags.tsx
  • cmd/api/src/api/v2/assetgrouptags.go
  • packages/javascript/bh-shared-ui/src/commonSearchesAGI.ts
  • cmd/api/src/api/v2/assetgrouptags_test.go
  • packages/javascript/bh-shared-ui/src/mocks/factories/explore.ts
  • packages/javascript/bh-shared-ui/src/commonSearchesAGT.ts

Comment thread cmd/api/src/database/migration/agi_integration_test.go
@MickeyDB
MickeyDB force-pushed the codex/decoy-asset-group branch from 78b3fc6 to 78923d7 Compare July 13, 2026 09:33
@MickeyDB

Copy link
Copy Markdown
Author

Updated the branch to address the remaining CodeRabbit thread by restoring the asset group cardinality assertion in agi_integration_test.go
I reran just prepare-for-codereview successfully after the fix

ddlees pushed a commit to itxaiohanglover/CLA that referenced this pull request Jul 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api A pull request containing changes affecting the API code. enhancement New feature or request go Pull requests that update go code javascript Pull requests that update javascript code user interface A pull request containing changes affecting the UI code.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: Add Decoy object tagging

1 participant