test(console): add cross-tenant regression coverage for chat-messages and conversation delete (DifyTap)#37776
Open
AnandSundar wants to merge 7 commits into
Open
Conversation
…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.
Contributor
Pyrefly Diffbase → 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]
|
Contributor
Pyrefly Type Coverage
|
…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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Regression tests and CI plumbing for two of the four DifyTap cross-tenant
IDORs in the console API:
read across tenants
another tenant's conversation
The CVE fix is the
WHERE App.tenant_id == current_tenant_idclause incontrollers/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.pyTestChatMessageCrossTenant(mock-based) — exercises the fullcontroller pipeline (CSRF, JWT auth, decorator order)
TestChatMessageCrossTenantRealDB(new) — exercises the real_load_app_model_from_scoped_sessionagainst a real cross-tenantApp 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.pyCI change
api/.github/workflows/api-integration-test.ymlpreviously collectedonly
api/tests/test_containers_integration_tests. This PR adds theconsole/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 broaderapi/tests/integration_tests/controllers. The broader path was tried onthe first push and exposed 69 pre-existing failures in
controllers/openapi/test_apps.pyandtest_auth.py(allsqlalchemy.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 itin place. The mock-based tests pass either way (they mock the load
function), which is why the real-DB class exists.
Checklist
make lint && make type-check: pending CI