Skip to content

test(console): add cross-tenant regression coverage for chat-messages and conversation delete (DifyTap)#37776

Open
AnandSundar wants to merge 7 commits into
langgenius:mainfrom
AnandSundar:test/difytap-cross-tenant-regression
Open

test(console): add cross-tenant regression coverage for chat-messages and conversation delete (DifyTap)#37776
AnandSundar wants to merge 7 commits into
langgenius:mainfrom
AnandSundar:test/difytap-cross-tenant-regression

Conversation

@AnandSundar

@AnandSundar AnandSundar commented Jun 23, 2026

Copy link
Copy Markdown

Summary

Regression tests and CI plumbing for two of the four DifyTap cross-tenant
IDORs in the console API:

The CVE fix is the WHERE App.tenant_id == current_tenant_id clause in
controllers/console/app/wraps.py:_load_app_model_from_scoped_session
(line 34-40). This PR does not modify production code — only tests and
the CI workflow.

Test files

  • api/tests/integration_tests/controllers/console/app/test_chat_message_cross_tenant.py
    • TestChatMessageCrossTenant (mock-based) — exercises the full
      controller pipeline (CSRF, JWT auth, decorator order)
    • TestChatMessageCrossTenantRealDB (new) — exercises the real
      _load_app_model_from_scoped_session against a real cross-tenant
      App row in PostgreSQL, so the test actually fails if the WHERE
      clause is removed
  • api/tests/integration_tests/controllers/console/app/test_conversation_delete_cross_tenant.py
    • Mock-based tests for the conversation delete endpoint

CI change

api/.github/workflows/api-integration-test.yml previously collected
only api/tests/test_containers_integration_tests. This PR adds the
console/app regression test path so the new tests run in CI.

Scope decision (added after first push): the pytest collection path
is api/tests/integration_tests/controllers/console/app, not the broader
api/tests/integration_tests/controllers. The broader path was tried on
the first push and exposed 69 pre-existing failures in
controllers/openapi/test_apps.py and test_auth.py (all
sqlalchemy.exc.ProgrammingError: relation "accounts" does not exist),
which are unrelated test-infrastructure bugs in the openapi conftest's
accounts fixture. Re-widening the CI path is deferred to a follow-up
PR that fixes the openapi conftest; this PR keeps the path scoped to
the directory that contains the new regression tests so the contribution
is reviewable on its merits.

R5 verification

The real-DB test was confirmed to fail with the WHERE clause removed
(returns the cross-tenant App row instead of None) and pass with it
in place. The mock-based tests pass either way (they mock the load
function), which is why the real-DB class exists.

Checklist

  • Test added per change
  • CI workflow updated to run the tests
  • No production code modified
  • Documentation update: N/A (test-only PR)
  • make lint && make type-check: pending CI

…at-messages

Covers GHSA-jg5j-c9pq-w894 (CVE-2025-59422): a tenant's authenticated user
must not be able to read another tenant's chat history by passing the
target app_id in the URL. The cross-tenant boundary is enforced by the
get_app_model decorator's load function
(controllers/console/app/wraps.py:34-40), which filters apps by the
current tenant_id. The negative case asserts 404 when the load returns
None for a cross-tenant app; the positive case asserts 200 for the
resource owner to guard against over-restriction regressions.
…nversation deletion

Covers GHSA-fxq3-hh7x-c63p: a tenant's authenticated user must not be
able to delete another tenant's conversation by passing the target
app_id and conversation_id in the URL. The cross-tenant boundary is
enforced by the get_app_model decorator's load function
(controllers/console/app/wraps.py:34-40), which filters apps by the
current tenant_id. The negative case asserts 404 when the load returns
None for a cross-tenant app; the positive case asserts 204 for the resource
owner to guard against over-restriction regressions.
The new regression tests added in test_chat_message_cross_tenant.py and
test_conversation_delete_cross_tenant.py live under
api/tests/integration_tests/controllers/console/, which the
api-integration job did not previously collect (it ran only
integration_tests/workflow, integration_tests/tools, and
test_containers_integration_tests/). Add the path so the cross-tenant
regressions run on every PR. Otherwise the tests would be advisory-only
and a future regression would not be caught by CI.
…reen

The original commit landed the test scaffolding but did not run it
locally; several pre-existing patterns in
test_chat_message_permissions.py (mock.Mock() without magic methods,
monkeypatching current_user into a module that does not import it,
and a tenant_id-less TenantAccountJoin dataclass) leaked into the
new fixture and blocked test collection or CSRF auth. Replace the
mock-account Session patch with a direct _current_tenant assignment
(the cross-tenant boundary only reads _current_tenant.id via the
shim the test patches, so the DB round-trip is unnecessary) and add
a csrf_auth_header fixture that mirrors
tests/test_containers_integration_tests/controllers/console/helpers.py
so non-OPTIONS requests clear libs.login.login_required's CSRF gate
before reaching the cross-tenant check.
The mock-based tests in this file patch _load_app_model_from_scoped_session
directly, so they verify the decorator chain ('load returns None -> 404')
but never exercise the WHERE App.tenant_id == current_tenant_id clause
the CVE fix added. R5 in the plan asked us to confirm the regression tests
fail against unpatched code; the mock-based tests pass even with the WHERE
clause removed, so they would not catch a regression that re-introduced
the IDOR.

TestChatMessageCrossTenantRealDB fixes that gap. It creates a real cross-
tenant App row in PostgreSQL and calls the real
_load_app_model_from_scoped_session against it. With the patch in place
the load returns None (filtered out by tenant_id); without the patch the
load returns the cross-tenant App row. R5 verified both directions:

  patched:     cross-tenant test PASSES (None as expected)
  unpatched:   cross-tenant test FAILS with explicit 'tenant filter is missing'
               message; same-tenant test still PASSES

The test patches only current_account_with_tenant to feed the real tenant
id into the production SQL. The SQL clause that consumes it is untouched,
so reverting the production code genuinely regresses the test.

The mock-based tests are retained because they exercise the full
controller pipeline (CSRF, JWT auth, decorator order) which the focused
real-DB test deliberately skips.
@AnandSundar AnandSundar marked this pull request as ready for review June 23, 2026 00:34
@dosubot dosubot Bot added the size:XS This PR changes 0-9 lines, ignoring generated files. label Jun 23, 2026
@github-actions

github-actions Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Pyrefly Diff

base → PR
--- /tmp/pyrefly_base.txt	2026-06-23 03:20:01.487694329 +0000
+++ /tmp/pyrefly_pr.txt	2026-06-23 03:19:46.217575847 +0000
@@ -2049,57 +2049,31 @@
 ERROR No attribute `MethodView` in module `builtins` [missing-attribute]
  --> tests/unit_tests/controllers/common/test_fields.py:9:5
 ERROR Argument `SimpleNamespace` is not assignable to parameter `current_user` with type `Account` in function `controllers.console.app.completion._create_chat_message` [bad-argument-type]
-    --> tests/unit_tests/controllers/console/agent/test_agent_controllers.py:1134:74
+    --> tests/unit_tests/controllers/console/agent/test_agent_controllers.py:1104:74
 ERROR Argument `SimpleNamespace` is not assignable to parameter `app_model` with type `App` in function `controllers.console.app.completion._create_chat_message` [bad-argument-type]
-    --> tests/unit_tests/controllers/console/agent/test_agent_controllers.py:1134:98
+    --> tests/unit_tests/controllers/console/agent/test_agent_controllers.py:1104:98
 ERROR Argument `SimpleNamespace` is not assignable to parameter `current_user` with type `Account` in function `controllers.console.app.completion._create_chat_message` [bad-argument-type]
-    --> tests/unit_tests/controllers/console/agent/test_agent_controllers.py:1171:30
+    --> tests/unit_tests/controllers/console/agent/test_agent_controllers.py:1154:30
 ERROR Argument `SimpleNamespace` is not assignable to parameter `app_model` with type `App` in function `controllers.console.app.completion._create_chat_message` [bad-argument-type]
-    --> tests/unit_tests/controllers/console/agent/test_agent_controllers.py:1172:27
-ERROR Argument `SimpleNamespace` is not assignable to parameter `current_user` with type `Account` in function `controllers.console.app.completion._resolve_current_user_agent_debug_conversation_id` [bad-argument-type]
-    --> tests/unit_tests/controllers/console/agent/test_agent_controllers.py:1199:22
-ERROR Argument `SimpleNamespace` is not assignable to parameter `app_model` with type `App` in function `controllers.console.app.completion._resolve_current_user_agent_debug_conversation_id` [bad-argument-type]
-    --> tests/unit_tests/controllers/console/agent/test_agent_controllers.py:1200:19
-ERROR Argument `SimpleNamespace` is not assignable to parameter `current_user` with type `Account` in function `controllers.console.app.completion._resolve_current_user_agent_debug_conversation_id` [bad-argument-type]
-    --> tests/unit_tests/controllers/console/agent/test_agent_controllers.py:1205:22
-ERROR Argument `SimpleNamespace` is not assignable to parameter `app_model` with type `App` in function `controllers.console.app.completion._resolve_current_user_agent_debug_conversation_id` [bad-argument-type]
-    --> tests/unit_tests/controllers/console/agent/test_agent_controllers.py:1206:19
-ERROR Argument `SimpleNamespace` is not assignable to parameter `current_user` with type `Account` in function `controllers.console.app.completion._create_chat_message` [bad-argument-type]
-    --> tests/unit_tests/controllers/console/agent/test_agent_controllers.py:1257:30
-ERROR Argument `SimpleNamespace` is not assignable to parameter `app_model` with type `App` in function `controllers.console.app.completion._create_chat_message` [bad-argument-type]
-    --> tests/unit_tests/controllers/console/agent/test_agent_controllers.py:1258:27
+    --> tests/unit_tests/controllers/console/agent/test_agent_controllers.py:1155:27
 ERROR Object of class `object` has no attribute `data` [missing-attribute]
-    --> tests/unit_tests/controllers/console/agent/test_agent_controllers.py:1340:50
+    --> tests/unit_tests/controllers/console/agent/test_agent_controllers.py:1235:50
 ERROR Object of class `object` has no attribute `limit` [missing-attribute]
-    --> tests/unit_tests/controllers/console/agent/test_agent_controllers.py:1341:30
+    --> tests/unit_tests/controllers/console/agent/test_agent_controllers.py:1236:30
 ERROR Object of class `object` has no attribute `has_more` [missing-attribute]
-    --> tests/unit_tests/controllers/console/agent/test_agent_controllers.py:1342:33
-ERROR Argument `SimpleNamespace` is not assignable to parameter `app_model` with type `App` in function `controllers.console.app.message._list_chat_messages` [bad-argument-type]
-    --> tests/unit_tests/controllers/console/agent/test_agent_controllers.py:1354:67
-ERROR Object of class `object` has no attribute `data` [missing-attribute]
-    --> tests/unit_tests/controllers/console/agent/test_agent_controllers.py:1380:50
-ERROR Object of class `object` has no attribute `limit` [missing-attribute]
-    --> tests/unit_tests/controllers/console/agent/test_agent_controllers.py:1381:30
-ERROR Object of class `object` has no attribute `has_more` [missing-attribute]
-    --> tests/unit_tests/controllers/console/agent/test_agent_controllers.py:1382:33
-ERROR Argument `SimpleNamespace` is not assignable to parameter `app_model` with type `App` in function `controllers.console.app.message._list_chat_messages` [bad-argument-type]
-    --> tests/unit_tests/controllers/console/agent/test_agent_controllers.py:1396:67
-ERROR Argument `SimpleNamespace` is not assignable to parameter `current_user` with type `Account | None` in function `controllers.console.app.message._list_chat_messages` [bad-argument-type]
-    --> tests/unit_tests/controllers/console/agent/test_agent_controllers.py:1396:91
+    --> tests/unit_tests/controllers/console/agent/test_agent_controllers.py:1237:33
 ERROR Argument `SimpleNamespace` is not assignable to parameter `app_model` with type `App` in function `controllers.console.app.message._list_chat_messages` [bad-argument-type]
-    --> tests/unit_tests/controllers/console/agent/test_agent_controllers.py:1416:27
-ERROR Argument `SimpleNamespace` is not assignable to parameter `current_user` with type `Account | None` in function `controllers.console.app.message._list_chat_messages` [bad-argument-type]
-    --> tests/unit_tests/controllers/console/agent/test_agent_controllers.py:1417:30
+    --> tests/unit_tests/controllers/console/agent/test_agent_controllers.py:1249:67
 ERROR Argument `SimpleNamespace` is not assignable to parameter `current_user` with type `Account` in function `controllers.console.app.message._update_message_feedback` [bad-argument-type]
-    --> tests/unit_tests/controllers/console/agent/test_agent_controllers.py:1432:30
+    --> tests/unit_tests/controllers/console/agent/test_agent_controllers.py:1265:30
 ERROR Argument `SimpleNamespace` is not assignable to parameter `app_model` with type `App` in function `controllers.console.app.message._update_message_feedback` [bad-argument-type]
-    --> tests/unit_tests/controllers/console/agent/test_agent_controllers.py:1433:27
+    --> tests/unit_tests/controllers/console/agent/test_agent_controllers.py:1266:27
 ERROR Argument `SimpleNamespace` is not assignable to parameter `current_user` with type `Account` in function `controllers.console.app.message._get_message_suggested_questions` [bad-argument-type]
-    --> tests/unit_tests/controllers/console/agent/test_agent_controllers.py:1469:26
+    --> tests/unit_tests/controllers/console/agent/test_agent_controllers.py:1302:26
 ERROR Argument `SimpleNamespace` is not assignable to parameter `app_model` with type `App` in function `controllers.console.app.message._get_message_suggested_questions` [bad-argument-type]
-    --> tests/unit_tests/controllers/console/agent/test_agent_controllers.py:1470:23
+    --> tests/unit_tests/controllers/console/agent/test_agent_controllers.py:1303:23
 ERROR Argument `Literal['00000000-0000-0000-0000-000000000002']` is not assignable to parameter `message_id` with type `UUID` in function `controllers.console.app.message._get_message_suggested_questions` [bad-argument-type]
-    --> tests/unit_tests/controllers/console/agent/test_agent_controllers.py:1471:24
+    --> tests/unit_tests/controllers/console/agent/test_agent_controllers.py:1304:24
 ERROR Object of class `int` has no attribute `lower` [missing-attribute]
    --> tests/unit_tests/controllers/console/app/test_annotation_security.py:221:38
 ERROR Object of class `int` has no attribute `lower` [missing-attribute]
@@ -6466,43 +6440,43 @@
 ERROR Argument `list[SimpleNamespace]` is not assignable to parameter `recipients` with type `Sequence[HumanInputFormRecipient]` in function `repositories.sqlalchemy_api_workflow_run_repository._build_human_input_required_reason` [bad-argument-type]
   --> tests/unit_tests/repositories/test_sqlalchemy_api_workflow_run_repository.py:56:9
 ERROR Object of class `object` has no attribute `prompt` [missing-attribute]
-   --> tests/unit_tests/services/agent/test_agent_services.py:592:12
+   --> tests/unit_tests/services/agent/test_agent_services.py:591:12
 ERROR Argument `FakeSession` is not assignable to parameter `session` with type `scoped_session[Unknown]` in function `services.app_service.AppService._build_app_list_filters` [bad-argument-type]
-    --> tests/unit_tests/services/agent/test_agent_services.py:1368:61
+    --> tests/unit_tests/services/agent/test_agent_services.py:1233:61
 ERROR Object of class `object` has no attribute `name` [missing-attribute]
-    --> tests/unit_tests/services/agent/test_agent_services.py:1774:16
+    --> tests/unit_tests/services/agent/test_agent_services.py:1634:16
 ERROR Object of class `object` has no attribute `mode` [missing-attribute]
-    --> tests/unit_tests/services/agent/test_agent_services.py:1775:16
+    --> tests/unit_tests/services/agent/test_agent_services.py:1635:16
 ERROR Object of class `object` has no attribute `agent_role` [missing-attribute]
-    --> tests/unit_tests/services/agent/test_agent_services.py:1776:16
+    --> tests/unit_tests/services/agent/test_agent_services.py:1636:16
 ERROR Argument `FakeSession` is not assignable to parameter `session` with type `Session` in function `services.agent.workflow_publish_service.WorkflowAgentPublishService.project_draft_bindings_to_graph` [bad-argument-type]
-    --> tests/unit_tests/services/agent/test_agent_services.py:2051:21
+    --> tests/unit_tests/services/agent/test_agent_services.py:1911:21
 ERROR Argument `FakeSession` is not assignable to parameter `session` with type `Session` in function `services.agent.workflow_publish_service.WorkflowAgentPublishService.sync_roster_agent_bindings_for_draft` [bad-argument-type]
-    --> tests/unit_tests/services/agent/test_agent_services.py:2110:21
+    --> tests/unit_tests/services/agent/test_agent_services.py:1970:21
 ERROR Argument `FakeSession` is not assignable to parameter `session` with type `Session` in function `services.agent.workflow_publish_service.WorkflowAgentPublishService.sync_agent_bindings_for_draft` [bad-argument-type]
-    --> tests/unit_tests/services/agent/test_agent_services.py:2179:21
+    --> tests/unit_tests/services/agent/test_agent_services.py:2039:21
 ERROR Argument `FakeSession` is not assignable to parameter `session` with type `Session` in function `services.agent.workflow_publish_service.WorkflowAgentPublishService.sync_agent_bindings_for_draft` [bad-argument-type]
-    --> tests/unit_tests/services/agent/test_agent_services.py:2234:25
+    --> tests/unit_tests/services/agent/test_agent_services.py:2094:25
 ERROR Argument `FakeSession` is not assignable to parameter `session` with type `Session` in function `services.agent.workflow_publish_service.WorkflowAgentPublishService.sync_agent_bindings_for_draft` [bad-argument-type]
-    --> tests/unit_tests/services/agent/test_agent_services.py:2266:25
+    --> tests/unit_tests/services/agent/test_agent_services.py:2126:25
 ERROR Argument `FakeSession` is not assignable to parameter `session` with type `Session` in function `services.agent.workflow_publish_service.WorkflowAgentPublishService.sync_agent_bindings_for_draft` [bad-argument-type]
-    --> tests/unit_tests/services/agent/test_agent_services.py:2298:25
+    --> tests/unit_tests/services/agent/test_agent_services.py:2158:25
 ERROR Argument `FakeSession` is not assignable to parameter `session` with type `Session` in function `services.agent.workflow_publish_service.WorkflowAgentPublishService.sync_agent_bindings_for_draft` [bad-argument-type]
-    --> tests/unit_tests/services/agent/test_agent_services.py:2344:25
+    --> tests/unit_tests/services/agent/test_agent_services.py:2204:25
 ERROR Argument `FakeSession` is not assignable to parameter `session` with type `Session` in function `services.agent.workflow_publish_service.WorkflowAgentPublishService.sync_roster_agent_bindings_for_draft` [bad-argument-type]
-    --> tests/unit_tests/services/agent/test_agent_services.py:2404:21
+    --> tests/unit_tests/services/agent/test_agent_services.py:2264:21
 ERROR Argument `FakeSession` is not assignable to parameter `session` with type `Session` in function `services.agent.workflow_publish_service.WorkflowAgentPublishService.sync_roster_agent_bindings_for_draft` [bad-argument-type]
-    --> tests/unit_tests/services/agent/test_agent_services.py:2474:21
+    --> tests/unit_tests/services/agent/test_agent_services.py:2334:21
 ERROR Argument `FakeSession` is not assignable to parameter `session` with type `Session` in function `services.agent.workflow_publish_service.WorkflowAgentPublishService.sync_roster_agent_bindings_for_draft` [bad-argument-type]
-    --> tests/unit_tests/services/agent/test_agent_services.py:2507:21
+    --> tests/unit_tests/services/agent/test_agent_services.py:2367:21
 ERROR Object of class `object` has no attribute `skills_files` [missing-attribute]
-    --> tests/unit_tests/services/agent/test_agent_services.py:2719:37
+    --> tests/unit_tests/services/agent/test_agent_services.py:2579:37
 ERROR Object of class `object` has no attribute `skills_files` [missing-attribute]
-    --> tests/unit_tests/services/agent/test_agent_services.py:2756:34
+    --> tests/unit_tests/services/agent/test_agent_services.py:2616:34
 ERROR Object of class `object` has no attribute `skills_files` [missing-attribute]
-    --> tests/unit_tests/services/agent/test_agent_services.py:2780:34
+    --> tests/unit_tests/services/agent/test_agent_services.py:2640:34
 ERROR Object of class `object` has no attribute `skills_files` [missing-attribute]
-    --> tests/unit_tests/services/agent/test_agent_services.py:2781:12
+    --> tests/unit_tests/services/agent/test_agent_services.py:2641:12
 ERROR Object of class `NoneType` has no attribute `workflow_prompt` [missing-attribute]
    --> tests/unit_tests/services/agent/test_composer_mention_validation.py:113:5
 ERROR Class member `ConcreteApiKeyAuth.validate_credentials` overrides a member in a parent class but is missing an `@override` decorator [missing-override-decorator]

@github-actions

github-actions Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Pyrefly Type Coverage

Metric Base PR Delta
Type coverage 50.88% 50.84% -0.04%
Strict coverage 50.39% 50.35% -0.04%
Typed symbols 30,087 30,071 -16
Untyped symbols 29,331 29,364 +33
Modules 2920 2922 +2

AnandSundar and others added 2 commits June 22, 2026 19:33
…t path

- Replace string literals with AccountStatus.ACTIVE / TenantStatus.NORMAL
  enum values in the real-DB fixture to satisfy Pyrefly strict typing.
- Split the compound 'unfiltered is not None and unfiltered.id == ...'
  assertion in test_real_load_filters_cross_tenant_app so ruff PT018
  passes and each failure mode reports separately.
- Reorder controllers.console import group to satisfy ruff I401.
- Narrow the api-integration pytest collection path from the broad
  'api/tests/integration_tests/controllers' (which exposed 69 pre-existing
  'relation accounts does not exist' errors in controllers/openapi/) to
  'api/tests/integration_tests/controllers/console/app' where the new
  regression tests live and pass cleanly. The broader path is deferred
  to a separate PR that fixes the openapi conftest's accounts fixture.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XS This PR changes 0-9 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant