IBM: hard-coded credentials
High8.9Published Aug 24, 2026
Security Advisory: IBM ContextForge MCP Gateway (IBM/mcp-context-forge) ▎ Scope and safety. This advisory is the result of a local, static source review of the public IBM/mcp-context-forge repository. No live IBM-operated service, hosted deployment, or third-party instance was accessed, scanned, or attacked. The proof of concept was constructed and validated locally against fabricated credentials (the repository's own published default value), using a JWT the reviewer minted on their own machine. Repository: https://github.com/IBM/mcp-context-forge (ContextForge MCP Gateway) Reviewed: main @ 99d864daa20a7e3ef2ddf7ba04d1a3166678b9b6, re-verified 2026-07-06. Findings: 1 (High) Finding 1 (HIGH): Predictable default JWT signing key enables authentication bypass in the default environment CWE: CWE-1188 (Insecure Default Initialization of Resource) + CWE-798 (Use of Hard-coded Credentials) CVSS v3.1: AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:L = 7.1 (High). The AC:H metric already accounts for the two documented defaults that must stay unchanged. Note that this combination is the shipped out-of-box state (ENVIRONMENT=development, JWT_SECRET_KEY unset), not an unusual misconfiguration, which ...
Affected versions
| Package | Affected | Fixed in |
|---|---|---|
| mcp-contextforge-gateway PyPI | < 1.0.7 | 1.0.7 |
Details and references
Security Advisory: IBM ContextForge MCP Gateway (IBM/mcp-context-forge) ▎ Scope and safety. This advisory is the result of a local, static source review of the public IBM/mcp-context-forge repository. No live IBM-operated service, hosted deployment, or third-party instance was accessed, scanned, or attacked. The proof of concept was constructed and validated locally against fabricated credentials (the repository's own published default value), using a JWT the reviewer minted on their own machine. Repository: https://github.com/IBM/mcp-context-forge (ContextForge MCP Gateway) Reviewed: main @ 99d864daa20a7e3ef2ddf7ba04d1a3166678b9b6, re-verified 2026-07-06. Findings: 1 (High) Finding 1 (HIGH): Predictable default JWT signing key enables authentication bypass in the default environment CWE: CWE-1188 (Insecure Default Initialization of Resource) + CWE-798 (Use of Hard-coded Credentials) CVSS v3.1: AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:L = 7.1 (High). The AC:H metric already accounts for the two documented defaults that must stay unchanged. Note that this combination is the shipped out-of-box state (ENVIRONMENT=development, JWT_SECRET_KEY unset), not an unusual misconfiguration, which is why the default value that anchors token trust is treated as High. Summary: In its shipped default configuration (ENVIRONMENT=development, JWT_SECRET_KEY unset), the gateway signs and verifies session/authorization JWTs with the publicly-known literal string "changeme" (HS256). The one guard that would reject a weak secret is explicitly skipped in the development environment, and development is the default. Any network-reachable client can mint a JWT signed with "changeme", set an arbitrary (admin) subject, and bypass AUTH_REQUIRED=true entirely. Technical detail: # mcpgateway/config.py jwt_secret_key: SecretStr = Field(default=SecretStr("changeme")) jwt_algorithm: str = "HS256" environment: Literal["development","staging","production"] = Field(default="development") derive_key_per_environment: bool = Field(default=False) # validate_security_combinations(): the weak-secret guard is bypassed in development if val.lower() in weak_secrets: if env != "development": # default env 'development' => guard skipped raise SecurityConfigurationError(...) # mcpgateway/utils/jwt_config_helper.py: HS branch returns the base secret verbatim (no derivation by default) => "changeme" if algorithm.startswith("HS"): base = settings.jwt_secret_key.get_secret_value() ... return _derive_env_key(base, settings.environment) if settings.derive_key_per_environment else base # mcpgateway/utils/verify_credentials.py (verify_jwt_token): decode_kwargs = {"key": get_jwt_public_key_or_secret(), "algorithms": [settings.jwt_algorithm], "options": options} payload = jwt.decode(token, **decode_kwargs) # validate_token_environment rejects ONLY a present-and-mismatched env claim; a missing env claim passes if settings.validate_token_environment: token_env = payload.get("env") if token_env is not None and token_env != settings.environment: raise HTTPException(status_code=401, ...) The remaining claim checks are not barriers to an attacker who knows the key: jwt_audience/jwt_issuer default to the public constants mcpgateway-api/mcpgateway (include matching aud/iss); require_token_expiration/require_jti only force including exp and jti, which is trivial. Net effect: in the default environment the gateway boots (emitting only a warning) and trusts any HS256 token signed with the world-readable string "changeme". Proof of concept (local, the project's own published default key; no real secret used): import jwt, time tok = jwt.encode( {"sub": "admin", "aud": "mcpgateway-api", "iss": "mcpgateway", "jti": "forged-1", "iat": int(time.time()), "exp": int(time.time()) + 3600}, "changeme", # the shipped default JWT_SECRET_KEY algorithm="HS256", ) # present to any
More IBM advisories
All IBM| Date | Advisory | Severity | Fixed in |
|---|---|---|---|
| Aug 25 | Default JWT Secret in Docker Compose Enables Forged Platform-Admin Tokens | Critical9.8 | v1.0.7 |
| Aug 25 | ContextForge SSRF protection bypass via outbound HTTP redirects (Tool/Gateway/A2A invocation paths) | High | v1.0.2 |
| Aug 25 | Broken Acess Control in Team Join Approval Flow | Medium | v1.0.6 |
| Aug 25 | [Security] OAuth token from lower environment works in higher environments | High | v1.0.5 |
| Aug 24 | IBM: improper input validation | Medium4.9 | v1.0.7 |
| Aug 24 | RestrictedPython sandbox bypass via getattr builtin in python_sandbox_server | Critical | 1.0.2 |