Payload CMS Security Scanner — Find public collection leaks
Pricing
Pay per usage
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
Maintained by CommunityActor 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 inusers,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:
users— anyone gets to enumerate emails, hashed passwords (yes, the hash + salt are returned), roles, login attempt countsorders/transactions— full purchase history, Stripe IDs, shipping addressesmedia— 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:
- Leave inputs empty + click Run for a DEMO sample report
- Provide your
payloadUrlto scan your actual instance
{"payloadUrl": "https://cms.your-domain.com","collectionHints": ["my-custom-collection"],"outputFormat": "both"}
What you get
- HTML report (
report.htmlin 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 anonymouslyTotal records: 2,891Sample columns: id, email, name, role, createdAt, updatedAt, loginAttempts, lockUntil, hash, saltSensitive columns detected: email, hash, saltReproducer: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.tsimport { 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=1to confirm exposure without exfiltrating contents
Related
- Stripe audit ($99 one-time): buy.stripe.com/00w9AT9TWdaW7yx9KkcAo01
- Weekly auto-scans ($29/mo): rls-monitor.vercel.app
- Sister scanners: Supabase, Firebase, Strapi, Directus, Payload CMS, Convex, Hasura, PocketBase, Appwrite, Nhost.
Built by Renzo.