Guide · Backend
Zeabur's October 2026 Security Incident Explained: Plaintext Admin Tokens
24 min readUpdated By Saket Bhatnagar
Researched and drafted with AI, reviewed and edited by Saket Bhatnagar.
In short
- The Zeabur security incident was a break-in on 14 and 15 October 2026 at Zeabur, a platform that hosts other people's apps. Its preliminary report of 19 October blames Zeabur's own admin tokens.
- A token is a long secret string a program sends to prove it may act. These admin tokens could control a whole shared cluster, the servers where many users' apps run together.
- The tokens never expired and sat in plaintext, readable exactly as written, in a private config repository. How the attacker got them is still unconfirmed as of 23 October 2026.
- The attacker read about 32 lakh (3.2 million) environment-variable records, the settings where apps keep keys and passwords. Zeabur is shutting down: shared-cluster services pause on 1 December 2026.
- Beginners can stop after the moving-off section. Engineers then get runnable Python checks for hashed tokens and credential expiry, and a test showing a repo scan misses secrets that git history keeps.
What goes wrong when a platform keeps admin tokens that never expire?
One leaked token opens everything it was ever allowed to touch, for as long as it lives. In the Zeabur security incident, leaked admin tokens and cloud keys reached a shared cluster, 101 rented servers and the platform database.
An admin token may do almost anything on the system it belongs to. If it never expires and sits in a readable file, anyone who copies that file gets the same power, months later.
Zeabur's own report, published on 19 October 2026, says this is what happened. Its shared-cluster admin token was created in January 2024 with no expiry. In January 2026 it was written in plaintext to a private configuration repository.
Picture a common setup. A small team runs a food-ordering app on Zeabur's shared cluster: a web service, a worker and a PostgreSQL database. The setup is made up, but each of its secrets sits where the report says data was read.
Its environment variables hold the database password, a payment gateway key, an SMS key and an AI API key. The app also issues API tokens to partner restaurants and stores them in that database.
This guide follows the team through three jobs. It replaces what leaked, moves off Zeabur before the dated deadlines, and fixes its own token handling so the same mistake is far less likely in its code.
Which words do you need before reading the Zeabur report?
- Environment variable: a named setting, such as DATABASE_URL, that a platform hands to your app when it starts. Apps keep keys and passwords here.
- Credential: anything that proves who you are to a system, such as a password, an API key, a token or a private key.
- Plaintext: stored exactly as written, so anyone who can read the file can use the secret.
- Hash: a one-way fingerprint of a value. You can check a value against it, but you cannot turn it back into the value.
- Expiry and rotation: expiry means a credential stops working on a set date. Rotation means issuing a new one, switching apps to it and switching the old one off.
- Shared cluster: Zeabur's glossary calls it a Kubernetes cluster where services from many users run together. Kubernetes runs a group of machines as one.
- Dedicated Server: in Zeabur's glossary, a server managed by Zeabur that runs services for a single user only.
- Blast radius: everything one leaked credential can reach.
What happened in the Zeabur security incident of October 2026?
An attacker used Zeabur's own admin tokens and AWS keys, access keys for Amazon's cloud, to enter its shared cluster on 14 October 2026. They then reached internal systems and Dedicated Servers that Zeabur ran on GCP, Google's cloud.
On 15 October they joined one of those servers to Zeabur's private network as a new node, a machine on that network. They read the platform database from it.
Zeabur started investigating on 15 October at 13:56 UTC, after a user report. That was about 36 hours after the first known admin-token use. The report says more than 16 hours of attack activity on 14 October triggered no alerts.
| When (UTC) | What happened |
|---|---|
| 10 Jan 2024 | Shared-cluster admin token created with no expiry |
| 20 Jan 2026 | Admin token written in plaintext to the configuration repository |
| 27 Aug 2026 | A separate break-in starts; Zeabur rotates keys afterwards, but not these tokens |
| 14 Oct, 01:36 | First known use of a leaked AWS key |
| 14 Oct, 01:40 | First admin-token use on the shared cluster |
| 14 Oct, 13:40 | Sign-ins to GCP Dedicated Servers begin |
| 15 Oct, 13:56 | Investigation starts after a user report |
| 15 Oct, 15:30 | Attacker adds a compromised GCP server to the internal network as a node |
| 15 Oct, 16:10 to 16:47 | Platform database read |
| 15 Oct, 17:58 | Attacker's node removed |
| 15 Oct, 20:25 | Login signing key rotated; every user signed out |
| 15 Oct, 21:05 | Last recorded attacker activity |
| 16 Oct, 01:09 | Hetzner and DigitalOcean tokens revoked |
How the attacker moved through Zeabur, per the 19 October report
Drag to exploreNotice the two paths. Cluster tokens led to the secrets manager, an old GCP key led to the servers, and they met where the node was added. How the keys first leaked is unconfirmed.
Diagram connections:
- Private config repo to Attacker (copied, route unknown)
- Attacker to Shared cluster admin (uses admin token)
- Shared cluster admin to Cluster secrets (reads)
- Cluster secrets to Secrets manager (key from an old backup)
- Attacker to 101 GCP servers (old GCP key)
- Secrets manager to 101 GCP servers (network admin key)
- 101 GCP servers to Attacker's internal node (adds node)
- Attacker's internal node to Platform database (reads platform DB)
- Private config repo: Admin tokens and AWS keys in plaintext
- Attacker: How the keys leaked: unconfirmed
- Shared cluster admin: Internet-reachable; token never expired
- Cluster secrets: Env vars and secrets across 9 regions
- Secrets manager: About 53 production secrets read
- 101 GCP servers: Old GCP key; root sign-ins
- Attacker's internal node: Added 15 Oct 15:30 UTC, removed 17:58
- Platform database: Read through a read-only account
Read the diagram as two paths. Path A started with the repository's cluster tokens and AWS keys. Zeabur assesses that a key found in an old cluster backup then opened its secrets manager, a vault for production keys.
Path B used an old GCP service account key, a key for a program rather than a person. Zeabur assesses it was used to read root passwords from server setup settings, giving root sign-ins on 101 servers.
Zeabur is still tracing where that old key leaked from, as of 23 October 2026.
The table below lists what the report says was reached. Statuses matter here, because the report separates what it confirmed from what it only assessed.
| What | How many | What the report says |
|---|---|---|
| Service environment variables (names and values) | 3,218,965 records | Read, confirmed |
| Accounts with contact and profile data | 321,810 | Read, confirmed |
| Platform access tokens | 11,536 | Stored only as hashes that cannot be reversed; all revoked |
| zSend API keys (for Zeabur's email-sending API, per its endpoint name) | 566 | Stored in plaintext; read, confirmed; all revoked |
| Shared-cluster variables and secrets | 9 regions | Partly confirmed |
| Database services on the shared cluster | 865 | Assessed; commands were logged, not their output |
| n8n workflow tools on PostgreSQL (credentials, workflows) | 736 | Assessed from logged commands |
| GCP Dedicated Servers | 101, owned by 92 users | Sign-ins confirmed; all treated as affected |
| Zeabur production secrets | About 53 | Read, including the GitHub App private key |
| Hetzner and DigitalOcean Dedicated Servers (admin access via leaked tokens) | 359 and 48 servers | Cannot be confirmed whether used |
| User database backups, Taipei and Hong Kong | Not given | Download not confirmed in the table |
One row has two answers. The data table says it cannot confirm whether the Taipei and Hong Kong backups were downloaded. The timeline says the attacker downloaded user database backups at 15:42. Zeabur tells users to treat that backup data as leaked, which is the safe reading.
What root cause did Zeabur name for the security incident?
Zeabur names itself. The report says "long-lived admin credentials were stored in plaintext and could be used from the public internet." Three failures had to meet for that to work.
- No expiry: the admin tokens had not been rotated since they were created in January 2024.
- Plaintext storage: the tokens and several AWS keys sat readable in a private Zeabur configuration repository.
- Public reach: the shared cluster's admin interface accepted them from the internet. Zeabur restricted it to its own systems during the incident.
Why the August rotation missed these tokens
In late August 2026, Zeabur had a separate break-in. Its 7 September report says it began with a flaw, CVE-2026-7644, in one user's NextChat app running on the shared cluster.
That report said Zeabur had "disabled or rotated every internal administrative key, database password, and cache password", and switched off Legacy API Key authentication. The October report corrects this. It says the August rotation did not cover the cluster admin tokens and AWS keys in the configuration repository.
The report does not say how the August list was built. A secrets migration started on 3 October had also not been merged when the attack began.
How the attacker got the tokens is unconfirmed
As of 23 October 2026, Zeabur says it cannot confirm how the attacker obtained the credentials. Its leading hypothesis links them to the GitHub App private key from the August incident, and it calls this unproven.
The report also lists unexplained repository downloads on 30 August and 2 September, made with an employee's GitHub authorisation. It says these cannot be ruled out as a separate way the credentials were obtained. Be wary of any article that states the entry point as fact.
What must Zeabur users rotate after the security incident?
Replace every password and key you ever stored on Zeabur, then check for anything the attacker left behind. The report asks this even for keys you already replaced after the August incident.
Replace every stored secret
At each provider, revoke the old key or password first, then create a new one and redeploy. That is the report's order, so expect a short outage. Include keys you replaced in August.
Recreate Zeabur tokens and zSend keys
Zeabur revoked all 11,536 platform access tokens and all 566 zSend keys, which it had stored in plaintext. Any script still using an old one already fails.
Check GitHub for the Zeabur App
Look in your GitHub audit log for Zeabur GitHub App actions from 15:05 to 18:54 UTC on 15 October. That is 20:35 IST on 15 October to 00:24 IST on 16 October.
Change passwords inside databases
Shared-cluster users must change each password inside the database, not just the variable. In PostgreSQL, look for roles, functions, triggers, jobs or extensions you did not create.
Replace app-specific keys
Shared-cluster users of n8n, a workflow tool, replace its encryption key and recreate its credentials. Revoke AI keys passed through new-api or one-api gateways. Treat Taipei and Hong Kong backups as leaked.
Rebuild GCP Dedicated Servers
Treat the server as compromised and rebuild it if you can. In /root/.ssh/authorized_keys, remove SSH login keys commented @zeabur-pentest or that you do not recognise. Check the google-sudoers group, system accounts, crontab, systemd services and k3s workloads. Change the root password, disable SSH password login and replace every credential on the server.
Check other providers and bills
Hetzner and DigitalOcean users should look for password resets or reinstalls after 14 October. Review usage from 14 October 2026, or from 1 September for GCP Dedicated Servers.
The 1 September date has its own reason. The report says an unknown source signed in to the same GCP servers with the root password between 1 and 5 September. It cannot yet say whether that was the same attacker.
For the team in our setup, that means a new database password, new payment, SMS and AI keys, and a check of the PostgreSQL roles. Its partner tokens sit in that database, so it reissues them too. The report tells shared-cluster users to revoke and regenerate any tokens, sessions or password hashes their own databases store.
Projects that moved their variables to their own Dedicated Server after August sit outside the platform database read, the report says. It adds that this does not rule out leaks from a compromised host, so rotate anyway.
How do you move off Zeabur before 1 December 2026?
Pick a new home, copy your data and secrets there, test it, then switch your domain. Finish before your own deadline: 1 December 2026 for shared-cluster services, or your server's expiry date for Dedicated Servers.
| Date | What happens | Who it affects |
|---|---|---|
| 17 Oct 2026 | New Dedicated Server and domain purchases stop; refund requests open | Everyone |
| Now, before you migrate | Transfer domains bought on Zeabur out from the Dashboard; Zeabur asks you to include this in your migration plan | Domain owners |
| 1 Dec 2026 | Running services pause; you can restart them by hand to back up data | Shared-cluster users |
| 1 Dec 2026 | Renewals stop; each server shuts down on its own expiry date | Dedicated Server users |
| 31 Dec 2026 | All shared clusters and Dedicated Servers stop | Everyone |
| To be announced | Data deletion date, final export deadline, Support closing date | Everyone |
Counted from 23 October 2026, that leaves 39 days to 1 December and 69 days to 31 December. Zeabur's migration guide says to plan around your own expiry date rather than the last day.
What the migration guide says breaks
- Images: Zeabur keeps built images in its own registry, so pulls fail after shutdown. Keep a Dockerfile or use your new platform's build feature.
- Domains: every name ending in zeabur.app stops working. You need your own domain and permission to change its DNS records.
- Variables: Zeabur passes variables between services in a project, so copy the real values. The guide calls missing variables a common failure that breaks database connections.
- Databases: Zeabur's daily backups disappear too. Export your data yourself, then restore that export once in the new place as a dry run.
The guide's cutover order is to suspend the apps on Zeabur, sync data one last time, then change DNS yourself. It does not pick a destination; it weighs managed platforms, self-hosted platforms, k3s (a small Kubernetes) and plain Docker Compose.
Whichever you pick, ask how it stores environment variables and how quickly you could rotate them all. The guide is written so a coding assistant can follow it, and it warns never to paste your access token into a chat.
Rotation and migration are separate jobs. Rotate first, because every leaked value keeps working for the attacker until you switch it off.
Why are hashed tokens safer than plaintext tokens?
A stolen hash cannot be used as the token, so a database leak does not hand over working keys. Zeabur's own report shows it: its 11,536 platform access tokens were stored only as hashes.
The report uses that fact to rule them out as the source of the attacker's impersonation, which began at 16:12 UTC. Its assessment is that the attacker forged login credentials with the login signing key instead.
The same table shows the other way. Zeabur stored its 566 zSend API keys, keys customers send to its email API, in plaintext. Those were confirmed read, and all had to be revoked.
Think of a hotel that keeps the locks and lets guests keep the keys. The desk can test any key in its lock, but someone who steals a lock still has no key. Here the key is the token and the lock is its hash.
Where the picture breaks: a locksmith can study a lock and cut a key. A hash of a long random token gives no such shortcut, but a hash of a short password can be cracked by fast guessing.
The code below issues one partner token for the team's app and stores it both ways. Pause and predict: an attacker copies both tables and presents each stored value as a token. Which one gets in?
import hashlib
import hmac
import secrets
# The pepper is a server-side key kept OUTSIDE the database (e.g. in a secret manager).
# Demo only: real code loads one fixed pepper, or every stored hash stops matching.
PEPPER = secrets.token_bytes(32)
def token_hash(token: str) -> str:
return hmac.new(PEPPER, token.encode(), hashlib.sha256).hexdigest()
# Issue: show the token to the partner once, store only its hash.
token = "demo_" + secrets.token_urlsafe(32) # 32 random bytes
plaintext_table = {"partner_7": token} # the risky way
hashed_table = {"partner_7": token_hash(token)} # the safer way
def is_valid(partner: str, presented: str) -> bool:
return hmac.compare_digest(token_hash(presented), hashed_table[partner])
# An attacker copies both tables and tries each stolen value as a token.
print("stolen plaintext works:", is_valid("partner_7", plaintext_table["partner_7"]))
print("stolen hash works: ", is_valid("partner_7", hashed_table["partner_7"]))
print("stored hash length: ", len(hashed_table["partner_7"]), "hex characters")Run it with Python 3.13 and it prints True, then False, then 64 hex characters. The stolen plaintext works as a token. The stolen hash does not, because the check hashes it again.
- secrets.token_urlsafe(32) draws 32 random bytes, which is 256 bits. Python's docs say that was believed sufficient for typical use as of 2015.
- hmac.new builds an HMAC, a hash that mixes in a secret key. Here that key is the pepper, kept outside the database, so someone holding only the table cannot test guesses offline.
- hmac.compare_digest avoids the content-based shortcut of ==, which Python's docs say helps resist timing attacks.
Why a fast hash suits tokens but not passwords
Guessing one 256-bit random value is far beyond any attacker, so a fast hash like SHA-256 is enough for tokens. Passwords are short and human, so guesses succeed. OWASP says fast hashes such as SHA-256 are not suitable for passwords.
For passwords, OWASP's Password Storage Cheat Sheet recommends Argon2id first, scrypt if it is missing, bcrypt for older systems and PBKDF2 where FIPS-140 rules apply.
Which credentials you cannot hash
Hashing protects credentials you check. A credential you must send has to exist in usable form, so a hash cannot replace it. Zeabur's admin tokens were this kind: a program presented them to the cluster.
The same goes for your app's payment key or database password. For these, the defences are short lifetimes, a secret manager instead of a repository, and network rules so a stolen copy fails from the internet.
How should expiry and rotation cover every credential type?
Give every credential an end date, and rotate from one list that covers every type. Zeabur's admin tokens had no end date and were not on the list its August rotation used.
Expiry turns a leak into a time-limited problem. A token that died after ten minutes is worthless in a repository copied months later.
Short-lived tokens in Kubernetes
If your cluster runs Kubernetes, ask for a short-lived token per job: kubectl create token deployer --duration 10m. The kubectl reference says the server may return a longer or shorter lifetime than you asked for.
Kubernetes docs say tokens kept in Secrets "don't expire and don't rotate". They call that method not recommended. Zeabur's report does not say which kind of token its admin token was.
Rotation must reach backups and history
Rotation is a loop, and it includes every copy of a secret. Zeabur's daily cluster backups stored all secrets, and the backup access key sat beside other secrets.
The report adds that some credentials rotated during the incident were also captured by later backups. A rotation that leaves an old or new value in a readable backup has not finished.
A rotation loop that does not skip credential types
Drag to exploreNotice that the loop starts from the inventory, not from memory, and that old values are revoked only after nothing still uses them. That order suits routine rotation. For a value you know has leaked, Zeabur's report says revoke first, then issue and redeploy.
Diagram connections:
- Credential inventory to Issue new credential (next row)
- Issue new credential to Deploy the new value
- Deploy the new value to Old value still used?
- Old value still used? to Find and switch user (Yes)
- Find and switch user to Old value still used? (check again)
- Old value still used? to Revoke the old value (No)
- Revoke the old value to Record the rotation
- Record the rotation to Credential inventory (updates)
- Credential inventory: Every type: tokens, keys, passwords, backups
- Issue new credential: With an expiry date, least permissions
- Deploy the new value: Secret manager to app, never a repo
- Old value still used?: Check logs and last-used dates
- Find and switch user: A forgotten job or script
- Revoke the old value: Then purge it from backups
- Record the rotation: New dates go back into the inventory
How do you build an inventory of long-lived credentials?
List every credential with its type, where it is stored, when it was made, when it expires and which service owns it. Then let a script flag the risky rows every week.
Once the team leaves Zeabur and runs its own small cluster, it holds the same kinds of credentials a platform does. The rows below are made up, modelled on the types in Zeabur's report. The 90-day limit is an example policy, not a standard.
from datetime import date
TODAY = date(2026, 10, 23)
MAX_AGE_DAYS = 90
# One row per credential, of EVERY type. Made-up example data.
inventory = [
{"name": "cluster-admin", "type": "cluster token", "stored_in": "config repo",
"created": date(2024, 1, 10), "expires": None, "owner": "deployer", "owner_live": True},
{"name": "backup-bucket", "type": "cloud key", "stored_in": "config repo",
"created": date(2025, 6, 2), "expires": None, "owner": "backups", "owner_live": True},
{"name": "old-docs-deploy", "type": "deploy key", "stored_in": "secret manager",
"created": date(2025, 3, 1), "expires": None, "owner": "docs-site", "owner_live": False},
{"name": "payments-api", "type": "api key", "stored_in": "secret manager",
"created": date(2026, 9, 1), "expires": date(2026, 12, 1), "owner": "checkout", "owner_live": True},
{"name": "orders-db", "type": "db password", "stored_in": "secret manager",
"created": date(2026, 8, 29), "expires": date(2026, 11, 27), "owner": "orders", "owner_live": True},
]
def problems(c):
found = []
if c["expires"] is None:
found.append("never expires")
elif c["expires"] < TODAY:
found.append("expired but still listed")
age = (TODAY - c["created"]).days
if age > MAX_AGE_DAYS:
found.append(f"{age} days old")
if not c["owner_live"]:
found.append("owner service retired")
if c["stored_in"] == "config repo":
found.append("plaintext in a repo")
return found
for c in inventory:
issues = problems(c)
print(("FIX " if issues else "ok ") + c["name"].ljust(16), c["type"].ljust(13), "; ".join(issues))
# A rotation run that picked credentials by type, not from the inventory:
rotated = {c["name"] for c in inventory if c["type"] in {"api key", "db password"}}
missed = [c["name"] for c in inventory if c["name"] not in rotated]
print("rotation missed:", missed)FIX cluster-admin cluster token never expires; 1017 days old; plaintext in a repo
FIX backup-bucket cloud key never expires; 508 days old; plaintext in a repo
FIX old-docs-deploy deploy key never expires; 601 days old; owner service retired
ok payments-api api key
ok orders-db db password
rotation missed: ['cluster-admin', 'backup-bucket', 'old-docs-deploy']On Python 3.13 the script marks three rows FIX, with ages counted to 23 October 2026. The rotation line misses exactly those three, because it chose credentials by type instead of reading the list.
| Where | Zeabur's example | How to find yours |
|---|---|---|
| Config repositories | Admin tokens and AWS keys in plaintext | Scan the files and the git history |
| Server setup settings | Root passwords in GCP initialisation settings | Read your init scripts and instance metadata |
| Backups | Daily cluster backups held all secrets | List what each backup contains and who can read it |
| Retired services | A deploy key valid four months after shutdown | Revoke credentials as part of retiring a service |
| Over-broad cloud keys | A key meant for one bucket could read every bucket | Review each key's permissions, not its name |
| App settings | 602 of 607 checked n8n instances kept their encryption key in a variable | Search variables for keys that protect other secrets |
Scan the repository, then its history
A scanner finds secrets in your files, but only in the files you point it at. We tested detect-secrets 1.5.0 on a small repo holding a fake cluster token and a sample AWS key.
pip install detect-secrets==1.5.0
detect-secrets scan > baseline.json # scans git-tracked files only
detect-secrets scan --all-files # also scans untracked files
# A secret deleted from a file can still sit in history:
git log -p --all -S admin_tokenThe scan reported an AWS Access Key in one file and a Hex High Entropy String in the other. After a commit deleted the token line, the scan no longer reported it, but git log -p still printed it.
By default the scan also skipped an untracked file holding a secret, and --all-files found it. So treat any secret that was ever committed as leaked, and rotate it rather than deleting the line.
Ask your cloud for its own list
On AWS, the IAM credential report lists each user's access keys with when they were last rotated and last used. Create it with aws iam generate-credential-report, then download it with aws iam get-credential-report.
AWS's docs say it covers only the first two access keys per user, and you can generate a new one at most every four hours.
Why did a few leaked keys reach so much of Zeabur?
Because many parts trusted more than they needed to. The report's "why it happened" section lists permissions and layouts that let a handful of keys reach almost everything.
- Internal and user services ran on the same cluster, so reading user settings also exposed internal credentials.
- Network rules let any node with the right tag reach the platform database.
- The internal read-only database account could read all data.
- The default service account on GCP servers had project-level write permissions.
- Root passwords in GCP initialisation settings were found on 3 September and not fixed before the incident.
Detection lagged too. The report says storage, GCP and GitHub logs are incomplete. As of 23 October, alerts for unusual admin access, bulk reads, new internal nodes and cloud keys used from unfamiliar sources are still in progress.
For the team in our setup, the lesson is small and concrete. Give the worker a database user that can touch only its tables, and alert when a partner token suddenly reads every order.
How do you explain the Zeabur security incident in a system design interview?
Tell it as a story about credential lifecycles and blast radius, not about one attacker. The steps below give you the frame.
State the root cause
Long-lived admin tokens stored in plaintext in a config repo, usable from the internet, never rotated since January 2024.
Separate known from unknown
The way in is unconfirmed, per Zeabur's 19 October report. Say so instead of guessing.
Draw the blast radius
Repository tokens and keys reached a shared cluster in 9 regions and the secrets manager. An old GCP key reached 101 servers. An added network node reached the platform database.
Fix it in layers
Hash tokens you receive. Make tokens you send short-lived. Rotate from an inventory that includes backups. Block admin interfaces from the internet.
Add detection
Alert on admin access from new places, bulk reads and new network nodes. More than 16 hours of attack activity on 14 October raised no alert.
Questions people ask
- Is Zeabur shutting down because of the security incident?
- Partly, by its own account. The 17 October 2026 post says Zeabur's growth had started to run into limits and its quickly built system had become hard to maintain. It adds that the two security incidents this year, in August and on 14 October, made its security and operations shortcomings clearer. Shared-cluster services pause on 1 December and all services stop by 31 December 2026.
- Was my data exposed in the Zeabur security incident?
- If you stored environment variables on Zeabur, assume yes. The report confirms that 3,218,965 variable records, names and values, were read. Projects that moved their variables to their own Dedicated Server after August were outside that read, but the report says host leaks are not ruled out. Rotate either way.
- Do I need to replace keys I already replaced after August?
- Yes. The report asks users to replace every password and key stored on Zeabur, even if they replaced them after the August incident. If you put the new August keys back into Zeabur's environment variables, the October database read could include them. Replace them again in the report's order: revoke the old values at each provider first, then create new ones and redeploy.
- Are hashed tokens enough on their own?
- No. Hashing protects tokens you check, such as API tokens you issue to users. It cannot protect credentials your systems must send, like Zeabur's admin tokens or your payment key. Those need short lifetimes, a secret manager instead of a repository, and network limits. Zeabur hashed its platform access tokens, yet its plaintext zSend keys and admin tokens were still exposed.
- How long should a token live?
- There is no universal number. Match the lifetime to the use. A deploy job's cluster token can last minutes, as Kubernetes allows with kubectl create token and --duration. Keys a provider cannot issue short-lived should be rotated on a schedule from an inventory. The 90 days in this guide's script is an example policy.
- What happens to my zeabur.app address after the shutdown?
- It stops working, according to Zeabur's migration guide of 20 October 2026. Move to your own domain, make sure you can change its DNS records, and arrange HTTPS certificates through your new platform or a reverse proxy. Transfer any domain you bought through Zeabur to another registrar from the Dashboard; Zeabur asks you to include this in your migration plan.
Sources and further reading
- Zeabur: October 2026 security incident (preliminary report, 19 Oct 2026)
- Zeabur: Zeabur is shutting down (17 Oct 2026)
- Zeabur: Migrate from Zeabur guide (20 Oct 2026)
- Zeabur: August 2026 security incident (7 Sep 2026)
- Zeabur changelog: Zeabur Email, the zSend sending API (5 Feb 2026)
- Kubernetes docs: Service Accounts (token expiry)
- Kubernetes docs: kubectl create token reference
- AWS IAM docs: Generate credential reports
- Python docs: hmac module (compare_digest)
- Python docs: secrets module (token length)
- OWASP Password Storage Cheat Sheet
- detect-secrets 1.5.0 on PyPI
- BleepingComputer: Zeabur shutting down after second breach (20 Oct 2026, secondary)