Back

C8: Leverage Browser Security Features - Configuration Examples

Each pair below shows an insecure configuration—where the browser is told to enforce nothing—and the secure version that declares the right headers, cookie flags, and directives. These are a client-side defense-in-depth layer: apply them and keep your server-side output encoding, anti-CSRF tokens, and access control.

1. Express (Node.js) — Security Headers & Cookies

Insecure

const express = require('express');
const app = express();

// no security headers; x-powered-by banner on
app.post('/login', (req, res) => {
  // cookie readable by JS, sent over http, sent cross-site
  res.cookie('session', token);
  res.json({ ok: true });
});

app.get('/', (req, res) => res.send(renderPage()));   // no CSP, framable, sniffable
app.listen(3000);

Secure

const express = require('express');
const helmet = require('helmet');
const crypto = require('crypto');
const app = express();

app.disable('x-powered-by');
app.use(helmet());                                    // nosniff, frameguard, referrer, etc.
app.use(helmet.strictTransportSecurity({
  maxAge: 63072000, includeSubDomains: true, preload: true
}));

// per-request nonce + strict CSP
app.use((req, res, next) => {
  const nonce = crypto.randomBytes(16).toString('base64');
  res.locals.nonce = nonce;
  res.setHeader('Content-Security-Policy',
    `default-src 'self'; script-src 'nonce-${nonce}' 'strict-dynamic'; ` +
    `object-src 'none'; base-uri 'none'; frame-ancestors 'none'; connect-src 'self'`);
  res.setHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');
  next();
});

app.post('/login', (req, res) => {
  res.cookie('__Host-session', token, {
    httpOnly: true, secure: true, sameSite: 'lax', path: '/'
  });
  res.json({ ok: true });
});
app.listen(3000);

2. Flask (Python) — Headers & Session Cookie

Insecure

from flask import Flask, make_response
app = Flask(__name__)

app.config.update(
    SESSION_COOKIE_HTTPONLY=False,   # readable by JavaScript
    SESSION_COOKIE_SECURE=False,     # sent over plaintext
    SESSION_COOKIE_SAMESITE=None,    # sent on cross-site requests
)

@app.route('/')
def index():
    return make_response(render())   # no security headers at all

Secure

import secrets
from flask import Flask, make_response, g
app = Flask(__name__)

app.config.update(
    SESSION_COOKIE_HTTPONLY=True,
    SESSION_COOKIE_SECURE=True,
    SESSION_COOKIE_SAMESITE='Lax',
)

@app.before_request
def make_nonce():
    g.nonce = secrets.token_urlsafe(16)

@app.after_request
def set_security_headers(resp):
    resp.headers['Content-Security-Policy'] = (
        f"default-src 'self'; script-src 'nonce-{g.nonce}' 'strict-dynamic'; "
        "object-src 'none'; base-uri 'none'; frame-ancestors 'none'; connect-src 'self'"
    )
    resp.headers['Strict-Transport-Security'] = 'max-age=63072000; includeSubDomains; preload'
    resp.headers['X-Content-Type-Options'] = 'nosniff'
    resp.headers['Referrer-Policy'] = 'strict-origin-when-cross-origin'
    resp.headers['Permissions-Policy'] = 'camera=(), microphone=(), geolocation=()'
    return resp

3. nginx — Response Headers

Insecure

server {
    listen 80;                       # plaintext, no HTTPS redirect, no HSTS
    server_tokens on;                # advertises version
    location / {
        root /var/www/html;          # no CSP, no nosniff, framable
    }
}

Secure

server { listen 80; return 301 https://$host$request_uri; }   # force HTTPS

server {
    listen 443 ssl;
    server_tokens off;
    ssl_protocols TLSv1.2 TLSv1.3;

    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
    add_header Content-Security-Policy
        "default-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'" always;
    add_header X-Frame-Options "DENY" always;          # legacy fallback

    location / { root /var/www/html; }
}

4. HTML — Third-Party Scripts, Framing & Sandbox

Insecure

<!-- CDN script with no integrity check: a tampered file runs with full trust -->
<script src="https://cdn.example.com/widget.js"></script>

<!-- untrusted third-party content embedded with no restrictions -->
<iframe src="https://untrusted.example/ad"></iframe>

<!-- inline handler that a strict CSP would (rightly) block -->
<button onclick="doTransfer()">Send</button>

Secure

<!-- Subresource Integrity: a modified script is refused by the browser -->
<script src="https://cdn.example.com/widget.js"
        integrity="sha384-BASE64_HASH_OF_EXACT_FILE"
        crossorigin="anonymous"></script>

<!-- restricted sandbox: only the capabilities the embed needs -->
<iframe src="https://untrusted.example/ad"
        sandbox="allow-scripts"></iframe>

<!-- no inline handlers; behavior attached from a nonce-approved script -->
<button id="send">Send</button>
<script nonce="{RANDOM}">
  document.getElementById('send').addEventListener('click', doTransfer);
</script>

5. CORS — Reflection vs. Allow-List

Insecure

// reflects any origin AND allows credentials -> any site can read responses
app.use((req, res, next) => {
  res.setHeader('Access-Control-Allow-Origin', req.headers.origin || '*');
  res.setHeader('Access-Control-Allow-Credentials', 'true');
  next();
});

Secure

const ALLOW = new Set(['https://app.example.com']);
app.use((req, res, next) => {
  const origin = req.headers.origin;
  if (ALLOW.has(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin);   // echo only known origins
    res.setHeader('Access-Control-Allow-Credentials', 'true');
    res.setHeader('Vary', 'Origin');
  }
  next();
});

6. Trusted Types — DOM-XSS Sink

Insecure

// untrusted input flows straight into a dangerous sink
element.innerHTML = location.hash.slice(1);   // DOM-based XSS

Secure

// Response header:
// Content-Security-Policy: require-trusted-types-for 'script'; trusted-types app-policy

const policy = trustedTypes.createPolicy('app-policy', {
  createHTML: (s) => DOMPurify.sanitize(s)     // sanitized before it can become HTML
});
element.innerHTML = policy.createHTML(location.hash.slice(1));
// a raw string assignment now throws instead of executing

What Changed, and Why

AreaInsecureSecure (browser enforces)
ScriptsNo CSP; inline handlers; injected script runsNonce + strict-dynamic CSP; only vetted code runs
TransportPlaintext allowed; no HSTSHTTPS forced; HSTS with preload
CookiesScript-readable, cross-site, plaintextHttpOnly, Secure, SameSite, __Host-
FramingFramable by any siteframe-ancestors 'none' + X-Frame-Options
Third-partyUnverified CDN scriptSubresource Integrity hash
CORSReflected origin + credentialsExplicit allow-list + Vary: Origin
DOM sinksRaw innerHTMLTrusted Types policy

Defense-in-depth reminder: every "secure" column above is a backstop enforced in the browser. Keep encoding output, using parameterized queries, validating input, and issuing anti-CSRF tokens on the server. The browser holds the second line; the server must still hold the first.

Next Steps