My Store Gave Away Every Product It Sold. The AI That Built It Never Noticed.

7 min read

Gareth Hoyle found that anyone with a URL could download all 765 products from his AI-built Claude skills store for free. How it happened, four more problems the fix turned up, and the checks he now runs before shipping.

On this page

I run a store that sells AI skills. In October 2026 I found that anyone with a URL could download all 765 of them for free. Here's how it happened, the four other problems the fix turned up, and the checks I now run on everything I ship.

How I found it

I'm not a developer. I've run an SEO agency since 2006, and since February this year I've built two products almost entirely with Claude Code. One of them, authority.md, sells Claude skills: thinking frameworks drawn from people like Warren Buffett and Chris Voss, plus task tools priced up to $199.

One afternoon in October I was adding a free download to the site, a joke skill called Just Be British that makes your AI write overly British emails.

To see how the existing paid downloads worked, I pasted a product URL into the browser. The file downloaded. No login. No payment. A $149 tool, free to anyone who could guess the address.

It wasn't one file. Every framework, tool and playbook on the site, 765 products and nearly 900 files, was sitting in a public folder. The addresses followed the product names, so guessing them took no skill at all.

I've no way of knowing how many people found it before I did.

Why the AI did it, and why I didn't notice

The AI did exactly what it was asked. The site needed the files somewhere it could read them, and the quickest place in Next.js is the public folder. Anything in that folder is served to the whole internet. That's what it's for.

The actual delivery was built properly. Files are emailed only after Stripe confirms payment. Every test purchase worked. Every demo worked. The leak never showed up, because a leak doesn't throw an error.

That's the pattern. AI coding tools are brilliant at making things work. They're bad at asking who else it works for. You ask for a download link and you get one. Whether a stranger can use it too isn't a question anyone asked.

It was worse than a folder

When I went to fix it, I found the site wasn't just leaving the files lying around. It was handing out directions.

authority.md publishes a products.json feed for AI shopping agents, and it runs an MCP server (listed on HOL Plugins) so any agent can search the catalogue and buy. Both were returning a direct file URL for every product. The feed alone listed 1,530 download links. Ask the MCP server about a framework, and it gives you the price, a preview and a link to the full file.

If you build MCP servers, this is the bit to take away. Whatever your tools return is published. An agent will pass it on to a user, a log, a context window or another agent. A field that seemed harmless in a JSON response, a URL or an internal ID, is now public. I'd built a sales channel for agents and given it a free sample of everything.

The fix took an afternoon. The files moved to a folder the server can read but the web can't. The feed and the MCP server stopped returning file URLs. Old links now redirect to the product page instead of a 404. Then I checked that the delivered files came out byte-for-byte identical to before, and that the live MCP server could still read them. That matters, because the payment code reads the same files.

Four more things the same week turned up

Once you start looking, you find more. None of these were dramatic. All of them were invisible from the front end.

The demo that never cut anything

My home page has a "see the difference" demo that shows half of a paid framework. The browser downloaded the whole file, then trimmed it for display. Except the trim had an off-by-one check that never fired, so it showed the lot. Even if it had worked, the full file was sitting in the network tab. Anything you send to the browser is public. Trimming it there is decoration.

The key I left in a chat

Four weeks earlier I'd pasted a full-access email API key into an AI chat to fix a broadcast. It did the job. It was also still live a month later, with permission to send email from my domain and read my contact list. If you paste a key into a chat to get unstuck, delete it the same day. Better, make a key that can only do one job.

The opt-in box nobody could see

I gave away a free skill in exchange for an email address, with an optional box to tick for marketing. One line of site-wide CSS had stripped the native styling from every form field, checkboxes included. The box was there. It was just invisible, so nobody knew to tick it, and the same rule had hidden a checkbox on another form too.

The fix that broke something I'd forgotten

Moving the files out of the public folder tripped a GitHub Action that generates new frameworks. It went looking in the old folder, found nothing, and decided all 641 frameworks were missing. It failed safely, because the generator itself checked the new folder. When AI helps you move something, it rarely remembers the automation that depended on it.

The mistakes AI-built apps keep making

My leak was one example of a pattern. These are the mistakes I now check for in everything I ship. None of them break a demo.

MistakeWhat it looks likeQuick check
Paid or private files in a public folderDownloads work with no loginPaste a file URL into a private browser window
Tool or API responses that publish too muchFile URLs, internal IDs or emails in JSONRead one raw response from each endpoint and MCP tool
Trusting the browserPrices, roles or user IDs sent from the pageChange the price in your browser's dev tools and check out
Payment webhooks nobody verifiesOrders fulfilled from any request that looks rightFind the line in your webhook that checks Stripe's signature
Delivering on the success pageVisiting /success gives you the goodsOpen your success URL without paying
Open AI endpointsA route that calls a paid model with no login or limitCall it from a private window, then check your usage bill
Secrets in the browser bundleSecret keys with a public prefix like NEXT_PUBLIC_Search your site's JavaScript for sk_live_ and sk-ant-
Hidden, not protectedAdmin pages that are unlinked but openVisit them logged out
Keys and logins that outlive the jobKeys pasted into chats, old collaboratorsList every key and user, revoke what you can't explain

The common thread is that each one works. AI tools are graded on whether the thing works, and so are we.

What I do now before anything ships

This takes an afternoon, and you don't need to read code to do most of it.

  1. Write down where the money moves. Checkout, discount codes, fulfilment, downloads. Each one is a place where trusting the wrong thing costs you.
  2. Use your own site as a stranger. A private browser window, logged out, no payment. Try every download link, success page and admin URL you know.
  3. Read what your app says to machines. Your feeds, your API responses, your MCP tool outputs. If a stranger shouldn't have it, the response shouldn't contain it.
  4. Search what you ship to browsers. View the page source, open the JavaScript files, search for key prefixes. Publishable keys are fine. Secret keys are not.
  5. Count your keys and logins. Every API key, every user on every connected account. Anything you can't explain gets revoked. Anything you've ever pasted into a chat gets rotated.
  6. After every fix, check what else it touched. Scheduled jobs, GitHub Actions, old preview deployments still serving yesterday's files. Then try the original problem again and make sure it's gone.

A free skill that does this for you

I turned the whole process into a Claude skill and gave it away. It's called Security Audit, and it's built for people like me who ship with AI and don't have a security engineer.

It maps your app first: every route, who can call it and where the money moves. Then it works through the mistakes above and more, and proves each finding or labels it as unproven. With your permission, it also checks your live site using read-only requests, because the code alone can't tell you what's exposed. My leak didn't look like a bug in the code. It looked like a URL that worked.

Everything is ranked by what it could cost you: money, customer data, accounts or a surprise bill. It ends with a fix brief you paste straight into Claude Code, then re-checks once you've fixed things.

It has rules it won't break. It only audits apps you own. It never writes exploit code. And it will never tell you your app is "secure", because nobody honest can. It isn't a penetration test, and if a customer or insurer needs one, get one.

It isn't the only option either. Cloudflare's open-source security-audit-skill runs a multi-phase audit with separate agents verifying each finding, and it's built for security researchers working through larger codebases. krinalme's ai-security-audit includes a scanner script you can run on its own, and I borrowed four of its ideas for our version 1.1. Use whichever fits. Just use something before your customers find the problem for you.

You can get it free at authority.md/free/security-audit. You'll need an email address, because that's how it's delivered.

All articles

Keep exploring

The engineering desk continues across the ecosystem: security research on shipping agents safely, and plugins that put the standards to work.