Docs For AI
Browser & Network

Web Security

XSS, CSRF, CSP, and secure coding practices for frontend development

Web Security

Web security is critical for protecting users and data. This covers common vulnerabilities and how to prevent them.

Common Vulnerabilities

XSS (Cross-Site Scripting)

XSS allows attackers to inject malicious scripts into web pages.

Types of XSS

XSS Types:
├── Stored XSS: Script stored in database, served to all users
├── Reflected XSS: Script in URL parameters, reflected in response
└── DOM-based XSS: Script manipulates DOM directly in browser

Vulnerable Code

// DANGEROUS - Direct HTML insertion
element.innerHTML = userInput;
document.write(userInput);
element.outerHTML = userInput;

// DANGEROUS - URL without validation
location.href = userInput;
window.open(userInput);

// DANGEROUS - Eval and similar
eval(userInput);
new Function(userInput);
setTimeout(userInput, 0);

Prevention

// Use textContent instead of innerHTML
element.textContent = userInput;

// Escape HTML entities
function escapeHtml(text: string): string {
  const div = document.createElement('div');
  div.textContent = text;
  return div.innerHTML;
}

// Sanitize HTML if you must allow it
import DOMPurify from 'dompurify';
element.innerHTML = DOMPurify.sanitize(userInput);

// React automatically escapes
function SafeComponent({ userInput }) {
  return <div>{userInput}</div>;  // Safe - auto-escaped
}

// DANGEROUS in React - avoid dangerouslySetInnerHTML
<div dangerouslySetInnerHTML={{ __html: userInput }} />  // Vulnerable

// Validate URLs
function isValidUrl(url: string): boolean {
  try {
    const parsed = new URL(url);
    return ['http:', 'https:'].includes(parsed.protocol);
  } catch {
    return false;
  }
}

CSRF (Cross-Site Request Forgery)

CSRF tricks users into performing unwanted actions on authenticated sites.

Attack Example

<!-- Attacker's site -->
<img src="https://bank.com/transfer?to=attacker&amount=10000" />

<!-- Or hidden form -->
<form action="https://bank.com/transfer" method="POST" id="evil-form">
  <input type="hidden" name="to" value="attacker" />
  <input type="hidden" name="amount" value="10000" />
</form>
<script>document.getElementById('evil-form').submit();</script>

Prevention

// 1. Use CSRF tokens
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content;

fetch('/api/transfer', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-CSRF-Token': csrfToken,
  },
  body: JSON.stringify(data),
});

// 2. Use SameSite cookies
// Set-Cookie: session=abc123; SameSite=Strict; Secure; HttpOnly

// 3. Verify Origin/Referer headers (server-side)

// 4. Use POST for state-changing operations
// GET /api/delete/123  // DANGEROUS
// POST /api/items/123/delete  // Better

Clickjacking

Clickjacking tricks users into clicking hidden elements.

<!-- Attacker's site - transparent iframe over fake UI -->
<style>
  iframe {
    position: absolute;
    opacity: 0;
    z-index: 100;
  }
</style>
<button>Click to win!</button>
<iframe src="https://bank.com/transfer?to=attacker"></iframe>

Prevention

# Prevent framing
X-Frame-Options: DENY
X-Frame-Options: SAMEORIGIN

# Or use CSP
Content-Security-Policy: frame-ancestors 'none';
Content-Security-Policy: frame-ancestors 'self';
// JavaScript frame-busting (less reliable)
if (window.self !== window.top) {
  window.top.location = window.self.location;
}

Content Security Policy (CSP)

CSP restricts which resources can be loaded and executed.

CSP Header

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'unsafe-inline' https://cdn.example.com;
  style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
  img-src 'self' data: https:;
  font-src 'self' https://fonts.gstatic.com;
  connect-src 'self' https://api.example.com;
  frame-src 'none';
  object-src 'none';
  base-uri 'self';
  form-action 'self';
  upgrade-insecure-requests;

CSP Directives

DirectivePurpose
default-srcFallback for other directives
script-srcJavaScript sources
style-srcCSS sources
img-srcImage sources
font-srcFont sources
connect-srcAJAX, WebSocket, fetch
frame-srciframe sources
object-srcPlugins (Flash, Java)
base-uriBase URL restrictions
form-actionForm submission targets

Nonces for Inline Scripts

<!-- Server generates unique nonce per request -->
<script nonce="abc123">
  // This inline script is allowed
</script>
Content-Security-Policy: script-src 'nonce-abc123'

Report-Only Mode

Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report

Secure Cookies

Set-Cookie: session=abc123; Secure; HttpOnly; SameSite=Strict; Path=/; Max-Age=86400

# Secure: Only sent over HTTPS
# HttpOnly: Not accessible via JavaScript
# SameSite=Strict: Not sent with cross-site requests
# SameSite=Lax: Sent with top-level navigations
# SameSite=None: Sent with all requests (requires Secure)

Input Validation

// Client-side validation (UX, not security)
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
  setError('Invalid email format');
}

// Always validate on server too!

// Use validation libraries
import { z } from 'zod';

const UserSchema = z.object({
  email: z.string().email(),
  password: z.string().min(8).max(100),
  age: z.number().int().positive().max(150),
  website: z.string().url().optional(),
});

function validateUser(data: unknown) {
  return UserSchema.parse(data);
}

Secure Communication

HTTPS Enforcement

# HSTS - Force HTTPS
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
<!-- Upgrade HTTP requests to HTTPS -->
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">

Subresource Integrity

<!-- Verify CDN resources haven't been tampered with -->
<script
  src="https://cdn.example.com/lib.js"
  integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
  crossorigin="anonymous"
></script>

Authentication Security

Token Storage

// Recommended: HttpOnly cookies (set by server)
// Access token in HttpOnly cookie = most secure

// If you must store in JS:
// - sessionStorage for sensitive data (cleared on tab close)
// - Avoid localStorage for tokens (XSS vulnerable)

// Token refresh pattern
async function fetchWithAuth(url: string, options: RequestInit = {}) {
  let response = await fetch(url, {
    ...options,
    credentials: 'include', // Send cookies
  });

  if (response.status === 401) {
    // Try to refresh token
    const refreshed = await refreshToken();
    if (refreshed) {
      response = await fetch(url, {
        ...options,
        credentials: 'include',
      });
    }
  }

  return response;
}

Password Handling

// Never store or log passwords in frontend
// Always use HTTPS for login forms
// Implement rate limiting (server-side)

// Good: Use browser password manager
<input
  type="password"
  name="password"
  autocomplete="current-password"
/>

// New password
<input
  type="password"
  name="new-password"
  autocomplete="new-password"
/>

Security Headers

# Comprehensive security headers
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 0
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: geolocation=(), microphone=(), camera=()

Best Practices

Security Guidelines

  1. Never trust user input - validate and sanitize
  2. Use Content Security Policy
  3. Set secure cookie attributes
  4. Implement CSRF protection
  5. Use HTTPS everywhere
  6. Keep dependencies updated
  7. Use security headers
  8. Avoid storing sensitive data in localStorage
  9. Validate on both client and server
  10. Regular security audits

On this page