CSP expansion

pull/40/head
Aevann1 2022-11-30 22:03:32 +02:00
parent 836d3bfd98
commit 8b985a6ac9
39 changed files with 112 additions and 74 deletions

View File

@ -1,7 +1,7 @@
<head>
<title>No Internet Connection</title>
<meta name="viewport" content="width=500">
<style>
<style nonce="{{g.nonce}}">
:root {
--background: #123;
--primary: #238dcf;

View File

@ -3,7 +3,7 @@
{% set path = "assets/media/event/music" %}
{% set song = "/" + path + "/" + listdir('files/' + path)|random() + '?v=45' %}
{% endif %}
<script>
<script nonce="{{g.nonce}}">
const audio = new Audio("{{song}}");
audio.loop=true;

View File

@ -2,6 +2,7 @@ import secrets
from files.helpers.const import *
from files.helpers.settings import get_setting
from files.helpers.security import generate_hash
from files.helpers.cloudflare import CLOUDFLARE_AVAILABLE
from files.routes.wrappers import *
from files.__main__ import app, limiter
@ -30,6 +31,8 @@ def before_request():
if not get_setting('Bots') and request.headers.get("Authorization"): abort(403)
g.nonce = generate_hash(f'{time.time()}+{session.get("session_id")}')
g.webview = '; wv) ' in ua
if ' firefox/' in ua:
@ -51,6 +54,31 @@ def before_request():
g.db = db_session()
CSP = {
"upgrade-insecure-requests": "",
"object-src": "'none'",
"form-action": "'self'",
"manifest-src": "'self'",
"worker-src": "'self'",
"base-uri": "'self'",
"style-src-elem": "'self' 'nonce-{nonce}'",
"script-src-elem": "'self' 'nonce-{nonce}' challenges.cloudflare.com rdrama.net",
"script-src": "'unsafe-inline'",
"frame-src": "challenges.cloudflare.com www.youtube-nocookie.com platform.twitter.com",
"connect-src": "'self' tls-use1.fpapi.io api.fpjs.io 00bb6d59-7b11-4339-b1ae-b1f1259d1316.pushnotifications.pusher.com",
"report-to": "csp",
"report-uri": "csp",
}
if not IS_LOCALHOST:
CSP["default-src"] = "https:"
CSP["img-src"] = "https: data:"
CSP_str = ''
for k, val in CSP.items():
CSP_str += f'{k} {val}; '
@app.after_request
def after_request(response):
if response.status_code < 400:
@ -62,6 +90,10 @@ def after_request(response):
g.db.commit()
g.db.close()
del g.db
response.headers.add("Report-To", {"group":"csp","max_age":10886400,"endpoints":[{"url":"/sex"}]})
response.headers.add("Content-Security-Policy", CSP_str.format(nonce=g.nonce))
return response

View File

@ -421,3 +421,11 @@ if not os.path.exists(f'files/templates/donate_{SITE_NAME}.html'):
@auth_desired_with_logingate
def donate(v):
return render_template(f'donate_{SITE_NAME}.html', v=v)
@app.post('/sex')
@limiter.exempt
def sex():
print('sex!', flush=True)
print(request.values.items(), flush=True)
return ''

View File

@ -67,7 +67,7 @@
</section>
{% endif %}
<script>
<script nonce="{{g.nonce}}">
function submitAddAlt(element) {
if (!element || !element.form) return;
const isLinking = element.id == 'add-alt-form-link';

View File

@ -73,7 +73,7 @@
</form>
<style>
<style nonce="{{g.nonce}}">
@media (max-width: 767.98px) {
table {
display: inline-block;

View File

@ -3,7 +3,7 @@
{% block content %}
<script>
<script nonce="{{g.nonce}}">
function unbanDomain(t, domain) {
postToastSwitch(t,'/admin/unban_domain/' + domain);
t.parentElement.parentElement.remove();

View File

@ -50,7 +50,7 @@
{% endif %}
{% if p.award_count("tilt", v) %}
<style>
<style nonce="{{g.nonce}}">
@keyframes post-tilt {
0% {transform: rotate(0deg);}
25% {transform: rotate({{p.award_count("tilt", v)}}deg);}

View File

@ -7,7 +7,7 @@
{% endif %}
{% if p.award_count("crab", v) %}
<script>
<script nonce="{{g.nonce}}">
let audio = new Audio('/assets/media/crab.mp3');
audio.loop=true;
@ -20,7 +20,7 @@
</script>
{% endif %}
<style>
<style nonce="{{g.nonce}}">
.toe img {
width: 100px !important;
height: auto !important;

View File

@ -1,7 +1,7 @@
{% extends "casino/game_screen.html" %}
{% block script %}
<script>
<script nonce="{{g.nonce}}">
function makeBlackjackRequest(action) {
const xhr = new XMLHttpRequest();
xhr.open("post", `/casino/twentyone/${action}`);
@ -236,7 +236,7 @@
{% endblock %}
{% block actions %}
<style>
<style nonce="{{g.nonce}}">
.blackjack-cardset {
position: relative;
display: flex;

View File

@ -1,7 +1,7 @@
{% extends "default.html" %}
{% block pagetitle %}{{game.capitalize()}}{% endblock %}
{% block content %}
<style>
<style nonce="{{g.nonce}}">
.game_screen-title {
display: flex;
align-items: center;
@ -27,7 +27,7 @@
}
</style>
<script type="text/javascript">
<script nonce="{{g.nonce}}">
/**
* This script block contains generic helper function usable across casino games:
* - Wagers
@ -233,7 +233,7 @@
{% block script %} {% endblock %}
<style>
<style nonce="{{g.nonce}}">
@keyframes drawing {
from {
left: 0;

View File

@ -1,7 +1,7 @@
{% extends "casino/game_screen.html" %} {% block result %} N/A {% endblock %}
{% block script %}
<script type="text/javascript">
<script nonce="{{g.nonce}}">
if (
document.readyState === "complete" ||
(document.readyState !== "loading" && !document.documentElement.doScroll)
@ -393,7 +393,7 @@
{% endblock %}
{% block screen %}
<style>
<style nonce="{{g.nonce}}">
.roulette-table-number {
flex: 1;
height: 60px;

View File

@ -1,7 +1,7 @@
{% extends "casino/game_screen.html" %} {% block result %} N/A {% endblock %}
{% block script %}
<script type="text/javascript">
<script nonce="{{g.nonce}}">
function pullSlots() {
const { amount, currency } = getWager();
@ -73,7 +73,7 @@
{% endblock %}
{% block screen %}
<style>
<style nonce="{{g.nonce}}">
.slots_reels {
display: flex;
align-items: center;

View File

@ -17,7 +17,7 @@
data-avatar="{{v.profile_url}}"
data-hat="{{v.hat_active}}">
</div>
<script>window.global = window</script>
<script nonce="{{g.nonce}}">window.global = window</script>
{% if IS_LOCALHOST %}
<script defer src="https://rdrama.net/assets/js/chat_done.js"></script>
{% else %}

View File

@ -1,7 +1,7 @@
{%- import 'util/macros.html' as macros with context -%}
{% if not request.headers.get("xhr") %}
{% if comment_info %}
<script>
<script nonce="{{g.nonce}}">
if (location.hash != 'context')
location.hash = 'context'
</script>
@ -238,7 +238,7 @@
{% endif %}
{% if c.award_count("tilt", v) %}
<style>
<style nonce="{{g.nonce}}">
@keyframes c{{c.id}}-tilt {
0% {transform: rotate(0deg);}
25% {transform: rotate({{c.award_count("tilt", v)}}deg);}
@ -816,7 +816,7 @@
<script defer src="{{'js/comments+submission_listing.js' | asset}}"></script>
<script defer src="{{'js/comments.js' | asset}}"></script>
<script>
<script nonce="{{g.nonce}}">
{% if p and (not v or v.highlightcomments) %}
comments = JSON.parse(localStorage.getItem("comment-counts")) || {}
lastCount = comments['{{p.id}}']
@ -881,7 +881,7 @@
{% if SITE_NAME == 'rDrama' and not request.headers.get("xhr") and v and 'SamsungBrowser' not in g.agent and v.theme != 'transparent' %}
<div id="detection" style="display:none;background-color:canvas;color-scheme:light"></div>
<script>
<script nonce="{{g.nonce}}">
const detectionDiv = document.querySelector('#detection');
const isAutoDark = getComputedStyle(detectionDiv).backgroundColor != 'rgb(255, 255, 255)';
if (!isAutoDark) {

View File

@ -5,7 +5,7 @@
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline'; connect-src 'self'; object-src 'none';">
<style>
<style nonce="{{g.nonce}}">
:root{--primary:#ff66ac}
.mod:before {
content: "(((";

View File

@ -5,7 +5,7 @@
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline'; connect-src 'self'; object-src 'none';">
<style>
<style nonce="{{g.nonce}}">
:root{--primary:#ff66ac}
.mod:before {
content: "(((";

View File

@ -5,7 +5,7 @@
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline'; connect-src 'self'; object-src 'none';">
<style>
<style nonce="{{g.nonce}}">
:root{--primary:#ff66ac}
.mod:before {
content: "(((";

View File

@ -3,7 +3,7 @@
{% block pagetype %}message{% endblock %}
{% block banner %}
<style>
<style nonce="{{g.nonce}}">
.shop-tabs {
padding-top: 50px;
}
@ -59,7 +59,7 @@
{% endblock %}
{% block content %}
<script>
<script nonce="{{g.nonce}}">
function equip_hat(t, hat_id, hat_name) {
const profile_pic_hat = document.getElementById("profile-pic-35-hat");
function extra_actions(xhr) {

View File

@ -4,7 +4,7 @@
{%- endif -%}
<nav class="shadow-md fixed-top">
<style>
<style nonce="{{g.nonce}}">
body {padding-top: 83px !important}
@media (max-width: 767.98px) {
body {
@ -55,7 +55,7 @@
{% if sub %}
<a id="sub-name" href="/h/{{sub}}" class="font-weight-bold ml-2 flex-grow-1 mt-1" {% if sub.name|length >= 17 %}style="font-size:max(10px,1.2vw)"{% else %}style="font-size:max(10px,1.2vw)"{% endif %}>{% if not HOLE_STYLE_FLAIR %}/h/{% endif %}{{sub}}</a>
{% elif has_logo %}
<style>
<style nonce="{{g.nonce}}">
{% if SITE_NAME == 'WPD' %}
@media (min-width: 1000px) {
{% else %}
@ -343,12 +343,12 @@
<div id="formkey" class="d-none">{{v|formkey}}</div>
{% endif %}
<script>
<script nonce="{{g.nonce}}">
IMAGE_FORMATS = {{IMAGE_FORMATS|safe}};
</script>
{% if not v %}
<style>
<style nonce="{{g.nonce}}">
.pad {
padding-bottom: 7.4px;
padding-top: 7.4px;
@ -357,7 +357,7 @@
{% endif %}
{% if v and v.poor -%}
<style>
<style nonce="{{g.nonce}}">
* :not(img[src="/i/hand.webp"] + img, img.golden, img[g], img[glow], .live-circle) {
animation: unset !important;
}

View File

@ -62,7 +62,7 @@
{% block navbar %}
<div class="d-flex align-items-center">
{% if SITE_NAME != 'WPD' and not sub %}
<style>
<style nonce="{{g.nonce}}">
@media (max-width: 427px) {
.smol-fp {
font-size: 2.7vw;
@ -171,7 +171,7 @@
<div class="d-none" id="strid">{{SITE}}{{v.id}}</div>
<div class="d-none" id="pusherid">{{PUSHER_ID}}</div>
<script defer src="{{'js/vendor/pusher.js' | asset}}"></script>
<script>
<script nonce="{{g.nonce}}">
if (typeof Android != 'undefined') {
Android.Subscribe('{{SITE}}{{v.id}}');
}
@ -179,7 +179,7 @@
{% endif %}
{% if request.path == '/' and not g.webview and time.time() > session.get('tooltip_last_dismissed',0)+86400*30 %}
<style>
<style nonce="{{g.nonce}}">
.dismiss-beg {
color: #919191;
float: left;
@ -193,7 +193,7 @@
<div id="mobile-prompt" data-bs-toggle="tooltip" data-bs-container="#mobile-prompt-container" data-bs-placement="top" data-bs-trigger="click" data-bs-html="true" title="<i class='dismiss-beg fas fa-x'></i>Click me to install the {{SITE_NAME}} mobile app!"></div>
</div>
<script>
<script nonce="{{g.nonce}}">
if (!window.matchMedia('(display-mode: minimal-ui)')['matches']) {
if (window.innerWidth <= 737) {
document.addEventListener('DOMContentLoaded', function() {
@ -214,7 +214,7 @@
}
</script>
<style>
<style nonce="{{g.nonce}}">
#mobile-prompt + .bs-tooltip-bottom {
transform: None !important;
inset: 0px 0px auto auto !important;
@ -224,7 +224,7 @@
{% if request.path == '/' and v and FP %}
{% if not v.fp %}
<script>
<script nonce="{{g.nonce}}">
function fp(fp) {
const xhr = new XMLHttpRequest();
xhr.open("POST", '/fp/'+fp);

View File

@ -1,7 +1,7 @@
{% extends "default.html" %}
{% block pagetitle %}Live{% endblock %}
{% block content %}
<style>
<style nonce="{{g.nonce}}">
tr:hover {
border: 2.5px solid var(--primary);
}
@ -12,7 +12,7 @@
width: 130px;
}
</style>
<script>
<script nonce="{{g.nonce}}">
function go_to(e, link) {
if (!e.target.classList.contains('remove-streamer'))
window.open(link, '_blank');

View File

@ -2,22 +2,22 @@
{% block pagetitle %}Moderation Log{% endblock %}
{% block content %}
{% if v %}
<style>:root{--primary:#{{v.themecolor}}}</style>
<style nonce="{{g.nonce}}">:root{--primary:#{{v.themecolor}}}</style>
<link rel="stylesheet" href="{{'css/main.css' | asset}}">
<link rel="stylesheet" href="{{('css/'~v.theme~'.css') | asset}}">
{% if v.agendaposter %}
<style>
<style nonce="{{g.nonce}}">
html {
cursor:url('/i/dildo.webp?v=2000'), auto;
}
</style>
{% elif v.css %}
<style>
<style nonce="{{g.nonce}}">
{{v.css | safe}}
</style>
{% endif %}
{% else %}
<style>:root{--primary:#{{DEFAULT_COLOR}}</style>
<style nonce="{{g.nonce}}">:root{--primary:#{{DEFAULT_COLOR}}</style>
<link rel="stylesheet" href="{{'css/main.css' | asset}}">
<link rel="stylesheet" href="{{('css/'~DEFAULT_THEME~'.css') | asset}}">
{% endif %}

View File

@ -1,6 +1,6 @@
<div id="form" class="d-none"></div>
<div class="modal fade" id="emojiModal" tabindex="-1" role="dialog" aria-labelledby="emojiModalTitle" aria-hidden="true">
<style>
<style nonce="{{g.nonce}}">
#emojiTabs {height: 80%;}
@media (max-width: 650px) {
#emojiTabs {height: 100%;}
@ -70,7 +70,7 @@
I've took the liberty to populate this tab with a selection of Anton's emojis 😽. Next time you'll find the ones you used the most in there 📚
</div>
<div id="tab-content" class="tab-content d-flex flex-wrap py-3 pl-2" hidden>
<style>
<style nonce="{{g.nonce}}">
.emoji2 {
/*background: None!important;*/
width:60px;

View File

@ -152,7 +152,7 @@
</ul>
</nav>
{% endif %}
<style>
<style nonce="{{g.nonce}}">
.comment {
margin-top: 0.3rem;
}

View File

@ -42,7 +42,7 @@
</div>
</div>
<script>
<script nonce="{{g.nonce}}">
document.addEventListener('DOMContentLoaded', function() {
for (const x of ['css','profilecss']) {
const ta = document.getElementById(`${x}-textarea`);

View File

@ -1,7 +1,7 @@
{% extends "settings.html" %}
{% block pagetitle %}Personal Settings{% endblock %}
{% block content %}
<style>
<style nonce="{{g.nonce}}">
.bg-image {
padding: 0.25rem;
width: 15rem;
@ -261,7 +261,7 @@
{% include "modals/gif.html" %}
{% if v.flairchanged %}
<script>
<script nonce="{{g.nonce}}">
document.addEventListener('DOMContentLoaded', function() {
const date = formatDate(new Date({{v.flairchanged}}*1000));
const text = ` - Your flair has been locked until ${date}`;
@ -308,7 +308,7 @@
{{permanent_filter_modal('profanityreplacer', '/settings/personal', 'profanityreplacer', 'Profanity Replacer', 'Soapy-Mouthed Angel')}}
{%- endif %}
{% if FEATURES['USERS_PERMANENT_WORD_FILTERS'] -%}
<script>
<script nonce="{{g.nonce}}">
document.addEventListener("DOMContentLoaded", function (event) {
const sr_toggle = document.getElementById("slurreplacer");
const sr_link = document.getElementById('slurreplacer-perma-link');

View File

@ -8,7 +8,7 @@
{% block pagetitle %}Shop{% endblock %}
{% block pagetype %}message{% endblock %}
{% block banner %}
<style>
<style nonce="{{g.nonce}}">
.shop-tabs {
padding-top: 50px;
}

View File

@ -2,7 +2,7 @@
{% block pagetitle %}/h/{{sub}} Mods{% endblock %}
{% block content %}
<script>
<script nonce="{{g.nonce}}">
function removeMod(e) {
sendFormXHR(e,
() => {

View File

@ -8,7 +8,7 @@
{% endblock %}
{% block pagetype %}thread{% endblock %}
{% block head_final %}
<style>
<style nonce="{{g.nonce}}">
body > .container {
padding-left: 20px !important;
padding-right: 20px !important;
@ -411,7 +411,7 @@
</div>
{% if offset %}
<script>
<script nonce="{{g.nonce}}">
function viewmore(pid,sort,offset,ids) {
btn = document.getElementById("viewbtn");
btn.disabled = true;
@ -492,7 +492,7 @@
{% include "comments.html" %}
{% endif %}
<script>
<script nonce="{{g.nonce}}">
{% if not v or v.highlightcomments %}
document.addEventListener('DOMContentLoaded', function() {
showNewCommentCounts('{{p.id}}', {{p.comment_count}})
@ -509,7 +509,7 @@
</script>
{% if success %}
<script>
<script nonce="{{g.nonce}}">
history.pushState(null, null, '{{p.permalink}}');
localStorage.setItem("post-title", "")
localStorage.setItem("post-text", "")
@ -526,7 +526,7 @@
{% endif %}
{% if fart and not (v and v.has_badge(128)) %}
<script>
<script nonce="{{g.nonce}}">
fart = Math.floor(Math.random() * 5) + 1
let audio = new Audio(`/assets/images/${fart}.webp`);

View File

@ -75,7 +75,7 @@
</div>
{% if offset %}
<script>
<script nonce="{{g.nonce}}">
function viewmore(pid,sort,offset,ids) {
btn = document.getElementById("viewbtn");
btn.disabled = true;

View File

@ -223,7 +223,7 @@
</div>
{% endif %}
<script>
<script nonce="{{g.nonce}}">
{% if not v or v.highlightcomments %}
document.addEventListener('DOMContentLoaded', function() {
showNewCommentCounts({{p.id}}, {{p.comment_count}})

View File

@ -113,7 +113,7 @@
</div>
{% endblock %}
{% if request.path == '/submit' %}
<script>
<script nonce="{{g.nonce}}">
let sub = document.getElementById('sub')
if (sub) sub.value = localStorage.getItem("sub")
</script>

View File

@ -67,7 +67,7 @@
</div>
</div>
<script>
<script nonce="{{g.nonce}}">
document.onpaste = function(event) {
files = structuredClone(event.clipboardData.files);

View File

@ -2,7 +2,7 @@
{% block pagetitle %}Submit Marseys{% endblock %}
{% block pagetype %}message{% endblock %}
{% block content %}
<style>
<style nonce="{{g.nonce}}">
input:not(.btn) {
text-transform: lowercase;
}
@ -69,7 +69,7 @@
</div>
</div>
<script>
<script nonce="{{g.nonce}}">
document.onpaste = function(event) {
files = structuredClone(event.clipboardData.files);

View File

@ -61,7 +61,7 @@
</div>
</div>
<script>
<script nonce="{{g.nonce}}">
document.onpaste = function(event) {
files = structuredClone(event.clipboardData.files);

View File

@ -12,7 +12,7 @@
<script defer src="{{'js/vendor/bootstrap.js' | asset}}"></script>
<script defer src="{{'js/core.js' | asset}}"></script>
<script>
<script nonce="{{g.nonce}}">
if (window.matchMedia('(display-mode: minimal-ui)')['matches']) {
const links = document.querySelectorAll('a[data-target="t"]');
for (const link of links) {
@ -108,9 +108,9 @@
<link rel="stylesheet" href="{{'css/main.css' | asset}}">
<link id="favicon" rel="icon" type="image/webp" href="/icon.webp?v=1">
{% if v %}
<style>:root{--primary:#{{v.themecolor}}}</style>
<style nonce="{{g.nonce}}">:root{--primary:#{{v.themecolor}}}</style>
{% if v.agendaposter %}
<style>
<style nonce="{{g.nonce}}">
html {
cursor:url('/i/dildo.webp?v=2000'), auto;
}
@ -119,12 +119,12 @@
{% if include_user_css and not EVENT_STYLES %}
<link rel="stylesheet" href="{{('css/'~v.theme~'.css') | asset}}">
{% if v.css and not request.path.startswith('/settings/css') %}
<style>
<style nonce="{{g.nonce}}">
{{v.css | safe}}
</style>
{% endif %}
{% if v.themecolor == '30409f' %}
<style>
<style nonce="{{g.nonce}}">
p a {
color: #2a96f3;
}
@ -168,7 +168,7 @@
{% endif %}
{% if background %}
<style>
<style nonce="{{g.nonce}}">
body {
background:url("/i/backgrounds/{{background}}.webp?v=4") center center fixed;
background-color: var(--background);
@ -181,7 +181,7 @@
{% endmacro %}
{% macro default_theme() %}
<style>:root{--primary:#{{DEFAULT_COLOR}}}</style>
<style nonce="{{g.nonce}}">:root{--primary:#{{DEFAULT_COLOR}}}</style>
<link rel="stylesheet" href="{{('css/'~DEFAULT_THEME~'.css') | asset}}">
{% endmacro %}

View File

@ -5,7 +5,6 @@ add_header Referrer-Policy "same-origin";
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
add_header X-Frame-Options "deny";
add_header X-Content-Type-Options "nosniff";
add_header Content-Security-Policy "script-src 'self' 'unsafe-inline' challenges.cloudflare.com; connect-src 'self' tls-use1.fpapi.io api.fpjs.io 00bb6d59-7b11-4339-b1ae-b1f1259d1316.pushnotifications.pusher.com; object-src 'none';";
sendfile on;
sendfile_max_chunk 1m;

View File

@ -8,7 +8,6 @@ server {
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
add_header X-Frame-Options "deny";
add_header X-Content-Type-Options "nosniff";
add_header Content-Security-Policy "script-src 'self' 'unsafe-inline' challenges.cloudflare.com rdrama.net; connect-src 'self' tls-use1.fpapi.io api.fpjs.io 00bb6d59-7b11-4339-b1ae-b1f1259d1316.pushnotifications.pusher.com; object-src 'none';";
location / {
proxy_pass http://localhost:5000/;