Anthropic Found Both Halves: Postiz Leaks the Key That Signs Its Tokens, Then Signs Whatever You Ask

Two critical CVEs landed against Postiz, the open-source AI social-media scheduler, on 24 October 2026. Taken individually they are a path traversal and a token-confusion bug. Taken together they are the same compromise reached twice, by independent routes, and the pairing is the point: one of them steals the secret that signs every token in the application, and the other makes stealing it unnecessary.

CVE-2026-115932 (advisory PSA-2026-E9AHFV, CVSS v3.1 9.6, CWE-22) is an unconfined file read in the publishing path. CVE-2026-115933 (PSA-2026-YT2HMH, CVSS v3.1 9.9, CWE-290 with CWE-345 and CWE-915) is a signing oracle. Both are fixed in v2.25.3, published 24 October at 14:16 UTC. Both credit Anthropic, PBC as finder, with Enno Gelhaus coordinating and Postiz maintainer Nevo David writing the fixes.

The file read: a media path that is not a URL is a path

Postiz lets a post carry media. When the publishing worker encounters a media reference that is not a web address, it treats it as a file on local disk — and, per the advisory, “does not check that the file lies inside the upload directory.” A registered user stores a crafted media path on a post, gets it past the checks that normally constrain it, and the worker reads the file and delivers it as an attachment to a Discord server the user controls.

The exfiltration channel is what makes this severe rather than merely interesting. There is no need to read a response body, no blind oracle to grind: the file arrives, as the advisory puts it, “byte for byte” in the attacker’s own Discord channel. The advisory enumerates what is in reach — files holding the JWT signing secret, the database address, provider client secrets, the instance environment file, mounted secret files, application logs, and the worker’s process environment — and adds the detail that decides the blast radius: the shipped container runs the worker as root, so every file root can read is exposed.

Two further routes reach the same unconfined reads. One, requiring a connected Dribbble, TikTok or Pinterest channel, reports whether a path exists and whether it is a file or a directory, and can exhaust the backend’s memory with a single request pointed at a device file — which stops the backend answering any organisation until it is killed. The third applies to instances on local storage with an empty cloud bucket URL. The affected range is ≥ 2.23.0; the fix is commit 12db9bd, pushed 24 October at 08:38 UTC, touching the media controller, the posts service, the shared social-provider abstraction, three individual providers and the local storage adapter — a spread that tells you the missing containment check was not in one place.

The signing oracle: one secret, no token type

The second bug is the more instructive one, and its root cause is a design decision rather than a missing validation. Postiz signs its session, invitation and password-reset tokens with one shared secret and without any marker of what a token is for. That is the whole vulnerability class in one sentence. Once a single secret covers every token purpose and nothing in the payload says which purpose a given token serves, any route that will sign attacker-influenced data becomes a universal credential minting service.

Postiz had such a route. An endpoint that refreshes the credentials of a browser-extension channel — reachable by a user who connects their own Skool account — signed data taken from the request itself and stored the result where the requesting user could read it back. The advisory describes the consequence precisely: a user could obtain a validly signed token containing any claims they choose, “which the application accepts as a session, as an organization invitation with a chosen role, as a password reset or as an enterprise provisioning token.” A second, narrower route that creates team invitations signs extra request-body fields the same way.

The documented impacts follow mechanically. Log in as another user — and, where database filter objects are accepted as claims, without knowing any identifier for the victim. Join any other organisation as super administrator, using an organisation id that any shared post-preview link exposes, then read its API key and channel list and post through its connected social accounts. Reset the password of any user whose id you hold. Obtain a lifetime top-tier subscription through the enterprise provisioning endpoint. The advisory adds the line that should decide how operators treat an exposed instance: “Forged tokens do not expire and cannot be told apart from genuine ones.”

That last property is why upgrading is not sufficient. A forged token is, by construction, a correctly signed token; v2.25.3 closes the routes that mint them but cannot invalidate ones already issued, and no log will distinguish them. Anyone running an affected instance that was reachable by untrusted registrants needs to rotate the JWT signing secret — which invalidates every outstanding token of every type at once — and then rotate the organisation API keys and the connected social-account credentials those tokens could reach. The affected range here starts at 2.15.0, considerably earlier than the file-read bug, so the exposure window is long. The fix is commit a5affe62, 24 October at 13:27 UTC.

Self-registration is open by default, so neither bug requires an existing account on a public instance. The published workarounds are containment, not remediation, and the advisories say so: setting DISABLE_REGISTRATION=true “reduces reach, it does not close the issue”; blocking POST /integrations/extension-refresh at the reverse proxy removes the main signing route while the invitation route remains; leaving the Discord client id, secret and bot token unset — they are empty in the shipped docker-compose.yaml — removes the main exfiltration route while the other two remain.

Why the pairing matters more than either CVE

Chain them and the dependency inverts in a way that should change how you read the severity scores. The file read reaches the JWT secret; the secret alone gives an attacker everything the signing oracle gives them, with no need for a Skool account, a Discord provider, or any particular configuration. So CVE-2026-115932 is not just a confidentiality bug in a scheduler — it is a full authentication bypass with extra steps, and its 9.6 understates it precisely because CVSS scores the file read and not what the file contains. Conversely, CVE-2026-115933 means an attacker who cannot satisfy the Discord precondition does not need the secret at all: the application will sign on request.

This is the recurring shape in AI-adjacent application tooling rather than anything exotic. Postiz holds long-lived OAuth credentials for its users’ social accounts, which is the entire reason it exists, and a scheduler that can post as you is an impersonation platform the moment its session layer fails. We have written about this codebase twice before — a pwn-request in its GitHub Actions workflows and a stored XSS — and the trajectory from workflow and injection bugs to a complete break of the token layer is the normal maturation curve for a fast-moving self-hosted product, not a surprise.

The response was fast, and the published timeline is specific enough to check. Anthropic disclosed on 23 October at 17:45 with a working proof of concept; Postiz reproduced it fifteen minutes later; the file-read fix was pushed at 12:26 the next day, the token fix at 13:27, and the release and both advisories went out at 14:18. That is roughly 20.5 hours from report to patched release for two separate critical bugs across eight and seven files respectively. Both advisories state that exploitation was demonstrated end to end against unmodified builds at commit 3a7d28b7ad96 — a second self-registered user became super administrator of another user’s organisation and read its API key, and a file outside the upload directory arrived in the attacker’s Discord channel.

The attribution is the story’s second half

Anthropic being named as finder on both records is worth stating plainly, and worth being careful about. The advisories credit the company, not a model, and they do not describe the methodology; we are not going to characterise what was automated and what was not, because the primary sources do not say. What is verifiable is that the credit line is institutional and that it appeared on two critical findings in a third-party open-source project, with a proof of concept good enough that the maintainer reproduced it inside a quarter of an hour.

That is the measurable form of the program we covered on 24 October, when Claude mapped 493 issues across 25 open-source projects, and of the open-source scanning effort announced on 9 October. Counts of candidate issues are an input metric. Two fixed criticals in a shipped release, with CVEs, a named coordinator and a reproduction inside fifteen minutes, is an output metric, and output metrics are the only ones that distinguish a vulnerability-discovery program from a finding-generation program. On the current evidence this one is producing the former, in at least this instance.

The honest caveat is scope. Two advisories in one small project do not establish a rate, and nothing here tells us the denominator — how many reports were filed, how many were duplicates or noise, how much human triage stood between the tooling and the disclosure. The right conclusion is narrow and still worth drawing: this is what the program’s output looks like when it lands, and it landed well.

Our verification was read-only. Both CVE records — identifiers, publication timestamps, CVSS v3.1 vectors and scores, CWE classifications, descriptions and references — were read from the NVD 2.0 API. The affected ranges, workarounds, configuration preconditions, impact statements, exploitation notes, credits and the hour-level disclosure timelines quoted above were read first-hand from the GAdvisory advisories PSA-2026-E9AHFV and PSA-2026-YT2HMH. The v2.25.3 release and its publication time, and the two fix commits with their timestamps and changed-file lists, were read from the GitHub REST API. We did not install, run, deploy or contact any Postiz instance, did not obtain or write any proof of concept, did not register an account anywhere, did not contact Skool, Discord or any other provider named in the advisories, and did not reproduce any finding. The methodology behind Anthropic’s credit is not described in the primary sources and is not characterised here. Brave Search was used for discovery only.

Sources: