# Web Security

> When to activate: CSP, CORS, XSS prevention, CSRF, SRI, iframe sandbox, Trusted Types, web security headers

- Skill: `mattakushi432/web-security` (Agent Skill)
- Install (CLI): `npx skillmds@latest add mattakushi432/web-security`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mattakushi432/web-security/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: Mattakushi432 (https://skillmd.com/u/mattakushi432)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/mattakushi432/web-security

---

# Web Security Patterns

## Content Security Policy (CSP)

```http
# Strict nonce-based CSP (recommended)
Content-Security-Policy:
  default-src 'self';
  script-src 'nonce-{RANDOM_BASE64}' 'strict-dynamic';
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: https:;
  font-src 'self';
  connect-src 'self' https://api.example.com;
  frame-src 'none';
  object-src 'none';
  base-uri 'self';
  upgrade-insecure-requests;
```

```js
// Next.js: generate nonce per request
import crypto from 'crypto';

export function middleware(req) {
  const nonce = crypto.randomBytes(16).toString('base64');
  const csp = `script-src 'nonce-${nonce}' 'strict-dynamic'; object-src 'none';`;
  const res = NextResponse.next();
  res.headers.set('Content-Security-Policy', csp);
  res.headers.set('x-nonce', nonce);
  return res;
}
```

## Security Headers

```http
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Resource-Policy: same-origin
```

## XSS Prevention

```js
// WRONG: direct innerHTML injection
element.innerHTML = userInput;

// RIGHT: textContent for plain text
element.textContent = userInput;

// RIGHT: sanitize HTML when HTML is needed
import DOMPurify from 'dompurify';
element.innerHTML = DOMPurify.sanitize(userInput, {
  ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'],
  ALLOWED_ATTR: ['href', 'title']
});

// React: dangerouslySetInnerHTML must always sanitize
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(html) }} />
```

## Trusted Types

```js
// Enforce via CSP: require-trusted-types-for 'script'
if (window.trustedTypes && window.trustedTypes.createPolicy) {
  const policy = trustedTypes.createPolicy('default', {
    createHTML: (input) => DOMPurify.sanitize(input),
    createScriptURL: (url) => {
      if (new URL(url).origin === location.origin) return url;
      throw new Error('Untrusted script URL');
    }
  });
  element.innerHTML = policy.createHTML(userInput);
}
```

## CORS

```js
// Server (Express)
import cors from 'cors';
app.use(cors({
  origin: ['https://app.example.com'],
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true,
  maxAge: 86400
}));
```

## CSRF Protection

```js
// Double-submit cookie pattern
// Server sets csrf cookie on login
// Client reads cookie and sends as header

async function fetchWithCSRF(url, options = {}) {
  const token = document.cookie.match(/csrf=([^;]+)/)?.[1];
  return fetch(url, {
    ...options,
    headers: { ...options.headers, 'X-CSRF-Token': token }
  });
}
```

## Subresource Integrity (SRI)

```html
<!-- Hash ensures CDN file hasn't been tampered with -->
<script
  src="https://cdn.example.com/lib.min.js"
  integrity="sha384-abc123..."
  crossorigin="anonymous">
</script>
```

```bash
# Generate SRI hash
openssl dgst -sha384 -binary lib.min.js | openssl base64 -A
```

## iframe Sandbox

```html
<!-- Allow only what's needed -->
<iframe
  src="https://widget.example.com"
  sandbox="allow-scripts allow-same-origin"
  allow="payment"
  referrerpolicy="no-referrer"
  loading="lazy">
</iframe>
```

## Cookie Security

```http
Set-Cookie: session=abc; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=3600
```

