Am I Hackable?
Back to Learn

How to Secure a Replit App (Yes, Even the Free Tier)

Benji··7 min read

Replit is how millions of people start coding. It's a browser based IDE with instant deployment, AI code generation, and zero setup. Students, hobbyists, and startup founders use it to build everything from homework projects to production SaaS apps.

That accessibility is Replit's strength and its security challenge. Many Replit users are building their first app. They're learning as they go. Security isn't on their radar, and Replit's defaults don't push them toward it.

The result: Replit apps are some of the most consistently vulnerable we see in scans. Not because Replit is bad, but because its users haven't had a reason to think about security yet.

This guide is that reason. For the broader picture of AI generated code risks, see our [complete vibe coding security guide](/learn/vibe coding security).

Issue 1: Your code is public by default

On Replit's free tier, every Repl is public. Anyone can browse to your Repl and read your entire source code. This is fine for learning projects. It's a disaster if your code contains API keys, database credentials, or business logic you want to keep private.

Even on paid tiers where you can make Repls private, the deployed app URL is public. If secrets leaked into your code at any point while the Repl was public, they've been exposed.

The fix

Use Replit's builtin Secrets tool for every credential:

# WRONG: Hardcoded in source (visible to everyone on free tier)
OPENAI_API_KEY = "sk-proj-abc123..."
DATABASE_URL = "postgresql://user:password@host/db"

# RIGHT: Using Replit Secrets (environment variables)
import os

openai_api_key = os.environ.get("OPENAI_API_KEY")
database_url = os.environ.get("DATABASE_URL")

if not openai_api_key:
    raise ValueError("OPENAI_API_KEY not set in Secrets")

In Node.js:

// WRONG
const apiKey = "sk-proj-abc123...";

// RIGHT
const apiKey = process.env.OPENAI_API_KEY;
if (!apiKey) {
  throw new Error("OPENAI_API_KEY not set in Secrets");
}

Action items:

  1. Open your Repl's Secrets tab (lock icon in sidebar)
  2. Move every API key, password, and credential there
  3. Search your code for any remaining hardcoded secrets:
grep -rn "sk-proj-\|sk_live_\|password\s*=\s*['\"]" --include="*.py" --include="*.js" --include="*.ts" .
  1. If secrets were ever in your public code, rotate them immediately: they've been exposed

Issue 2: Shared URLs expose everything

When you share a Replit app, you share a URL like https://your-app.your-username.repl.co. This URL is publicly accessible. Combined with common Replit patterns, this creates several risks:

The fix

Before sharing any Replit URL:

# Flask example: disable debug mode
from flask import Flask
app = Flask(__name__)

# WRONG: Debug mode on (exposes stack traces, enables debugger)
# app.run(debug=True)

# RIGHT: Debug off in production
app.run(debug=False, host='0.0.0.0', port=8080)
// Express example: remove debug routes before sharing
const express = require('express');
const app = express();

// Remove these before deploying
// app.get('/debug', (req, res) => res.json(process.env));
// app.get('/test-db', (req, res) => { /* ... */ });

// Don't serve your entire project directory
// WRONG: app.use(express.static('.'));
// RIGHT: Only serve the public folder
app.use(express.static('public'));

// Add basic security headers
app.use((req, res, next) => {
  res.setHeader('X-Frame-Options', 'DENY');
  res.setHeader('X-Content-Type-Options', 'nosniff');
  res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
  next();
});

Search your code for any debug or test routes and remove them:

grep -rn "\/debug\|\/test\|\/admin\|\.run(debug=True)" --include="*.py" --include="*.js" .

Issue 3: Replit DB has no access control

Replit provides a builtin key value database (Replit DB) that's convenient for simple apps. The problem: it has zero access control. There are no users, no permissions, no row level security. Any code running in your Repl can read and write any key.

This matters because:

# Typical Replit DB usage, no access control
from replit import db

# Store user data
db["user_123"] = {"email": "alice@example.com", "plan": "premium"}
db["user_456"] = {"email": "bob@example.com", "plan": "free"}

# Any request handler can read any user's data
# There's nothing stopping user_456 from accessing user_123's data

The fix

For simple projects, add application level access control:

from flask import Flask, session, abort
from replit import db

app = Flask(__name__)
app.secret_key = os.environ.get("SESSION_SECRET")

@app.route('/api/profile')
def get_profile():
    user_id = session.get('user_id')
    if not user_id:
        abort(401)

    # Only return the authenticated user's data
    user_data = db.get(f"user_{user_id}")
    if not user_data:
        abort(404)

    return user_data

For production apps with real user data, don't use Replit DB. Use a proper database with access control:

# Use Supabase with RLS instead of Replit DB
from supabase import create_client

supabase = create_client(
    os.environ.get("SUPABASE_URL"),
    os.environ.get("SUPABASE_ANON_KEY")
)

# RLS policies on Supabase handle access control
# Users can only read their own data
result = supabase.table("profiles").select("*").execute()

Issue 4: Deployment security gaps

Replit's deployment options range from the builtin hosting to custom domain deployments. Each has security considerations:

The fix

Add a security middleware layer to every Replit app:

// security.js, import this in your main server file
const rateLimit = new Map();

function securityMiddleware(req, res, next) {
  // Security headers
  res.setHeader('X-Frame-Options', 'DENY');
  res.setHeader('X-Content-Type-Options', 'nosniff');
  res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
  res.setHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');
  res.setHeader('Content-Security-Policy', "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'");

  // Basic rate limiting
  const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
  const now = Date.now();
  const windowMs = 60000; // 1 minute
  const maxRequests = 60;

  if (!rateLimit.has(ip) || now - rateLimit.get(ip).start > windowMs) {
    rateLimit.set(ip, { count: 1, start: now });
  } else {
    rateLimit.get(ip).count++;
    if (rateLimit.get(ip).count > maxRequests) {
      return res.status(429).json({ error: 'Too many requests' });
    }
  }

  next();
}

module.exports = { securityMiddleware };
// main server file
const express = require('express');
const { securityMiddleware } = require('./security');

const app = express();
app.use(securityMiddleware);

// ... your routes

For Python/Flask:

from flask import Flask, request, jsonify
from functools import wraps
import time

app = Flask(__name__)

# Security headers
@app.after_request
def add_security_headers(response):
    response.headers['X-Frame-Options'] = 'DENY'
    response.headers['X-Content-Type-Options'] = 'nosniff'
    response.headers['Referrer-Policy'] = 'strict-origin-when-cross-origin'
    response.headers['Permissions-Policy'] = 'camera=(), microphone=(), geolocation=()'
    return response

# Simple rate limiter
rate_limit_store = {}

def rate_limit(max_requests=60, window_seconds=60):
    def decorator(f):
        @wraps(f)
        def wrapper(*args, **kwargs):
            ip = request.headers.get('X-Forwarded-For', request.remote_addr)
            now = time.time()

            if ip not in rate_limit_store or now - rate_limit_store[ip]['start'] > window_seconds:
                rate_limit_store[ip] = {'count': 1, 'start': now}
            else:
                rate_limit_store[ip]['count'] += 1
                if rate_limit_store[ip]['count'] > max_requests:
                    return jsonify({'error': 'Too many requests'}), 429

            return f(*args, **kwargs)
        return wrapper
    return decorator

@app.route('/api/generate')
@rate_limit(max_requests=10, window_seconds=60)
def generate():
    # Your endpoint logic
    pass

The Replit security checklist

Run through this before sharing your Replit app URL with anyone:

  1. Move all secrets: Every API key and password should be in Replit's Secrets tool, not in code files
  2. Rotate exposed credentials: If secrets were ever in your public code, they're compromised. Rotate them now
  3. Remove debug routes: Delete or protect any /debug, /test, /admin endpoints
  4. Disable debug mode: debug=False in Flask, NODE_ENV=production in Node
  5. Add security headers: Use the middleware templates above
  6. Add rate limiting: Protect API endpoints from abuse
  7. Don't use Replit DB for sensitive data: Use a proper database with access control for user data
  8. Scan your app: Run it through AmIHackable to catch what you missed

The Replit AI prompt for secure code

If you're using Replit's AI features to generate code, add this to your prompt:

Security requirements for this Replit app:
- Never hardcode API keys or secrets, always use environment variables via os.environ or process.env
- Add security headers (X-Frame-Options, X-Content-Type-Options, CSP) to all responses
- Disable debug mode in all server configurations
- Add rate limiting to all API endpoints
- Validate and sanitize all user input server-side
- Don't serve the entire project directory, only serve the public folder
- Use parameterized queries for all database operations

Databricks research shows this kind of prompt reduces vulnerabilities by 50 80%. That one paragraph could save you from a breach.

Accessible doesn't mean insecure

Replit is a great platform. It makes building accessible to everyone, and that's genuinely valuable. The security issues aren't Replit's fault, they're the result of optimizing for getting started fast, which is exactly what beginners need.

But when your learning project becomes something other people use, security becomes your responsibility. The checklist above takes 15 minutes. That's less time than you spent picking your app's color scheme.

Scan your Replit app. Fix the top issues. Ship with confidence.


Sources: Veracode GenAI Code Security Report (2025) · Wiz, Common Security Risks in Vibe Coded Apps (2025) · Databricks, Passing the Security Vibe Check (2025) · Stanford, Do Users Write More Insecure Code with AI Assistants? (2023)

Frequently Asked Questions

Are Replit apps public by default?
On Replit's free tier, your code (the Repl itself) is public, anyone can view the source. Deployed apps are accessible via a public URL. This means any secrets hardcoded in your source code are visible to everyone browsing Replit. You must use Replit's Secrets tool for all credentials and never put sensitive values directly in code files.
Is Replit's database secure?
Replit DB (the built-in key-value store) is scoped to your Repl and not directly accessible from outside. However, it has no built-in access control, any code in your Repl can read or write any key. If your app has an injection vulnerability or you expose a debug endpoint, an attacker can read your entire database. For production apps with user data, use a proper database like Supabase or PostgreSQL with row-level security.
How do I manage secrets in Replit?
Use Replit's built-in Secrets tool (the lock icon in the sidebar). Secrets are stored as environment variables that are only available at runtime, they don't appear in your source code. Never put API keys, passwords, or tokens directly in your code files, especially on the free tier where your source is public. Access secrets via process.env.SECRET_NAME in Node.js or os.environ['SECRET_NAME'] in Python.

Your AI writes the code. We find what it missed.

Paste your URL. Security audit in 60 seconds.

Scan my app