Skip to content

Content Security Policy (CSP) - Deep Dive

Understanding why Airbase enforces strict Content Security Policy

This explanation provides deep understanding of Content Security Policy, why it's critical for government applications, and how it prevents security vulnerabilities.


What is Content Security Policy?

Content Security Policy (CSP) is a web security standard that helps prevent Cross-Site Scripting (XSS) attacks by controlling which resources a web page can load and execute.

Simple analogy: CSP is like a bouncer at a club - it checks every piece of JavaScript trying to run and only allows in the ones from trusted sources.

HTTP header example:

Content-Security-Policy: script-src 'self'

What it means: "Only allow JavaScript from the same origin (domain) as the web page itself."


Why Airbase Enforces CSP

1. Government Security Requirements

Context: Singapore Government applications handle: - Citizen data - Sensitive information - Critical services - Public trust

Requirement: Must meet IM8 (Information Management 8) security standards.

CSP role: Prevents entire categories of attacks that could compromise citizen data.

2. XSS Prevention

Cross-Site Scripting (XSS) is one of the most common web vulnerabilities.

How XSS works: 1. Attacker injects malicious JavaScript 2. Victim's browser executes malicious code 3. Attacker steals data, hijacks sessions, or redirects user

Example XSS attack:

<!-- User input not sanitized -->
<div>Welcome, <?php echo $_GET['name']; ?></div>

<!-- Attacker visits: -->
<!-- ?name=<script>steal_cookies()</script> -->

<!-- Browser executes attacker's script -->

CSP protection: Even if malicious script gets into HTML, CSP blocks execution.

3. Defense in Depth

Security principle: Multiple layers of protection.

Layers in Airbase: 1. Input validation - Sanitize user input 2. Output encoding - Escape HTML/JS 3. CSP - Block execution even if previous layers fail

CSP is the last line of defense.


Airbase's CSP Policy

The Policy

Content-Security-Policy: script-src 'self'

What This Means

script-src: Controls where JavaScript can load from

'self': Only from the same origin (same domain)

Blocked: - ❌ Inline <script> tags - ❌ Inline event handlers (onclick="...") - ❌ eval() and similar - ❌ javascript: URLs - ❌ External scripts from CDNs

Allowed: - ✅ External .js files from same domain - ✅ Scripts loaded via <script src="/app.js">

Why So Strict?

Question: Why not allow inline scripts with nonces or hashes?

Answer: Simplicity and consistency.

'nonce-...' approach: - Requires server-side nonce generation - Different nonce per page load - Complex to implement correctly - Framework-dependent

'self' approach: - Simple rule to understand - Works across all frameworks - No server-side logic needed - Harder to misconfigure

Trade-off: Stricter but simpler and more robust.


How CSP Prevents Attacks

Attack Scenario 1: Reflected XSS

Without CSP:

  1. Attacker crafts malicious URL:

    https://app.gov.sg/search?q=<script>steal()</script>
    

  2. Application reflects input in HTML:

    <div>Results for: <script>steal()</script></div>
    

  3. Browser executes malicious script

  4. Attack succeeds

With CSP:

1-2. Same as above

  1. Browser blocks inline script (CSP violation)
  2. User sees console error, but attack fails

CSP blocks execution even though code is in HTML.

Attack Scenario 2: Stored XSS

Without CSP:

  1. Attacker posts comment with malicious script:

    Great post! <script>steal_data()</script>
    

  2. Server stores comment in database

  3. Victim visits page, comment loads:

    <div class="comment">
      Great post! <script>steal_data()</script>
    </div>
    

  4. Browser executes script, attack succeeds

With CSP:

1-3. Same as above

  1. CSP blocks inline script execution
  2. Attack fails

CSP protects all users from stored attacks.

Attack Scenario 3: DOM-Based XSS

Without CSP:

  1. Application reads URL parameter:

    let username = location.hash.substring(1);
    document.write("Welcome " + username);
    

  2. Attacker crafts URL:

    https://app.gov.sg/#<img src=x onerror=steal()>
    

  3. Code injects HTML with inline event handler

  4. Browser executes onerror handler
  5. Attack succeeds

With CSP:

1-3. Same as above

  1. CSP blocks inline event handler
  2. Attack fails

CSP blocks execution from DOM manipulation.


Technical Details

How Browsers Enforce CSP

1. Server sends CSP header:

HTTP/1.1 200 OK
Content-Security-Policy: script-src 'self'
Content-Type: text/html

2. Browser parses policy

3. For every script: - Check source against policy - If matches: Execute - If doesn't match: Block and log violation

4. Violations logged to console

CSP Violation Reports

Browser console:

Refused to execute inline script because it violates the following
Content Security Policy directive: "script-src 'self'".

What this tells developers: - Policy violation occurred - What was blocked (inline script) - Which policy directive blocked it

Developers can fix violations before users are affected.

CSP and the Same-Origin Policy

Same-Origin Policy: Browser security feature restricting cross-origin access.

CSP: Additional layer on top of Same-Origin Policy.

Relationship: - Same-Origin Policy: Controls what scripts can access - CSP: Controls what scripts can execute

Both work together for defense in depth.


Common Misconceptions

Misconception 1: "CSP Breaks My App"

Reality: CSP doesn't break apps, it reveals insecure patterns.

Example:

<!-- This was insecure before CSP -->
<button onclick="handleClick()">Click</button>

<!-- CSP makes the insecurity visible -->

Truth: Inline event handlers were always risky. CSP just enforces secure alternatives.

Misconception 2: "CSP Is Too Restrictive"

Reality: CSP script-src 'self' allows all legitimate use cases.

What you CAN still do: - ✅ Load external JS files - ✅ Use modern frameworks (React, Vue, Angular) - ✅ Use build tools (Webpack, Vite) - ✅ Use addEventListener - ✅ Dynamic content

What you CAN'T do: - ❌ Inline scripts (never secure) - ❌ eval() (dangerous) - ❌ Inline event handlers (XSS risk)

All blocked patterns have secure alternatives.

Misconception 3: "I Need to Relax CSP for My Library"

Reality: Modern libraries are CSP-compatible.

CSP-compatible libraries: - React, Vue, Angular, Svelte - Express, Next.js, Nuxt - Bootstrap, Tailwind - Chart.js, D3.js

If a library isn't CSP-compatible: - It's likely using insecure patterns - Find an alternative - Or contribute a fix to the library

Exception: Some Python visualization libraries (workaround available).

Misconception 4: "CSP Prevents All XSS"

Reality: CSP significantly reduces XSS risk but isn't a complete solution.

CSP prevents: - Inline script execution - eval() exploitation - Inline event handler attacks

CSP doesn't prevent: - Server-side injection - Logic errors in JavaScript - Other vulnerability types (CSRF, SQLi, etc.)

CSP is one layer in defense in depth.


CSP and Modern Frameworks

React / Vite

Framework approach: Build-time bundling

CSP compatibility: Excellent

How it works: 1. React components compile to JavaScript 2. Vite bundles into external .js files 3. HTML loads bundled files 4. No inline scripts needed

Example:

// src/App.jsx - CSP-compliant
function App() {
  const [count, setCount] = useState(0);

  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  );
}

Compiles to external JS, fully CSP-compliant.

Next.js

Framework approach: Server-side rendering + bundling

CSP compatibility: Excellent (with configuration)

Configuration needed:

// next.config.js
module.exports = {
  compiler: {
    removeConsole: false, // Keep for debugging
  },
  // Don't use `next/script` with inline scripts
}

Avoid:

// ❌ This violates CSP
<Script id="inline">
  {`console.log('inline')`}
</Script>

Use:

// ✅ This is CSP-compliant
<Script src="/scripts/analytics.js" />

Static Sites

Approach: Pre-built HTML + external JS

CSP compatibility: Excellent

Pattern:

<!DOCTYPE html>
<html>
<head>
  <title>My Site</title>
</head>
<body>
  <div id="app"></div>
  <!-- ✅ External script -->
  <script src="/dist/bundle.js"></script>
</body>
</html>

All build tools (Webpack, Vite, Parcel) generate external JS by default.


The Security Trade-Off

What We Gain

1. XSS Prevention: - Blocks most XSS attack vectors - Protects user data - Prevents session hijacking

2. Zero-Day Protection: - Even unknown XSS vulnerabilities are mitigated - Attacker can inject code but can't execute it

3. Audit Trail: - Violations logged to console - Easy to identify and fix issues

4. Compliance: - Meets government security standards - Demonstrates security commitment

What We Give Up

1. Inline Convenience: - Can't use inline scripts - Must use external files

2. Some Old Patterns: - onclick="..." not allowed - Must use addEventListener

3. Dynamic Script Generation: - Can't use eval() - Can't use new Function() - Must use safe alternatives

Is It Worth It?

For government applications: Absolutely.

Consider: - Cost of data breach: Millions of dollars, loss of public trust - Cost of CSP compliance: Few hours of development time

The trade-off heavily favors security.


CSP Evolution

CSP Level 1 (2012)

Features: - Basic script-src directive - 'unsafe-inline', 'unsafe-eval'

Limitation: All-or-nothing (no granular control)

CSP Level 2 (2016)

Features: - Nonces ('nonce-random123') - Hashes ('sha256-...') - 'strict-dynamic'

Improvement: More flexible while maintaining security

CSP Level 3 (Draft)

Features: - 'strict-dynamic' improvements - Better error reporting - External hash sources

Status: Being adopted by browsers

Airbase's Choice

Policy: CSP Level 1 (script-src 'self')

Rationale: - Universal browser support - Simple to understand - Sufficient for our needs - Easier to audit

Future: May adopt Level 2 features (nonces) if needed.


Global CSP Landscape

Industry Adoption

Tech companies: - Google: Strict CSP on most services - GitHub: CSP enforced - Twitter: CSP enforced - Facebook: CSP enforced

Government: - UK Government Digital Service: CSP required - US Government (NIST): CSP recommended - Singapore Government (Airbase): CSP enforced

Trend: CSP adoption increasing globally.

Why Not Everyone Uses CSP

Challenges: - Legacy code with inline scripts - Third-party widgets - Developer unfamiliarity - Framework incompatibility (old frameworks)

Airbase advantage: Greenfield platform, can enforce from day one.


Best Practices

For Developers

1. Plan for CSP from the start: - Use external JS files - Avoid inline scripts - Use addEventListener

2. Test with CSP locally:

<meta http-equiv="Content-Security-Policy"
      content="script-src 'self'">

3. Check console for violations: - Open DevTools (F12) - Check Console tab - Fix all CSP errors

4. Use modern frameworks: - React, Vue, Angular are CSP-friendly - Avoid jQuery plugins with inline scripts

For Teams

1. Make CSP compliance a requirement: - Code review checklist - Automated testing - CI/CD gates

2. Educate team members: - CSP training - Share examples - Document patterns

3. Build CSP-compliant component libraries: - Reusable components - Pre-tested for CSP - Team-wide adoption


Future of CSP in Airbase

Planned Enhancements

1. CSP Reporting API: - Collect violation reports - Analytics dashboard - Proactive issue detection

2. Stricter Policies: - style-src 'self' (CSS) - img-src 'self' data: (images) - connect-src 'self' (API calls)

3. Developer Tools: - CSP testing utilities - Automated violation scanning - Migration helpers

4. Documentation: - More framework examples - Video tutorials - Interactive playground

Feedback Welcome

Have CSP issues? - Report via GitHub issues - Share your use case - Suggest improvements

CSP evolves based on developer needs.


Summary

CSP (script-src 'self') is non-negotiable on Airbase because:

  1. Security: Prevents XSS attacks, protects citizen data
  2. Compliance: Meets government security standards
  3. Defense in depth: Additional layer beyond input validation
  4. Modern development: Compatible with all modern frameworks
  5. Industry standard: Adopted by major tech companies and governments

The cost is minimal: - Use external JS files instead of inline - Use addEventListener instead of inline handlers - Use safe alternatives to eval()

The benefit is massive: - Protection against entire attack categories - User trust - Regulatory compliance - Sleep better at night

Bottom line: CSP makes applications secure by default, which is exactly what government services need.


See Also