Skip to content

Nginx Proxy Workaround for CSP (Python Apps)

Last resort solution for Python applications with unavoidable CSP violations

⚠️ Security Warning

This workaround weakens your application's security posture by removing or modifying Content Security Policy headers. Only use this approach when:

  • You have exhausted all CSP-compliant alternatives
  • The CSP violation comes from a critical library with no alternatives
  • You understand and accept the security trade-offs

This should be your absolute last resort.


When to Use This Workaround

✅ Valid Use Cases

  1. Streamlit Applications
  2. Streamlit's core framework generates inline scripts
  3. Some Streamlit components violate CSP
  4. No CSP-compliant alternative exists for Streamlit

  5. Government-Specific Libraries

  6. Specialized data visualization libraries required for government work
  7. Libraries mandated by agency policy
  8. No alternative libraries available

  9. Python Web Frameworks with Inline Scripts

  10. Dash applications
  11. Plotly Dash
  12. Other Python frameworks that inject inline scripts

❌ Do NOT Use If

  • You're building a Node.js/React/Vue application (these can be CSP-compliant)
  • Alternative CSP-compliant libraries exist
  • You haven't tried other solutions first
  • Application handles sensitive data (reconsider architecture instead)

How It Works

The nginx proxy workaround uses a multi-stage Docker build:

┌─────────────┐      ┌──────────┐      ┌─────────────┐
│   Browser   │ ───> │  Nginx   │ ───> │  Python App │
│             │      │  (Port   │      │  (Port 8000)│
│             │      │   3000)  │      │             │
└─────────────┘      └──────────┘      └─────────────┘
                           │ Modifies/removes
                           │ CSP headers

Key points:

  1. Nginx listens on port 3000 (configured in airbase.json)
  2. Python app runs on internal port 8000
  3. Nginx proxies requests to Python app
  4. Nginx intercepts responses and removes/modifies CSP headers
  5. Browser receives responses without strict CSP enforcement

Prerequisites

Before proceeding:

  • You've confirmed CSP violations exist (checked browser console)
  • You've tried CSP-compliant alternatives
  • You've searched for updated versions of the problematic library
  • You understand the security implications

Implementation Steps

Step 1: Modify Your Dockerfile

Update your Dockerfile to use a multi-stage build with nginx:

# Stage 1: Python application
FROM gdssingapore/airbase:python-3.13 AS python-app
ENV PYTHONUNBUFFERED=TRUE
COPY --chown=app:app requirements.txt ./
RUN pip install -r requirements.txt
COPY --chown=app:app . ./

# Stage 2: Nginx proxy
FROM gdssingapore/airbase:nginx-1.28

# Copy Python app files
COPY --from=python-app --chown=app:app /app /app

# Copy nginx configuration
COPY --chown=app:app nginx.conf /etc/nginx/conf.d/default.conf

# Install Python in nginx container
USER root
RUN apt-get update && \
    apt-get install -y python3 python3-pip python3-venv && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

# Switch to app user
USER app

# Start both nginx and Python app
CMD ["/bin/sh", "-c", "python3 /app/app.py & nginx -g 'daemon off;'"]

Key changes:

  • Multi-stage build: Python app → Nginx container
  • Nginx listens on port 3000 (Airbase nginx image default)
  • Python app runs on port 8000 internally
  • Both services start via CMD

Step 2: Create nginx.conf

Create an nginx.conf file in your project root:

# Remove CSP headers from upstream responses
proxy_hide_header Content-Security-Policy;

server {
    listen 3000;
    server_name _;

    # Proxy to Python application
    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Remove CSP headers
        proxy_hide_header Content-Security-Policy;
        proxy_hide_header X-Content-Security-Policy;

        # WebSocket support (for Streamlit)
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 86400;
    }
}

What this does:

  • Removes Content-Security-Policy headers from Python app responses
  • Proxies all traffic to 127.0.0.1:8000 (Python app)
  • Configures WebSocket support for Streamlit
  • Nginx listens on port 3000

Step 3: Update Python Application Port

Ensure your Python app listens on port 8000 (not 3000):

For Streamlit (app.py):

import streamlit as st

# Your Streamlit app code here
st.title("My Dashboard")

Run command in Dockerfile CMD:

CMD ["/bin/sh", "-c", "streamlit run /app/app.py --server.port=8000 --server.address=127.0.0.1 & nginx -g 'daemon off;'"]

For Flask:

from flask import Flask

app = Flask(__name__)

if __name__ == '__main__':
    app.run(host='127.0.0.1', port=8000)

For FastAPI:

import uvicorn
from fastapi import FastAPI

app = FastAPI()

if __name__ == '__main__':
    uvicorn.run(app, host='127.0.0.1', port=8000)

Step 4: Verify airbase.json Configuration

Ensure airbase.json specifies port 3000 (nginx port):

{
  "framework": "container",
  "handle": "team/project",
  "port": 3000,
  "instanceType": "nano"
}

Important: The port must be 3000 (where nginx listens), not 8000.

Step 5: Update Project Structure

Your project should look like this:

streamlit-app/
├── airbase.json          ← Port 3000
├── Dockerfile            ← Multi-stage with nginx
├── nginx.conf            ← CSP header removal
├── requirements.txt
├── app.py                ← Runs on port 8000
└── .env

Complete Working Examples

Example 1: Streamlit Dashboard

Directory structure:

streamlit-dashboard/
├── airbase.json
├── Dockerfile
├── nginx.conf
├── requirements.txt
└── app.py

airbase.json:

{
  "framework": "container",
  "handle": "data-team/analytics-dashboard",
  "port": 3000,
  "instanceType": "b.small"
}

requirements.txt:

streamlit==1.31.1
pandas==2.2.0
plotly==5.18.0

app.py:

import streamlit as st
import pandas as pd
import plotly.express as px

st.set_page_config(page_title="Analytics Dashboard", layout="wide")

st.title("📊 Analytics Dashboard")

# Sample data
data = pd.DataFrame({
    'Month': ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
    'Revenue': [45000, 52000, 48000, 61000, 58000, 67000],
    'Expenses': [32000, 35000, 31000, 42000, 39000, 44000]
})

# Display metrics
col1, col2, col3 = st.columns(3)
col1.metric("Total Revenue", f"${data['Revenue'].sum():,}")
col2.metric("Total Expenses", f"${data['Expenses'].sum():,}")
col3.metric("Net Profit", f"${data['Revenue'].sum() - data['Expenses'].sum():,}")

# Interactive chart
st.subheader("Revenue vs Expenses")
fig = px.line(data, x='Month', y=['Revenue', 'Expenses'],
              title='Monthly Trends')
st.plotly_chart(fig, use_container_width=True)

# Data table
st.subheader("Raw Data")
st.dataframe(data, use_container_width=True)

nginx.conf:

proxy_hide_header Content-Security-Policy;

server {
    listen 3000;
    server_name _;

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_hide_header Content-Security-Policy;
        proxy_hide_header X-Content-Security-Policy;

        # WebSocket support for Streamlit
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 86400;
    }
}

Dockerfile:

# Stage 1: Python dependencies
FROM gdssingapore/airbase:python-3.13 AS python-app
ENV PYTHONUNBUFFERED=TRUE
COPY --chown=app:app requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY --chown=app:app . ./

# Stage 2: Nginx + Python runtime
FROM gdssingapore/airbase:nginx-1.28

# Copy Python app
COPY --from=python-app --chown=app:app /app /app

# Copy nginx config
COPY --chown=app:app nginx.conf /etc/nginx/conf.d/default.conf

# Install Python
USER root
RUN apt-get update && \
    apt-get install -y python3 python3-pip && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

USER app

# Start Streamlit and Nginx
CMD ["/bin/sh", "-c", "streamlit run /app/app.py --server.port=8000 --server.address=127.0.0.1 & nginx -g 'daemon off;'"]

Example 2: Plotly Dash Application

app.py:

import dash
from dash import html, dcc
import plotly.express as px
import pandas as pd

app = dash.Dash(__name__)

df = pd.DataFrame({
    'Category': ['A', 'B', 'C', 'D'],
    'Values': [10, 25, 15, 30]
})

fig = px.bar(df, x='Category', y='Values')

app.layout = html.Div([
    html.H1('Dashboard'),
    dcc.Graph(figure=fig)
])

if __name__ == '__main__':
    app.run(host='127.0.0.1', port=8000, debug=False)

Dockerfile:

FROM gdssingapore/airbase:python-3.13 AS python-app
ENV PYTHONUNBUFFERED=TRUE
COPY --chown=app:app requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY --chown=app:app . ./

FROM gdssingapore/airbase:nginx-1.28
COPY --from=python-app --chown=app:app /app /app
COPY --chown=app:app nginx.conf /etc/nginx/conf.d/default.conf

USER root
RUN apt-get update && \
    apt-get install -y python3 python3-pip && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

USER app

CMD ["/bin/sh", "-c", "python3 /app/app.py & nginx -g 'daemon off;'"]

Build and Deploy

Build

airbase container build

Deploy

airbase container deploy --yes

Verify

  1. Visit your application URL
  2. Open browser console (F12)
  3. You should see NO CSP errors (headers are removed)
  4. Verify functionality works

Security Implications

What You're Giving Up

By removing CSP headers, you lose protection against:

  1. Cross-Site Scripting (XSS) Attacks
  2. Malicious scripts can be injected into your app
  3. User data can be stolen via injected JavaScript

  4. Data Injection Attacks

  5. Attackers can inject malicious content
  6. Forms can be hijacked

  7. Clickjacking Protection

  8. Your app can be embedded in malicious iframes
  9. Users can be tricked into unwanted actions

Mitigation Strategies

Even without CSP, you can improve security:

  1. Input Validation

    # Validate and sanitize all user inputs
    from markupsafe import escape
    
    user_input = request.form.get('data')
    safe_input = escape(user_input)
    

  2. Output Encoding

    # Encode data before displaying
    from html import escape
    
    output = escape(unsafe_data)
    

  3. Authentication and Authorization

  4. Implement strong authentication
  5. Use Airbase IP whitelisting in airbase.json
  6. Require SGTS/TechPass login

  7. Limited Exposure

  8. Deploy to staging, not production
  9. Restrict access via IP whitelisting
  10. Use for internal tools only

  11. Regular Security Audits

  12. Monitor application logs
  13. Review dependencies regularly
  14. Update libraries promptly

When NOT to Use This

Absolutely avoid this workaround if:

  • Application handles PII (Personally Identifiable Information)
  • Application handles authentication credentials
  • Application processes financial data
  • Application is public-facing
  • Application is production-critical

Consider alternative architecture instead:

  • Rebuild with CSP-compliant framework
  • Use API backend + static CSP-compliant frontend
  • Use alternative libraries
  • Request security exception from security team

Troubleshooting

Issue: Nginx fails to start

Error:

nginx: [emerg] bind() to 0.0.0.0:3000 failed (98: Address already in use)

Cause: Port 3000 already in use or nginx config error

Solution:

  1. Check nginx.conf syntax:

    nginx -t -c /path/to/nginx.conf
    

  2. Ensure Python app uses port 8000, not 3000

  3. Verify Dockerfile CMD starts both services correctly

Issue: Python app not accessible

Symptom: Nginx starts but returns 502 Bad Gateway

Cause: Python app not running or wrong port

Solution:

  1. Check Python app listens on 127.0.0.1:8000:

    # For Streamlit
    streamlit run app.py --server.port=8000 --server.address=127.0.0.1
    
    # For Flask
    app.run(host='127.0.0.1', port=8000)
    

  2. Verify both services start in CMD:

    CMD ["/bin/sh", "-c", "python3 /app/app.py & nginx -g 'daemon off;'"]
    

  3. Check Container logs in Airbase Console for Python errors

Issue: Still seeing CSP errors

Symptom: CSP violations still appear in browser console

Cause: CSP headers not being removed properly

Solution:

  1. Verify proxy_hide_header directives in nginx.conf:

    proxy_hide_header Content-Security-Policy;
    proxy_hide_header X-Content-Security-Policy;
    

  2. Check if Python app sets CSP headers directly:

    # Remove any CSP header setting in Python code
    # ❌ Remove this:
    # response.headers['Content-Security-Policy'] = '...'
    

  3. Clear browser cache and hard reload (Ctrl+Shift+R)

Issue: WebSocket connection fails (Streamlit)

Symptom: Streamlit app loads but doesn't update dynamically

Cause: WebSocket proxy not configured

Solution:

Add WebSocket configuration to nginx.conf:

location / {
    # ... other proxy settings ...

    # WebSocket support
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_read_timeout 86400;
}

Issue: High memory usage

Symptom: Application crashes or becomes unresponsive

Cause: Running both nginx and Python in nano instance

Solution:

Upgrade to b.small instance in airbase.json:

{
  "instanceType": "b.small"
}

Then redeploy:

airbase container deploy --yes

Alternative Approaches to Consider

Before using this workaround, consider these alternatives:

1. Refactor to API + Static Frontend

Architecture:

┌──────────────┐        ┌──────────────┐
│  Static      │  API   │   Python     │
│  Frontend    │ ─────> │   Backend    │
│  (CSP-OK)    │  Calls │   (No CSP)   │
└──────────────┘        └──────────────┘
  • Build CSP-compliant React/Vue frontend
  • Python provides API only
  • Frontend makes API calls
  • Deploy frontend and backend separately

2. Use CSP-Compatible Libraries

Instead of Streamlit, consider:

  • Build custom Flask app with vanilla JavaScript
  • Use React + Plotly for charts
  • Use vanilla HTML/CSS/JS

Trade-off: More development time, but better security

3. Request Security Exception

For critical government applications:

  1. Document why CSP workaround is necessary
  2. Detail security mitigations in place
  3. Request formal security exception from agency security team
  4. Implement additional security controls

See Also


Summary

This workaround:

  • ✅ Allows Python apps with CSP violations to work on Airbase
  • ✅ Uses nginx proxy to remove CSP headers
  • ✅ Supports Streamlit, Dash, and other Python frameworks
  • ⚠️ Weakens security posture significantly
  • ⚠️ Should only be used as last resort
  • ⚠️ Requires understanding of security trade-offs

Remember: Always try CSP-compliant alternatives first. Only use this workaround when absolutely necessary and with appropriate security controls in place.