From cbedcd2882ae4f7b2fd597b56647f34dcb87eebd Mon Sep 17 00:00:00 2001 From: -LAN- Date: Tue, 12 May 2026 13:35:24 +0800 Subject: [PATCH] fix(security): harden self-hosted SECRET_KEY bootstrap (#36049) Co-authored-by: EndlessLucky <66432853+EndlessLucky@users.noreply.github.com> --- api/app_factory.py | 2 +- api/configs/feature/__init__.py | 6 +- api/configs/secret_key.py | 38 ++++++++++ api/core/app/workflow/file_runtime.py | 2 +- .../datasource/datasource_file_manager.py | 14 +++- api/core/tools/signature.py | 19 +++-- api/core/tools/tool_file_manager.py | 14 +++- api/extensions/ext_set_secretkey.py | 11 ++- api/models/dataset.py | 8 +- .../unit_tests/configs/test_dify_config.py | 41 ++++++++++ .../test_datasource_file_manager.py | 33 --------- .../extensions/test_set_secretkey.py | 74 +++++++++++++++++++ api/tests/unit_tests/libs/test_passport.py | 23 +----- docker/.env.example | 3 +- docker/README.md | 2 +- docker/envs/security.env.example | 3 +- 16 files changed, 209 insertions(+), 84 deletions(-) create mode 100644 api/configs/secret_key.py create mode 100644 api/tests/unit_tests/extensions/test_set_secretkey.py diff --git a/api/app_factory.py b/api/app_factory.py index 48e50ceae9..5583071980 100644 --- a/api/app_factory.py +++ b/api/app_factory.py @@ -181,7 +181,6 @@ def initialize_extensions(app: DifyApp): ext_import_modules, ext_orjson, ext_forward_refs, - ext_set_secretkey, ext_compress, ext_code_based_extension, ext_database, @@ -189,6 +188,7 @@ def initialize_extensions(app: DifyApp): ext_migrate, ext_redis, ext_storage, + ext_set_secretkey, ext_logstore, # Initialize logstore after storage, before celery ext_celery, ext_login, diff --git a/api/configs/feature/__init__.py b/api/configs/feature/__init__.py index 26b8ea670b..ccb97d96ef 100644 --- a/api/configs/feature/__init__.py +++ b/api/configs/feature/__init__.py @@ -23,9 +23,9 @@ class SecurityConfig(BaseSettings): """ SECRET_KEY: str = Field( - description="Secret key for secure session cookie signing." - "Make sure you are changing this key for your deployment with a strong key." - "Generate a strong key using `openssl rand -base64 42` or set via the `SECRET_KEY` environment variable.", + description="Secret key for secure session cookie signing. " + "Leave empty to let Dify generate a persistent key in the storage directory, " + "or set a strong value via the `SECRET_KEY` environment variable.", default="", ) diff --git a/api/configs/secret_key.py b/api/configs/secret_key.py new file mode 100644 index 0000000000..f8c33f6a2c --- /dev/null +++ b/api/configs/secret_key.py @@ -0,0 +1,38 @@ +"""SECRET_KEY persistence helpers for runtime setup.""" + +from __future__ import annotations + +import secrets + +from extensions.ext_storage import storage + +GENERATED_SECRET_KEY_FILENAME = ".dify_secret_key" + + +def resolve_secret_key(secret_key: str) -> str: + """Return an explicit SECRET_KEY or a generated key persisted in storage.""" + if secret_key: + return secret_key + + return _load_or_create_secret_key() + + +def _load_or_create_secret_key() -> str: + try: + persisted_key = storage.load_once(GENERATED_SECRET_KEY_FILENAME).decode("utf-8").strip() + if persisted_key: + return persisted_key + except FileNotFoundError: + pass + + generated_key = secrets.token_urlsafe(48) + + try: + storage.save(GENERATED_SECRET_KEY_FILENAME, f"{generated_key}\n".encode()) + except Exception as exc: + raise ValueError( + f"SECRET_KEY is not set and could not be generated at {GENERATED_SECRET_KEY_FILENAME}. " + "Set SECRET_KEY explicitly or make storage writable." + ) from exc + + return generated_key diff --git a/api/core/app/workflow/file_runtime.py b/api/core/app/workflow/file_runtime.py index 3a6f9d575a..587f700286 100644 --- a/api/core/app/workflow/file_runtime.py +++ b/api/core/app/workflow/file_runtime.py @@ -128,7 +128,7 @@ class DifyWorkflowFileRuntime(WorkflowFileRuntimeProtocol): @staticmethod def _secret_key() -> bytes: - return dify_config.SECRET_KEY.encode() if dify_config.SECRET_KEY else b"" + return dify_config.SECRET_KEY.encode() def _sign_query(self, *, payload: str) -> dict[str, str]: timestamp = str(int(time.time())) diff --git a/api/core/datasource/datasource_file_manager.py b/api/core/datasource/datasource_file_manager.py index 492b507aa9..79b84a28be 100644 --- a/api/core/datasource/datasource_file_manager.py +++ b/api/core/datasource/datasource_file_manager.py @@ -35,8 +35,11 @@ class DatasourceFileManager: timestamp = str(int(time.time())) nonce = os.urandom(16).hex() data_to_sign = f"file-preview|{datasource_file_id}|{timestamp}|{nonce}" - secret_key = dify_config.SECRET_KEY.encode() if dify_config.SECRET_KEY else b"" - sign = hmac.new(secret_key, data_to_sign.encode(), hashlib.sha256).digest() + sign = hmac.new( + dify_config.SECRET_KEY.encode(), + data_to_sign.encode(), + hashlib.sha256, + ).digest() encoded_sign = base64.urlsafe_b64encode(sign).decode() return f"{file_preview_url}?timestamp={timestamp}&nonce={nonce}&sign={encoded_sign}" @@ -47,8 +50,11 @@ class DatasourceFileManager: verify signature """ data_to_sign = f"file-preview|{datasource_file_id}|{timestamp}|{nonce}" - secret_key = dify_config.SECRET_KEY.encode() if dify_config.SECRET_KEY else b"" - recalculated_sign = hmac.new(secret_key, data_to_sign.encode(), hashlib.sha256).digest() + recalculated_sign = hmac.new( + dify_config.SECRET_KEY.encode(), + data_to_sign.encode(), + hashlib.sha256, + ).digest() recalculated_encoded_sign = base64.urlsafe_b64encode(recalculated_sign).decode() # verify signature diff --git a/api/core/tools/signature.py b/api/core/tools/signature.py index 3c7b523ff1..ca4756f2a4 100644 --- a/api/core/tools/signature.py +++ b/api/core/tools/signature.py @@ -8,6 +8,10 @@ import urllib.parse from configs import dify_config +def _secret_key() -> bytes: + return dify_config.SECRET_KEY.encode() + + def sign_tool_file(tool_file_id: str, extension: str, for_external: bool = True) -> str: """ sign file to get a temporary url for plugin access @@ -19,8 +23,7 @@ def sign_tool_file(tool_file_id: str, extension: str, for_external: bool = True) timestamp = str(int(time.time())) nonce = os.urandom(16).hex() data_to_sign = f"file-preview|{tool_file_id}|{timestamp}|{nonce}" - secret_key = dify_config.SECRET_KEY.encode() if dify_config.SECRET_KEY else b"" - sign = hmac.new(secret_key, data_to_sign.encode(), hashlib.sha256).digest() + sign = hmac.new(_secret_key(), data_to_sign.encode(), hashlib.sha256).digest() encoded_sign = base64.urlsafe_b64encode(sign).decode() return f"{file_preview_url}?timestamp={timestamp}&nonce={nonce}&sign={encoded_sign}" @@ -39,8 +42,7 @@ def sign_upload_file_preview_url(upload_file_id: str, extension: str) -> str: timestamp = str(int(time.time())) nonce = os.urandom(16).hex() data_to_sign = f"image-preview|{upload_file_id}|{timestamp}|{nonce}" - secret_key = dify_config.SECRET_KEY.encode() if dify_config.SECRET_KEY else b"" - sign = hmac.new(secret_key, data_to_sign.encode(), hashlib.sha256).digest() + sign = hmac.new(_secret_key(), data_to_sign.encode(), hashlib.sha256).digest() encoded_sign = base64.urlsafe_b64encode(sign).decode() return f"{file_preview_url}?timestamp={timestamp}&nonce={nonce}&sign={encoded_sign}" @@ -51,8 +53,7 @@ def verify_tool_file_signature(file_id: str, timestamp: str, nonce: str, sign: s verify signature """ data_to_sign = f"file-preview|{file_id}|{timestamp}|{nonce}" - secret_key = dify_config.SECRET_KEY.encode() if dify_config.SECRET_KEY else b"" - recalculated_sign = hmac.new(secret_key, data_to_sign.encode(), hashlib.sha256).digest() + recalculated_sign = hmac.new(_secret_key(), data_to_sign.encode(), hashlib.sha256).digest() recalculated_encoded_sign = base64.urlsafe_b64encode(recalculated_sign).decode() # verify signature @@ -71,8 +72,7 @@ def get_signed_file_url_for_plugin(filename: str, mimetype: str, tenant_id: str, timestamp = str(int(time.time())) nonce = os.urandom(16).hex() data_to_sign = f"upload|{filename}|{mimetype}|{tenant_id}|{user_id}|{timestamp}|{nonce}" - secret_key = dify_config.SECRET_KEY.encode() if dify_config.SECRET_KEY else b"" - sign = hmac.new(secret_key, data_to_sign.encode(), hashlib.sha256).digest() + sign = hmac.new(_secret_key(), data_to_sign.encode(), hashlib.sha256).digest() encoded_sign = base64.urlsafe_b64encode(sign).decode() query = urllib.parse.urlencode( { @@ -92,8 +92,7 @@ def verify_plugin_file_signature( """Verify the signature used by the plugin-facing file upload endpoint.""" data_to_sign = f"upload|{filename}|{mimetype}|{tenant_id}|{user_id}|{timestamp}|{nonce}" - secret_key = dify_config.SECRET_KEY.encode() if dify_config.SECRET_KEY else b"" - recalculated_sign = hmac.new(secret_key, data_to_sign.encode(), hashlib.sha256).digest() + recalculated_sign = hmac.new(_secret_key(), data_to_sign.encode(), hashlib.sha256).digest() recalculated_encoded_sign = base64.urlsafe_b64encode(recalculated_sign).decode() if sign != recalculated_encoded_sign: diff --git a/api/core/tools/tool_file_manager.py b/api/core/tools/tool_file_manager.py index c87e8a3ae0..f2552e7cbd 100644 --- a/api/core/tools/tool_file_manager.py +++ b/api/core/tools/tool_file_manager.py @@ -51,8 +51,11 @@ class ToolFileManager: timestamp = str(int(time.time())) nonce = os.urandom(16).hex() data_to_sign = f"file-preview|{tool_file_id}|{timestamp}|{nonce}" - secret_key = dify_config.SECRET_KEY.encode() if dify_config.SECRET_KEY else b"" - sign = hmac.new(secret_key, data_to_sign.encode(), hashlib.sha256).digest() + sign = hmac.new( + dify_config.SECRET_KEY.encode(), + data_to_sign.encode(), + hashlib.sha256, + ).digest() encoded_sign = base64.urlsafe_b64encode(sign).decode() return f"{file_preview_url}?timestamp={timestamp}&nonce={nonce}&sign={encoded_sign}" @@ -63,8 +66,11 @@ class ToolFileManager: verify signature """ data_to_sign = f"file-preview|{file_id}|{timestamp}|{nonce}" - secret_key = dify_config.SECRET_KEY.encode() if dify_config.SECRET_KEY else b"" - recalculated_sign = hmac.new(secret_key, data_to_sign.encode(), hashlib.sha256).digest() + recalculated_sign = hmac.new( + dify_config.SECRET_KEY.encode(), + data_to_sign.encode(), + hashlib.sha256, + ).digest() recalculated_encoded_sign = base64.urlsafe_b64encode(recalculated_sign).decode() # verify signature diff --git a/api/extensions/ext_set_secretkey.py b/api/extensions/ext_set_secretkey.py index dfb87c0167..ca59a2de4d 100644 --- a/api/extensions/ext_set_secretkey.py +++ b/api/extensions/ext_set_secretkey.py @@ -1,6 +1,13 @@ from configs import dify_config +from configs.secret_key import resolve_secret_key from dify_app import DifyApp -def init_app(app: DifyApp): - app.secret_key = dify_config.SECRET_KEY +def init_app(app: DifyApp) -> None: + """Resolve SECRET_KEY after config loading and before session/login setup.""" + secret_key = dify_config.SECRET_KEY + if not secret_key: + secret_key = resolve_secret_key(secret_key) + dify_config.SECRET_KEY = secret_key + app.config["SECRET_KEY"] = secret_key + app.secret_key = secret_key diff --git a/api/models/dataset.py b/api/models/dataset.py index f823e0aa10..65ea39969c 100644 --- a/api/models/dataset.py +++ b/api/models/dataset.py @@ -945,7 +945,7 @@ class DocumentSegment(Base): nonce = os.urandom(16).hex() timestamp = str(int(time.time())) data_to_sign = f"image-preview|{upload_file_id}|{timestamp}|{nonce}" - secret_key = dify_config.SECRET_KEY.encode() if dify_config.SECRET_KEY else b"" + secret_key = dify_config.SECRET_KEY.encode() sign = hmac.new(secret_key, data_to_sign.encode(), hashlib.sha256).digest() encoded_sign = base64.urlsafe_b64encode(sign).decode() @@ -962,7 +962,7 @@ class DocumentSegment(Base): nonce = os.urandom(16).hex() timestamp = str(int(time.time())) data_to_sign = f"file-preview|{upload_file_id}|{timestamp}|{nonce}" - secret_key = dify_config.SECRET_KEY.encode() if dify_config.SECRET_KEY else b"" + secret_key = dify_config.SECRET_KEY.encode() sign = hmac.new(secret_key, data_to_sign.encode(), hashlib.sha256).digest() encoded_sign = base64.urlsafe_b64encode(sign).decode() @@ -981,7 +981,7 @@ class DocumentSegment(Base): nonce = os.urandom(16).hex() timestamp = str(int(time.time())) data_to_sign = f"file-preview|{upload_file_id}|{timestamp}|{nonce}" - secret_key = dify_config.SECRET_KEY.encode() if dify_config.SECRET_KEY else b"" + secret_key = dify_config.SECRET_KEY.encode() sign = hmac.new(secret_key, data_to_sign.encode(), hashlib.sha256).digest() encoded_sign = base64.urlsafe_b64encode(sign).decode() @@ -1019,7 +1019,7 @@ class DocumentSegment(Base): nonce = os.urandom(16).hex() timestamp = str(int(time.time())) data_to_sign = f"image-preview|{upload_file_id}|{timestamp}|{nonce}" - secret_key = dify_config.SECRET_KEY.encode() if dify_config.SECRET_KEY else b"" + secret_key = dify_config.SECRET_KEY.encode() sign = hmac.new(secret_key, data_to_sign.encode(), hashlib.sha256).digest() encoded_sign = base64.urlsafe_b64encode(sign).decode() diff --git a/api/tests/unit_tests/configs/test_dify_config.py b/api/tests/unit_tests/configs/test_dify_config.py index 57dbf453de..919ebbc656 100644 --- a/api/tests/unit_tests/configs/test_dify_config.py +++ b/api/tests/unit_tests/configs/test_dify_config.py @@ -8,6 +8,47 @@ from yarl import URL from configs.app_config import DifyConfig +def _set_basic_config_env(monkeypatch: pytest.MonkeyPatch) -> None: + os.environ.clear() + monkeypatch.setenv("CONSOLE_API_URL", "https://example.com") + monkeypatch.setenv("CONSOLE_WEB_URL", "https://example.com") + monkeypatch.setenv("DB_TYPE", "postgresql") + monkeypatch.setenv("DB_USERNAME", "postgres") + monkeypatch.setenv("DB_PASSWORD", "postgres") + monkeypatch.setenv("DB_HOST", "localhost") + monkeypatch.setenv("DB_PORT", "5432") + monkeypatch.setenv("DB_DATABASE", "dify") + + +def test_dify_config_keeps_secret_key_empty_when_missing( + monkeypatch: pytest.MonkeyPatch, + tmp_path, +) -> None: + _set_basic_config_env(monkeypatch) + monkeypatch.delenv("SECRET_KEY", raising=False) + monkeypatch.setenv("OPENDAL_FS_ROOT", str(tmp_path)) + + config = DifyConfig(_env_file=None) + + assert config.SECRET_KEY == "" + assert not hasattr(config, "OPENDAL_FS_ROOT") + assert not (tmp_path / ".dify_secret_key").exists() + + +def test_dify_config_preserves_explicit_secret_key( + monkeypatch: pytest.MonkeyPatch, + tmp_path, +) -> None: + _set_basic_config_env(monkeypatch) + monkeypatch.setenv("SECRET_KEY", "explicit") + monkeypatch.setenv("OPENDAL_FS_ROOT", str(tmp_path)) + + config = DifyConfig(_env_file=None) + + assert config.SECRET_KEY == "explicit" + assert not (tmp_path / ".dify_secret_key").exists() + + def test_dify_config(monkeypatch: pytest.MonkeyPatch): # clear system environment variables os.environ.clear() diff --git a/api/tests/unit_tests/core/datasource/test_datasource_file_manager.py b/api/tests/unit_tests/core/datasource/test_datasource_file_manager.py index 4f39d38831..cee7d46083 100644 --- a/api/tests/unit_tests/core/datasource/test_datasource_file_manager.py +++ b/api/tests/unit_tests/core/datasource/test_datasource_file_manager.py @@ -34,20 +34,6 @@ class TestDatasourceFileManager: assert f"nonce={mock_urandom.return_value.hex()}" in signed_url assert "sign=" in signed_url - @patch("core.datasource.datasource_file_manager.time.time") - @patch("core.datasource.datasource_file_manager.os.urandom") - @patch("core.datasource.datasource_file_manager.dify_config") - def test_sign_file_empty_secret(self, mock_config, mock_urandom, mock_time): - # Setup - mock_config.FILES_URL = "http://localhost:5001" - mock_config.SECRET_KEY = None # Empty secret - mock_time.return_value = 1700000000 - mock_urandom.return_value = b"1234567890abcdef" - - # Execute - signed_url = DatasourceFileManager.sign_file("file_id", ".png") - assert "sign=" in signed_url - @patch("core.datasource.datasource_file_manager.time.time") @patch("core.datasource.datasource_file_manager.dify_config") def test_verify_file(self, mock_config, mock_time): @@ -76,25 +62,6 @@ class TestDatasourceFileManager: mock_time.return_value = 1700000500 # 700 seconds after timestamp (300 is timeout) assert DatasourceFileManager.verify_file(datasource_file_id, timestamp, nonce, encoded_sign) is False - @patch("core.datasource.datasource_file_manager.time.time") - @patch("core.datasource.datasource_file_manager.dify_config") - def test_verify_file_empty_secret(self, mock_config, mock_time): - # Setup - mock_config.SECRET_KEY = "" # Empty string secret - mock_config.FILES_ACCESS_TIMEOUT = 300 - mock_time.return_value = 1700000000 - - datasource_file_id = "file_id_123" - timestamp = "1699999800" - nonce = "some_nonce" - - # Calculate with empty secret - data_to_sign = f"file-preview|{datasource_file_id}|{timestamp}|{nonce}" - sign = hmac.new(b"", data_to_sign.encode(), hashlib.sha256).digest() - encoded_sign = base64.urlsafe_b64encode(sign).decode() - - assert DatasourceFileManager.verify_file(datasource_file_id, timestamp, nonce, encoded_sign) is True - @patch("core.datasource.datasource_file_manager.db") @patch("core.datasource.datasource_file_manager.storage") @patch("core.datasource.datasource_file_manager.uuid4") diff --git a/api/tests/unit_tests/extensions/test_set_secretkey.py b/api/tests/unit_tests/extensions/test_set_secretkey.py new file mode 100644 index 0000000000..8a8e4e2b19 --- /dev/null +++ b/api/tests/unit_tests/extensions/test_set_secretkey.py @@ -0,0 +1,74 @@ +from __future__ import annotations + +import pytest +from flask import Flask + +from extensions import ext_set_secretkey + + +class InMemoryStorage: + def __init__(self, files: dict[str, bytes] | None = None) -> None: + self.files = files or {} + self.saved_files: list[tuple[str, bytes]] = [] + + def load_once(self, filename: str) -> bytes: + try: + return self.files[filename] + except KeyError: + raise FileNotFoundError(filename) + + def save(self, filename: str, data: bytes) -> None: + self.files[filename] = data + self.saved_files.append((filename, data)) + + +def test_init_app_uses_configured_secret_key(monkeypatch: pytest.MonkeyPatch) -> None: + secret_key = "configured-secret-key" + storage = InMemoryStorage() + monkeypatch.setattr("extensions.ext_set_secretkey.dify_config.SECRET_KEY", secret_key) + monkeypatch.setattr("configs.secret_key.storage", storage) + app = Flask(__name__) + app.config["SECRET_KEY"] = secret_key + + ext_set_secretkey.init_app(app) + + assert app.secret_key == secret_key + assert app.config["SECRET_KEY"] == secret_key + assert storage.saved_files == [] + + +def test_init_app_generates_and_persists_secret_key_when_missing( + monkeypatch: pytest.MonkeyPatch, +) -> None: + storage = InMemoryStorage() + monkeypatch.setattr("extensions.ext_set_secretkey.dify_config.SECRET_KEY", "") + monkeypatch.setattr("configs.secret_key.storage", storage) + app = Flask(__name__) + app.config["SECRET_KEY"] = "" + + ext_set_secretkey.init_app(app) + + persisted_key = storage.files[".dify_secret_key"].decode("utf-8").strip() + assert persisted_key + assert storage.saved_files == [(".dify_secret_key", f"{persisted_key}\n".encode())] + assert persisted_key == ext_set_secretkey.dify_config.SECRET_KEY + assert persisted_key == app.config["SECRET_KEY"] + assert persisted_key == app.secret_key + + +def test_init_app_reuses_persisted_secret_key_when_missing( + monkeypatch: pytest.MonkeyPatch, +) -> None: + persisted_key = "persisted-secret-key" + storage = InMemoryStorage({".dify_secret_key": f"{persisted_key}\n".encode()}) + monkeypatch.setattr("extensions.ext_set_secretkey.dify_config.SECRET_KEY", "") + monkeypatch.setattr("configs.secret_key.storage", storage) + app = Flask(__name__) + app.config["SECRET_KEY"] = "" + + ext_set_secretkey.init_app(app) + + assert persisted_key == ext_set_secretkey.dify_config.SECRET_KEY + assert persisted_key == app.config["SECRET_KEY"] + assert persisted_key == app.secret_key + assert storage.saved_files == [] diff --git a/api/tests/unit_tests/libs/test_passport.py b/api/tests/unit_tests/libs/test_passport.py index f33484c18d..90b58ae548 100644 --- a/api/tests/unit_tests/libs/test_passport.py +++ b/api/tests/unit_tests/libs/test_passport.py @@ -143,28 +143,13 @@ class TestPassportService: assert str(exc_info.value) == "401 Unauthorized: Token has expired." # Configuration tests - def test_should_handle_empty_secret_key(self): - """Test behavior when SECRET_KEY is empty""" + def test_should_use_configured_secret_key_without_policy_validation(self): + """Test that policy decisions are owned by config, not PassportService.""" with patch("libs.passport.dify_config") as mock_config: - mock_config.SECRET_KEY = "" + mock_config.SECRET_KEY = "configured" service = PassportService() - # Empty secret key should still work but is insecure - payload = {"test": "data"} - token = service.issue(payload) - decoded = service.verify(token) - assert decoded == payload - - def test_should_handle_none_secret_key(self): - """Test behavior when SECRET_KEY is None""" - with patch("libs.passport.dify_config") as mock_config: - mock_config.SECRET_KEY = None - service = PassportService() - - payload = {"test": "data"} - # JWT library will raise TypeError when secret is None - with pytest.raises((TypeError, jwt.exceptions.InvalidKeyError)): - service.issue(payload) + assert service.sk == "configured" # Boundary condition tests def test_should_handle_large_payload(self, passport_service): diff --git a/docker/.env.example b/docker/.env.example index 5a012973c0..c708a40c15 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -28,7 +28,8 @@ LANG=C.UTF-8 LC_ALL=C.UTF-8 PYTHONIOENCODING=utf-8 UV_CACHE_DIR=/tmp/.uv-cache -SECRET_KEY=sk-9f73s3ljTXVcMT3Blb3ljTqtsKiGHXVcMT3BlbkFJLK7U +# Leave empty to auto-generate a persistent key in the storage directory. +SECRET_KEY= INIT_PASSWORD= DEPLOY_ENV=PRODUCTION CHECK_UPDATE_URL=https://updates.dify.ai diff --git a/docker/README.md b/docker/README.md index a2d9b2eeba..26b1dac9ac 100644 --- a/docker/README.md +++ b/docker/README.md @@ -87,7 +87,7 @@ The root `.env.example` file contains the essential startup settings. Optional a 1. **Server Configuration**: - `LOG_LEVEL`, `DEBUG`, `FLASK_DEBUG`: Logging and debug settings. - - `SECRET_KEY`: A key for encrypting session cookies and other sensitive data. + - `SECRET_KEY`: A key for signing sessions, JWTs, and file URLs. Leave it empty to let Dify generate a persistent key in the storage directory, or set a unique value yourself. 1. **Database Configuration**: diff --git a/docker/envs/security.env.example b/docker/envs/security.env.example index 787aef2706..d7556d91e5 100644 --- a/docker/envs/security.env.example +++ b/docker/envs/security.env.example @@ -36,5 +36,6 @@ TIDB_PUBLIC_KEY=dify TIDB_PRIVATE_KEY=dify VIKINGDB_ACCESS_KEY=your-ak VIKINGDB_SECRET_KEY=your-sk -SECRET_KEY=sk-9f73s3ljTXVcMT3Blb3ljTqtsKiGHXVcMT3BlbkFJLK7U +# Leave empty to auto-generate a persistent key in the storage directory. +SECRET_KEY= INIT_PASSWORD=