Skip to content

Commit

Permalink
Show nice CSP warning in terminal
Browse files Browse the repository at this point in the history
  • Loading branch information
wwwillchen committed Aug 4, 2024
1 parent b09a0a1 commit 0102003
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 1 deletion.
61 changes: 60 additions & 1 deletion mesop/server/static_file_serving.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
from collections import OrderedDict
from io import BytesIO
from typing import Any, Callable
from urllib.parse import urlparse

from flask import Flask, Response, g, request, send_file
from flask import Flask, Response, g, make_response, request, send_file
from werkzeug.security import safe_join

from mesop.exceptions import MesopException
from mesop.runtime import runtime
from mesop.server.constants import WEB_COMPONENTS_PATH_SEGMENT
from mesop.utils import terminal_colors as tc
from mesop.utils.runfiles import get_runfile_location, has_runfiles
from mesop.utils.url_utils import sanitize_url_for_csp

Expand Down Expand Up @@ -112,6 +114,62 @@ def serve_file(path: str):
else:
return send_file(retrieve_index_html(), download_name="index.html")

@app.route("/__csp__", methods=["POST"])
def csp_report():
# Get the CSP violation report from the request
# Flask expects the MIME type to be application/json
# but it's actually application/csp-report
report = request.get_json(force=True)

document_uri: str = report["csp-report"]["document-uri"]
path = urlparse(document_uri).path
blocked_uri: str = report["csp-report"]["blocked-uri"]
# Remove the path from blocked_uri, keeping only the origin.
blocked_site = (
urlparse(blocked_uri).scheme + "://" + urlparse(blocked_uri).netloc
)
violated_directive: str = report["csp-report"]["violated-directive"]
if violated_directive == "script-src-elem":
keyword_arg = "allowed_script_srcs"
elif violated_directive == "connect-src":
keyword_arg = "allowed_connect_srcs"
elif violated_directive == "frame-ancestors":
keyword_arg = "allowed_iframe_parents"
elif violated_directive == "require-trusted-types-for":
keyword_arg = "dangerously_disable_trusted_types"
else:
raise Exception("Unexpected CSP violation:", violated_directive, report)
keyword_arg_value = f"""[
'{tc.CYAN}{blocked_site}{tc.RESET}',
]"""
if keyword_arg == "dangerously_disable_trusted_types":
keyword_arg_value = f"{tc.CYAN}True{tc.RESET}"
print(
f"""
{tc.RED}⚠️ Content Security Policy Error ⚠️{tc.RESET}
{tc.YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{tc.RESET}
{tc.CYAN}Directive:{tc.RESET} {tc.GREEN}{violated_directive}{tc.RESET}
{tc.CYAN}Blocked URL:{tc.RESET} {tc.GREEN}{blocked_uri}{tc.RESET}
{tc.CYAN}App path:{tc.RESET} {tc.GREEN}{path}{tc.RESET}
{tc.YELLOW}ℹ️ If this is coming from your web component,
update your security policy like this:{tc.RESET}
{tc.MAGENTA}@me.page({tc.RESET}
{tc.BLUE}security_policy={tc.RESET}{tc.MAGENTA}me.SecurityPolicy({tc.RESET}
{tc.GREEN}{keyword_arg}={tc.RESET}{keyword_arg_value}
{tc.MAGENTA}){tc.RESET}
{tc.MAGENTA}){tc.RESET}
{tc.YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{tc.RESET}
""" # noqa: RUF001
)

response = make_response()
response.status_code = 204
return response

@app.before_request
def generate_nonce():
g.csp_nonce = secrets.token_urlsafe(16)
Expand Down Expand Up @@ -150,6 +208,7 @@ def add_security_headers(response: Response):
# https://angular.io/guide/security#enforcing-trusted-types
"trusted-types": "angular angular#unsafe-bypass lit-html",
"require-trusted-types-for": "'script'",
"report-uri": "/__csp__",
}
)
if page_config and page_config.stylesheets:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ style-src 'self' 'unsafe-inline' fonts.googleapis.com
script-src 'self' 'nonce-{{NONCE}}'
trusted-types angular angular#unsafe-bypass lit-html
require-trusted-types-for 'script'
report-uri /__csp__
frame-ancestors 'self' google.com
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ style-src 'self' 'unsafe-inline' fonts.googleapis.com http://google.com/styleshe
script-src 'self' 'nonce-{{NONCE}}' http://google.com/allowed_script_srcs/1%2C1%3B2 http://google.com/allowed_script_srcs/2%2C1%3B2
trusted-types angular angular#unsafe-bypass lit-html
require-trusted-types-for 'script'
report-uri /__csp__
connect-src 'self' http://google.com/allowed_connect_srcs/1%2C1%3B2 http://google.com/allowed_connect_srcs/2%2C1%3B2
frame-ancestors 'self' http://google.com/allowed_iframe_parents/1%2C1%3B2 http://google.com/allowed_iframe_parents/2%2C1%3B2
1 change: 1 addition & 0 deletions mesop/tests/e2e/snapshots/web_security_test.ts_csp.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ style-src 'self' 'unsafe-inline' fonts.googleapis.com
script-src 'self' 'nonce-{{NONCE}}'
trusted-types angular angular#unsafe-bypass lit-html
require-trusted-types-for 'script'
report-uri /__csp__
frame-ancestors 'self' https://google.github.io
7 changes: 7 additions & 0 deletions mesop/utils/terminal_colors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
RED = "\033[31m"
GREEN = "\033[32m"
YELLOW = "\033[33m"
BLUE = "\033[34m"
MAGENTA = "\033[35m"
CYAN = "\033[36m"
RESET = "\033[0m"

0 comments on commit 0102003

Please sign in to comment.