Write CSP-Compliant Code¶
Learn how to write applications that comply with Airbase's Content Security Policy (CSP) requirements.
Critical Requirement
Airbase enforces strict CSP headers for all deployed applications. Applications with CSP violations will not function correctly.
What You'll Learn¶
- The golden rules of CSP compliance
- How to avoid inline scripts and event handlers
- Framework-specific guidance
- How to test CSP compliance locally
The Golden Rules¶
Rule 1: No Inline Scripts¶
❌ Never do this:
✅ Always do this:
Place all JavaScript code in external .js files and reference them with <script src="..."></script> tags.
Rule 2: No Inline Event Handlers¶
❌ Never do this:
<button onclick="handleClick()">Click me</button>
<body onload="init()">
<div onmouseover="showTooltip()">
✅ Always do this:
// In external JavaScript file
document.getElementById('myButton').addEventListener('click', handleClick);
Rule 3: No eval() or Similar Functions¶
❌ Never do this:
✅ Always do this:
Rule 4: Use External Files Only¶
All JavaScript must be in external files served from the same origin (your application).
Framework-Specific Guidance¶
React / Vite Applications¶
Good news: Vite bundles all code into external JavaScript files by default!
What to watch out for:
// ❌ Don't use dangerouslySetInnerHTML with scripts
function BadComponent() {
return (
<div dangerouslySetInnerHTML={{
__html: '<script>alert("bad")</script>'
}} />
);
}
// ✅ Use normal JSX
function GoodComponent() {
return (
<div>
<button onClick={handleClick}>Click me</button>
</div>
);
}
Vite CSP compatibility:
- ✅ Component code → bundled into external JS files
- ✅
onClickhandlers → work fine (they're not inline HTML) - ✅ CSS-in-JS → Vite handles it correctly
- ✅ Dynamic imports → work fine
Next.js Applications¶
Good news: Next.js 13+ is CSP-compliant by default!
What to watch out for:
// ❌ Don't add inline scripts in _document.js
export default function Document() {
return (
<Html>
<Head>
<script dangerouslySetInnerHTML={{__html: 'console.log("bad")'}} />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
// ✅ Use external scripts or Next.js Script component
import Script from 'next/script';
export default function Document() {
return (
<Html>
<Head>
<Script src="/scripts/analytics.js" />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
Next.js CSP compatibility:
- ✅ Server components → CSP-compliant
- ✅ Client components → CSP-compliant
- ✅
next/script→ Use withsrcattribute - ✅ API routes → Not affected by CSP
Static HTML Applications¶
For plain HTML apps, follow these patterns:
HTML structure:
<!DOCTYPE html>
<html>
<head>
<title>My App</title>
<!-- ✅ External CSS is fine -->
<link rel="stylesheet" href="/styles.css">
</head>
<body>
<h1>Hello World</h1>
<button id="myButton">Click me</button>
<!-- ✅ External JavaScript at the end -->
<script src="/js/main.js"></script>
</body>
</html>
JavaScript file (/js/main.js):
// ✅ All JavaScript in external file
document.addEventListener('DOMContentLoaded', function() {
document.getElementById('myButton').addEventListener('click', function() {
console.log('Button clicked!');
});
});
Key points:
- Load external
.jsfiles - Use
addEventListenerfor all event handling - Use
DOMContentLoadedto ensure DOM is ready
Python Applications (Flask, Streamlit)¶
Most Python frameworks generate CSP-compliant output by default.
Flask example:
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def index():
# ✅ Jinja2 templates with external JS are fine
return render_template('index.html')
Template (templates/index.html):
<!DOCTYPE html>
<html>
<head>
<title>Flask App</title>
</head>
<body>
<h1>{{ title }}</h1>
<!-- ✅ External JavaScript -->
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
</body>
</html>
Streamlit:
Streamlit generally works fine with CSP. However, some visualization libraries may generate inline scripts.
If you encounter CSP issues with Python libraries:
- Check if the library has CSP configuration options
- Update to the latest library version
- As a last resort, see Nginx Proxy Workaround
Common Patterns and Solutions¶
Pattern: Dynamic Content¶
❌ Wrong way:
✅ Right way:
// Create elements programmatically
const button = document.createElement('button');
button.textContent = 'Click me';
button.addEventListener('click', doSomething);
element.appendChild(button);
Pattern: Analytics/Tracking¶
❌ Wrong way:
✅ Right way:
<!-- External script -->
<script src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"></script>
<script src="/js/analytics.js"></script>
// /js/analytics.js
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'GA_MEASUREMENT_ID');
Pattern: Configuration/Data Injection¶
❌ Wrong way:
✅ Right way:
<!-- Use data attributes -->
<div id="app" data-api-url="{{ api_url }}"></div>
<script src="/js/app.js"></script>
// /js/app.js
const app = document.getElementById('app');
const config = {
apiUrl: app.dataset.apiUrl
};
Pattern: Form Submission¶
❌ Wrong way:
✅ Right way:
<form id="myForm">
<input type="text" name="name">
<button type="submit">Submit</button>
</form>
<script src="/js/form-handler.js"></script>
// /js/form-handler.js
document.getElementById('myForm').addEventListener('submit', function(e) {
e.preventDefault();
// Handle form submission
});
Testing CSP Compliance Locally¶
Before deploying to Airbase, test your application with CSP headers locally.
For Node.js/Express Apps¶
Install helmet:
Add CSP middleware:
const helmet = require('helmet');
app.use(helmet.contentSecurityPolicy({
directives: {
scriptSrc: ["'self'"],
defaultSrc: ["'self'"]
}
}));
For Static Sites¶
Use a local server that supports custom headers.
Example with Python:
# server.py
from http.server import HTTPServer, SimpleHTTPRequestHandler
class CSPRequestHandler(SimpleHTTPRequestHandler):
def end_headers(self):
self.send_header('Content-Security-Policy', "script-src 'self'")
SimpleHTTPRequestHandler.end_headers(self)
HTTPServer(('localhost', 8000), CSPRequestHandler).serve_forever()
Run: python server.py
Verification Steps¶
- Start your local server with CSP headers
- Open the application in a browser
- Open browser console (F12)
- Look for CSP violation errors
- Fix any violations before deploying
Quick Checklist¶
Before deploying to Airbase, verify:
- No
<script>tags with inline JavaScript - No inline event handlers (
onclick,onload, etc.) - All JavaScript in external
.jsfiles - No
eval(),Function(), or similar dynamic code - No
setTimeout/setIntervalwith string arguments - Framework build output doesn't inject inline scripts
- Tested locally with CSP headers
- Browser console shows no CSP violations
See Also¶
- Debugging: Troubleshoot CSP Violations
- Reference: CSP Policy Specification
- Understanding: Why CSP Matters
- Advanced: Nginx Proxy Workaround for Python Apps
- Testing: Test CSP Locally
Summary¶
Remember the golden rules:
- ✅ External JavaScript files only
- ✅ Use
addEventListenerfor events - ✅ No
eval()or dynamic code execution - ✅ Test with CSP headers before deploying
Following these guidelines ensures your application will work correctly on Airbase!