PocketBase Self-Hosted: 7 Ways Your Backend Gets Owned
PocketBase is a self-hosted, single-binary open-source backend-as-a-service written in Go. It's elegant, fast, and shipping in thousands of projects. It also has a consistent pattern of misconfiguration we find on audits. Admin panels exposed, permissive record rules, auth bypass patterns, and hook misuse that turn a clean little binary into a data exposure.
Founder of Valtik Studios. Pentester. Based in Connecticut, serving US mid-market.
Why PocketBase is everywhere
Here's the part consultants don't put in the glossy PDF.
PocketBase is one of the most popular self-hosted BaaS platforms of the last two years. Single binary. SQLite database embedded. Admin UI included. Authentication, file storage, real-time, hooks, and an API auto-generated from your schema. All in one 25MB executable.
Developers love it. Ship an MVP in an afternoon. Scale to thousands of users before needing to think about architecture. Host anywhere. A $5 VPS, a Raspberry Pi, a corporate Kubernetes cluster.
The same properties that make it a great developer tool make it a recurring security finding. PocketBase deployments we audit show consistent patterns: permissive default configurations, admin panels reachable from the internet, record rules that allow more access than intended. And hooks that introduce logic bugs.
This post covers the seven attack patterns we find most often on PocketBase deployments, the specific misconfigurations that enable them. And the hardening each one needs.
Pattern 1: Admin panel exposed to the internet
PocketBase ships with an admin UI at /_/. This interface provides full database access. View any record, modify any collection, create/delete anything. It's behind admin authentication but the authentication is a single username/password (email + password) pair.
The misconfiguration: developers bind PocketBase to 0.0.0.0:8090 and expose port 8090 to the internet (directly, via Docker, or through a reverse proxy). The admin panel is then reachable to anyone who finds the domain.
Enumeration is trivial. Shodan queries for PocketBase-specific signatures find thousands of exposed admin panels. The PocketBase default page returns a specific HTML structure that's fingerprintable.
Attack:
- Find exposed admin panel:
https://target.example.com/_/ - Try default credentials (email
admin@example.comor similar, common passwords) - Brute-force credentials (PocketBase has rate limiting but not aggressive)
- On success: full database access
The fix:
- Never expose the admin panel to the public internet. Put it behind VPN, IP allowlist, or reverse proxy with additional auth.
- Use a reverse proxy with authentication for the admin path specifically:
# Caddyfile
target.example.com {
@admin path /_/*
handle @admin {
basic_auth {
admin $2a$14$...
}
reverse_proxy localhost:8090
}
handle {
reverse_proxy localhost:8090
}
}
- Use strong admin credentials. No one should be running PocketBase with default or weak passwords. Force strong passwords, rotate regularly, use a password manager.
- Bind PocketBase only to localhost, then reverse-proxy through a properly-configured nginx / Caddy / Traefik.
./pocketbase serve --http=127.0.0.1:8090.
Pattern 2: Permissive record rules
PocketBase collections (think: database tables) have five rule types:
- List rule. Who can list all records in the collection
- View rule. Who can read individual records
- Create rule. Who can create new records
- Update rule. Who can update records
- Delete rule. Who can delete records
Rules are expressed as filter expressions. Common safe patterns:
@request.auth.id != "". Only authenticated usersuser = @request.auth.id. Only the record owner@request.auth.verified = true. Only verified users
Common unsafe patterns we find:
- Empty rules. All actions allowed to anyone. PocketBase's UI shows this as "Public." Developers often leave rules empty during development and forget to set them.
- Only authentication, no ownership check.
@request.auth.id != ""allows any authenticated user to act on any record. A legitimate user can modify other users' data. - Rules based on client-provided data.
@request.data.isAdmin = trueallows the client to declare themselves admin in the request.
Attack scenarios:
- Unauthenticated user lists all users from a collection that wasn't meant to be public → PII exposure
- Authenticated user modifies another user's profile by guessing record IDs → integrity attack
- Attacker creates admin records by setting admin flags in request data
Real finding: a customer support tool had a tickets collection with list rule = empty. Any unauthenticated user could list every ticket across every customer. The application relied on the UI not showing tickets to unauthorized users, but the raw API returned them to anyone.
The fix:
- Review every rule for every collection. The right mental model is: "who should be able to do this operation, and under what conditions?"
- Test rules with API calls, not the UI. The UI may filter what it shows. The API doesn't.
- Use PocketBase's rule-tester in the admin panel to verify each rule works as expected.
- Default-deny. Start with restrictive rules. Loosen only where needed.
Pattern 3: The "@collection" request syntax leak
PocketBase supports cross-collection queries in rules:
@collection.users.id?= @request.auth.id && @collection.users.role = "admin"
This syntax joins data from other collections during rule evaluation. It's powerful but commonly misused.
The vulnerability: when rules reference other collections, the querying user needs at least view access on the referenced collection for the join to work. If that view rule is permissive, the join leaks data.
Real finding: an application used @collection.permissions.user = @request.auth.id && @collection.permissions.level = "admin" to authorize admin-level operations. The permissions collection had an overly permissive view rule. Any authenticated user could read the permissions collection, observe that their own level was "admin". And wasn't. But then enumerate other users' permissions to identify admins for further targeting.
The fix:
- Minimize use of
@collectionreferences in rules - When using them, ensure the referenced collection's view rules don't accidentally leak data
- Consider using PocketBase's internal user role system than custom permission collections
Pattern 4: Authentication bypass via auth-method confusion
PocketBase supports multiple authentication methods: email/password, OAuth (Google, GitHub, etc.), anonymous. And one-time tokens. Each is configurable per collection.
Common bugs:
- Anonymous auth enabled on collections that should require password auth. Anonymous users can sometimes access features intended for authenticated users.
- OAuth with email verification disabled. A user can sign up with any email address without owning it. This enables account takeover if the application links accounts by email.
- One-time tokens with weak or predictable generation. Tokens used for password resets or magic links, if predictable, allow account takeover.
Real finding: an application linked user accounts across OAuth providers by email. A user who signed up via Google (email verified by Google) could be impersonated by an attacker who signed up via a custom OAuth with the same email. Because the custom OAuth didn't verify email ownership, the attacker's account linked to the original user's data.
The fix:
- Audit each auth method's configuration. Disable methods you don't need.
- Require email verification for all auth methods.
- Don't automatically link accounts across OAuth providers. Require explicit linking with re-authentication.
- Use strong token generation for password resets and magic links.
Pattern 5: File storage enumeration
PocketBase supports file upload. Files are stored in a configurable backend (local filesystem or S3-compatible storage). Each file has a generated unique filename and can have per-file view rules.
Common misconfigurations:
- File view rule empty or permissive. Files intended for specific users are readable by anyone who guesses the URL.
- Files stored with predictable names. Sequential or timestamp-based naming enables enumeration.
- No access control on the underlying storage. S3 bucket with public read access. Local filesystem with directory listing enabled.
Real finding: a photo-sharing app stored user photos with view rule set to authenticated users. Any authenticated user could list another user's photos via the API and download them.
The fix:
- File view rules should match the parent record's view rules. If the record is user-specific, the file should be user-specific.
- Use PocketBase's generated filenames (random, not predictable).
- Secure the underlying storage. Local filesystem should have no directory listing. S3 buckets should be private; PocketBase generates signed URLs.
Pattern 6: Hook misuse and side effects
PocketBase has hooks. Event callbacks that run custom Go code or JavaScript in response to database events (create, update, delete, auth).
Hooks are powerful. They can:
- Modify records before / after save
- Call external APIs
- Send emails
- Enforce complex business logic
- Bypass permissions in some cases (run as admin)
Common hook-related bugs:
Bug 1: Permission bypass via hook with admin context
A hook runs with admin privileges by default. If the hook's logic trusts user-provided data, an attacker can escalate.
// BAD: hook that trusts user-provided admin flag
onRecordBeforeCreateRequest((e) => {
// User provides data.isAdmin in request
if (e.record.get('isAdmin')) {
e.record.set('role', 'admin'); // escalation
}
});
Bug 2: External API calls without auth validation
// BAD: hook that calls external API with user-provided URL
onRecordAfterCreateRequest((e) => {
const webhookUrl = e.record.get('webhookUrl');
// User controls webhookUrl. SSRF
fetch(webhookUrl, { method: 'POST', body: JSON.stringify(e.record) });
});
Bug 3: Email sending without rate limiting
// BAD: hook that sends email without rate limit
onRecordAfterCreateRequest((e) => {
sendEmail(e.record.get('email'), 'Welcome');
// Attacker creates thousands of records → thousands of emails
});
The fix:
- Validate all user-provided data in hooks
- Use allowlists for external API destinations
- Rate-limit external side effects
- Test hooks against the attacker's perspective
Pattern 7: Outdated / unpatched PocketBase versions
PocketBase releases updates regularly. Security patches happen. Installations that haven't been updated in 6+ months accumulate known vulnerabilities.
Common issues:
- Self-hosted deployments forgotten after initial setup
- No automatic update mechanism
- Docker images pinned to old tags
- Cloud provider marketplace images running old versions
The fix:
- Subscribe to PocketBase GitHub releases
- Automate PocketBase updates via your deployment pipeline
- Test updates in staging before production
- Maintain a quarterly (minimum) update cadence
The hardening checklist
For any PocketBase deployment, work through this checklist:
Infrastructure
- [ ] PocketBase binds only to localhost, reverse proxy handles external traffic
- [ ] Admin panel
/_/protected by IP allowlist or additional auth layer - [ ] TLS configured (via reverse proxy like Caddy)
- [ ] Regular PocketBase version updates
- [ ] Backups configured with access controls
- [ ] Logs enabled and monitored
Admin access
- [ ] Admin credentials strong and unique
- [ ] Admin access via personal accounts, not shared
- [ ] MFA on admin accounts (via reverse proxy, since PocketBase's native admin doesn't support MFA)
- [ ] Periodic review of who has admin access
Collections and rules
- [ ] Every collection's rules explicitly set (no empty rules unless public is intended)
- [ ] Ownership-based rules (
user = @request.auth.id) for user data - [ ] Rule testing for each collection confirmed via API calls
- [ ] Cross-collection rule references minimized
Authentication
- [ ] Only needed auth methods enabled
- [ ] Email verification required
- [ ] Strong password policy enforced
- [ ] OAuth account linking requires re-auth
- [ ] Password reset tokens cryptographically random
Files
- [ ] File view rules match parent record view rules
- [ ] Generated filenames used (not user-provided)
- [ ] Underlying storage secured (S3 private, local with no directory listing)
Hooks
- [ ] Hooks validate user-provided data
- [ ] External API calls use allowlists
- [ ] Side effects rate-limited
- [ ] Hooks tested from attacker perspective
Monitoring
- [ ] Admin login attempts logged
- [ ] Rate-limiting on auth endpoints
- [ ] Alerting on unusual patterns
- [ ] Regular log review
Specific hardening examples
Caddyfile for PocketBase with admin IP allowlist
target.example.com {
# Admin access restricted to office IP
@admin {
path /_/*
not remote_ip 203.0.113.10/32
}
respond @admin 403 {
body "Admin access denied"
}
# Health check for monitoring
@healthz {
path /api/health
}
reverse_proxy @healthz 127.0.0.1:8090
# General API
reverse_proxy 127.0.0.1:8090
# Logs
log {
output file /var/log/caddy/pocketbase.log
}
}
Rule examples for common scenarios
User-owned records (profile collection):
List rule: @request.auth.id!= ""
View rule: user = @request.auth.id
Create rule: @request.auth.id!= "" && @request.data.user = @request.auth.id
Update rule: user = @request.auth.id
Delete rule: user = @request.auth.id
Public-readable, authenticated-writable (posts collection):
List rule: // (public)
View rule: // (public)
Create rule: @request.auth.id!= "" && @request.data.author = @request.auth.id
Update rule: author = @request.auth.id
Delete rule: author = @request.auth.id
Admin-only collection:
List rule: @request.auth.id!= "" && @collection.users.id?= @request.auth.id && @collection.users.role = "admin"
View rule: @request.auth.id!= "" && @collection.users.id?= @request.auth.id && @collection.users.role = "admin"
Create rule: @request.auth.id!= "" && @collection.users.id?= @request.auth.id && @collection.users.role = "admin"
Update rule: @request.auth.id!= "" && @collection.users.id?= @request.auth.id && @collection.users.role = "admin"
Delete rule: @request.auth.id!= "" && @collection.users.id?= @request.auth.id && @collection.users.role = "admin"
(For admin-only, consider using PocketBase's native admin system instead of a role-based collection.)
Detection: is your PocketBase already compromised?
Signs of compromise to check:
- Unexpected admin accounts. Review the admins list for any account you don't recognize.
- Unusual record modifications. Review audit logs for anomalous data changes.
- Unknown collections. Check if any collections have been created that you don't recognize.
- Unusual file uploads. Files uploaded outside normal user activity windows.
- Anomalous outbound connections. Monitor network traffic for unexpected external connections (potential hook-based data exfiltration).
- Modified hooks. Check pb_hooks directory for unexpected files.
If you find any of these, treat as potential compromise: rotate credentials, audit logs, consider full database export and integrity review.
For small projects and indie developers
PocketBase is popular with indie developers and small projects specifically because of its simplicity. Those contexts often have limited security budgets.
The minimum-effort security posture for a small PocketBase project:
- Reverse proxy PocketBase through Caddy (automatic HTTPS, minimal config)
- IP-allowlist the admin panel to your home/office IP
- Set explicit rules on every collection
- Use strong admin password from a password manager
- Update PocketBase monthly
This gets you from "publicly exploitable" to "reasonably hardened" with maybe 90 minutes of work. If your project grows beyond indie scale, the additional hardening from the full checklist becomes warranted.
For businesses using PocketBase
If you're running PocketBase for a commercial product at meaningful scale, the considerations expand:
- Compliance requirements. Does your industry require additional controls? HIPAA, PCI DSS, SOC 2 all have requirements that may push you beyond what PocketBase alone provides.
- Audit logging. PocketBase's native logging may not be sufficient for compliance. Consider sidecar logging, external SIEM.
- High availability. PocketBase is single-binary. Clustering is limited. For high-availability scenarios, you may need to migrate to different infrastructure.
- Backup and disaster recovery. Formal backup strategy with tested restoration.
- Penetration testing. Annual third-party assessment scoped to your specific PocketBase deployment.
If PocketBase is core to a production commercial product, treat it with the same rigor as any other production system.
For Valtik clients
Valtik's BaaS platform audits include PocketBase reviews:
- Infrastructure and exposure review (admin panel, reverse proxy, network)
- Collection rule audit (every collection, every rule)
- Authentication configuration review
- Hook logic review for security bugs
- File storage security review
- Update cadence and patch management
Typical PocketBase audit is 1-2 weeks from kickoff to findings report. Smaller than general application audits because PocketBase's attack surface is narrower. But still produces findings on most deployments. Reach out via https://valtikstudios.com.
The honest summary
PocketBase is an excellent tool. Fast to deploy, easy to operate, capable enough for production use. It ships with reasonable defaults for developer experience. Security hardening requires explicit attention: admin panel isolation, record rule discipline, auth method review, hook validation, update cadence.
The patterns in this post aren't unique to PocketBase. They appear in every self-hosted BaaS platform. What makes PocketBase worth its own post is its popularity and its "quick to deploy" characteristic. Those two combined put security review low on developers' to-do lists until something bad happens.
Audit your PocketBase deployment before someone else does.
Sources
- PocketBase Official Documentation
- PocketBase Security Best Practices
- PocketBase API Rules and Filters
- PocketBase Authentication Methods
- PocketBase Hooks
- PocketBase GitHub Repository
- PocketBase GitHub Security Advisories
- Caddy Server Documentation
- OWASP API Security Top 10
- BaaS Platform Security Research. Valtik
Want us to check your PocketBase setup?
Our scanner detects this exact misconfiguration. plus dozens more across 38 platforms. Free website check available, no commitment required.
