CVE-2026-94456 Overview
Postiz, an open-source social media scheduling platform, generates security-sensitive credentials using JavaScript's Math.random() instead of a cryptographically secure random source. The same helper produces OAuth access tokens, authorization codes, client secrets, organization API keys, and PKCE verifiers. All of these values depend on V8's deterministic xorshift128+ pseudorandom number generator (PRNG) state.
An unauthenticated OAuth dynamic client registration endpoint returns freshly generated client credentials to any caller. Attackers can collect enough consecutive PRNG output to reconstruct the internal state and derive past and future values from the same generator.
Critical Impact
Reconstruction of the PRNG state allows unauthenticated attackers to forge OAuth tokens, API keys, and PKCE verifiers belonging to other users and organizations.
Affected Products
- Postiz (gitroomhq/postiz-app) versions prior to v2.24.0
- OAuth dynamic client registration endpoint in affected releases
- Any Postiz-managed OAuth tokens, client secrets, API keys, and PKCE verifiers issued before patching
Discovery Timeline
- 2026-09-22 - CVE-2026-94456 published to the National Vulnerability Database (NVD)
- 2026-09-22 - Last updated in NVD database
Technical Details for CVE-2026-94456
Vulnerability Analysis
The weakness is classified as [CWE-330] Use of Insufficiently Random Values. Postiz uses a shared credential-generation helper backed by Math.random(), which in Node.js resolves to V8's xorshift128+ PRNG. That generator is fast but not cryptographically secure and exposes its full internal state after a bounded number of observed outputs.
Because the same helper produces OAuth access tokens, authorization codes, client secrets, organization API keys, and PKCE verifiers, every credential type shares a single predictable entropy source. An attacker who recovers the state from one credential stream can predict subsequent outputs used elsewhere in the application.
Root Cause
The credential helper calls Math.random() and derives credential material from its output rather than using crypto.randomBytes() or a similar cryptographically secure primitive. V8's xorshift128+ produces 64 bits of output per call from a 128-bit internal state, which can be recovered algebraically from a small number of consecutive samples.
Attack Vector
The OAuth dynamic client registration endpoint is unauthenticated and returns freshly generated client_id and client_secret values. An attacker repeatedly invokes the endpoint, collects consecutive PRNG-derived credentials, and solves for the xorshift128+ state using a satisfiability (SAT) or Z3-based solver. Once the state is known, the attacker rewinds or advances the PRNG to derive tokens, authorization codes, API keys, and PKCE verifiers issued to other tenants.
// Related security patch context from apps/backend/src/api/routes/enterprise.controller.ts
// Source: https://github.com/gitroomhq/postiz-app/commit/9259cf2429e8cc0c88414e88d5e6c783d7ffba63
private _postsService: PostsService
) {}
+ private verifyEnterpriseToken<T extends object>(params: string): T {
+ const payload = AuthService.verifyJWT(params) as any;
+ if (
+ !payload ||
+ typeof payload !== 'object' ||
+ 'providerName' in payload || // login token (full User row)
+ 'orgId' in payload || // team invite token
+ 'expires' in payload // password reset token
+ ) {
+ throw new Error('Invalid enterprise token');
+ }
+
+ return payload as T;
+ }
+
@Post('/create-user')
async createUser(@Body('params') params: string) {
try {
- const { id, name, saasName, email } = AuthService.verifyJWT(params) as {
+ const { id, name, saasName, email } = this.verifyEnterpriseToken<{
id: string;
name: string;
email: string;
saasName: string;
- };
+ }>(params);
Source: GitHub Commit 9259cf24
Detection Methods for CVE-2026-94456
Indicators of Compromise
- High-volume, unauthenticated requests to the OAuth dynamic client registration endpoint from a single client or narrow IP range
- Sequential or bursty issuance of OAuth client_id and client_secret pairs immediately followed by token exchanges for unrelated organizations
- Successful OAuth authorizations that do not correlate with any preceding user consent or login event
Detection Strategies
- Alert on repeated calls to dynamic client registration endpoints from unauthenticated sources over short time windows
- Correlate newly registered OAuth clients with subsequent token use across multiple organizations to identify cross-tenant credential predictions
- Flag PKCE verifier or authorization code reuse patterns that suggest values were predicted rather than generated by legitimate clients
Monitoring Recommendations
- Log every dynamic client registration request, including source IP, user agent, and returned client identifier, and retain for retrospective analysis
- Monitor Postiz application logs for token validation events tied to accounts that never initiated an authorization flow
- Track version metadata across Postiz deployments to confirm all instances are running v2.24.0 or later
How to Mitigate CVE-2026-94456
Immediate Actions Required
- Upgrade Postiz to v2.24.0 or later, which replaces Math.random() with a cryptographically secure random source
- Revoke and reissue all OAuth access tokens, authorization codes, client secrets, organization API keys, and PKCE verifiers generated by affected versions
- Restrict or disable the OAuth dynamic client registration endpoint until the upgrade is complete
Patch Information
The fix is included in GitHub Release v2.24.0. Related repository context is available in the GitHub Repository for Postiz App and the Gadvisory Security Advisory. Administrators should verify the deployed commit matches or is later than GitHub Commit 9259cf24.
Workarounds
- Place the dynamic client registration endpoint behind authentication or a reverse proxy that requires an API key
- Apply strict rate limiting to registration and token endpoints to reduce the number of PRNG samples an attacker can collect
- Rotate all long-lived credentials on a defined schedule until the patched version is deployed
# Example: block unauthenticated access to the OAuth dynamic client registration endpoint at the reverse proxy
location /oauth/register {
satisfy all;
allow 10.0.0.0/8;
deny all;
auth_basic "restricted";
auth_basic_user_file /etc/nginx/.htpasswd;
limit_req zone=oauth_reg burst=5 nodelay;
proxy_pass http://postiz_backend;
}
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.