Skip to content

Commit

Permalink
(WIP) Set up Content-Security-Policy "reporting" (monitoring)
Browse files Browse the repository at this point in the history
  • Loading branch information
YoshiRulz committed Oct 15, 2024
1 parent bbdef9c commit 6b4c549
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 1 deletion.
27 changes: 26 additions & 1 deletion TASVideos/Extensions/ApplicationBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using Microsoft.AspNetCore.StaticFiles;
#define SHOULD_INCLUDE_STAGING_CSP

using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.FileProviders;
using TASVideos.Core.Settings;
using TASVideos.Middleware;
Expand Down Expand Up @@ -48,6 +50,7 @@ public static IApplicationBuilder UseStaticFilesWithExtensionMapping(this IAppli

public static IApplicationBuilder UseMvcWithOptions(this IApplicationBuilder app, IHostEnvironment env, AppSettings settings)
{
var userAgentReportUrl = $"{settings.BaseUrl}/Diagnostics/UserAgentInterventionReports";
string[] trustedJsHosts = [
"https://cdn.jsdelivr.net",
"https://cdnjs.cloudflare.com",
Expand All @@ -63,12 +66,26 @@ public static IApplicationBuilder UseMvcWithOptions(this IApplicationBuilder app
"form-action 'self'", // domains allowed for `<form action/>` (POST target page)
"frame-src 'self' https://www.youtube.com/embed/", // allow these domains in <iframe/>
"img-src *", // allow hotlinking images from any domain in UGC (not great)
/*
"report-to for-csp", // browsers using CSP Level 3+ will look up this key in the `Reporting-Endpoints` header and use that URI
$"report-uri {userAgentReportUrl}?kind=csp&csp-lvl=lteq2", // browsers from before CSP Level 3 will use this
*/
"require-trusted-types-for 'script'", // experimental, but Google seems to be pushing it: should block `HTMLScriptElement.innerHTML = "user.pwn();";`, and similarly block adding in-line scripts as attrs
$"script-src 'self' {string.Join(' ', trustedJsHosts)}", // `<script/>`s will be blocked unless they're from one of these domains
"style-src 'unsafe-inline' 'self' https://cdnjs.cloudflare.com/ajax/libs/font-awesome/", // allow `<style/>`, and `<link rel="stylesheet"/>` if it's from our domain or trusted CDN
"upgrade-insecure-requests", // browser should automagically replace links to any `http://tasvideos.org/...` URL (in UGC, for example) with HTTPS
];
var contentSecurityPolicyValue = string.Join("; ", cspDirectives);
#if SHOULD_INCLUDE_STAGING_CSP
var contentSecurityPolicyStagingValue = string.Join("; ", [
"report-to for-csp-staging", // browsers using CSP Level 3+ will look up this key in the `Reporting-Endpoints` header and use that URI
$"report-uri {userAgentReportUrl}?kind=csp-staging&csp-lvl=lteq2", // browsers from before CSP Level 3 will use this
"object-src 'none'", // new directive for testing (should probably be changed to this anyway, currently it falls back to `default-src`)
..cspDirectives, // at end because, in the case of a duplicate (like `report-to`), the first is used
]);
#else
var contentSecurityPolicyStagingValue = contentSecurityPolicyValue;
#endif
var permissionsPolicyValue = string.Join(", ", [
"camera=()", // defaults to `self`
"display-capture=()", // defaults to `self`
Expand All @@ -81,12 +98,20 @@ public static IApplicationBuilder UseMvcWithOptions(this IApplicationBuilder app

// ...and that's all the non-experimental options listed on MDN as of 2024-04
]);
var reportingEndpointsValue = string.Join(", ", [
$"for-csp=\"{userAgentReportUrl}?kind=csp&csp-lvl=gteq3\"",
$"for-csp-staging=\"{userAgentReportUrl}?kind=csp-staging&csp-lvl=gteq3\"",
]);
app.Use(async (context, next) =>
{
#if SHOULD_INCLUDE_STAGING_CSP
context.Response.Headers.ContentSecurityPolicyReportOnly = contentSecurityPolicyStagingValue;
#endif
context.Response.Headers["Cross-Origin-Embedder-Policy"] = "unsafe-none"; // this is as unsecure as before, but can't use `credentialless`, due to breaking YouTube Embeds, see https://github.com/TASVideos/tasvideos/issues/1852
context.Response.Headers["Cross-Origin-Opener-Policy"] = "same-origin";
context.Response.Headers["Cross-Origin-Resource-Policy"] = "cross-origin"; // TODO this is as unsecure as before; should be `same-site` or `same-origin` when serving auth-gated responses
context.Response.Headers["Permissions-Policy"] = permissionsPolicyValue;
context.Response.Headers["Reporting-Endpoints"] = reportingEndpointsValue; // used with `report-to` CSP directive
context.Response.Headers.XXSSProtection = "1; mode=block";
context.Response.Headers.XFrameOptions = "DENY";
context.Response.Headers.XContentTypeOptions = "nosniff";
Expand Down
14 changes: 14 additions & 0 deletions TASVideos/Pages/Diagnostics/UserAgentInterventionReports.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
@model UserAgentInterventionReportsModel
@{
ViewData.SetTitle("User agent intervention reports");
}

This is the endpoint for reports automatically submitted by user agents, which can happen for several reasons:<ul>
<li>CSP violations;</li>
<li><code>Permissions-Policy</code> violations;</li>
<li>use of deprecated features;</li>
<li>crashes; or</li>
<li>pther miscellaneous user-agent interventions.</li>
</ul>
(Paraphrased from <a href="https://developer.mozilla.org/en-US/docs/Web/API/Reporting_API">MDN</a>.)
A JSON payload will be POST'd to one of <code>Reporting-Endpoints</code> (should all be pointing at this page) if any of these are encountered by a conformant browser.
10 changes: 10 additions & 0 deletions TASVideos/Pages/Diagnostics/UserAgentInterventionReports.cshtml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace TASVideos.Pages.Diagnostics;

[RequirePermission(PermissionTo.SeeDiagnostics)]
public class UserAgentInterventionReportsModel(ILogger<ILogger> logger) : BasePageModel
{
public void OnPost()
{
logger.LogInformation("got POST payload maybe?");
}
}

0 comments on commit 6b4c549

Please sign in to comment.