Your vulnerability dashboard had a quiet October. The month’s most damaging infrastructure incidents didn’t show up on it, because none of them needed a CVE.
In a span of about five weeks, a popular AI gateway shipped a malicious release signed by its own maintainers’ accounts. A campaign planted secret-dumping workflows on more than 13,500 branches across some 850 GitHub accounts. A WordPress backup vendor’s stolen management keys were used to backdoor customer sites in 18 hours. A PaaS provider told every customer to rotate their environment variables. Different victims, different tooling, same verb: the attacker logged in.
This piece is a position: the unit of compromise in 2026 is no longer the vulnerable service, it’s the credential that was never supposed to outlive its purpose. If your hardening roadmap is still organized around patch latency, you are optimizing the metric attackers have already routed around. Facts below come from vendor disclosures and press reporting as of November 1; where a detail is single-sourced or still “likely,” I say so.
Four incidents, one pattern
LiteLLM 1.107.0: the maintainers did it (as far as GitHub could tell)
On October 21, LiteLLM confirmed that a malicious 1.107.0 had been published to PyPI and Docker Hub. It was live for roughly three hours and reportedly downloaded around 49,000 times. The payload was an infostealer/RAT that encrypted and shipped data to litellm[.]dev; Wiz reported the stolen AWS credentials were then used to query Amazon Bedrock models, i.e. LLMjacking.
The interesting part is the release path. LiteLLM’s security lead said the malicious actions ran through three engineers’ GitHub accounts, apparently using stolen GitHub CLI OAuth tokens. One account triggered the release workflow; another approved it. The two-person rule was satisfied, twice, by an attacker holding two people’s tokens. A lookalike repository (Berri-AI/litellm versus the real BerriAI/litellm) was reportedly involved in signing and publishing.
Where did the tokens come from? LiteLLM traced them to an innocuously named user on an internet-exposed internal LiteLLM deployment. SecurityWeek reports the leak was likely via an AI coding assistant embedding tokens in a response body that was then logged by the gateway. That attribution is unconfirmed; treat it as a hypothesis. The architectural point survives either way: an LLM gateway’s logs are a credential store, because everything passing through it, including tool output and environment content, is persisted in cleartext by default in many deployments.
This is also the second time. The project was hit in March as part of the TeamPCP campaign (see our earlier coverage of the cascade).
Gheist: GitHub Actions as a secret-extraction API
Wiz Research tracked “Gheist” from September 15 through at least October 24. The mechanics are almost elegant in their minimalism:
- Take a stolen GitHub token (infostealer logs and earlier supply-chain compromises, including March’s Trivy incident, are the suspected sources).
- Enumerate every repo it can write to.
- Push a branch named
ci-sync-plus six characters containing a workflow calledsync-checkthat prints the repo’s secrets into the run log (reportedly base32-encoded, which defeats log masking). - Download the log via the API with user agent
gheist, then delete the run seconds later.
No C2, no payload hosting, no outbound beaconing from the victim. The attacker’s infrastructure is GitHub itself.
Credit where due: a control GitHub introduced on July 28 holds workflow runs from potentially malicious public-repo contributors in action_required, and Wiz found every public Gheist run stuck there. The control does not cover private repositories or GitHub Enterprise Server, where Wiz saw workflows complete on attacker branches and private repos get cloned. The people with the most valuable secrets got the least protection.
Roughly two-thirds of implicated accounts were gone from GitHub by October 24, likely a response to Wiz’s report. Deleted runs and branches don’t un-leak a secret. Rotation is the only remediation.
UpdraftCentral: the management plane is the product
TeamUpdraft’s website was breached, and attackers obtained UpdraftCentral connection keys. Between roughly 15:32 UTC on October 18 and the following day, per Patchstack, those keys were used to instruct connected WordPress sites to install and activate malicious plugins. At least 99 distinct plugin names were observed, dressed as maintenance tools (“Site Health Cache” is one). No WordPress or UpdraftPlus vulnerability was involved. Fixes in 1.26.13 / 2.26.13 revoke the old keys but remove nothing already planted, so the patch is not the remediation (a theme we hit hard in Patching Is Not Eviction).
Exposure estimates vary wildly (one outlet says up to 400,000 sites); only sites with an active key were reachable. Assume yes if you ever connected the site.
Railway: the platform that holds your environment
On October 21, Railway disclosed unusual activity in systems storing customer environment variables, with an unauthorized party possibly reaching encrypted values and, for some, decrypted contents. Railway tied the intrusion to a broader campaign against platform companies aimed at compromising AI inference providers, notified those providers to revoke keys, and recommended all customers rotate environment variables and registry credentials. Deployments were paused during recovery. Some secondary reports claim all API and project tokens were invalidated; Railway’s own post is the source to trust, and the full vector and affected-account list had not been published when I looked.
PingCAP’s separate disclosure fits the same shape: stolen service-account credentials, September 9 to October 7, internal systems and source code accessed. (I couldn’t reach the primary page from this environment, so I’m relying on search summaries for that one.)
What the four have in common
1. The credential outlived any plausible need for it. A developer’s CLI OAuth token from a laptop session. A connection key issued once at plugin setup and valid indefinitely. A service account that ran for four weeks undetected. None of these had an expiry that would have mattered during the attack window.
2. The blast radius was the credential’s scope, not the compromised host’s. A token that can write to every repo its owner touches turns one stolen token into 13,500 branches. A management key that can install plugins turns one vendor breach into customer-site code execution.
3. The legitimate control plane was the delivery mechanism. GitHub Actions ran Gheist. The project’s own release workflow shipped LiteLLM. WordPress’s own plugin installer shipped the UpdraftCentral backdoors. Allowlisting and egress filtering see nothing wrong, because nothing is wrong at the protocol level.
4. Approval gates were built for a different threat. Two-person review assumes two humans with independent compromise probabilities. When both approvers’ tokens live in the same infostealer log or the same logging pipeline, the gate is one control, not two.
5. Secrets accumulate in places nobody classifies as secret stores. Gateway logs. CI run logs. Platform env-var tables. Backup-plugin config. The AI-infrastructure angle is not incidental: inference gateways and agent tooling concentrate API keys for many upstream providers in one place, which is why Railway says the target was inference providers.
Opinion: stop treating token theft as an “initial access” problem
Most programs file this under identity hygiene and move on. I think that framing is why it keeps working. These aren’t failures of authentication, they’re failures of authorization lifetime and scope. The attacker did authenticate correctly; the system granted exactly what the token was entitled to. The fix is to make the entitlement worth less.
Three claims I’ll defend:
- Long-lived personal tokens in CI/CD-adjacent workflows should be treated like a vulnerability, with an SLA. If a token can publish a release, it should not be an OAuth token on a developer laptop.
- Release approval must be bound to something an attacker with API tokens can’t forge: hardware-key-backed approval, environment protection rules with required reviewers who can’t be the triggering actor, and trusted publishing so the registry verifies which workflow on which ref produced the artifact, not merely which account.
- Detection should key on token behavior, not host behavior. The endpoint was never the interesting telemetry.
What to do this week
GitHub
Search every org for the Gheist indicators:
| |
Then the structural fixes:
- Replace classic PATs and
ghOAuth tokens with fine-grained tokens or GitHub App installation tokens, short expiry, per-repo. - Require approval for workflow runs from outside collaborators on private repos too, and set the default
GITHUB_TOKENto read-only. - Use environment-scoped secrets with required reviewers where the reviewer cannot be the actor who triggered the run. Stop storing production secrets at repo or org level.
- Restrict who can push workflow files: a CODEOWNERS rule on
.github/workflows/plus a branch ruleset that blocks creation of branches that modify workflows is cheap.
Publishing
- Enable PyPI/npm trusted publishing (OIDC) and remove long-lived upload tokens. Pin the trusted publisher to a specific workflow file and environment, not just the repository.
- Alert on any release not preceded by a tagged commit on the protected default branch.
- Watch for lookalike org names that publish provenance claims. If your docs link to a registry or org, check that the canonical namespace is the only one trusted by your own consumers.
Consumers of other people’s packages
- Pin by hash. Delay adoption of new releases of critical dependencies by 24 to 72 hours; LiteLLM’s bad version lived about three hours.
- If you ran LiteLLM 1.107.0 anywhere: treat that host as compromised, rotate every credential it could read (cloud keys, provider API keys, GitHub tokens), and check cloud audit logs for unexpected Bedrock invocations from those identities. Block and hunt
litellm[.]dev.
Gateways, logs, and AI tooling
- Don’t expose inference gateways or their admin UIs to the internet, and don’t leave default or innocuously named accounts alive.
- Scrub or disable request/response body logging in LLM gateways; treat existing logs as secret material, and rotate anything that has transited them.
- Give coding agents their own narrowly scoped, short-lived credentials rather than inheriting a developer’s
gh authsession.
WordPress
- Delete UpdraftCentral keys and reconnect only if you need them. Update to 1.26.13 / 2.26.13.
- List plugins that nobody on your team installed:
wp plugin list --fields=name,status,update --format=table, and diff against a known-good inventory. Check filesystem timestamps from October 18 onward. If a site shows signs of compromise, restore from a pre-October 17 backup and rotate admin credentials and salts.
Platforms
- Assume every secret you’ve placed in a PaaS env-var store is one vendor incident from exposure. Prefer workload identity (OIDC federation to your cloud) so the platform holds no long-lived cloud keys at all. Where static keys are unavoidable, scope them per-service and cap spend with provider-side quotas, which is what limits the LLMjacking bill.
Detection that works regardless of which vendor is next
| Signal | Why it catches this class |
|---|---|
| Workflow file created or modified on a non-default branch by a token, not via PR | Gheist’s core step |
| Workflow run + immediate run deletion by the same actor | Log-harvest-and-cover pattern |
| API calls with an unfamiliar or tool-like user agent against Actions logs endpoints | gheist UA, and its successors |
| Release workflow triggered and approved by two accounts from the same ASN/IP in minutes | Collapsed two-person control |
| New cloud API calls (Bedrock, other inference APIs) from a key last used by a CI job | LLMjacking after theft |
| Plugin install/activate events with no admin-session login preceding them | Management-key abuse |
| Service-account logins from networks outside its documented runtime | The PingCAP shape |
Takeaways
- October’s incidents were credential events. Your patch SLA would not have helped; token lifetime and scope would have.
- A deleted run, a revoked key, or a patched plugin is not remediation. Rotate everything the credential could reach and hunt for what was planted.
- Two-person rules only count if the two identities are independently compromisable. Bind approval to something that isn’t a bearer token.
- Treat logs, CI output, and AI gateways as secret stores and classify them accordingly.
- Move from static secrets to workload identity wherever a provider supports it. Every secret you eliminate is one that can’t appear in the next infostealer dump.
Primary sources worth reading: Wiz Research’s Gheist write-up, Railway’s October 21 disclosure, Patchstack’s UpdraftCentral analysis, and LiteLLM’s incident statement. Reporting here draws on The Hacker News, SecurityWeek, BleepingComputer, Dark Reading, CSO Online, and Help Net Security.