AI-coded apps

    Replit Agent: how do I secure the Postgres connection string?

    A Replit workspace showing the Secrets pane open with DATABASE_URL highlighted, beside a built JavaScript bundle that contains a hardcoded Postgres connection string ready to ship to every browser

    A Replit Agent app moves fast: by the time you remember to look at the database tab, Agent has spun up a Postgres database, pasted a connection string into DATABASE_URL, and wired Drizzle or SQLAlchemy on top. The question, the day before you flip the deployment to public, is whether that string is reachable from anywhere it should not be.

    Short answer

    Replit Agent stores the Postgres connection string in Replit Secrets as DATABASE_URL, encrypted at rest with AES-256 according to Replit's Secrets documentation. The vault holds. The risk lives in what Agent writes back into the project: a connection string inlined as a VITE_ prefixed variable, a fallback string in .replit or a config file, a route that lets the client query the database directly. Audit the bundle, lock down the Postgres role, force sslmode=require, and route every query through a server handler before publishing.

    What you should know

    • Replit Secrets encrypt DATABASE_URL at rest with AES-256 and serve it as a runtime environment variable, per the Replit Secrets documentation.
    • A connection string used as a VITE_, NEXT_PUBLIC_, or REACT_APP_ prefixed variable is inlined into the browser bundle and leaks on first load.
    • Replit's current SQL database documentation says a DATABASE_URL is scoped to your app, but legacy Neon-backed Repls still expose a portable connection string that anyone who reads it can use.
    • Neon, which powers Replit Agent's Postgres under the hood, mandates SSL/TLS for every connection and adds channel binding as protection against man-in-the-middle attacks.
    • Static Deployments do not load Secrets at runtime, so a Replit Agent project deployed as static cannot read DATABASE_URL from the vault and will either fail closed or be patched with a hardcoded string.

    How does Replit Agent set the DATABASE_URL in your project?

    Replit Agent detects persistence intent in your prompt ("save users", "store posts", "track sessions") and provisions a Postgres database through Neon. Once the database is created, Agent injects a single environment variable named DATABASE_URL into the Repl's Secrets, which the Neon team documents in its walkthrough of adding a Postgres database to a Replit Agent project. Agent then writes the application code that reads the variable through process.env.DATABASE_URL or os.getenv("DATABASE_URL") and passes it to whichever ORM it picked.

    The vault piece is sound. According to the Replit Secrets documentation, values are encrypted at rest with AES-256 and travel between the workspace and the runtime over TLS. Visitors who fork or remix a public Repl see secret names but not values. Agent itself can read the values during a session, which is the trade-off you accept when you ask a coding model to wire up Stripe, Auth, or Postgres for you.

    The connection string itself follows a standard Postgres URI shape: postgresql://user:password@host:5432/dbname. Older Replit Repls are still on what Replit calls the legacy Neon development database, and the Replit SQL database documentation is direct: if you are still on the legacy Neon development database, do not share your DATABASE_URL, because it contains your database credentials and anyone who reads it can access and modify your database. Newer development databases are app-scoped, which Replit describes by saying the string can only be used by your app and, even if leaked, cannot be used by anyone else. Both can exist in the same account, and Agent does not always tell you which one it picked.

    Where does Replit Agent leak the Postgres connection string?

    The leak is rarely the vault. The leak is what Agent writes into your codebase while building. Four patterns show up over and over in pre-submission audits of Replit Agent apps.

    The first is the bundler-inlined prefix. Vite, Next.js, and Create React App inline any variable whose name starts with VITE_, NEXT_PUBLIC_, or REACT_APP_ into the static JavaScript bundle that ships to every browser. If Agent decides to rename DATABASE_URL to VITE_DATABASE_URL to make it available to the client, the encrypted Secret is still encrypted at rest in Replit, but its value is copied verbatim into a public .js file at build time. Every visitor can open DevTools and read the password.

    The second is the fallback constant. When Agent cannot read a Secret during a particular phase of generation, it writes a literal fallback into a config file or into application code: const DATABASE_URL = process.env.DATABASE_URL || "postgresql://user:hunter2@...". In the workspace this works. In a deploy, the fallback path remains, and any contributor with read access to the Repl, or anyone who forks a public Repl, sees the credentials.

    The third is the direct-from-browser query. Agent sometimes generates a frontend that connects to the database from the client side using a library like postgres-js or @neondatabase/serverless. The connection string is read from import.meta.env or window.ENV, which means it must be inlined at build time. The Neon driver runs over the HTTP serverless endpoint, so it works in the browser, but it also means whoever opens the page can run select * from users.

    The fourth is the .replit and replit.nix files. Some Agent generations write environment values directly into the .replit config file, which is checked into the Repl and is part of any fork. Anyone who clones the Repl after that gets the connection string for free.

    How do you harden the connection before publishing?

    Three settings and one role change do most of the work. None of them are exotic.

    Move the Repl to private. Public Repls allow visitors to view source. The Replit Secrets documentation clarifies that secret values are hidden on the Cover Page and on Remix, but anything Agent wrote into your code that references those values is visible in the source tree. Privacy on the Repl does not encrypt your code, but it cuts the unauthenticated reader off.

    Use a Reserved VM or Autoscale deployment, not a Static deployment. Per the same Replit documentation, Secrets are available for all deployment types except Static Deployments. A static site has no server, so any database call must run from the browser, which forces the connection string into the bundle. A Reserved VM or Autoscale deployment runs your server code with the Secrets injected at runtime, so DATABASE_URL never reaches the client.

    Strip every client-side reference to DATABASE_URL. Grep the project for DATABASE_URL, postgres://, and postgresql://. Any match in a file that lives in src/, public/, client/, or anything that gets bundled by Vite or Next.js is a leak. The same goes for any variable Agent might have proxied: PGHOST, PGPASSWORD, NEON_DATABASE_URL.

    The fourth move is on the database side, in the Neon SQL editor. A connection string is only as safe as the role it authenticates as. Replit Agent, by default, hands you the owner role on the Neon project, which can drop tables, create roles, and read every row. Create a least-privilege role for the application:

    create role app_user with login password 'long-random-string';
    grant connect on database neondb to app_user;
    grant usage on schema public to app_user;
    grant select, insert, update on table public.posts to app_user;
    

    Then update DATABASE_URL in Replit Secrets to authenticate as app_user instead of the owner. If the string leaks, the blast radius is limited to the tables you granted, not the entire database. Force SSL on the connection too. Neon's connect-securely guide states that Neon requires all connections to use SSL/TLS encryption so that data sent over the Internet cannot be viewed or manipulated by third parties. Append ?sslmode=require to the URL. For higher assurance, the same guide recommends adding channel_binding=require, which makes the client and server mutually authenticate each other using SCRAM-SHA-256-PLUS and blocks a class of man-in-the-middle attacks that are otherwise plausible on shared networks.

    Here is how the common patterns compare in practice:

    PatternWhere the string livesSafe?Fix
    process.env.DATABASE_URL in server codeReplit Secrets at runtimeYesKeep
    VITE_DATABASE_URL in client codeBundled into public JSNoRename, move to server
    Hardcoded fallback in sourceSource treeNoDelete, rotate role password
    Neon HTTP driver from the browserInlined at buildNoMove queries behind a server route
    Static DeploymentNo runtime SecretsNoSwitch to Autoscale or Reserved VM
    .replit config with literal valueForkable fileNoMove to Secrets, rotate

    When should the database be reachable from the client at all?

    For most Replit Agent projects, never. The database belongs behind your own HTTP routes, not as a direct dependency of the browser code. If the app reads or writes user data on behalf of someone signed in, the chain should look like this: the browser calls /api/posts on your Replit-deployed server, the server reads DATABASE_URL from Replit Secrets through process.env, the server runs a parameterized query through your ORM, and the server returns JSON. The browser never sees the connection string, the password, or the host.

    This pattern matters for mobile too. A React Native or Flutter app pointed at a Replit Agent backend has the same boundary: the server endpoint is in the bundle, the DATABASE_URL is not. Mobile traffic should pin the TLS certificate of your Replit Reserved VM domain or rely on the App Transport Security defaults on iOS and the Network Security Configuration on Android. Direct database connections from a mobile app are the same mistake as from a web app, with the extra cost that rotating a string baked into a published binary requires a new build and a new App Store Connect or Google Play submission.

    For builders who want an external second read of the compiled build, PTKD.com is one of the platforms focused on pre-submission scanning aligned with OWASP MASVS for no-code and AI-coded apps. It will not audit your Replit Postgres role, but it will tell you whether your mobile binary still carries a database string from an earlier Agent prompt.

    What to watch out for

    A few patterns trip people up even after the obvious mistakes are fixed.

    Scoped connection strings are not unconditionally safe. The current Replit SQL database documentation says a leaked string from the new app-scoped database cannot be used by anyone else, but that scoping is enforced by Replit's networking layer, not by Postgres. The protection holds inside the Replit infrastructure, not against an attacker who has compromised your Repl itself. Treat the string as a secret regardless of which generation of Replit Postgres it belongs to.

    A Repl that was public for ten minutes is permanently public. Replit preserves file history. A connection string committed at any point lives in the history viewer until you rotate the credential and delete the Repl outright. The honest response after any exposure is to rotate the password in Neon, then move the project to a private Repl.

    The Replit Agent's promise that it has set up the database does not include the promise that it has configured SSL. A surprising number of Agent generations omit sslmode=require from the connection string, even though Neon mandates SSL on the wire. Postgres will still connect, since Neon negotiates TLS, but the client code does not refuse plaintext fallback. Add the parameter explicitly.

    Static Deployments are the most common silent trap. Agent will happily target a Static Deployment for a project that secretly relies on DATABASE_URL at runtime, and the first signs are runtime errors after the deploy, which Agent then tries to fix by hardcoding the value. Choose Autoscale or Reserved VM at deploy time, not when you discover the bug.

    Key takeaways

    • Replit Agent stores DATABASE_URL in encrypted Replit Secrets, which is sound. The exposure lives in what Agent writes back into client-bundled files, not in the vault.
    • A connection string prefixed with VITE_, NEXT_PUBLIC_, or REACT_APP_ ends up in the browser bundle on every build. Rename it and move every database call into a server route.
    • Create a least-privilege Neon role for the application and append ?sslmode=require&channel_binding=require to every connection string, per Neon's secure-connection guidance.
    • Audit the project bundle before flipping the Repl to public or deploying. Some teams outsource that pre-submission scan to platforms like PTKD.com (https://ptkd.com) when the build targets iOS or Android.
    • Treat any leaked connection string as a rotation event. Replit preserves Repl history, so deleting the file is not enough.
    • #replit
    • #replit-agent
    • #postgres
    • #database-url
    • #neon
    • #secrets
    • #ai-coded-apps
    • #vibe-coding

    Frequently asked questions

    Does Replit Secrets keep DATABASE_URL out of my Replit Agent app's bundle automatically?
    No. Replit Secrets encrypt the value at rest and inject it as an environment variable, but the application code decides where the value goes. If Replit Agent renames the variable to VITE_DATABASE_URL or reads it through import.meta.env in client code, Vite or Next.js inlines the value into the public JavaScript bundle at build time. Keep DATABASE_URL server side and call the database only through your own HTTP routes.
    Why does the Replit documentation say a leaked DATABASE_URL is safe on the new database?
    Replit's SQL database documentation states that the current app-scoped DATABASE_URL cannot be used by anyone else to access and modify your database. That protection is enforced by Replit's networking layer, not by Postgres itself, and it does not apply to legacy Neon development databases. Treat the string as a secret either way, because the scoping only holds as long as the network rule does.
    Should I add ?sslmode=require to a Replit Agent Postgres connection string?
    Yes. Neon's connect-securely guide states that Neon requires all connections to use SSL/TLS encryption, and sslmode=require has the client verify the server certificate. Replit Agent often omits the parameter from the string it generates. Adding it explicitly removes the risk that a misconfigured client falls back to plaintext, and pairing it with channel_binding=require blocks SCRAM-related man-in-the-middle scenarios.
    Can I connect from a mobile app directly to my Replit Postgres database?
    You can, technically, but the connection string ends up inside the compiled binary, and any user can extract it with standard reverse-engineering tools. Mobile apps should call your Replit-deployed server endpoints, which then run the Postgres queries. Rotating a string that shipped inside an APK or IPA forces a new build and a new App Store Connect or Google Play submission, which is rarely worth it.
    What happens if I deploy a Replit Agent Postgres app as a Static Deployment?
    The Static Deployment cannot read Replit Secrets at runtime, because Replit's documentation says Secrets are available for all deployment types except Static Deployments. The deploy will either fail to read DATABASE_URL, or Replit Agent will respond by hardcoding the value into the static bundle to fix the runtime error. Switch the deployment type to Autoscale or Reserved VM before publishing.

    Keep reading

    Scan your app in minutes

    Upload an APK, AAB, or IPA. PTKD returns an OWASP-aligned report with copy-paste fixes.

    Try PTKD free