Default JWT Secret in Docker Compose Enables Forged Platform-Admin Tokens
Critical9.8Published Aug 25, 2026
## Default JWT Secret in Docker Compose Enables Forged Platform-Admin Tokens ### Summary IBM mcp-context-forge (mcpgateway) ships with a publicly known HMAC JWT signing secret (`my-test-key-but-now-longer-than-32-bytes`) as the default fallback value in `docker-compose.yml` and `docker-compose-verbose-logging.yml`. Because the default deployment also sets `REQUIRE_USER_IN_DB=false` and `ENVIRONMENT=development`, a remote unauthenticated attacker can craft a valid HS256 JWT bearing `sub=admin@example.com`, `is_admin=true`, and `teams=null`, present it to any bearer-auth–protected endpoint, and obtain full platform-administrator access with no credentials required. The vulnerability is trivially exploitable against any deployment that uses the documented Docker Compose quickstart without explicitly overriding `JWT_SECRET_KEY`. CVSS Base Score: **9.8 (Critical)**. --- ### Details **Root cause — publicly known default secret** `docker-compose.yml` line 355 defines the JWT signing key using a shell-parameter-expansion fallback: ```yaml # docker-compose.yml:355 JWT_SECRET_KEY: ${JWT_SECRET_KEY:-my-test-key-but-now-longer-than-32-bytes} ``` If an operator runs `docker-compose up` ...
Affected versions
| Package | Affected | Fixed in |
|---|---|---|
| mcp-contextforge-gateway PyPI | < v1.0.7 | v1.0.7 |
Details and references
## Default JWT Secret in Docker Compose Enables Forged Platform-Admin Tokens ### Summary IBM mcp-context-forge (mcpgateway) ships with a publicly known HMAC JWT signing secret (`my-test-key-but-now-longer-than-32-bytes`) as the default fallback value in `docker-compose.yml` and `docker-compose-verbose-logging.yml`. Because the default deployment also sets `REQUIRE_USER_IN_DB=false` and `ENVIRONMENT=development`, a remote unauthenticated attacker can craft a valid HS256 JWT bearing `sub=admin@example.com`, `is_admin=true`, and `teams=null`, present it to any bearer-auth–protected endpoint, and obtain full platform-administrator access with no credentials required. The vulnerability is trivially exploitable against any deployment that uses the documented Docker Compose quickstart without explicitly overriding `JWT_SECRET_KEY`. CVSS Base Score: **9.8 (Critical)**. --- ### Details **Root cause — publicly known default secret** `docker-compose.yml` line 355 defines the JWT signing key using a shell-parameter-expansion fallback: ```yaml # docker-compose.yml:355 JWT_SECRET_KEY: ${JWT_SECRET_KEY:-my-test-key-but-now-longer-than-32-bytes} ``` If an operator runs `docker-compose up` without setting `JWT_SECRET_KEY` in the shell environment or an `.env` file, the literal string `my-test-key-but-now-longer-than-32-bytes` is used as the HMAC-SHA256 signing key. The same value is hardcoded without any substitution in `docker-compose-verbose-logging.yml:223`: ```yaml # docker-compose-verbose-logging.yml:223 JWT_SECRET_KEY=my-test-key-but-now-longer-than-32-bytes ``` The Python-level application default in `mcpgateway/config.py:396` is `"changeme"`, but the Docker Compose files override this with the above well-known string. The secret is explicitly listed in `mcpgateway/_security_constants.py:9-10` under `WEAK_VALUES`, confirming the vendor was aware of its weakness. However, when `ENVIRONMENT=development` (the Docker Compose default, `docker-compose.yml:368`), the startup check only emits a warning (`🔓 SECURITY WARNING - jwt_secret_key: Default/weak secret detected!`) and does **not** abort startup. **Attack path** The full exploitation chain proceeds as follows: 1. **Secret exposure** — `docker-compose.yml:355` sets `JWT_SECRET_KEY` to the public value when no override is provided. 2. **Token verification** — `mcpgateway/utils/verify_credentials.py:302–314` validates incoming JWTs with the configured key and algorithm (`HS256`). An attacker who knows the key can produce a valid signature. 3. **Bearer auth acceptance** — `mcpgateway/auth.py:1426–1428` calls `verify_jwt_token_cached`; any correctly signed token passes. 4. **RBAC bypass** — `mcpgateway/auth.py:539–575` (`normalize_token_teams`): when `teams=null` and `is_admin=true`, the function returns `None`, which is treated as unrestricted admin scope, bypassing all RBAC checks. 5. **Virtual admin bootstrap** — `mcpgateway/auth.py:1869–1878`: when `REQUIRE_USER_IN_DB=false` (default in Docker Compose, `docker-compose.yml:422`) and the token's `sub` matches `PLATFORM_ADMIN_EMAIL` (`admin@example.com`), the server synthesizes a virtual admin user object without any database lookup. 6. **Platform-admin email bypass** — `mcpgateway/services/permission_service.py:131–133` → `mcpgateway/utils/admin_check.py:84–86`: if `user_email == settings.platform_admin_email`, all permission checks return `True`. 7. **Protected route access** — `mcpgateway/main.py:6842–6849`: the `GET /gateways` route (and all other `@require_permission`-decorated routes) becomes fully accessible to the forged token. **Server startup warning (not a blocker)** The server logs the following at startup but continues to accept connections: ``` 🔓 SECURITY WARNING - jwt_secret_key: Default/weak secret detected! Using potentially ephemeral storage with platform admin bootstrap enabled ``` --- ### PoC **Environment setup** Save the following as `Docker
- CVSS 3.1
- CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
- Severity from
- GitHub (reviewed advisory)
- Weakness
- CWE-798
More IBM advisories
All IBM| Date | Advisory | Severity | Fixed in |
|---|---|---|---|
| Aug 25 | Parse-gap bypasses in mcp-context-forge: SQL Sanitizer (privesc/DROP/mass-DELETE) and resource_filter SSRF | High8.8 | v1.0.7 |
| Aug 25 | Scoped admin OAuth DCR endpoints ignore token_teams restrictions and expose global registered clients | Medium6.4 | v1.0.7 |
| Aug 25 | Server-Side Request Forgery (SSRF) in url_to_markdown_server via Unvalidated URL Fetching | Critical9.1 | v1.0.7 |
| Aug 25 | Server-Side Request Forgery (SSRF) in mcp-rss-search via Unvalidated URL Fetching | High8.6 | v1.0.7 |
| Aug 25 | Session auth-context reuse ignores JWT exp — expired JWT replayable within session_auth_reuse_ttl | Medium | v1.0.5 |
| Aug 25 | Cross-tenant read of plugin-binding policies (BOLA) in ContextForge MCP Gateway | High | v1.0.5 |