{"record":{"id":"f8500bac8df09727","repo":"apache/superset","slug":"please-provide-a-jwt-secret-at-least-32-bytes-long","errorCode":null,"errorMessage":"Please provide a JWT secret at least 32 bytes long","messagePattern":"Please provide a JWT secret at least 32 bytes long","errorType":"exception","errorClass":"AsyncQueryTokenException","httpStatus":null,"severity":"critical","filePath":"superset/async_events/async_query_manager.py","lineNumber":144,"sourceCode":"        self._load_chart_data_into_cache_job: Any = None\n        # pylint: disable=invalid-name\n\n    def init_app(self, app: Flask) -> None:\n        cache_type = app.config.get(\"CACHE_CONFIG\", {}).get(\"CACHE_TYPE\")\n        data_cache_type = app.config.get(\"DATA_CACHE_CONFIG\", {}).get(\"CACHE_TYPE\")\n        if cache_type in [None, \"null\"] or data_cache_type in [None, \"null\"]:\n            raise Exception(  # pylint: disable=broad-exception-raised\n                \"\"\"\n                Cache backends (CACHE_CONFIG, DATA_CACHE_CONFIG) must be configured\n                and non-null in order to enable async queries\n                \"\"\"\n            )\n\n        self._cache = get_cache_backend(app.config)\n        logger.debug(\"Using GAQ Cache backend as %s\", type(self._cache).__name__)\n\n        if len(app.config[\"GLOBAL_ASYNC_QUERIES_JWT_SECRET\"]) < 32:\n            raise AsyncQueryTokenException(\n                \"Please provide a JWT secret at least 32 bytes long\"\n            )\n\n        self._stream_prefix = app.config[\"GLOBAL_ASYNC_QUERIES_REDIS_STREAM_PREFIX\"]\n        self._stream_limit = app.config[\"GLOBAL_ASYNC_QUERIES_REDIS_STREAM_LIMIT\"]\n        self._stream_limit_firehose = app.config[\n            \"GLOBAL_ASYNC_QUERIES_REDIS_STREAM_LIMIT_FIREHOSE\"\n        ]\n        self._jwt_cookie_name = app.config[\"GLOBAL_ASYNC_QUERIES_JWT_COOKIE_NAME\"]\n        self._jwt_cookie_secure = app.config[\"GLOBAL_ASYNC_QUERIES_JWT_COOKIE_SECURE\"]\n        self._jwt_cookie_samesite = app.config[\n            \"GLOBAL_ASYNC_QUERIES_JWT_COOKIE_SAMESITE\"\n        ]\n        self._jwt_cookie_domain = app.config[\"GLOBAL_ASYNC_QUERIES_JWT_COOKIE_DOMAIN\"]\n        self._jwt_secret = app.config[\"GLOBAL_ASYNC_QUERIES_JWT_SECRET\"]\n        self._jwt_expiration_seconds = app.config[\n            \"GLOBAL_ASYNC_QUERIES_JWT_EXPIRATION_SECONDS\"\n        ]","sourceCodeStart":126,"sourceCodeEnd":162,"githubUrl":"https://github.com/apache/superset/blob/f4587218dd19d046c3e4d00063e7d27f8a2ed354/superset/async_events/async_query_manager.py#L126-L162","documentation":"AsyncQueryTokenException raised during AsyncQueryManager.init_app() when GLOBAL_ASYNC_QUERIES_JWT_SECRET is shorter than 32 bytes. The JWT secret signs the per-user channel tokens stored in a cookie that authorize access to the Redis event stream, so a short secret would be brute-forceable; Superset enforces a minimum length at startup.","triggerScenarios":"Enabling GLOBAL_ASYNC_QUERIES with GLOBAL_ASYNC_QUERIES_JWT_SECRET set to a short string (e.g. 'secret' or a 16-char value), or leaving it unset so an empty/default value is checked. Raised at init_app time, blocking app and worker startup.","commonSituations":"Copy-pasting example config with a placeholder secret; generating a secret with too few characters; upgrading deployments where the secret was previously optional; CI configs that reuse a short dummy secret.","solutions":["Generate a proper secret: python -c \"import secrets; print(secrets.token_urlsafe(64))\" and set GLOBAL_ASYNC_QUERIES_JWT_SECRET to it (>= 32 chars).","Store it in a secret manager / environment variable rather than hardcoding, then reference it in superset_config.py.","Set the same secret on webserver and all Celery workers — tokens are signed and verified across processes."],"exampleFix":"# before\nGLOBAL_ASYNC_QUERIES_JWT_SECRET = \"supersecret\"\n\n# after\nimport os\nGLOBAL_ASYNC_QUERIES_JWT_SECRET = os.environ[\"SUPERSET_JWT_SECRET\"]  # >= 32 chars, e.g. secrets.token_urlsafe(64)","handlingStrategy":"validation","validationCode":"import os, secrets\nsecret = os.environ.get(\"GLOBAL_ASYNC_QUERIES_JWT_SECRET\", \"\")\nif len(secret.encode()) < 32:\n    secret = secrets.token_urlsafe(64)\n    # persist via your secret manager; never fall back silently in production\nassert len(secret.encode()) >= 32","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Provision the JWT secret alongside other secrets in IaC with a minimum-length policy.","Distribute the same secret to webserver and workers via a shared secret store."],"tags":["configuration","security","jwt","async-queries","startup"],"backgroundTag":null,"analyzedSha":"f4587218dd19d046c3e4d00063e7d27f8a2ed354","analyzedAt":"2026-08-14T22:39:27.425Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}