CodeVetter · Repo Unpack · Risk map
langgenius/dify
The highest-risk surface is execution of untrusted work: SSRF-bearing HTTP egress, sandboxed code execution, shell job runtime with Landlock, and plugin-daemon RPC — all defended in depth but dependent on every caller routing through the right proxy. Auth is layered (session JWT, passport JWT, API tokens, shared inner-api key) with enterprise license gating able to fail closed across nearly every surface. Fragile spots: committed static test secrets, `ignoreBuildErrors` on the web build, a giant ordered extension-init list, and tenant-scoping discipline enforced by convention.
- SSRF is a designed-in risk domain: generic outbound HTTP must route through `core/helper/ssrf_proxy.py` (Squid at `SSRF_PROXY_*_URL`, `docker/ssrf_proxy/squid.conf.template`); remote file retrieval is split into `core/file/remote_fetcher` — any future code path using raw httpx bypasses egress policy.
- Sandboxed execution: `dify-sandbox` is a separate container image for code nodes; `dify-agent-runtime` executes user shell jobs via tmux with Landlock filesystem restrictions — but `landlock_stub.go` implies no sandbox on non-Linux kernels, so dev/other-OS runs are unsandboxed.
- The entire `/inner/api` enterprise/plugin control plane hangs on one shared secret: `X-Inner-Api-Key` string-equality against `dify_config.INNER_API_KEY`, gated by the `INNER_API` flag (404 when off) — key rotation/leak blast radius covers workspace, agent, knowledge, and runtime-credentials endpoints.
- Enterprise license failure is fail-closed across `/console/api`, `/api`, `/v1`, `/mcp`, `/triggers`: unverifiable license (`None` on exception) produces the same denial as INACTIVE/EXPIRED/LOST — a license-service outage can take down the whole product except `/files`, `/inner/api`, `/health`.
- Committed secret-shaped values in `api/pytest.ini` (PLUGIN_DAEMON_KEY, INNER_API_KEY_FOR_PLUGIN, ANTHROPIC/OPENAI keys) are test fakes but are static, long-lived, and grep-visible — risk of pattern-copy into real envs. (inferred)
- Web production builds cannot fail on type errors: `next.config.ts` sets `typescript.ignoreBuildErrors: true`; correctness relies on separate lint/type-check CI steps staying green.
- Tenant isolation is enforced by convention, not framework: AGENTS.md mandates propagating `tenant_id` across every layer and re-scoping reads by 'the complete owner chain', and `decode_jwt_token`/`AppExecutionParams` rehydrate entities from DB after trust boundaries — a missed `tenant_id` filter in any new service method is a cross-tenant data leak.
- Celery payloads carry user ids and raw `args` through the Redis broker (`AppExecutionParams.model_dump_json()`); broker access reveals tenant inputs, and non-idempotent retried tasks would double side effects — AGENTS.md requires idempotency and bounded transactions (no external I/O inside open DB transactions).
- Route enumeration is a known concern: `RESTX_ERROR_404_HELP` is explicitly disabled because flask-restx appends URL-map suggestions to 404 bodies; `controllers/console/spec.py` and the `/openapi` blueprint expose generated specs (OPENAPI_ENABLED gated).
- The ordered `initialize_extensions` list is fragile coupling: init order is load-bearing (key_provider after storage, logstore before celery, blueprints after everything) and an `is_enabled()` skip can silently drop a dependency of a later ext. (inferred)
- Blocking-vs-streaming asymmetry: Agent App (`AppMode.AGENT`) is SSE-only — `response_mode='blocking'` raises 400 — and debug single-node/loop runs execute in the request process, so a runaway node can pin a web worker greenlet.
- Webapp auth has layered token semantics (`token_source`, `auth_type`, `granted_at` SSO-staleness checks against external/workspace SSO update timestamps); regressions in `_validate_user_accessibility` silently lock out or over-admit end users.
- Credential confidentiality depends on `ext_key_provider`'s RSAKeyProvider (reads private keys from object storage at boot) and the optional `kms` dependency group (azure-keyvault); storage misconfiguration at startup poisons all credential decryption.
- Dead/legacy surfaces exist: `AppMode.CHANNEL`/`RAG_PIPELINE` are explicitly rejected by generation paths; `agent-chat` is documented as the legacy ReAct app superseded by `agent`; `tasks/collect_agent_resources_task.py` is marked 'retired' yet still imported by the worker.
- The editable cross-package dep `dify-agent = { path = "../dify-agent" }` means api version compatibility with the agent package is unpinned inside the lock beyond the workspace — drift between api and dify-agent breaks agent apps at runtime. (inferred)