If you have self-hosted Metabase running in your homelab or in prod — stop reading the rest and check your version right now. On August 6, Metabase published an advisory about a critical vulnerability with a CVSS score of 10.0, which had already been exploited in real attacks. No password required, user interaction not needed; the result is administrator rights to the instance and access to saved credentials for all connected databases.
Below is not a news recap, but a step-by-step guide: how to quickly determine in five minutes whether you are in the affected zone, what to close if you cannot update right now, and how to check logs to see if it has already hit you.
Vulnerable branches: v58.0–v58.22, v59.0–v59.19, v60.0–v60.15, v61.0–v61.9, v62.0–v62.7, v63.0–v63.2.
Fixed versions: 0.58.24, 0.59.21, 0.60.17, 0.61.11, 0.62.9, 0.63.5 (for Enterprise/Pro — same numbers with the 1. prefix).
Metabase Cloud is patched by the vendor. Self-hosted — only by hand.
What exactly is broken
The vulnerability has the identifier GHSA-vwf4-m7j8-wcjf. CVE has not been assigned at the time of publication — this is important because if you scan your infrastructure only by CVE names, this vulnerability will not show up.
Entry point — the endpoint POST /api/session/reset_password, the same one that serves the “forgot password” form. Through it an unauthenticated client can inject arbitrary SQL into the Metabase service database (the application database — the one where users, dashboards, permissions, and connection settings live). Advisory wording: an injection that “can give them administrator access to the instance.”
In CVSS v3.1 terms: Network, low complexity, privileges not required, user interaction not required, Scope: Changed — hence the 10.0 score. You don’t see such a rating every day in real life.
Why a BI system is the worst place for a hole of this class
Here we should pause, because many underestimate the scale. Metabase by its nature is a cabinet of keys. To build dashboards, it stores credentials of connected data sources: production-Postgres, MySQL replica, ClickHouse, S3-like storages, whatever you have configured there. Once an attacker obtains admin in Metabase, they:
- read and export data through the built-in SQL editor — from your DB perspective, this is a legitimate query from a legitimate user;
- access saved connection credentials and can go directly to databases, bypassing Metabase;
- change settings, create API keys, and stay in the system after you update.
Compromise of Metabase almost always means compromise of everything Metabase is connected to. The update closes the entry but does not eject the one who is already inside. Therefore, “updated and forgot” does not work here.
Step 1. Find out your version (30 seconds)
Metabase exposes the version on a public endpoint — authentication is not required:
curl -s http://localhost:3000/api/session/properties | jq -r '.version.tag'
# → "v0.62.7"
If jq is not available:
curl -s http://localhost:3000/api/session/properties | grep -o '"tag":"[^"]*"' | head -1
For Docker deployments, it’s helpful to see which image is actually running — the latest tag tends to diverge from what you think:
docker inspect --format '{{.Config.Image}} {{.Image}}' metabase
About numbering. Metabase has two lines of the same code: 0.x — open version (OSS), 1.x — Enterprise/Pro. So 0.63.5 and 1.63.5 are the same build with a different license feature set. In the advisory, branches are named without a prefix (“v63”), in Docker image tags the prefix exists. Don’t get tangled in comparisons.
Step 2. Update — or close the endpoint manually
The proper path is to bump to the fixed version in your branch. For Docker this means changing the tag to a specific number (not latest) and recreating the container:
services:
metabase:
image: metabase/metabase:v0.63.5 # or v0.62.9, v0.61.11, v0.60.17, v0.59.21, v0.58.24
If updating is currently impossible — maintenance window, dependencies, schema migrations — the advisory explicitly allows a temporary workaround: close /api/session/reset_password on the reverse proxy. Functionally you only lose the self-service password reset feature.
nginx:
location = /api/session/reset_password {
return 404;
}
Caddy:
@reset path /api/session/reset_password
respond @reset 404
Traefik (label on the service, path-based middleware):
- "traefik.http.middlewares.mb-block.replacepath.path=/blocked"
- "traefik.http.routers.mb-block.rule=Host(`bi.example.com`) && Path(`/api/session/reset_password`)"
- "traefik.http.routers.mb-block.middlewares=mb-block"
What not to do: assume that being exposed behind a VPN or on a private network will save you. Check this claim rather than taking it on faith. A common self-hosting trap is a container published as 3000:3000 on a host with a white IP, while UFW says everything is closed: Docker writes rules in DOCKER-USER/nat and bypasses user INPUT chains. There is a separate discussion about this on the forum — UFW-Docker: how to close ports of Docker containers.
Step 3. Check logs: have they reached you
Metabase published a clear indicator of compromise. The sign of a successful attack is a pair of requests from the same address:
POST /api/session/reset_password→ response 400- followed by
GET /api/user/current→ response 200
The second request with code 200 means that the client has a valid session — meaning the attempt worked. A single 400 on the first endpoint does not by itself indicate anything: it can also be a typo in the recovery form.
Crude search in nginx access logs:
grep -E 'POST /api/session/reset_password HTTP/[0-9.]+" 400' /var/log/nginx/access.log
A bit more careful — extract IPs that have both halves of the pair:
awk '$0 ~ /reset_password/ && $0 ~ / 400 / {bad[$1]=1}
$0 ~ /\/api\/user\/current/ && $0 ~ / 200 / && bad[$1] {print $1}' \
/var/log/nginx/access.log | sort -u
The script is naive (it does not compare order and time), so treat it as a first-approximation filter: you get a list of addresses — go inspect their raw lines.
If Metabase is exposed without a reverse proxy, look for the same requests in the app’s internal logs (docker logs metabase) and in the activity audit log. Remember about retention: if logs live only a week and the attack was on August 3, you may be looking at emptiness. Absence of evidence does not mean no breach.
Step 4. If the version was vulnerable — assume you were compromised
This is unpleasant but the right default: an instance exposed with a vulnerable version from August 3 to August 7 should be treated as if it was accessed. The order from the advisory, plus common sense:
Post-update checklist
- Kill all sessions — clear the
core_sessiontable in Metabase’s service DB. For external Postgres/MySQL:DELETE FROM core_session;. For embedded H2 (default “play around”) you probably don’t have a separate client — it’s easier to recreate the instance from scratch. - Check API keys — Admin → Settings → Authentication → API keys. Delete anything you didn’t create.
- Review the list of administrators — new accounts, unexpectedly elevated roles, changed email addresses of existing admins.
- Rotate passwords of all connected databases — not in Metabase, but on the databases themselves. This is the key point: without it, the update is meaningless.
- Check storage logs, not only Metabase: unusual
SELECT *on large tables, exports, access from unknown addresses. - Review service account permissions under which Metabase accesses the databases. Read-only and restricted schemas — at minimum, which should have been done yesterday.
Who’s already affected
Publicly about access to their data through this vector were reported by at least two manufacturers of laptops, Framework, and the services Tally. Framework describes the leaked set as names, email addresses, login IPs, payment and address data, phones, and company names; payment details, according to the company, were not affected as they are processed on the Stripe side. Both companies used not their own self-hosted solution, but the cloud Metabase — meaning those who never managed BI systems were also hit.
Separately, one should take note of Framework’s wording in its response: the company now writes that it “assesses the scope and depth of data transmitted to BI platforms, and restricts access to the columns necessary for analysis.” Essentially, this is an admission of a typical error — analytics is fed everything by default because that’s easier, and column-level access control is put off for later.
A useful evening exercise, not tied to this particular hole: open your Metabase connections (or Superset, Redash, Grafana — it doesn’t matter) and honestly answer why the analytical pipeline needs access to the table with phone numbers, addresses, and password hashes. In 90% of home labs and small production installations the answer is — “we simply gave access to the whole database so as not to bother.” That is exactly the variable that turns a “dashboard was hacked” into a “customer database leaked.”
What’s unchecked here
I will honestly define the boundaries. The technical details of the exploit (how exactly the payload is formed) are not disclosed by the vendor, and this is correct — a public PoC right now would only speed up mass scanning. There are no official figures on the number of reachable vulnerable instances; numbers that will appear in the news in the coming days should be considered estimates until data from Shadowserver or Censys becomes available. There is no attribution for the attackers — who they were has not been publicly established.
Sources
- Metabase Security Advisory GHSA-vwf4-m7j8-wcjf — primary source: versions, IoCs, response steps
- Metabase blog — Security update — vendor’s official statement from August 6, 2026
- Framework Community — leak discussion — company statement and user reactions
- BleepingComputer — Framework, Tally disclose Metabase data theft attacks
- The Hacker News — Metabase Zero-Day Exploited in the Wild
How do you segregate access to analytics to live databases — a separate read-only role with an explicit schema list, replication, or do you still “give the same password as the application”? And do you keep BI open to the outside world at all, or does it live behind a VPN only?
