Documentsdify
File Access Control
File Access Control
Type
Topic
Status
Published
Created
Jul 7, 2026
Updated
Jul 7, 2026
Created by
Dosu Bot
Updated by
Dosu Bot

File Access Control#

Dify enforces workflow-layer file access control through the api/core/app/file_access/ module. The system gates database lookups for UploadFile and ToolFile records using a request-scoped ownership context carried as a Python ContextVar. Access is always tenant-scoped; end-user executions apply additional per-user ownership checks, with an execution-local grant mechanism for files surfaced through trusted knowledge retrieval paths.

The module's public API : DatabaseFileAccessController, FileAccessControllerProtocol, FileAccessScope, bind_file_access_scope, get_current_file_access_scope, grant_upload_file_access, grant_retriever_segment_access, is_retriever_segment_access_granted.


Core Data Structures#

FileAccessScope#

FileAccessScope is a frozen dataclass held in a ContextVar. Key fields:

FieldPurpose
tenant_idMandatory isolation boundary — always applied
user_idIdentity of the invoking user
user_fromACCOUNT or END_USER
invoke_fromOriginating invocation path
granted_upload_file_idsExecution-local allowlist of UploadFile IDs
granted_retriever_segment_idsExecution-local allowlist of knowledge segment IDs

The requires_user_ownership property returns True when user_from == END_USER, triggering stricter ownership filtering.

FileAccessControllerProtocol#

The protocol is the testable interface implemented by DatabaseFileAccessController. It defines:

  • apply_upload_file_filters / apply_tool_file_filters — append access-control predicates to SQLAlchemy Select statements
  • get_upload_file / get_tool_file — load a single authorized record, returning None on access denial

Three-Layer Filtering Logic#

DatabaseFileAccessController.apply_upload_file_filters applies three conditions in order:

  1. Tenant scope — unconditionally adds UploadFile.tenant_id == scope.tenant_id
  2. User ownership — when requires_user_ownership, adds created_by_role == END_USER AND created_by == user_id
  3. Execution-local grants — if granted_upload_file_ids is non-empty, the ownership condition is OR-expanded with UploadFile.id IN (granted_ids)

ToolFile access follows the same tenant + user_id pattern, but has no grant list .


Execution-Local Grant Mechanism#

The grant system solves a concrete problem: knowledge base images are indexed as UploadFile records owned by the indexing account, but published apps execute as end-users who fail the per-user ownership check . Before the fix, this caused Invalid upload file errors when a Knowledge Retrieval node returned segments containing images — only reproducible in the published app, not Studio Preview (which runs as the account user).

The fix, landed in PR #36175 / PR #36195, added two grant functions to scope.py:

Both mutate the scope via an immutable replace() on the ContextVar, keeping the dataclass frozen .

is_retriever_segment_access_granted(segment_id) returns True for account users unconditionally, or when the segment ID is in the execution's allowlist.

Grant Call Sites#

LocationWhat is granted
dataset_retrieval.py L332Segment IDs from knowledge retrieval results
dataset_retrieval.py L525Upload file IDs from segment attachment bindings
retrieval_service.py L923, L969Upload file IDs for individual/batch attachment bindings
node_runtime.py L379Upload file IDs just before attachment loader returns File refs

node_runtime.py also enforces grants before loading: the DifyRetrieverAttachmentLoader returns [] if is_retriever_segment_access_granted rejects the segment or if the context-aware segment_access_checker (injected from node_factory.py) cannot find the segment in the workflow's current variable pool .


Scope Lifecycle#

bind_file_access_scope is a context manager that sets the ContextVar for the duration of an app execution and resets it on exit. BaseAppGenerator._bind_file_access_scope constructs the initial FileAccessScope from tenant_id, user (distinguishing Account vs EndUser), and invoke_from, then wraps the execution generator. All app types — completion, chat, agent, advanced chat, workflow — call this base method.


Key Files#

FileRole
api/core/app/file_access/scope.pyFileAccessScope, ContextVar, all grant/check functions
api/core/app/file_access/controller.pyDatabaseFileAccessController — SQL filter logic
api/core/app/file_access/protocols.pyFileAccessControllerProtocol — injectable interface
api/core/app/file_access/__init__.pyPublic re-exports
api/core/app/apps/base_app_generator.pyScope binding at app execution start
api/core/rag/retrieval/dataset_retrieval.pySegment + file grant calls during retrieval
api/core/rag/datasource/retrieval_service.pyAttachment binding grant calls
api/core/workflow/node_runtime.pyAttachment loader enforcement + final file grants
File Access Control | Dosu