Skip to main content
Content Security Policy (CSP) is a W3C recommendation since 2016 and the industry standard for securing web applications against cross-site scripting (XSS) attacks.

Overview

Content Security Policy (CSP) significantly reduces the risk and impact of cross-site scripting attacks in modern browsers by controlling which resources can be loaded and executed on your web pages.

Current Vulnerability

The Normo Unsecure PWA currently lacks CSP headers, making it vulnerable to:
  • Cross-site scripting (XSS) attacks
  • Unauthorized script execution
  • Data injection attacks
  • Clickjacking
Inspecting templates/layout.html:1-35, there are no CSP headers defined, and main.py:1-74 shows no CSP decorator usage on routes.

Implementation Guide

1

Install Flask-CSP Extension

Install the Flask-CSP package to easily manage CSP headers:
pip install flask-csp
2

Design Your CSP Policy

Design a policy that fits your application’s needs. You can:
3

Add CSP Decorator to Routes

Import and apply the CSP decorator to your Flask routes:
from flask import Flask, render_template, request, redirect
from flask_cors import CORS
from flask_csp.csp import csp_header
import user_management as dbHandler

app = Flask(__name__)
CORS(app)

@app.route("/", methods=["POST", "GET"])
@app.route("/index.html", methods=["POST", "GET", "PUT", "PATCH", "DELETE"])
@csp_header({
    "base-uri": "'self'",
    "default-src": "'self'",
    "style-src": "'self'",
    "script-src": "'self'",
    "img-src": "*",
    "media-src": "'self'",
    "font-src": "'self'",
    "object-src": "'self'",
    "child-src": "'self'",
    "connect-src": "'self'",
    "worker-src": "'self'",
    "report-uri": "/csp_report",
    "frame-ancestors": "'none'",
    "form-action": "'self'",
    "frame-src": "'none'",
})
def home():
    if request.method == "GET" and request.args.get("url"):
        url = request.args.get("url", "")
        return redirect(url, code=302)
    elif request.method == "GET":
        msg = request.args.get("msg", "")
        return render_template("/index.html", msg=msg)
    elif request.method == "POST":
        username = request.form["username"]
        password = request.form["password"]
        isLoggedIn = dbHandler.retrieveUsers(username, password)
        if isLoggedIn:
            dbHandler.listFeedback()
            return render_template("/success.html", value=username, state=isLoggedIn)
        else:
            return render_template("/index.html")
    else:
        return render_template("/index.html")

@app.route("/signup.html", methods=["POST", "GET", "PUT", "PATCH", "DELETE"])
@csp_header({
    "base-uri": "'self'",
    "default-src": "'self'",
    "style-src": "'self'",
    "script-src": "'self'",
    "img-src": "*",
    "media-src": "'self'",
    "font-src": "'self'",
    "object-src": "'self'",
    "child-src": "'self'",
    "connect-src": "'self'",
    "worker-src": "'self'",
    "report-uri": "/csp_report",
    "frame-ancestors": "'none'",
    "form-action": "'self'",
    "frame-src": "'none'",
})
def signup():
    if request.method == "GET" and request.args.get("url"):
        url = request.args.get("url", "")
        return redirect(url, code=302)
    if request.method == "POST":
        username = request.form["username"]
        password = request.form["password"]
        DoB = request.form["dob"]
        dbHandler.insertUser(username, password, DoB)
        return render_template("/index.html")
    else:
        return render_template("/signup.html")
4

Add CSP Violation Reporting Endpoint

Create an endpoint to log CSP violations:
main.py
from flask_wtf.csrf import CSRFProtect

csrf = CSRFProtect(app)

@app.route("/csp_report", methods=["POST"])
@csrf.exempt
def csp_report():
    app.logger.critical(request.data.decode())
    return "done"
The CSP report endpoint should be exempt from CSRF protection to allow browsers to send violation reports.
5

Add CSP Meta Tag to Layout Template

Add a matching CSP meta tag to layout.html for additional protection:
templates/layout.html
<!doctype html>
<html>
   <head>
        <meta
          http-equiv="Content-Security-Policy"
          content="base-uri 'self'; default-src 'self'; style-src 'self'; script-src 'self'; img-src 'self' *; media-src 'self'; font-src 'self'; connect-src 'self'; object-src 'self'; worker-src 'self'; frame-src 'none'; form-action 'self'; manifest-src 'self'"
        />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <meta http-equiv="X-UA-Compatible" content="ie=edge" />
        <link rel="stylesheet" type="text/css" href="static/css/style.css">
        <title>Normo Unsecure PWA</title>
        <link rel="manifest" href="static/manifest.json" />
        <link rel="icon" type="image/x-icon" href="static/images/favicon.png" />
        <meta name="theme-color" content="#14E6DD" />
   </head>
   <body>  
    <!-- rest of template -->
   </body>
</html>

CSP Directives Explained

base-uri
string
default:"'self'"
Restricts the URLs that can be used in a document’s <base> element. Setting to 'self' prevents base tag hijacking.
default-src
string
default:"'self'"
Serves as a fallback for other CSP fetch directives. Only allows resources from the same origin.
script-src
string
default:"'self'"
Controls which scripts can be executed. 'self' only allows scripts from your domain, preventing inline scripts and external malicious scripts.
style-src
string
default:"'self'"
Specifies valid sources for stylesheets. Restricts to same-origin CSS files.
img-src
string
default:"*"
Defines valid sources for images. Using * allows images from any source (adjust based on your needs).
form-action
string
default:"'self'"
Restricts the URLs which can be used as the target of form submissions. Prevents forms from posting to external sites.
frame-ancestors
string
default:"'none'"
Specifies valid parents that may embed a page using <frame>, <iframe>, etc. 'none' prevents clickjacking attacks.
frame-src
string
default:"'none'"
Specifies valid sources for nested browsing contexts (frames/iframes).
report-uri
string
default:"/csp_report"
Instructs the browser to send CSP violation reports to this endpoint for monitoring.

Testing Your CSP

1

Check Browser Console

Open your browser’s developer console and look for CSP violation warnings when loading pages.
2

Monitor CSP Reports

Check your application logs for CSP violation reports sent to the /csp_report endpoint:
tail -f logs/app.log | grep CSP
3

Use CSP Evaluator

Use Google’s CSP Evaluator to analyze your policy for weaknesses.

Common Issues

Inline Scripts BlockedIf you have inline scripts in your templates (like <script>alert('test')</script>), they will be blocked by CSP. Move all scripts to external files or use nonces/hashes for specific inline scripts.
External Resources BlockedThe current policy blocks external scripts. The suspicious script in layout.html:32:
<script src="https//getfreebootstrap.ru/bootstrap.js"></script>
will be blocked (which is good - this appears to be malicious). Remove it or update your CSP if you need legitimate external resources.

Security Benefits

Protection Against XSSCSP prevents execution of malicious scripts injected through user input, protecting against the most common web vulnerability.
Defense in DepthEven if an attacker finds an injection point, CSP limits what malicious code can do (e.g., preventing data exfiltration to external domains).
Clickjacking PreventionThe frame-ancestors directive prevents your site from being embedded in iframes on malicious sites.

Additional Resources

Build docs developers (and LLMs) love