Skip to content

maintenance: scope AI conversations by creator - #4280

Open
zqr10159 wants to merge 3 commits into
apache:masterfrom
zqr10159:maintenance/chat-conversation-access
Open

maintenance: scope AI conversations by creator#4280
zqr10159 wants to merge 3 commits into
apache:masterfrom
zqr10159:maintenance/chat-conversation-access

Conversation

@zqr10159

@zqr10159 zqr10159 commented Jul 30, 2026

Copy link
Copy Markdown
Member

Summary

  • enforce the authenticated creator across conversation and SOP schedule create/list/get/update/delete/toggle operations
  • validate the persisted schedule owner against the target conversation before execution and again before result or error delivery
  • prevent protected monitor creation from loading secure-form data through an unscoped conversation lookup
  • disable ownerless legacy schedules, add creator-oriented indexes, and document the manual recovery path
  • keep background execution independent of a request-thread subject while persisting the validated owner on generated messages

Upgrade impact

  • conversations with a null creator remain isolated from all user queries; no ownership is guessed automatically
  • migration V182 disables schedules with a null or blank creator while preserving the rows for administrator review
  • schedules with a missing conversation or a creator mismatch are disabled before they can execute
  • the upgrade guide describes how to verify and restore an intended owner without bulk assignment

Validation

  • regression proof on the previous head: four creator-boundary tests failed because the service trusted a request creator and allowed cross-creator get/list/delete
  • ./mvnw -pl hertzbeat-ai -am -Dtest=SopScheduleServiceImplTest,SopScheduleExecutorTest,ConversationServiceImplTest,ChatConversationTest test -DskipITs -Dsurefire.failIfNoSpecifiedTests=false -DfailIfNoTests=false
  • ./mvnw -pl hertzbeat-ai -Dtest=MonitorToolsImplTest,SopScheduleServiceImplTest,SopScheduleExecutorTest,ConversationServiceImplTest test -DskipITs -Dsurefire.failIfNoSpecifiedTests=false -DfailIfNoTests=false (20 tests)
  • ./mvnw -pl hertzbeat-ai test -DskipITs (50 tests)
  • ./mvnw -pl hertzbeat-startup -am -DskipTests package
  • H2 fresh-migration proof: V181 followed by V182 preserved the owned schedule, disabled the ownerless schedule, and created the composite index
  • repository no-hardcoded-CJK scan and git diff --cached --check

AI assistance: used for draft implementation and test iteration.
Human validation: focused ownership tests, the complete AI module suite, a fresh H2 migration proof, and the startup source package reactor all completed successfully; Checkstyle reported no violations.
Risk notes: legacy ownerless conversations and schedules require deliberate administrator recovery after verifying the target conversation owner; the migration does not guess or bulk-assign ownership.

@github-actions github-actions Bot added the doc Improvements or additions to documentation label Jul 30, 2026
@zqr10159

Copy link
Copy Markdown
Member Author

Author remediation update:

The ownership boundary now includes conversations, messages, stream and security-data reads, plus every SOP schedule create, list, get, update, delete, and toggle path. A schedule persists its owner, creation verifies the target conversation, and background execution revalidates that persisted relationship without a login-time subject. Historical null creators use an explicit migration and isolation path with supporting indexes.

Two-user IDOR and scheduled-message injection regressions are covered. Focused AI tests (50) and the startup reactor (24 modules) passed locally; the current GitHub backend, E2E, docs, license, and label checks are green. The author-side blocker is resolved; maintainer review is still required.

@zqr10159
zqr10159 marked this pull request as ready for review July 31, 2026 02:51
@Duansg

Duansg commented Aug 3, 2026

Copy link
Copy Markdown
Member

Scoping these by creator is the right call. On master, ConversationServiceImpl reaches conversations through findById() / findAll() with no owner check, and MonitorToolsImpl.java:312 loads a conversation's securityData — AES-encrypted monitor credentials — through that same unscoped lookup, so any authenticated user could reach another user's conversation and the secrets attached to it. The DAO-level findByIdAndCreator / findByConversationIdAndCreator approach closes that cleanly, and re-validating ownership in getScheduleForExecution before both result and error delivery is a nice touch for the background path.

Two things I checked that are not problems, so they don't get re-raised:

  • The migration is type-correct. V181 creates hzb_sop_schedule with enabled SMALLINT on PostgreSQL and TINYINT on MySQL — not boolean — so SET enabled = 0 is valid on both, and creator VARCHAR(64) already exists in the original CREATE TABLE, so V182 has a column to filter on. Flyway also runs ahead of Hibernate, so the new @Index won't collide with the one V182 creates.
  • creator is set explicitly even though @createdby would populate it. Both entities have AuditingEntityListener and JpaAuditorConfig reads the same SurenessContextHolder subject, so the values agree — and setting it in the builder is necessary for the ownership check inside the same transaction. Redundant but correct.

What needs a maintainer decision

The PR discloses this, so it isn't a hidden bug, but it's the part I'd want agreed before merge:

  • Administrators lose access to other users' conversations entirely. Every other resource in HertzBeat (monitors, notice rules, bulletins) is globally visible, so AI conversations become the one creator-private resource type. That's a defensible choice, just an inconsistent one worth making deliberately.
  • Existing conversations with creator = null become unreachable for everyone, and V182 disables their schedules. Recovery is documented as manual SQL against hzb_ai_conversation and hzb_sop_schedule.

For an operator upgrading a running instance, "your AI history is gone unless you hand-edit the database" is a rough landing. An admin-facing recovery path in the app — list ownerless conversations, assign an owner, re-enable the schedule — would turn this from a support ticket into a UI action. Happy to see it as a follow-up rather than in this PR, but it'd be good to have a plan on record.

Minor

requireCurrentUserId() is duplicated verbatim in ConversationServiceImpl and SopScheduleServiceImpl. The subject lookup already appears in several places (JpaAuditorConfig, AccountServiceImpl, AuthTokenController), so a single shared helper would be worth extracting while this is fresh.

Also, IllegalStateException("No authenticated user") will likely surface as a 500 rather than a 401 — worth checking how the controllers map it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

doc Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants