Replit database publicly accessible: how to find the cause and fix it
A Replit database becomes public in two ways: the connection string leaks, or your own app routes return the rows to anyone who asks. The database itself is not open to the internet by default. In most cases the app is the open door. The fix has two parts. Regenerate the database credentials, then add an auth check to every route that reads or writes data.
This guide shows how to find which of the two cases applies to you. It then gives the steps in order of urgency, shows what to check after a leak, and lists controls that stop the problem from coming back.
What does “Replit database publicly accessible” mean?
It means that a person who is not you can read or change the data in your database. The person does not need your Replit account. There are three common causes.
| Cause | What the attacker needs | How common in AI-built apps |
|---|---|---|
| The connection string leaks | The string, for example from a public repository or a log | Common |
| An API route returns rows without auth | Only the URL of your deployed app | Very common |
| A query route accepts raw SQL or unchecked filters | Only the URL of your deployed app | Less common |
Replit gives each app a development database and a production database. According to the Replit connection details page, DATABASE_URL is scoped to your app, so other apps cannot reach your development database. The same page says that you can connect to the production database from any PostgreSQL-compatible SQL client. You only need the connection string from the Settings tab.
This is a normal design. The connection string works as a password. Anyone who has it can use it from anywhere. For this reason, the string must stay in a secret and out of your code, your logs, and your chat messages.
The second cause is more frequent. An AI agent builds a server with routes such as /api/users or /api/orders. The routes run SELECT * FROM users and return the result. The agent does not add a login check, because you did not ask for one. The database is private, but the route is public. The effect for the attacker is the same.
How do I check if my Replit database is exposed?
Run these checks in order. Each check takes a few minutes.
- Open your deployed app in a private browser window. Do not sign in. Use every page and watch the Network tab in the browser DevTools.
- Copy each API URL that returns data. Look for URLs that return lists of users, orders, messages, or files.
- Call each URL without a login. Use
curlfrom a terminal. The command below shows the pattern. - Change the ID in each URL. If
/api/orders/12works, try/api/orders/13. If you see another user’s order, the route has no owner check. - Search your code for the connection string. Search for
DATABASE_URL,postgres://, andpostgresql://. A real string in a file means a leak. - Search your git history. A string that you deleted later stays readable in old commits.
curl -i https://your-app.replit.app/api/users
curl -i https://your-app.replit.app/api/orders/13
Replace the host with your real deployed URL. A 200 status with JSON data, without any login, confirms the problem. A 401 or 403 status means that the route checks the caller.
Warning: Test only your own app. Do not call the routes of an app that you do not own.
Also check the Secrets tool of your Replit app. The Replit Secrets documentation says that secrets are encrypted and available to your code as environment variables. If your connection string is in a normal file and not in a secret, move it now.
What do I do first? Fix it now, in order of urgency
Do the steps in this order. Steps 1 and 2 stop an attacker who already has the string. The other steps remove the cause.
Caution: Step 1 changes the production password. The live app can fail for a short time. Replit redeploys the app if it has a live deployment.
- Regenerate the production credentials. The Replit connection details page gives the steps. In the Tools pane, select Database. Select Production Database, then the Settings tab. Under Advanced, expand Regenerate credentials. Select the button, confirm, and select Refresh to load the new string.
- Replace every copy of the old string. The page says that existing connection strings stop working at once. Update the SQL clients, the scripts, and the other tools that hold a copy.
- Take the string out of your code. Delete it from every file. Store it as a secret and read it with
process.env.DATABASE_URLor the equivalent in your language. - Remove the string from the git history. If the repository is public or shared, treat the old string as copied. Step 1 already makes it useless. Clean the history as well, so that scanners stop reporting it.
- Add an auth check to every data route. Read the next section.
- Add an owner check to every route that uses an ID. A signed-in user must read only the rows that belong to that user.
- Deploy again and repeat the checks. Use the
curltests from the previous section on the new deployment.
The regeneration changes only the password. The page says that the host, the database name, and the role stay the same. Your data stays in place.
How do I protect the routes of my Replit app?
A route that reads the database needs three things. It must know who the caller is. It must check that the caller may see the rows. It must return only the fields that the caller needs.
Ask the Replit Agent for a change in one clear sentence. Name the routes and the rule. For example: “Add a session check to every route under /api. Return 401 if there is no signed-in user. Filter each query by the user ID.”
Then read the code yourself. This is the pattern for a Node server with Express and the pg library:
app.get("/api/orders", requireLogin, async (req, res) => {
const { rows } = await pool.query(
"SELECT id, total, status FROM orders WHERE user_id = $1",
[req.user.id]
);
res.json(rows);
});
The requireLogin function is your own middleware. It checks the session and sets req.user. If there is no session, it returns 401. The query uses the user ID from the session, not from the request. The query lists the columns and does not use SELECT *.
Use one rule for the whole app: the server decides who the user is, and the client never does. Do not accept a user_id from the request body or from the URL as proof of identity.
Check three more things:
- Admin routes. A route that lists all users needs an admin check, not only a login check.
- Raw SQL routes. Delete any route that accepts a full SQL string from the client. A route like this gives every caller full control of your database.
- Write routes. Check
POST,PUT, andDELETEroutes. A public write route lets an attacker change or delete data.
For the same problem on a different stack, read users can see other users’ data. The cause and the fix are the same: the server does not check the owner.
How do I check the damage after a database leak?
You must find out if someone used the string or the open routes. The answer decides if you must tell your users.
- Read the logs of your deployment. Open the logs of your deployment in Replit and read the period of the leak. Look for requests to data routes from IP addresses that you do not know.
- Look for loops. An attacker who changes the ID in a URL sends many requests in a short time. Search for the same route with a rising ID.
- Check the data for changes. Look at the newest rows and the last-updated times. Look for rows that you did not create.
- Check for new database roles or tables. An attacker with a full connection string can create objects. List the tables and compare them with the schema that you expect.
- Check other places that held the string. If the string was in a public repository, read the activity of that repository. Check who cloned or forked it.
- Check what the data contains. Make a list of the personal data in the exposed tables: emails, names, addresses, payment details. This list tells you your legal duties.
A request log does not prove that nobody read the data. If the logs are short or missing, assume the worst case for the period that you cannot see.
Replit also gives recovery tools. The SQL database documentation describes time travel, which restores the database to an Agent checkpoint. Use it only if you find that an attacker changed or deleted data. Read the data recovery page of Replit first. A restore also removes the valid changes that your users made after the checkpoint.
What about the Replit key-value database?
Replit also has a simple key-value store, called Replit Database. The Replit Database documentation says that you use environment variables in your app to access the database.
The same rules apply. Treat the access URL or token as a password. Do not print it, do not commit it, and do not send it to the browser. Do not write a route that reads or writes any key that the client names. A route like /api/get?key=... gives every caller your whole store.
Use a key naming rule that includes the user ID, for example user:123:settings. Then the server builds the key from the session and not from the request. If you store personal data, a Postgres table with an owner column is easier to check than a flat key space.
How do I stop this from happening again?
Use these controls on every Replit app that stores user data.
- Keep all credentials in Secrets. The Secrets tool encrypts the values. The Replit Secrets documentation says that people who remix from the cover page see the secret names but not the values.
- Use separate development and production data. Replit keeps two databases for this purpose. Do not copy real user data into the development database.
- Make “auth required” the default. Add one middleware to the whole
/apipath. Then mark the few public routes as exceptions. A new route is then private from the first day. - Write a rule for the Agent. Add a line to your project instructions: “Every route that reads the database must check the session and filter by the user ID.” Review each change that adds a route.
- Return only the fields that you need. Do not return password hashes, tokens, or internal fields. Check each response by hand in the Network tab.
- Limit the database role. Use a role that cannot drop tables, for the app. Keep the admin string for manual work only.
- Scan the deployed app after each release. A scan calls your real URLs, as an attacker does, and finds routes that return data without a login.
For more background, read the Replit guide, the Replit security scanner overview, and database security best practices.
When should I get help?
Get help in these cases:
- The data contains personal or payment information, and you find signs that someone else read it. You may have a legal duty to report the breach. Ask a security engineer or a lawyer.
- You find tables, roles, or rows that you did not create. An attacker may have kept access in a way that a password change does not remove.
- The logs for the period of the leak are missing. You cannot prove what happened, so a professional review helps.
- You cannot find which route returns the data. A scan of the deployed app lists the routes that answer without a login.
- You find the same string in other apps or in a public repository. Rotate the credentials in each place, and check the logs of each place.
After the fix, repeat the tests from the beginning of this guide. Open a private window, call each data route without a login, and confirm that each one returns 401 or 403.
Common questions
Can anyone connect to my Replit database from the internet?
Is DATABASE_URL safe to put in my code?
Does regenerating credentials delete my data?
Why does my app show data to people who are not logged in?
Do Replit Secrets protect me if someone remixes my app?
Test your replit app
A VibeEval scan calls the public routes of your deployed Replit app and reports the routes that return database rows without a login.
14-day free trial · No credit card · Cancel anytime