Metabase as an Attack Surface: What the ShipMonk Trezor Incident Tells Developers to Audit First

Metabase as an Attack Surface: What the ShipMonk Trezor Incident Tells Developers to Audit First

pr0h0
metabasecybersecuritydata-breachapplication-security
AI Usage (86%)

The useful part of the ShipMonk/Trezor reporting is not the headline number by itself. The real clue is the architecture hiding inside it: if customer records moved through Metabase, then the reporting path was part of the exposure path.

My position is simple: Metabase should be treated as a privileged data access surface, not as a harmless dashboard layer. If you only audit the application in front of it, you can miss the path that actually leaks data.

What the ShipMonk/Trezor reporting actually confirms

The 67,000-record claim and what it refers to

From the source material provided here, the confirmed claims are limited but still meaningful:

  • the incident is described as involving ShipMonk and Trezor
  • the report says roughly 67,000 U.S. customer records were exposed
  • the reporting ties the exposure to Metabase
  • the report frames the issue as a zero-day vulnerability and gives a CVE label

That is enough to tell me this was not just a lost spreadsheet or a careless export. It points to a platform-level failure in a system trusted to sit between raw operational data and human eyes.

What the source material does not prove

The seed material does not prove several things people often assume after reading a short incident write-up:

  • it does not prove the exact initial access path
  • it does not prove whether the weakness was in Metabase itself, a misconfiguration around Metabase, or both
  • it does not prove whether the data was accessed through an unauthenticated dashboard, a public link, a leaked session, an overbroad service account, or another route
  • it does not prove the full scope of impacted records beyond the number reported
  • it does not independently verify the CVE details

So the right way to read the incident is: the public reporting says Metabase was involved in a customer-record exposure, and the exact mechanics still need primary-source confirmation.

📝

For this post, I am treating the report’s account as the starting point, not as a complete forensic record.

Why Metabase is not just a dashboard layer

The trust boundary developers often miss

Metabase looks like a UI product, but in a real deployment it usually sits on top of three things that matter more than charts:

  1. database credentials
  2. saved queries and access rules
  3. export and sharing features

That makes it a trust boundary. If Metabase can reach production tables, then Metabase can often reveal more than the original application ever shows in its own UI.

The mistake I see over and over is treating the dashboard as read-only, therefore safe. Read-only is not the same thing as low-risk. A read-only analytics path can still:

  • expose PII
  • expose internal identifiers
  • expose order histories
  • reveal customer contact data
  • leak operational metadata
  • provide aggregate views that are easy to de-anonymize

How internal analytics tools turn into data-exposure paths

A BI tool becomes an exposure path when it gets any of the following wrong:

  • it can query too much of the database
  • it can query the wrong database
  • it can be reached by the wrong people
  • it can share results outside the expected trust zone
  • it can export or embed results without enough control

In practice, this usually happens in stages. First a team wires Metabase to production because it is fast. Then someone makes a “temporary” permission exception. Then a dashboard gets shared broadly because the business wants visibility. Then a public link or embed is added for convenience. By the time anyone reviews it, the tool has become a secondary application with its own attack surface.

The first things I would audit in a Metabase deployment

Network exposure and authentication posture

The first question is not “does Metabase have a bug?” It is “who can talk to it?”

I would check:

  • is the instance reachable from the public internet
  • is login required for every dashboard and card
  • is SSO enforced, or is local auth still enabled
  • are public sharing links enabled
  • are embedded dashboards allowed without a tight allowlist
  • are admin endpoints exposed on the same network path as user-facing dashboards

A quick exposure check can start with simple HTTP probing from a machine that should not have privileged access:

curl -I https://metabase.example.internal/
curl -I https://metabase.example.internal/login
curl -I https://metabase.example.internal/api/health

What I am looking for is not just status code 200 or 302. I am looking for the security posture behind the response:

  • 200 on a dashboard that should be private is a finding
  • 200 on an API route that should require auth is a finding
  • a login page that accepts weak local auth while SSO is “supposed” to be mandatory is a gap
  • a public dashboard that renders customer data is a finding even if it is “only aggregate” data

If the instance is internet-reachable, I would treat that as a red flag unless the deployment is intentionally public and tightly limited.

Database permissions and service-account scope

The second audit target is the database user Metabase uses.

A lot of teams give analytics tools more database power than the app itself. That is backwards. Metabase should usually get the smallest possible read scope, ideally on views or a replica, not on raw operational tables.

The questions I ask are:

  • does Metabase connect with a dedicated read-only account
  • does that account have access to all schemas, or only a curated subset
  • does it touch production tables directly
  • can it see customer identifiers, emails, order details, or support notes
  • is row-level security preserved through the access path
  • are there materialized views or extracts that already contain sensitive joins

If you can inspect grants, do it. On PostgreSQL, for example, I would verify table access like this:

select grantee, table_schema, table_name, privilege_type
from information_schema.table_privileges
where grantee = 'metabase_ro'
order by table_schema, table_name, privilege_type;

A scary result is not just SELECT on a table. A scarier result is SELECT on everything in the production schema, including tables that were never meant to back analytics.

Saved questions, embeds, and public links

Saved questions are often where the real risk hides. A dashboard is just a wrapper; the query behind it is what matters.

I would inventory:

  • saved questions against sensitive tables
  • dashboards that combine multiple data sources
  • public sharing links
  • embedded cards and dashboards
  • download permissions for CSV, XLSX, or raw result sets
  • any link that bypasses normal application authorization

The thing to watch for is drift. A query starts as an internal finance dashboard, then gets reused by support, then gets embedded in an ops portal, then gets made public for convenience. Each step widens the blast radius.

Administrative access and tenant separation

If your company uses Metabase for multiple teams or customers, admin separation matters more than people expect.

I would check whether:

  • one admin can see every card and dashboard
  • tenant data is logically separated, not just labeled by collection
  • customer-facing dashboards are isolated from internal ones
  • there is any path from one tenant’s analytics to another tenant’s data
  • app administrators and Metabase administrators are the same people by default

My opinion is that shared admin access is one of the most underestimated risks in BI tooling. Once someone has admin in Metabase, “read-only” becomes a very weak word.

Reproducing the risk in a safe lab

A minimal test setup for checking exposure paths

You do not need production data to test the security shape of a Metabase deployment. A safe lab can be a local Metabase container plus a toy database with fake records.

A minimal local start looks like this:

docker run --rm -d \
  --name metabase-lab \
  -p 3000:3000 \
  metabase/metabase:latest

Then point it at a local test database and create one dashboard with synthetic data. The goal is not feature testing. The goal is to ask one question repeatedly: what can an unauthenticated or low-privilege user see?

I would test these paths:

  1. open the root URL without login
  2. try to access a dashboard link directly
  3. try to load an embedded card
  4. try to download a result set
  5. try to enumerate saved content through the UI
  6. try to reach any API route that should be authenticated

What responses or behaviors would count as a finding

A safe lab is useful because it gives you concrete pass/fail behavior.

A finding might look like this:

HTTP/1.1 200 OK
Content-Type: text/html
Cache-Control: no-cache

<html>
  <body>
    <div id="app">Customer revenue dashboard</div>
  </body>
</html>

That means a dashboard rendered without the expected authentication gate.

A safer result would look more like this:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="Metabase"

Or this, if the tool is intentionally private behind SSO:

HTTP/1.1 302 Found
Location: /auth/sso

The point is not the exact status code. The point is whether the response matches the trust model you think you configured.

What the incident suggests about application design

Why “internal tool” is not a security boundary

This is the part developers still get wrong.

Calling something “internal” does not make it safe. Internal tools are often more dangerous than public ones because they accumulate broad access quietly. They are built for convenience, then left out of the normal security review cycle.

The report’s Metabase angle suggests a familiar pattern: the application that was supposed to be internal ended up being a readable surface for sensitive customer data. That can happen even without a flashy exploit. A mis-scoped instance, a public link, or a weakly protected admin path is enough.

Why read-only access can still leak sensitive data

Read-only access is not a defense if the read surface is too rich.

A Metabase query can still reveal:

  • names and emails
  • shipping addresses
  • order histories
  • customer identifiers
  • device ownership details
  • support issues that should not be broadly visible

Even when the underlying tables are not directly editable, the combination of joins and exports can produce a very sensitive dataset. In my view, that is why BI tools deserve the same threat modeling you would give any other backend service.

Defensive controls that matter first

Restricting Metabase to private networks or strong SSO

If I had to rank controls, network restriction and auth would be first.

Prefer:

  • private-network only access
  • SSO enforced for all users
  • no local password fallback unless absolutely necessary
  • IP allowlists for admin paths where practical
  • separate admin access from normal user access

If you cannot make the instance private, you need compensating controls that are much stronger than “people know not to share links.”

Limiting the data it can query

The next control is reducing what Metabase can see.

Use:

  • read-only database roles
  • views instead of raw tables
  • replica databases when possible
  • row-level security where it survives the analytics path
  • denormalized analytics schemas with stripped identifiers
  • separate datasets for ops metrics versus customer data

The safest pattern is to feed Metabase curated data, not production tables.

Monitoring for unusual dashboard access and exports

If a BI tool leaks data, the first sign is often access behavior, not an obvious exploit.

Watch for:

  • dashboard access from unusual IPs
  • bursts of CSV exports
  • repeated access to high-sensitivity dashboards
  • new public links
  • admin role changes
  • spikes in query volume against sensitive collections

If your logging only tells you “someone viewed a dashboard,” that is not enough. You want enough detail to answer which card, which query, which role, and which export path.

Separating analytics data from production identifiers

One of the best defenses is boring but effective: stop putting production identifiers everywhere.

Where possible:

  • pseudonymize customer IDs
  • hash or tokenize direct identifiers
  • keep contact details out of analytics unless strictly needed
  • separate operational reporting from customer-facing data
  • limit joins that reconstruct a full identity profile

This does not remove risk. It lowers the value of every leaked query result.

What developers should do after this kind of report

A short audit checklist for product and platform teams

If a report mentions Metabase, I would audit in this order:

  1. Is the instance reachable outside the intended network?
  2. Is authentication mandatory for every user path?
  3. What database account does Metabase use?
  4. Which schemas and tables can that account see?
  5. Are public links or embeds enabled?
  6. Who can create or share dashboards?
  7. Are exports monitored?
  8. Is customer data separated from analytics data?
  9. Are tenant boundaries preserved?
  10. Can an admin or power user see more than they should?

That checklist is short on purpose. It catches the biggest mistakes first.

How to document confirmed facts versus assumptions

This is the habit I want teams to adopt after any incident report:

Statement typeExample
Confirmed by source“The report says 67,000 U.S. customer records were exposed through Metabase.”
Confirmed by testing“This dashboard returns 200 without login in our lab.”
Inference“The blast radius likely grew because the analytics account could reach production tables.”
Unconfirmed“The exact initial exploit path would need primary-source confirmation.”

That separation matters because incident discussions get sloppy very quickly. People start arguing about root cause before they have even pinned down the access path.

Conclusion: the real lesson from the breach

The lesson here is not “BI tools are bad.” The lesson is that BI tools are part of the application trust boundary, and they should be reviewed like one.

If the ShipMonk/Trezor reporting is accurate, the important failure was not only that Metabase existed. It was that Metabase had enough reach, visibility, or sharing power to turn a reporting surface into a customer-data exposure path.

That is the audit I would do first in any deployment: network exposure, auth, database scope, sharing, and admin separation. If those are weak, the rest of the stack does not matter much.

Share this post

More posts

Comments