Pocketbase security: 6 collection-rule checks before you self-host.

2026-05-08 · vibecheck team · 7 min read · Platform · Pocketbase

Quick answer Pocketbase has five rule slots per collection (List, View, Create, Update, Delete) and the default for each is blank — which means anyone, including unauthenticated visitors, can perform that operation. Vibe coders often skip the rules entirely while prototyping and never go back. The six checks: list rule, view rule, create rule, update rule, delete rule, and the admin UI's exposure (the /_/ path). All are checkable from outside without any credentials.

Pocketbase is the cleanest "Firebase replacement you can self-host" out there. The single binary + SQLite design is a joy. The security model is a clean rules engine that uses Pocketbase's own filter syntax — but the catch is that blank rules mean wide-open access, not blocked access.

This is the opposite of Supabase, where blank rules + RLS enabled means denied. New Pocketbase users coming from Supabase consistently get this wrong on launch day.

1. List rule

The List rule controls who can call GET /api/collections/<name>/records. If blank, anyone in the world can list every record.

Test from outside:

curl 'https://your-pocketbase.example.com/api/collections/users/records?perPage=1'

# If this returns { "items": [...] } without an Authorization header,
# the list rule is blank.

Fix: Pocketbase Admin UI → Collections → your-collection → API Rules → List rule. Recommended starting point for ownership-shaped data:

@request.auth.id != "" && user.id = @request.auth.id

That reads: caller must be authenticated, AND the record's user field must equal the caller's user ID.

For "everyone can read, only owner can write":

// List rule: empty string = blocked? No — empty in Pocketbase means OPEN.
// Use this instead to allow public reads:
@request.auth.id != "" || @collection.is_public = true

// Or for fully public read access:
@request.method = "GET"  // (this is implicit; just leave the rule blank
                          //  ONLY if you genuinely want public reads)

2. View rule

The View rule applies to GET /api/collections/<name>/records/<id> — fetching a single record by ID. Often used in conjunction with the List rule for cases where listing is restricted but individual lookups by ID are allowed (rare, usually wrong).

Test:

curl 'https://your-pocketbase.example.com/api/collections/users/records/<some-id>'

The View rule should match your List rule unless you have a specific reason for them to differ.

3. Create rule

Controls POST /api/collections/<name>/records. If blank, anyone can insert records — fake reviews, spam content, abusive payloads.

Recommended:

@request.auth.id != "" && @request.data.user = @request.auth.id

That requires an authenticated session, AND the user field of the new record must match the caller's user ID. Without the second clause, attackers can create records owned by other users.

4. Update rule

Controls PATCH /api/collections/<name>/records/<id>. If blank, anyone can modify any record.

Recommended:

@request.auth.id != "" && user.id = @request.auth.id

Note this references user.id (the existing record's user) not @request.data.user (the new value). The update rule must verify the caller owns the record they're trying to modify, not whatever user ID they sent in the body.

5. Delete rule

Controls DELETE /api/collections/<name>/records/<id>. If blank, anyone can delete any record.

This is the most catastrophic rule when left blank — bulk deletion across an entire collection is a single curl-loop away.

@request.auth.id != "" && user.id = @request.auth.id

6. Admin UI exposure

Pocketbase serves its admin UI at /_/. By default it's reachable from anywhere on the internet — only protected by the admin login.

Test:

curl -I 'https://your-pocketbase.example.com/_/'
# 200 means the admin UI is reachable.

If you've left the admin password as the one from the initial setup CLI, you're one credential-stuffing run away from full database compromise.

Fixes (in order of preference):

  1. Strong, unique admin password. Stored in a password manager. If you can't remember it, that's the right shape.
  2. IP-restrict the admin UI at your reverse proxy:
    # Caddy
    your-pocketbase.example.com {
      @admin path /_/*
      handle @admin {
        @allowed remote_ip 1.2.3.4 5.6.7.8
        handle @allowed { reverse_proxy localhost:8090 }
        respond 403
      }
      handle { reverse_proxy localhost:8090 }
    }
  3. Run Pocketbase behind a VPN-only IP for the admin endpoint, with a public reverse proxy for /api/* only.

Bonus: the files field exposure

Pocketbase's built-in file storage serves uploaded files at predictable URLs:

https://your-pocketbase.example.com/api/files/<collection>/<recordId>/<filename>

If your collection's View rule is blank or permissive, attackers can enumerate file URLs via the records API and download every uploaded file.

Verify by listing records (above) and checking that file URLs in the response actually require auth:

curl -I 'https://your-pocketbase.example.com/api/files/uploads/abc123/photo.jpg'
# 200 = anyone can fetch this file
# 401/403 = view rule is enforcing access

The deployed-side check

vibecheck's BaaS detector finds Pocketbase deployments via the *.pockethost.io URL pattern (or extracted from your client bundle for self-hosted instances), then probes /api/collections and common collection names (users, posts, messages, items, comments) for unauthenticated access.

Items 1, 3, 5 are the big three — list, create, delete. If you only have time for three checks, start there.

Inspect your Pocketbase deployment