# [CRIT] [GHSA / CRITICAL] CVE-2026-47410: praisonai-platform: JWT signing key defaults to hardcoded "dev-secret-change-me", allowing token forgery for any user when PLATFORM_ENV is unset

**Source:** GitHub Security Advisories
**Published:** 2026-05-29
**Article:** https://github.com/advisories/GHSA-3qg8-5g3r-79v5

## Threat Profile

praisonai-platform: JWT signing key defaults to hardcoded "dev-secret-change-me", allowing token forgery for any user when PLATFORM_ENV is unset

## Summary

**Type:** Insecure default cryptographic key. The JWT signing secret defaults to the hardcoded literal `"dev-secret-change-me"` when `PLATFORM_JWT_SECRET` is unset. A safety check exists but only fires when `PLATFORM_ENV != "dev"`; the default value of `PLATFORM_ENV` is `"dev"`, so the check is silently bypassed in any deployment that does …

## Indicators of Compromise (high-fidelity only)

- **CVE:** `CVE-2026-47410`

## MITRE ATT&CK Techniques

- **T1190** — Exploit Public-Facing Application
- **T1204.002** — User Execution: Malicious File
- **T1552.001** — Unsecured Credentials: Credentials In Files
- **T1078** — Valid Accounts
- **T1212** — Exploitation for Credential Access

## Kill chain phases observed

_(none detected from narrative keywords)_

## Recommended hunts

### Vulnerable praisonai-platform deployment hunt (uvicorn launching praisonai_platform.api.app)

`UC_163_2` · phase: **install** · confidence: **High** · AI-generated for this article

**Splunk SPL (CIM):**
```spl
| tstats `summariesonly` count min(_time) as firstTime max(_time) as lastTime from datamodel=Endpoint.Processes where Processes.process="*uvicorn*" Processes.process="*praisonai_platform*" by Processes.dest Processes.user Processes.process_name Processes.process Processes.parent_process_name | `drop_dm_object_name(Processes)` | where match(process, "(?i)praisonai[_-]platform(\.api\.app)?:?") AND NOT match(process, "(?i)PLATFORM_JWT_SECRET") AND NOT match(process, "(?i)--reload") | convert ctime(firstTime) ctime(lastTime) | sort - lastTime
```

**Defender KQL:**
```kql
DeviceProcessEvents
| where Timestamp > ago(14d)
| where ProcessCommandLine has "uvicorn"
| where ProcessCommandLine has_any ("praisonai_platform.api.app", "praisonai_platform", "praisonai-platform")
| where not(ProcessCommandLine has "--reload")              // dev-mode flag, common on developer workstations
| extend SecretInCmdLine = ProcessCommandLine has "PLATFORM_JWT_SECRET"
| extend ProdEnvInCmdLine = ProcessCommandLine matches regex @"(?i)PLATFORM_ENV\s*=\s*(prod|production|stage|staging)"
| project Timestamp, DeviceName, AccountName,
          FileName, FolderPath, ProcessCommandLine,
          InitiatingProcessFileName, InitiatingProcessCommandLine,
          SecretInCmdLine, ProdEnvInCmdLine, SHA256
| order by Timestamp desc
```

### praisonai-platform cross-tenant workspace operations from single source IP

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

**Splunk SPL (CIM):**
```spl
| tstats `summariesonly` count from datamodel=Web.Web where (Web.url="*/auth/me*" OR Web.url="*/workspaces/*" OR Web.url="*/auth/register*") by Web.src Web.url Web.http_method Web.user _time span=30m | `drop_dm_object_name(Web)` | rex field=url "/workspaces/(?<workspace_id>[0-9a-f\-]{8,})" | eval destructive=if(match(http_method,"(?i)DELETE|PATCH|POST") AND match(url,"/workspaces/"),1,0) | stats dc(workspace_id) as distinct_workspaces dc(user) as distinct_users sum(destructive) as destructive_ops values(http_method) as methods values(url) as urls min(_time) as firstTime max(_time) as lastTime by src | where distinct_workspaces >= 3 AND destructive_ops >= 1 | convert ctime(firstTime) ctime(lastTime) | sort - lastTime
```

**Defender KQL:**
```kql
// Best-effort proxy via inbound network telemetry — true detection lives in web logs (see sentinel_kql).
DeviceNetworkEvents
| where Timestamp > ago(24h)
| where ActionType == "InboundConnectionAccepted"
| where InitiatingProcessCommandLine has_any ("praisonai_platform", "praisonai-platform") and InitiatingProcessCommandLine has "uvicorn"
| where RemoteIPType == "Public"
| summarize InboundCount = count(), FirstSeen = min(Timestamp), LastSeen = max(Timestamp), DistinctPorts = dcount(LocalPort)
   by DeviceName, RemoteIP, InitiatingProcessFileName
| where InboundCount > 50
| order by InboundCount desc
```

### Article-specific behavioural hunt — [GHSA / CRITICAL] CVE-2026-47410: praisonai-platform: JWT signing key defaults t

`UC_163_1` · phase: **exploit** · confidence: **High**

**Splunk SPL (CIM):**
```spl
``` Article-specific bespoke detection — [GHSA / CRITICAL] CVE-2026-47410: praisonai-platform: JWT signing key defaults t ```
| tstats `summariesonly` count earliest(_time) AS firstTime latest(_time) AS lastTime
    from datamodel=Endpoint.Processes
    where (Processes.process_name IN ("auth_service.py"))
    by Processes.dest, Processes.user, Processes.process_name,
       Processes.process, Processes.parent_process_name, Processes.process_path
| `drop_dm_object_name(Processes)`
| `security_content_ctime(firstTime)`
| append [
| tstats `summariesonly` count
    from datamodel=Endpoint.Filesystem
    where Filesystem.action IN ("created","modified")
      AND (Filesystem.file_name IN ("auth_service.py"))
    by Filesystem.dest, Filesystem.user, Filesystem.process_name,
       Filesystem.file_path, Filesystem.file_name
| `drop_dm_object_name(Filesystem)`
]
```

**Defender KQL:**
```kql
// Article-specific bespoke detection — [GHSA / CRITICAL] CVE-2026-47410: praisonai-platform: JWT signing key defaults t
// Hunts the actual binaries / paths / commandline fragments named
// in the article instead of a generic technique-class template.
DeviceProcessEvents
| where Timestamp > ago(30d)
| where (FileName in~ ("auth_service.py"))
| project Timestamp, DeviceName, AccountName, FileName,
          FolderPath, ProcessCommandLine,
          InitiatingProcessFileName, InitiatingProcessCommandLine
| order by Timestamp desc

// File-creation events for the named binaries / paths
DeviceFileEvents
| where Timestamp > ago(30d)
| where ActionType in ("FileCreated","FileModified")
| where (FileName in~ ("auth_service.py"))
| project Timestamp, DeviceName, AccountName, FolderPath,
          FileName, ActionType, InitiatingProcessFileName,
          InitiatingProcessCommandLine
| 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-47410`


## Why this matters

Severity classified as **CRIT** based on: CVE present, 4 use case(s) fired, 5 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.
