NEON SECURITY CHECKLIST
Neon’s branching model is its killer feature and its most common security failure. Branches inherit data — including PII — from the parent at creation time, and the connection string for a branch can grant access to its data forever after. The checklist below is what we look for first when we audit a Neon-backed app.
Treat Critical as launch-blocking. High is week-one. Medium is the cleanup once the database is in production.
How to use this checklist
Walk it once in the Neon console for your production project, ticking items as you go. Audit each branch separately. After the whole list passes, scan the app that connects to verify no client-side leak undoes the database-side hardening.
Critical (fix before launch)
1. Restrict project access to the smallest team
Why it matters. Neon project members can create branches, generate connection strings, and read every database in the project. The wider the access, the larger the surface for a leaked credential to land somewhere it shouldn’t.
How to check. Settings → Access. Audit who has Owner / Member roles. Cross-reference against your active dev team.
How to fix. Remove inactive members. For temporary contributors, add and remove access on a tight schedule rather than leaving long-lived accounts.
2. Use scoped connection strings per service, not a master one
Why it matters. Neon roles are per-database. A connection string for the project’s primary role grants access to every database. A scoped role grants access only to the schemas and tables it needs. Production services should never use the broad role.
How to check. Audit each service’s DATABASE_URL. Decode the role; confirm it has minimum privileges via \du in psql.
How to fix. Create per-service roles with specific GRANTs. Switch the service to the scoped role, then revoke the broad role from the service.
3. Enable IP allowlisting on the production branch
Why it matters. Without IP restrictions, anyone with the connection string can connect from anywhere. Combined with credential exposure (which happens), a leaked password is immediately exploitable.
How to check. Settings → Network → IP Allow. Confirm only your app server IPs are allowed.
How to fix. Add the IP ranges of your app hosting (Vercel, Fly, AWS, etc.). For platforms with rotating IPs, use a NAT gateway with a stable IP.
4. Verify branching does not clone production data into dev
Why it matters. Neon branches are created with a copy-on-write of the parent’s data. A branch off main has every row in main, including production PII. Developers connecting to “their dev branch” are connecting to production data.
How to check. Audit which branches exist and what their parent is. For any non-production branch with main as parent, confirm the team is intentionally working with production data and the branch is treated as production-sensitive.
How to fix. For dev work, branch off a sanitized staging branch that has anonymized data, not from main. Document the branching policy.
5. Rotate connection strings if they appeared in committed env files
Why it matters. Connection strings end up in .env files that get committed to git, in browser bundles via NEXT_PUBLIC_DATABASE_URL mistakes, and in CI logs. Once leaked, they’re long-lived unless rotated.
How to check. gitleaks detect --source . and search git history for postgres:// connection patterns. Search the deployed bundle for the same patterns.
How to fix. Rotate the password (in Neon, this means resetting the role’s password). Switch the service to the new password, redeploy.
6. Confirm no application code uses superuser-equivalent roles
Why it matters. Neon’s project owner role can create extensions, modify privileges, and operate on any schema. App services should never use it.
How to check. Decode each DATABASE_URL’s role. In Neon console, confirm the role is not the project owner.
How to fix. Create an app-specific role with limited privileges. Use it everywhere; rotate the owner password.
High (fix in the first week)
7. Configure pooled vs direct connections per workload
Neon offers connection pooling on a separate hostname. Use the pooled hostname for serverless workloads (Vercel, Lambda, Cloud Run) where connection count would otherwise spiral. Use direct for workloads with stable connection counts.
8. Enable point-in-time recovery on production
Neon supports PITR. Set retention to a window that matches your incident response timing — at least 7 days for most teams. Test the restore path.
9. Audit which branches developers have access to
Branches inherit the project’s access list, but you can scope by role. Audit which branches each team member can read; restrict where appropriate.
10. Verify branch names don’t leak in URLs
Neon connection hostnames include the branch ID. Don’t put database hostnames in client-bundled code; the branch ID can give attackers context for further attacks.
11. Set query timeouts at the database level
Long-running queries are usually a bug or an attack. Configure statement_timeout per role so a runaway query can’t take down a workload.
12. Audit who can create branches
Branch creation is cheap to do and expensive to clean up — especially branches off main that drag PII into developer environments. Restrict branch creation to the team that should do it.
Medium (fix when you can)
13. Pin the Postgres version
Neon lets you choose the Postgres major version. Pin it explicitly so a project upgrade doesn’t surprise you with behavior changes.
14. Document branch promotion rules
A short doc covering: who can promote a branch to production, what review is needed, how data migrations are coordinated. Saves a lot of reactive work.
15. Set up alerting on connection-count anomalies
Sudden spikes in connection count (or in failed-auth attempts) signal an attack or a misconfigured service. Alert on outliers.
16. Audit installed Postgres extensions
Each extension is attack surface. Keep the list short; pin versions.
17. Restrict who can rotate passwords
Password rotation requires admin access. Audit who has it; restrict to the smallest possible group.
18. Set up backup verification on a schedule
PITR is great until you need it and it doesn’t work. Periodically test the restore to a scratch branch and verify the data is what you expected.
After every branching event
- Confirm the new branch’s data is appropriate for its purpose (sanitized for dev, production-equivalent for staging).
- Verify the new branch’s connection string is not granted to broader services than it should be.
- For dev branches, set a deletion date.
- Re-confirm IP allowlist includes whichever services should reach the branch.
Common attack patterns we see in Neon apps
The leaked dev connection. Developer commits .env.local with their personal dev branch URL. Branch was forked from main; “dev” is actually production data. Connection string sits in git history forever.
The superuser app role. Service uses the project owner role. SQL injection in the app gives the attacker DROP TABLE, CREATE EXTENSION, and the ability to add new admin users.
The unrestricted IP. Project skipped IP allowlisting “for convenience”. Leaked password is immediately usable from any laptop in the world.
The forgotten branch. Long-lived feature branch sits with production data on it for months after the feature was abandoned. Branch ID is in git logs; password was generated and forgotten.
Related Resources
How to Secure Neon
Step-by-step guide for hardening a Neon project — branching policy, role design, IP allowlisting, and the connection string handling above in long form.
Is Neon Safe?
In-depth analysis of Neon’s defaults — what branching does, what’s locked down, and what we find when we audit a production Neon project.
Automate Your Checklist
A checklist tells you what to look for. A scanner tells you what’s actually broken in the deployed app that connects to your database. VibeEval scans the app, attempts SQL injection and credential-leak vectors, and reports what got through.
SCAN YOUR APP
14-day trial. No card. Results in under 60 seconds.