HexBrain

Shopify · Report #3954327

Unauthenticated access to Checkout Blocks employee-only endpoints exposed merchant metadata

Validstatus
mediumseverity
$3,100bounty
33upvotes
3 Nov 2026disclosed

Summary

Missing authentication allowed unauthenticated visitors to access employee-only Checkout Blocks tools and see which stores had installed the app, when they installed it, and which features were enabled for them. Further internal review discovered that attackers could also turn Checkout Blocks features on or off for stores without permission.

Shopify added verified employee authentication, and production checks confirmed the anonymous bypass was blocked. The fix protects both the internal store information and the tools used to change store features.

How it was found

Summary:

checkout-blocks.shopifyapps.com has staff pages at /internal and /api/internal/. Ask for one without an employee session and you get redirected to minerva.shopifycloud.com, your employee login. That redirect is the only thing protecting them.

The rule matches on the text of the URL. This app runs on Next.js, which serves every page at a second URL too: /_next/data/<buildId>/<route>.json. That does not start with /internal, so the rule ignores it, and Next.js hands the request to the same code.

Those pages then answer 200 and JSON to a request with no cookie and no authorization header. All four staff routes behave this way. The build id is not a secret either; it is printed in the public /login page.

Shops Used to Test:

████████. I did not contact any other merchant's store.

Steps To Reproduce:

Run the attached poc.sh. No arguments, no credentials.

1 normal  /api/internal/shops      -> 302  https://minerva.shopifycloud.com/auth/minerva?...
2 bypass  /_next/data/ID/..shops   -> 200  628 bytes  application/json
3 ghost   /_next/data/NOPE/..      -> 404  (expect 404)
4 control /_next/data/ID/..admin   -> 302  .../login  (expect 302 login)
5 last page  -> {'total': 201520, 'pages': 40304, 'current': 40298} | shops returned: 5

Line 1 is the gate working, line 2 the same endpoint answering an anonymous client. Line 3 shows the second URL is not a catch all. Line 4 makes this a bypass rather than a public endpoint: at the same URL style, a route that checks its own session still refuses. Line 5 reaches page 40298 of 40304, and total is your server's own number.

Two things that make a re-test look like a false negative. The build id changes on every deploy, so a stale one returns 404 and looks fixed; read it from /login at run time as the script does. And test something under /api/internal/, because /api/admin/ is protected properly and will look like the boundary holding.

Remediation

Check the session in the route's own code, as /api/admin/* on this server already does at that same URL style. A rule at the edge matching the start of a URL cannot see a framework rewrite. If it stays at the edge, cover /_next/data/<buildId>/api/internal/* too.

Attached

poc.sh. Run with bash poc.sh. {F6507558}

Impact

Summary:

Anyone on the internet, with no Shopify account and without anyone clicking anything, accesses a console you protect with employee login.

What comes back is the app's merchant install list, which your server reports as 201,520 records across 40,304 pages, each with the shop domain, an internal shop id and the install date. It also returns which merchants have internal flags turned on, including checkout_blocks_allow_plus_features and allowPaidUpgrade. It includes non public staging stores belonging to identifiable brands, and it is live: the count rose four times while I tested.

I stopped deliberately. Every record in that list belongs to a merchant I have no relationship with, so the questions that would establish the real ceiling, how much can be pulled, whether anything can be written, what the other staff pages hold, all need handling of data that is not mine. I would rather send an under explored report than over explore other people's data.

For that same reason I am not proposing a severity and have left the field empty. Please rate it on what is actually reachable from this bypass, not on the little I was willing to demonstrate.

This report was disclosed publicly on HackerOne and belongs to its author and the program. See the original