Skip to content
IBMGHSA-5424-f25v-29r8

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 ...

GitHub advisory

Affected versions

PackageAffectedFixed in
mcp-contextforge-gateway
PyPI
< 1.0.71.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

CVSS 3.1
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:L
Severity from
GitHub (reviewed advisory)
Weakness
CWE-798, CWE-1188

More IBM advisories

All IBM

Critical advisories by email

Wednesdays: the week’s critical and high advisories in the AI and data stack, with the fixed versions. Only in weeks that have some.

Double opt-in. Unsubscribe any time.