RBAC: per-resource access control is not initialized on most App/Dataset creation paths — team members get 403 on resources they didn't create
- 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] 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] Please do not modify this template :) and fill in all the required fields.
### 1. Is this request related to a challenge you're experiencing? Tell me about your story.
### Self Checks
- [x] Searched existing issues; this concerns the RBAC access path.
## Summary
When er-resource access is initialized on creation via
`AppAccess/DatasetAccess.replace_whitelist(scope=ALL)` +
`try_sync_creator_access_policy_member_bindings` + `initialize_created_app_rbac_access_task`.
This initialization is only wired into the **primary console create controllers**. Every other creation entry point constructs the `App`/`Dataset` object directly and **skips it**. Resources created via those paths therefore have **no RBAC access record**, and any non-maintainer team member gets **403** when opening them.
For datasets there is an additional layer: the legacy `check_dataset_permission` still runs unconditionally under RBAC and reads the legacy `dataset.permission` / `DatasetPermission` model, which the RBAC access-config UI does not update. A knowledge base created via the file-upload flow defaults to `permission=only_me` **and** has no RBAC whitelist, so it cannot be shared even after assigning "Partial team members" — the legacy gate rejects first with `You do not have permission to access this dataset.`
The maintainer/creator keeps access via the maintainer short-circuit, so this is **silent for the creator** and only breaks collaboration. Because RBAC is default-off, it appears to be largely unexercised.
## Environment
- `main` (verified on latest)
## Coverage matrix (verified on `main`)
### App creation
| Entry point | RBAC init on create? | Ref |
| --- | --- | --- |
| Console create app | ✅ | `controllers/console/app/app.py:676` (+ `app_service.py:540`) |
| DSL import (`import_app`) | ❌ | `services/app_dsl_service.py:442` |
| Agent duplicate | ❌ | `services/agent/roster_service.py:456` |
| Workflow convert | ❌ | `services/workflow/workflow_converter.py:94` |
`import_app` is reused by console / `inner_api/app/dsl.py` / `openapi/app_dsl.py`; none
initialize RBAC.
### Dataset creation
| Entry point | RBAC init on create? | Ref |
| --- | --- | --- |
| Console empty dataset | ✅ | `controllers/console/datasets/datasets.py:603,611` |
| Public API (`service_api`) | ✅ | `controllers/service_api/dataset/dataset.py:536,544` |
| **File upload** (`save_document_without_dataset_id`) | ❌ (also defaults `permission=only_me`) | `services/dataset_service.py:3007` |
| RAG pipeline empty dataset | ❌ | `services/dataset_service.py:532` |
| External knowledge base | ❌ | `services/external_knowledge_service.py:295` |
| RAG pipeline DSL import | ❌ | `services/rag_pipeline/rag_pipeline_dsl_service.py:281,414` |
Legacy gate: `check_dataset_permission` has no `RBAC_ENABLED` guard and is invoked on read paths (8 call sites in `controllers/console/datasets/datasets_document.py`).
## Reproduction — dataset (file upload)
1. User A creates a knowledge base by uploading a file.
2. Set it to "Partial team members" and add user B.
3. User B opens it → **403 `You do not have permission to access this dataset.`**
4. User B opens it → **403 `You do not have permission to access this dataset.`**
## Reproduction — app (DSL import)
1. User A imports an app from DSL.
2. User B (a member who can normally open A's console-created apps) → **403** on the imported app.
## Root cause
The RBAC-on-create logic is duplicated inline in two controllers rather than being a single shared step invoked by all creation paths, so each additional creation entry point silently omits it.
## Questions for maintainers (the reason for this issue)
1. Is RBAC-on-create initialization intended for **all** creation paths, or only console create? If all, would you accept a shared `apply__creation_rbac()` helper called by every path (App ×4, Dataset ×6)?
2. For datasets, is `check_dataset_permission` meant to stay active under RBAC? If yes, should non-console create paths force `permission=ALL_TEAM` (as console empty-create does) to keep it a pass-through — or should the check be gated behind `if not RBAC_ENABLED`? The two differ in rollback semantics.
3. Should the `inner_api` / `openapi` DSL-import endpoints also initialize RBAC?
Happy to open a PR once the intended direction is confirmed.
### 2. Additional context or comments
_No response_
### 3. Can you help us with this feature?
- [ ] I am interested in contributing to this feature.
Contributor guide
Research direction
Start with the listed creation paths: services/app_dsl_service.py, services/agent/roster_service.py, services/workflow/workflow_converter.py, services/dataset_service.py, services/external_knowledge_service.py, and services/rag_pipeline/rag_pipeline_dsl_service.py. Compare them with the initialized console and service-API paths, then resolve the intended behavior for check_dataset_permission and inner_api/openapi imports. Done means every intended App and Dataset creation path gives non-creators the documented RBAC access without the legacy dataset gate causing an unexpected 403.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- authorization, backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100