Replit deployment leaking secrets: how to find and fix it
A secret in a published Replit app is leaked when it appears in the JavaScript, in a file the site serves, or in a log, and the fix is to rotate the key at the provider first and then move it to server code. Replit Secrets are safe on the server. The leak starts when your code or your build sends the value to the browser.
This guide shows the four common ways a Replit deployment leaks a secret. It shows how to test for each one, how to fix the leak, and how to check the damage.
What does “Replit deployment leaking secrets” mean?
It means that a value you meant to keep private is readable by someone who has no access to your Replit account. Any person on the internet can open a published app. Any person can read the files that the browser downloads.
Replit stores a secret in the Secrets tool. The Replit Secrets documentation says that Replit encrypts the data and gives it to your app as an environment variable. Server code reads the variable with process.env or os.environ. The value stays on the server.
The leak happens in the steps after that. These are the four common causes:
| Cause | How the secret reaches the public |
|---|---|
| Public prefix | A variable name that starts with VITE_ or NEXT_PUBLIC_ is copied into the JavaScript bundle at build time. |
| Hardcoded value | The code has the key as a string. The key ships in the bundle, or it sits in a file that the site serves. |
| Static Deployment | A Static Deployment has no server and no Secrets. Any key in the site is public. |
| Log output | The code prints the variable, and the value appears in logs or in an error page. |
AI tools often put an API call in a front-end component, because that is the shortest path to a working demo. The call needs a key, so the tool adds the key to the code or to a variable with a public prefix. The app works, and the leak stays hidden.
How do I check if my Replit app leaks a secret?
Use the checks below on the public URL of the published app. Do not use the editor preview. The editor shows files that visitors cannot see, so a clean preview proves nothing.
Check 1: Read the network requests
- Open the published app in a browser.
- Open DevTools and select the Network tab.
- Use each feature that calls an outside service, such as an AI model, a payment API, or an email API.
- Select each request that goes to an outside host.
- Read the request headers and the request URL.
If the browser sends the request to the outside host directly, and the request holds a key, the key is public. A safe app sends the request to your own server route. The server route adds the key.
Check 2: Search the bundle
Download the JavaScript that the page loads and search it for key patterns. Replace the URLs with your own.
curl -s https://your-app.replit.app/ | grep -o 'src="[^"]*\.js"'
curl -s https://your-app.replit.app/assets/index-abc123.js -o bundle.js
grep -o -E 'sk-[A-Za-z0-9_-]{10,}' bundle.js | head
grep -o -E 'Bearer [A-Za-z0-9._-]{10,}' bundle.js | head
grep -c -E 'apiKey|api_key|secret|password' bundle.js
The first command lists the script paths. The next commands search for common key shapes. The sk- pattern is only one format, so also search for the first characters of your own keys. Do not paste a found key into a chat or a ticket.
Check 3: Request common secret files
Some apps serve the project folder as static files. If that happens, a file such as .env can be downloaded. Test these paths on your public URL:
for p in .env .env.local .env.production config.json secrets.json .git/config; do
printf "%s " "$p"
curl -s -o /dev/null -w "%{http_code}\n" "https://your-app.replit.app/$p"
done
A 200 status code for a secret file is a leak. A 404 status code is the expected result. Also check that the response is not the home page of a single-page app. Open the response body to see what the server returns. See source maps and exposed git folders for more checks of this type.
Check 4: Search the project
Open the Replit search. Look for api_key, secret, token, sk_live, whsec_, VITE_, NEXT_PUBLIC_, and console.log(process.env. Each hit that holds a real value is a leak, if the file is part of the published app. Check the git history too. A key that you removed in a later commit stays readable in old commits.
Warning: Do not test a found key by sending requests with it from a public tool. Each request costs money and appears in the usage logs of the provider.
What do I do first? Fix it now, in order of urgency
Do the steps in this order. Steps 1 and 2 stop the loss. The other steps remove the cause.
Caution: When you delete a key, the feature that uses the key stops working. This is the correct result. A broken feature costs less than a stolen key.
- Rotate the leaked key at the provider. Open the dashboard of the service, such as OpenAI, Stripe, or Supabase. Create a new key. Delete the old key. A deleted key stops working at once.
- Set a spend limit where the provider offers one. A limit caps the cost if a second key leaks.
- Store the new key in the Secrets tool. Use a name without a public prefix. Use
OPENAI_API_KEY, notVITE_OPENAI_API_KEY. - Move the call to server code. The browser sends the user request to your own route. The route adds the key and calls the outside service. See the next section.
- Delete the old value from the code and from the files. Remove hardcoded strings. Remove
.envfiles that hold real values. Remove the old variable from the Secrets tool. - Use a server deployment, not a Static Deployment. The Replit Secrets documentation states that Secrets are available for all deployment types except Static Deployments.
- Add the new secret to the published app. The Replit docs describe a section named Production app secrets. Find it under Publishing, then Adjust settings. Make sure the production value is the new key.
- Publish again. An old published snapshot can still hold the old files. A new publish replaces them.
- Repeat the checks on the public URL. Run the network check, the bundle search, and the file requests again.
Note: Do step 1 before the code fix. The old key can be copied at any time while it is valid. A code change does not make the old key safe.
If the leaked value is a database password, a Supabase service_role key, or a payment secret, read the guide for that secret too. For the Supabase key, see what to do about a leaked service_role key.
How do I keep the key on the server?
The key must never be part of the code that runs in the browser. The browser must send only the user input. The server must add the secret.
The Vite and Next.js docs describe the rule for build variables. The Vite environment variable guide states that variables with the VITE_ prefix are exposed to the client source code, and that they must not hold sensitive information. The Next.js environment variable guide states that Next.js inlines NEXT_PUBLIC_ variables into the JavaScript that it sends to the browser. Variables without the prefix stay on the server.
Here is a small Express route for a Replit app. It checks a session, limits the input, and adds the key on the server:
import express from "express";
const app = express();
app.use(express.json({ limit: "10kb" }));
app.post("/api/ask", async (req, res) => {
if (!req.session?.userId) return res.status(401).json({ error: "Unauthorized" });
const { prompt } = req.body;
if (typeof prompt !== "string" || prompt.length > 2000) {
return res.status(400).json({ error: "Bad request" });
}
const upstream = await fetch("https://api.openai.com/v1/responses", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
},
body: JSON.stringify({ model: "gpt-4o-mini", input: prompt, max_output_tokens: 500 }),
});
res.status(upstream.status).json(await upstream.json());
});
The line req.session?.userId is a placeholder. Use the session check of your own auth library. Do not skip it. A route with no check is an open proxy, and an attacker can use your key through your own URL. For a full example with rate limits, read the OpenAI key fix. The same design works for any other paid API.
Then change the front-end code so that it calls your route:
const res = await fetch("/api/ask", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ prompt }),
});
If the app is only HTML and JavaScript, you must add a server. Change the deployment type to one that runs server code, such as Autoscale or Reserved VM. The Replit deployment overview lists the types.
How do I check the damage after a secret leak?
Read the logs of every service that the leaked key can reach. Look for use that you did not make. Start with the service where the key is most dangerous.
- Open the usage page of the provider. Compare the daily cost and the request count with your real traffic. Look for days with sudden increases.
- Check the models or endpoints. Look for models or API calls that your app never uses.
- Check the key name or ID. Many dashboards show which key made a request. Match the name to the key you deleted.
- Check payment and email services. Look for refunds, test charges, or sent messages that you did not start.
- Check the database. For a database password or a service key, read the database logs. Look for new users, changed rows, and bulk reads.
- Check other projects. If you used the same value in more than one app, rotate the value in each one.
- Check the deployment logs. Look for requests to unusual paths, such as
/.env.
A leaked key lets an attacker act as your app toward that one service. It does not give access to your Replit account. A leaked database credential is different. It can give access to all the data in the database. Treat each type by its own risk.
Who can see Replit Secrets inside the project?
Not every leak comes from the public web. The Replit docs describe who can read the values inside the workspace.
- Visitors to the cover page cannot see the names or the values.
- A person who remixes your project from the cover page can see the names but not the values.
- Collaborators in the project can see the values. Replit states that multiplayer collaborators can see both names and values.
- An organization member without the Owner role cannot view the values in the editor. The docs say that this member can still reach them by printing the environment variables.
Remove people who no longer need access. If a person left your team, rotate the secrets that the person could read. Also remember that the printenv command lists all environment variables. Any code that runs in the project can read them, including code from a package and code that an AI agent writes.
How do I stop the next leak?
Use these controls so that a secret does not reach the public again.
- Name secrets without a public prefix. Do not use
VITE_,NEXT_PUBLIC_, orREACT_APP_for anything secret. Only values that are safe for every visitor can have these prefixes. - Never write a key in the code. Read each key from
process.envoros.environ. Add a project rule that tells the AI tool to do the same. - Use a server deployment for anything private. Use a Static Deployment only for a site with no secrets and no private data.
- Give each app its own keys. A leak in one app then does not reach the others. Set a separate spend limit for each key.
- Keep real
.envfiles out of the project. Use the Secrets tool. - Do not print the environment. Remove each
console.log(process.envcall. Debug output can end up in logs. - Review generated code for direct API calls. Search the front-end files for
fetchcalls to outside hosts and forAuthorizationheaders. - Scan the published app after each release. The build can add values that you do not see in the source.
- Rotate keys on a schedule. A short key lifetime limits the harm from a leak that you never find.
For more on build-time and runtime variables, read environment variables security. For the platform in general, read the Replit security guide.
When should I get help?
Get help in these cases:
- The usage page of a provider shows large charges that you cannot explain. Contact the support team of the provider with the dates and the key name.
- The leaked value gives access to a database that holds personal data. Ask a security engineer to review the app. You may also have legal duties to report a breach.
- You cannot find where the secret enters the build. A fresh scan of the public app shows exactly what the browser receives.
- The secret was in a public repository. Delete the key first. Then remove the value from the history, and treat every copy as permanent.
After you finish, open the published app in a new browser window. Use each feature once and read the Network tab. Confirm that the browser sends requests only to your own routes.
Common questions
Are Replit Secrets safe to use?
Can I use Replit Secrets in a Static Deployment?
Does a private Repl protect a key that is in the published app?
Do I need to rotate a key if I see no abuse?
Can I remove the key from the code and keep the old key?
Find leaked secrets
A VibeEval scan loads your published Replit app, reads the files and JavaScript that the browser receives, and reports secret keys and exposed config files.
14-day free trial · No credit card · Cancel anytime