A self-hosted Dify instance holds the model provider keys, knowledge bases, plugin credentials and app API keys for every AI application built on it, yet it often starts life from a Docker Compose file with sample passwords and a pre-filled secret key. Dify is an open source platform for building LLM apps, agents, workflows and retrieval pipelines, available as Dify Cloud and as a self-hosted Community or Enterprise edition. This guide covers the self-hosted deployment: the secrets that must be replaced, workspace roles, model provider credentials, app, knowledge and MCP keys, plugin installation and signature verification, logging and patching against the project's published security advisories. It is written for platform engineers and security teams who run Dify or are reviewing a team's request to run it. For the wider category of visual agent builders, see the Low-Code Agent Platforms Guide.
Key takeaways
- Replace every sample secret before first start.
SECRET_KEY,DB_PASSWORD,REDIS_PASSWORD,CODE_EXECUTION_API_KEYand the agent backend secrets all ship with known development values. - Model provider keys are workspace-wide. A provider key added by an admin can be used by every app in the workspace, so separate workspaces by trust level and use provider keys with spending limits.
- App, knowledge and MCP credentials are bearer secrets. App API keys belong on a backend only, and the MCP server URL embeds credentials and must be handled like an API key.
- Keep plugin signature verification on. Self-hosted Dify enforces signatures by default; restrict who can install and debug plugins to admins.
- Patch on a schedule. Dify's advisories include plaintext API key exposure to non-admins, unauthenticated SSRF and cross-tenant data access, each fixed only by upgrading.
How identity and access work in Dify
Dify has three identity layers: console users who build apps, the services and clients that call apps through API keys, and the internal service-to-service secrets that hold the deployment together. Each needs its own controls.
| Identity or credential | Used by | Where it is set | Security relevance |
|---|---|---|---|
| Console account | Builders and admins | Email and password, email code, or GitHub and Google OAuth | Access tokens last 60 minutes (ACCESS_TOKEN_EXPIRE_MINUTES); refresh tokens keep a user signed in for 30 days (REFRESH_TOKEN_EXPIRE_DAYS). |
| Workspace role | Console accounts | Workspace members settings | Owner, Admin, Editor and Normal decide who manages providers, apps and members. |
| App API key | Backends calling a published app | Inside each app | Scoped to one app and shared by all its end users; sent as Authorization: Bearer. |
| Knowledge API key | Ingestion and retrieval services | Knowledge, Service API | Scoped to specific knowledge bases or to all of them. |
| MCP server URL | MCP clients such as IDE assistants | App Access Point tab | Disabled by default; the URL itself contains credentials. |
| Model provider key | Every app in the workspace | Model providers settings | The documentation states that these keys "grant workspace-wide model access". |
| Internal secrets | Dify services | .env or orchestrator secrets | SECRET_KEY signs sessions, JWTs and file URLs and encrypts OAuth credentials. |
Sign-up is closed by default (ALLOW_REGISTER=false) and new accounts do not get their own workspace (ALLOW_CREATE_WORKSPACE=false), so a fresh install is invite-only. Keep it that way for any instance that holds real data. The /install setup page that creates the first account is protected only by INIT_PASSWORD if you set one; complete setup before the instance is reachable from any untrusted network.
Admin roles and privileged access
| Role | Can do | Hardening note |
|---|---|---|
| Owner | Full control of the workspace; one per workspace; cannot be removed except by ownership transfer | Use a named individual account protected by strong authentication, not a shared mailbox. Transfer needs an emailed code, so configure MAIL_TYPE. |
| Admin | Manage members and model providers, plus everything an Editor can do | Admins control the provider keys every app uses. Keep the group small. |
| Editor | Create, edit and delete apps and knowledge bases | Editors decide which tools, HTTP endpoints and knowledge an app can reach, which is privileged in practice. |
| Normal | Use published apps only | The right default for consumers of internal AI apps. |
- Removal behaviour: removing a member cuts off access immediately, but the apps and knowledge bases they created remain, along with any API keys issued for those apps. Review and reassign them as part of offboarding. See the NHI Offboarding and Decommissioning Guide.
- Invitations: invite links expire after 72 hours by default (
INVITE_EXPIRY_HOURS) and are built fromCONSOLE_WEB_URL. Links copied into chat can be used by anyone who sees them within that window. - Granularity: custom roles and per-app or per-knowledge-base access are Dify Enterprise features. On Community edition, the workspace is the only real boundary, so create separate workspaces for teams or data classifications that must not share provider keys or knowledge.
- Infrastructure admins: anyone with access to the host, the
.envfile or the database effectively holds every secret in Dify. Treat them as privileged and manage that access through your PAM process. See the Privileged Access Management Guide.
Integrations, API tokens and service accounts
Model provider credentials
Only the workspace Owner and Admins can manage model providers. A provider key is shared across that provider's models and becomes available to every app in the workspace; custom models carry their own keys. Several keys can be added to one provider, for example to separate development and production accounts. Practical controls:
- Spending limits at the provider: issue a dedicated provider key per Dify workspace with a budget cap and model allow-list at the provider, so a stolen key or runaway agent is bounded. See the Denial-of-Wallet and AI Agent Cost Runaway Guide.
- Do not assume the console hides keys: advisory GHSA-phpv-94hg-fv9g (CVE-2025-67732, CVSS 4.0 score 8.4) showed that up to version 1.10.1-fix.1 the endpoint
/console/api/workspaces/current/model-providersreturned custom model API keys in plaintext to non-admin users. It was fixed in 1.11.0. If you ran an affected version with untrusted members, rotate those provider keys. - Encryption key location: per-workspace encryption keys are kept according to
KEY_PROVIDER_TYPE(defaultlocal). Credentials encrypted by one provider cannot be decrypted by another, so decide before go-live and back up the key material with the database.
App, knowledge and MCP keys
| Credential | Scope | Main risk | Control |
|---|---|---|---|
| App API key | One app, all its end users | Embedded in frontend or mobile code and extracted | The documentation says to "call the API from your backend only". Pass a per-person user value so usage can be attributed. |
| Knowledge API key | Selected knowledge bases or all | All-knowledge keys used for one ingestion job | Scope each key to the knowledge bases a service needs. |
| MCP server URL | One app | URL pasted into shared IDE configurations or chat | Treat as an API key; use the regenerate button if exposed, which stops the old URL immediately. |
| OpenAPI bearer tokens | /openapi/v1/* | Programmatic access enabled without need | OPENAPI_ENABLED and ENABLE_OAUTH_BEARER default to false; keep them off unless used. Default rate limit 60 per minute per token. |
Advisory GHSA-fw4f-cghv-2v33 (February 2026) reported a cryptographically weak random number generator used for API key generation. After upgrading past an advisory like this, regenerate the affected keys, because the fix protects new keys, not old ones. Store all Dify keys in a secrets manager and scan repositories for them. See the API Key Management Guide.
Plugins
Dify plugins provide models, tools, agent strategies, extensions, data sources and triggers, and run in the separate plugin_daemon service. They can be installed from the Dify Marketplace, from any public GitHub repository, or from a local .difypkg package. A plugin that holds tool credentials or calls external services is a supply chain dependency with access to your data.
- Signature verification: self-hosted Dify enforces plugin signature verification by default. To allow internal plugins, sign them with your own key pair (
dify signature generate, producing a.signed.difypkg) and configureFORCE_VERIFYING_SIGNATURE=true,THIRD_PARTY_SIGNATURE_VERIFICATION_ENABLED=trueandTHIRD_PARTY_SIGNATURE_VERIFICATION_PUBLIC_KEYSonplugin_daemon. Never switch verification off to get one plugin installed. Guard the private signing key, because anyone holding it can sign plugins your instance will trust. - Permissions: at the bottom of the Integrations page, set "Who can install and manage?" and "Who can debug?" to admins only (options are everyone, admins only or no one). The debugging key lets a member attach a plugin under development to the workspace, so set it to no one on production instances.
- Updates: auto-update can be off, patch versions only, or always latest, per category. Patch-only is a reasonable balance for tools that hold credentials; always-latest trusts the publisher with every future release.
- Enterprise restrictions: Dify Enterprise can limit installs to official plugins, official plus verified partners, or Marketplace only.
See the AI Agent Tool and Plugin Security Guide.
Logging and detection
Dify writes application logs at LOG_LEVEL (default INFO) to the console and, if LOG_FILE is set (default /app/logs/server.log), to a rotated file (20 MB, five backups). Ship container logs to your SIEM; rotated files on the host are not an audit trail. Do not run production with DEBUG=true: the documentation warns it may expose sensitive data in logs, including prompts and responses. App conversation logs and workflow run history in the console are useful for investigations but are product features, not tamper-resistant audit logs.
| Signal | Source | Why it matters |
|---|---|---|
Requests to /install after setup | Reverse proxy logs | Probing for an unfinished or reset instance. |
| Console logins from new locations or failed logins in bursts | Proxy and API logs | Password spraying against email and password login. |
| Plugin installs, upgrades and debugging key use | Integrations page and plugin_daemon logs | New code with access to credentials. |
Requests blocked by ssrf_proxy | Access logs of the ssrf_proxy container | Workflows or plugins trying to reach internal addresses or cloud metadata. |
| Model provider spend and token spikes | Provider console and billing alerts | Stolen provider or app keys, or an agent loop. |
App API calls with missing or constant user values | API logs and app logs | An app key used outside its intended backend. |
Metrics that show the deployment is under control:
- Days between a Dify security release and production upgrade (target within your critical patch window).
- Number of sample or default secrets in the running configuration (target zero, checked in CI against
.env.examplevalues). - Percentage of app and knowledge keys with a named owning service and a rotation date.
- Number of members with Admin or Owner rights per workspace.
- Plugins installed from GitHub or local packages without a recorded review.
Dify can also send traces to external LLM observability tools such as LangSmith and Langfuse; treat that export as another copy of prompt data. See the LLM Observability Guide and the LangSmith Security Guide.
Hardening baseline
Secrets and configuration
| Setting | Shipped value | Production setting |
|---|---|---|
SECRET_KEY | Pre-filled sample | Long random value from a secrets manager. Changing it later logs out every user. |
DB_PASSWORD, REDIS_PASSWORD | difyai123456 | Unique random values; database and Redis not reachable from outside the deployment. |
CODE_EXECUTION_API_KEY / SANDBOX_API_KEY | dify-sandbox | Matching random value on both services. |
DIFY_AGENT_SERVER_SECRET_KEY, DIFY_AGENT_API_TOKEN | Development values | Random values; the documentation warns that anyone who knows them can forge tokens or start runs. |
INIT_PASSWORD | Empty | Set before first start, up to 30 characters. |
WEB_API_CORS_ALLOW_ORIGINS, CONSOLE_CORS_ALLOW_ORIGINS | * | Your own console and app origins only. |
REDIS_USE_SSL | false | true where Redis is outside the host or cluster. |
HTTP_REQUEST_NODE_SSL_VERIFY, CODE_EXECUTION_SSL_VERIFY | true | Leave on. |
PASSWORD_PEPPER | Empty | Decide before accounts exist; setting or changing it later breaks current passwords. |
Network and runtime
- SSRF proxy: outbound requests from HTTP Request nodes and tools go through
ssrf_proxy(SSRF_PROXY_HTTP_URLandSSRF_PROXY_HTTPS_URL, defaulthttp://ssrf_proxy:3128), which blocks internal and private ranges. Keep it in the path and add network egress rules as a second layer, because advisories show SSRF paths that did not depend on workflows: GHSA-8235-vv5j-mmvg, an unauthenticated SSRF in/console/api/remote-files/upload(CVSS 3.1 score 8.3), was fixed in 1.13.0. See the SSRF Prevention Guide. - Code sandbox: the code execution sandbox (default
http://sandbox:8194) should be reachable only by the API service and have no route to cloud metadata endpoints or internal systems. See the AI Agent Sandboxing and Code Execution Guide. - Exposure: publish only the web front end through a TLS-terminating reverse proxy. Set
CONSOLE_API_URLto the public HTTPS address so OAuth callbacks and secure cookies work. Put the console behind SSO or a private network where possible.
Rollout order
- Before first start: generate every secret in the table above, set
INIT_PASSWORD, keep registration and workspace creation off, and store the.envvalues in a secrets manager. - Pilot: run a non-production instance with test provider keys and confirm the SSRF proxy blocks internal addresses and cloud metadata.
- Workspaces and roles: create workspaces per team or data class, assign Normal by default and keep Admins few.
- Providers and plugins: add capped provider keys, set plugin install and debug permissions to admins or no one, choose an update policy.
- Go live: issue app and knowledge keys to backends only, record owners, connect logs to the SIEM.
- Operate: watch the GitHub security advisories, upgrade within your patch window and rotate credentials that an advisory could have exposed. Keep a database and volume backup taken before each upgrade so you can roll back.
Common misconfigurations
- Sample secrets in production: a Docker Compose deployment started with
.env.examplevalues forSECRET_KEY, database and Redis passwords, or sandbox keys. - Console on the internet without SSO or network restriction: AI builders on the public internet are actively targeted. The Langflow CVE-2025-3248 exploitation shows the pattern in a comparable tool: an unauthenticated flaw, then a dump of environment variables full of AI and cloud keys.
- App API keys in browser code: one extracted key serves the whole app and its provider budget to anyone.
- Everyone can install plugins: members install GitHub plugins that hold credentials and call external services, with no review.
- Running old versions: advisories since 2025 cover access control on app editing and DSL export, stored and DOM XSS, cross-tenant file preview and API detail disclosure (July 2026), and plaintext key exposure. An unpatched instance carries all of them.
- Wildcard CORS and
DEBUG=trueleft from testing: the defaults suit a laptop, not a shared service.
Framework mapping
| Control area | NIST SP 800-53 Rev 5 | NIST CSF 2.0 | ATT&CK threat addressed |
|---|---|---|---|
| Roles, workspaces and offboarding | AC-2, AC-6 | PR.AA-05 | T1078.004 Cloud Accounts |
| Secrets, provider and app keys | IA-5, SC-28 | PR.DS-01 | T1552.001 Credentials In Files, T1528 Steal Application Access Token |
| Plugins and signature verification | SR-3, CM-7 | PR.PS-01 | Supply chain compromise of plugins |
| SSRF proxy, sandbox and exposure | SC-7, SC-8, CM-6 | PR.PS-01 | T1190 Exploit Public-Facing Application |
| Patching against advisories | SI-2 | PR.PS-01 | T1190 Exploit Public-Facing Application |
| Logging and monitoring | AU-2, AU-6 | DE.CM-09 | All of the above |
For the application layer, the OWASP Top 10 for LLM Applications 2025 entries LLM02 Sensitive Information Disclosure and LLM03 Supply Chain apply to provider keys and plugins respectively.
Practitioner checklist
- Replace every shipped secret (
SECRET_KEY, database, Redis, sandbox, agent backend) and setINIT_PASSWORDbefore first start. - Keep
ALLOW_REGISTERandALLOW_CREATE_WORKSPACEoff, and restrict CORS to your own origins. - Separate workspaces by team or data class; keep Owner and Admin to named individuals.
- Use dedicated, budget-capped provider keys per workspace and rotate any key exposed by an advisory.
- Issue app keys to backends only, scope knowledge keys, and treat MCP server URLs as secrets.
- Leave plugin signature verification on and set plugin install and debug permissions to admins or no one.
- Keep
ssrf_proxyin the request path and isolate the sandbox from internal networks and metadata services. - Run with
DEBUG=false, ship logs to the SIEM and alert on plugin changes and SSRF proxy blocks. - Subscribe to the Dify GitHub security advisories and upgrade within your patch window.
Standards and references
- Dify: Environment variables reference
- Dify: Third-party plugin signature verification
- Dify: Integrations (plugins)
- Dify: Manage members
- Dify: Developing with APIs
- Dify: Publish as MCP server
- Dify (GitHub): Security advisories
- OWASP: Top 10 for LLM Applications 2025
Related NHI Mgmt Group resources: Low-Code Agent Platforms Guide · LangSmith Security Guide · AI Agent Tool and Plugin Security Guide · LLMjacking Guide · Self-Hosted LLM Security Guide