Bug: Dataset Service API ignores per-user knowledge base permissions — every token is authorized as the workspace Owner (list vs direct GET inconsistent)
- Dominant language
- TypeScript
- Stars
- 156k
- Forks
- 24.6k
- Avg merge
- 22h 9m
- Merged PRs (30d)
- 610
Description
### Self Checks
- [x] I have read the [Contributing Guide](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md) and [Language Policy](https://github.com/langgenius/dify/issues/1542).
- [x] This is only for bug report, if you would like to ask a question, please head to [Discussions](https://github.com/langgenius/dify/discussions/categories/general).
- [x] I have searched for existing issues [search for existing issues](https://github.com/langgenius/dify/issues), including closed ones.
- [x] I confirm that I am using English to submit this report, otherwise it will be closed.
- [x] 【中文用户 & Non English User】请使用英语提交,否则会被关闭 :)
- [x] Please do not modify this template :) and fill in all the required fields.
### Dify version
1.17.0 (langgenius/dify-api:1.17.0)
### Cloud or Self Hosted
Self Hosted (Docker Compose)
### Steps to reproduce
1. In a workspace, have an **Owner** account and a **Member** account (editor).
2. As the Member, create 5 knowledge bases with permission **`only_me`**, and 1 knowledge base with **`partial_members`** shared to the Owner.
3. As the Owner, create 3 knowledge bases with permission **`only_me`**.
4. Create a Knowledge Base Service API key (it does not matter which account creates it — keys are workspace-scoped, `api_tokens` has no creator column).
5. Call `GET /v1/datasets` with that token.
**Observed result:** the API returns exactly the **Owner's view** — the Owner's three `only_me` datasets plus the `partial_members` dataset shared to the Owner — and **omits all five `only_me` datasets created by the Member**, even when the token is used by the Member who created them.
Two different API keys created by different accounts return byte-identical results.
Additionally:
6. With the same token, call `GET /v1/datasets/{id_of_member_only_me_dataset}` directly.
**Observed result:** HTTP **200** with full dataset detail. So the dataset is invisible in the list but fully operable via its direct endpoints.
### ✔️ Expected Behavior
Consistent, predictable semantics for the workspace-scoped Knowledge Base API. Per the docs ("a single knowledge base API key can access all knowledge bases **visible** to ..."), at minimum:
- Listing and direct access should agree on visibility, and
- A Member's `only_me` datasets should not silently disappear from `GET /v1/datasets` (the Member cannot manage their own private datasets programmatically with any token), or this limitation should be explicitly documented.
### ❌ Actual Behavior
- Every dataset Service API request is executed **as the workspace Owner account**: `validate_dataset_token` looks up `TenantAccountJoin.role IN ('owner')`, fetches that Account and calls `_update_request_context_with_user(account)` (see `api/controllers/service_api/wraps.py`, ~lines 342–361; the code even carries a `TODO: only owner information is required` comment).
- The list endpoint then applies the legacy per-user visibility filter against that impersonated identity (`DatasetService.get_datasets` in `api/services/dataset_service.py`: `ONLY_ME AND maintainer == user.id OR ALL_TEAM OR PARTIAL_TEAM AND id IN permitted_ids`) — so results reflect the Owner's personal view, not workspace-level API access.
- The single-dataset path (`DatasetApiResource` dispatch in the same wraps module) only validates tenant ownership and `enable_api`, with no permission filter — hence list/direct-GET inconsistency.
### Evidence from our instance (9 datasets in DB, sanitized)
| Dataset | permission | creator | in `GET /v1/datasets`? | direct `GET /v1/datasets/{id}` |
|---|---|---|---|---|
| kb-A | only_me | Owner | ✅ | ✅ |
| kb-B | only_me | Owner | ✅ | ✅ |
| kb-C | only_me | Owner | ✅ | ✅ |
| kb-D…kb-H (5) | only_me | Member | ❌ missing | ✅ 200 |
| kb-I | partial_members (shared to Owner) | Member | ✅ | ✅ |
Both tokens (created by Owner and by Member respectively) returned identical results.
### Root cause analysis
Knowledge Base Service API keys are intentionally workspace-scoped (`api_tokens.tenant_id`, no creator binding). However `validate_dataset_token` additionally impersonates the tenant's Owner account to satisfy code paths that read `current_user`. Because `GET /v1/datasets` feeds that same `current_user` into the per-user visibility filter, the workspace-scoped key effectively inherits the **Owner's private-view semantics** — which contradicts both the documented contract and the unfiltered single-resource endpoints.
### Suggested fix
Pick one consistent contract, e.g.:
1. Treat the dataset Service API as workspace-administrative access: skip the per-user visibility filter on list (matching today's single-resource behavior); **or**
2. Bind dataset API keys to their creating account (`created_by`) and apply that account's visibility rules; **or**
3. Keep current behavior but document it clearly and align the docs ("visible" = visible to the workspace Owner).
Option 1 is the smallest change and removes the list/direct inconsistency.
### Environment details
- Docker Compose deployment (api 1.17.0) behind bundled nginx.
- Reproduced 100% with two distinct API keys; verified against DB state directly.
Contributor guide
Research direction
Start in api/controllers/service_api/wraps.py at validate_dataset_token and the DatasetApiResource dispatch, then trace DatasetService.get_datasets in api/services/dataset_service.py. Reproduce the list and direct-GET calls with owner and member-only datasets, and confirm the intended workspace-key contract with maintainers. Done means list visibility and direct access follow one documented, tested permission model.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- api, authorization, backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100