Payload CMS Security Scanner — Find public collection leaks avatar

Payload CMS Security Scanner — Find public collection leaks

Pricing

Pay per usage

Go to Apify Store
Payload CMS Security Scanner — Find public collection leaks

Payload CMS Security Scanner — Find public collection leaks

Probes a public Payload CMS instance for collections readable without auth. Default templates leave read access wide open during development.

Pricing

Pay per usage

Rating

0.0

(0)

Developer

Renzo Madueno

Renzo Madueno

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

18 hours ago

Last modified

Categories

Share

Every Payload CMS template I've checked (template-blog, template-website, template-ecommerce) ships with access: { read: () => true } on most collections so you can browse the API in development. That setting often makes it to production unchanged — and the moment it does, anyone with your CMS URL can list every record in users, orders, media, custom collections, you name it. This actor finds those leaks in 30 seconds.

Probes a public Payload CMS instance for collections readable without authentication. Sends /api/{collection}?limit=1 per collection. Returns counts + a verbatim curl reproducer per finding. Counts only — never row data.

💸 Found a leak? I do turnkey Payload audits + access-function rewrites for $99 (Stripe — 48h, money-back). Or $29/mo weekly auto-scans at rls-monitor.vercel.app.

⭐️ Solo dev competing with bigger Apify publishers. A 30-second review is the single thing that lifts ranking. Thank you.

Why this exists

Payload CMS uses per-collection access functions for authorization. The framework is excellent — but the defaults in every official template are permissive:

// from payloadcms/template-blog (default):
export const Posts: CollectionConfig = {
slug: 'posts',
access: {
read: () => true, // ← anyone can read all posts
},
// ...
};

That's fine for posts (you probably want them public). But the same () => true pattern is copy-pasted onto:

  1. users — anyone gets to enumerate emails, hashed passwords (yes, the hash + salt are returned), roles, login attempt counts
  2. orders/transactions — full purchase history, Stripe IDs, shipping addresses
  3. media — file metadata + the signed URLs to download them

This scanner probes ~30 common collection slugs plus any you pass as hints.

How to run

Either:

  1. Leave inputs empty + click Run for a DEMO sample report
  2. Provide your payloadUrl to scan your actual instance
{
"payloadUrl": "https://cms.your-domain.com",
"collectionHints": ["my-custom-collection"],
"outputFormat": "both"
}

What you get

  • HTML report (report.html in run's KV store): severity-coded findings, copy-pasteable curl reproducers, exact code change to fix each leaky collection
  • Dataset rows: one structured row per finding

Sample finding

[CRITICAL] users — readable anonymously
Total records: 2,891
Sample columns: id, email, name, role, createdAt, updatedAt, loginAttempts, lockUntil, hash, salt
Sensitive columns detected: email, hash, salt
Reproducer:
curl 'https://cms.your-domain.com/api/users?limit=1' -I

How to fix (code change, ~5 min per collection)

In your Payload config, for each leaky collection, set access.read:

// collections/Users.ts
import { CollectionConfig } from 'payload/types';
export const Users: CollectionConfig = {
slug: 'users',
access: {
read: ({ req: { user } }) => Boolean(user), // ← require auth
// or strict per-record:
// read: ({ req: { user } }) => ({ id: { equals: user?.id } }),
},
// ...
};

If the collection should be public (blog posts, product catalog), use Payload's field-level access control:

fields: [
{ name: 'title', type: 'text' }, // public
{ name: 'authorEmail', type: 'email',
access: { read: ({ req: { user } }) => Boolean(user) } }, // auth-only
],

Ethical use

  • Only scan instances you own or have explicit permission to scan
  • Probe queries use ?limit=1 to confirm exposure without exfiltrating contents

Built by Renzo.