# [CRIT] [GHSA / CRITICAL] CVE-2026-48031: Go Restful API Boilerplate: Hardcoded JWT Secret "random" Allows Token Forgery

**Source:** GitHub Security Advisories
**Published:** 2026-06-10
**Article:** https://github.com/advisories/GHSA-mqq6-462x-jxmm

## Threat Profile

Go Restful API Boilerplate: Hardcoded JWT Secret "random" Allows Token Forgery

## Vulnerability: CWE-798 — Hardcoded JWT Secret + Broken Mitigation

### Affected Component
- `github.com/dhax/go-base` — Go REST API boilerplate (go-chi/jwtauth/v5, Viper, PostgreSQL/Bun)
- 1,685 stars on GitHub

### Vulnerability Locations

| File | Line | Role |
|------|------|------|
| `dev.env` | 10 | `AUTH_JWT_SECRET=random` — template default shipped to all users |
| `cmd/serve.go` | 35 | `viper.SetDefault("a…

## Indicators of Compromise (high-fidelity only)

- **CVE:** `CVE-2026-48031`

## MITRE ATT&CK Techniques

- **T1190** — Exploit Public-Facing Application
- **T1592.002** — Gather Victim Host Information: Software
- **T1083** — File and Directory Discovery
- **T1195.001** — Supply Chain Compromise: Compromise Software Dependencies and Development Tools
- **T1518** — Software Discovery
- **T1550.001** — Use Alternate Authentication Material: Application Access Token
- **T1078** — Valid Accounts
- **T1593.003** — Search Open Websites/Domains: Code Repositories
- **T1588.001** — Obtain Capabilities: Malware
- **T1606.001** — Forge Web Credentials: Web Cookies

## Kill chain phases observed

_(none detected from narrative keywords)_

## Recommended hunts

### Web-facing exposure of dev.env / .env config file (returns 200)

`UC_64_1` · phase: **recon** · confidence: **High** · AI-generated for this article

**Splunk SPL (CIM):**
```spl
| tstats `summariesonly` count min(_time) as firstTime max(_time) as lastTime values(Web.url) as urls values(Web.src) as src values(Web.user_agent) as ua values(Web.status) as status from datamodel=Web.Web where Web.status IN (200,206,301,302) Web.url IN ("*/dev.env","*/.env","*/.env.dev","*/.env.development","*/.env.local","*/dev.env?*","*/.env?*") by Web.dest Web.src Web.url Web.status Web.user_agent Web.http_method | `drop_dm_object_name(Web)` | where status<400 | sort - lastTime
```

**Defender KQL:**
```kql
DeviceNetworkEvents
| where Timestamp > ago(7d)
| where RemoteUrl has_any ("/dev.env","/.env","/.env.dev","/.env.local","/.env.development")
| where InitiatingProcessFileName !in~ ("code.exe","devenv.exe","goland64.exe","idea64.exe")
| project Timestamp, DeviceName, InitiatingProcessFileName, InitiatingProcessCommandLine, RemoteIP, RemotePort, RemoteUrl, LocalIP
| order by Timestamp desc
```

### github.com/dhax/go-base supply-chain footprint in go.mod / build artifacts

`UC_64_2` · phase: **recon** · confidence: **High** · AI-generated for this article

**Splunk SPL (CIM):**
```spl
| tstats `summariesonly` count min(_time) as firstTime max(_time) as lastTime values(Filesystem.file_path) as paths values(Filesystem.user) as users from datamodel=Endpoint.Filesystem where Filesystem.file_name IN ("go.mod","go.sum","modules.txt","Dockerfile","Dockerfile.*") by Filesystem.dest Filesystem.file_path Filesystem.file_hash | `drop_dm_object_name(Filesystem)` | search file_hash=* OR paths=* | join type=outer dest [| tstats `summariesonly` count from datamodel=Endpoint.Processes where Processes.process IN ("*go build*","*go install*","*go mod download*","*docker build*") Processes.process="*dhax/go-base*" by Processes.dest | `drop_dm_object_name(Processes)`] | sort - lastTime
```

**Defender KQL:**
```kql
DeviceFileEvents
| where Timestamp > ago(30d)
| where FileName in~ ("go.mod","go.sum","modules.txt","Dockerfile")
| where ActionType in ("FileCreated","FileModified","FileRenamed")
| union (
    DeviceProcessEvents
    | where Timestamp > ago(30d)
    | where ProcessCommandLine has "dhax/go-base"
       or InitiatingProcessCommandLine has "dhax/go-base"
    | project Timestamp, DeviceId, DeviceName, ActionType="ProcessCmdline", FileName, FolderPath, InitiatingProcessAccountName, ProcessCommandLine
)
| project Timestamp, DeviceName, AccountName=InitiatingProcessAccountName, FileName, FolderPath, ActionType, ProcessCommandLine
| order by Timestamp desc
```

### Admin / privileged API call without a preceding SUCCESSFUL login (JWT forgery indicator)

`UC_64_3` · phase: **actions** · confidence: **Medium** · AI-generated for this article

**Splunk SPL (CIM):**
```spl
| tstats `summariesonly` count min(_time) as firstTime max(_time) as lastTime values(Web.url) as urls values(Web.user) as users values(Web.status) as statuses from datamodel=Web.Web where Web.url IN ("*/api/v1/admin/*","*/api/v1/me","*/api/v1/token/refresh") Web.status IN (200,201,204) by Web.src Web.dest Web.url | `drop_dm_object_name(Web)` | join type=outer src [ search index=web (url="*/api/v1/login/*" OR url="*/api/v1/auth/*") status<400 earliest=-24h | stats max(_time) as login_time by src ] | where isnull(login_time) OR login_time < (firstTime - 86400) | sort - lastTime
```

**Defender KQL:**
```kql
let AdminHits = DeviceNetworkEvents
    | where Timestamp > ago(24h)
    | where RemoteUrl has_any ("/api/v1/admin/","/api/v1/me","/api/v1/token/refresh")
    | project Timestamp, DeviceName, InitiatingProcessFileName, RemoteIP, RemotePort, RemoteUrl;
let LoginHits = DeviceNetworkEvents
    | where Timestamp > ago(48h)
    | where RemoteUrl has_any ("/api/v1/login/","/api/v1/auth/")
    | summarize LastLogin = max(Timestamp) by RemoteIP;
AdminHits
| join kind=leftouter LoginHits on RemoteIP
| where isnull(LastLogin) or datetime_diff('hour', Timestamp, LastLogin) > 24
| project Timestamp, DeviceName, RemoteIP, RemoteUrl, LastLogin, InitiatingProcessFileName
| order by Timestamp desc
```

### Internal host clones / curls github.com/dhax/go-base or raw dev.env

`UC_64_4` · phase: **recon** · confidence: **Medium** · AI-generated for this article

**Splunk SPL (CIM):**
```spl
| tstats `summariesonly` count min(_time) as firstTime max(_time) as lastTime values(Processes.process) as cmds values(Processes.user) as users values(Processes.parent_process_name) as parents from datamodel=Endpoint.Processes where (Processes.process_name IN ("git.exe","git","go.exe","go","curl.exe","curl","wget.exe","wget") AND Processes.process="*dhax/go-base*") by Processes.dest Processes.user Processes.process_name Processes.process | `drop_dm_object_name(Processes)` | sort - lastTime
```

**Defender KQL:**
```kql
DeviceProcessEvents
| where Timestamp > ago(30d)
| where FileName in~ ("git.exe","go.exe","curl.exe","wget.exe","powershell.exe","pwsh.exe")
| where ProcessCommandLine has "dhax/go-base"
   or ProcessCommandLine matches regex @"(?i)raw\.githubusercontent\.com/dhax/go-base/[^\s]+dev\.env"
| project Timestamp, DeviceName, AccountName, FileName, FolderPath, ProcessCommandLine, InitiatingProcessFileName, InitiatingProcessCommandLine
| order by Timestamp desc
```

### JWT token in HTTP Authorization header verifying against secret 'random'

`UC_64_5` · phase: **actions** · confidence: **High** · AI-generated for this article

**Splunk SPL (CIM):**
```spl
| tstats `summariesonly` count values(Web.url) as urls values(Web.src) as src from datamodel=Web.Web where Web.http_method!="" by Web.dest Web.src Web.url _time | `drop_dm_object_name(Web)` | eval auth_header = mvfilter(match(http_request_headers, "(?i)Authorization: Bearer ")) | rex field=auth_header "(?i)Authorization: Bearer (?<jwt>[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+)" | where isnotnull(jwt) | rex field=jwt "^(?<jwt_signing_input>[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+)\.(?<jwt_sig>[A-Za-z0-9_-]+)$" | eval expected_sig = tostring(base64url(hmac_sha256("random", jwt_signing_input))) | where expected_sig == jwt_sig | stats count min(_time) as firstTime max(_time) as lastTime values(url) as urls by src dest jwt | sort - lastTime
```

**Defender KQL:**
```kql
// Requires custom Authorization-header capture into AdditionalFields on DeviceNetworkEvents.
// Decoded JWT verification is performed at ingest; this query surfaces records flagged JWTForged=true.
DeviceNetworkEvents
| where Timestamp > ago(7d)
| extend Forged = tostring(parse_json(AdditionalFields).JWTForgedWithKnownWeakSecret)
| where Forged == "true"
| extend JWTSubject = tostring(parse_json(AdditionalFields).JWTSubject)
| extend JWTRoles = tostring(parse_json(AdditionalFields).JWTRoles)
| project Timestamp, DeviceName, RemoteIP, RemoteUrl, JWTSubject, JWTRoles, InitiatingProcessFileName
| order by Timestamp desc
```

### IOC-driven hunts (use shared templates)

These are standard IOC-substitution hunts — the canonical SPL and KQL live once in [`_TEMPLATES.md`](../_TEMPLATES.md), so we don't repeat the same boilerplate on every CVE / hash / network-IOC briefing.

- **Asset exposure — vulnerability matches article CVE(s)** ([template](../_TEMPLATES.md#asset-exposure)) — phase: **recon**, confidence: **High**
  - CVE(s): `CVE-2026-48031`


## Why this matters

Severity classified as **CRIT** based on: CVE present, 6 use case(s) fired, 10 technique(s) inferred. Read the full article for actor attribution, tooling details, and any defanged IOCs in the body that aren't visible in the RSS summary.
