1
mirror of https://github.com/jakejarvis/jarv.is.git synced 2025-10-29 00:15:47 -04:00

improved CSP reporting middleware

This commit is contained in:
2021-11-08 07:09:58 -05:00
parent 6ff4bc52e5
commit 4c56707026
4 changed files with 116 additions and 20 deletions

100
api/report.js Normal file
View File

@@ -0,0 +1,100 @@
import * as Sentry from "@sentry/node";
import fetch from "node-fetch";
import getStream from "get-stream";
Sentry.init({
dsn: process.env.SENTRY_DSN || "",
environment: process.env.NODE_ENV || process.env.VERCEL_ENV || "",
});
// this "proxy" to report-uri.com is temporary until I'm bored enough to make my own reporting API from scratch
// https://report-uri.com/account/setup/
const REPORT_URI_SUBDOMAIN = "jarvis";
export default async (req, res) => {
try {
// permissive access control headers
res.setHeader("Access-Control-Allow-Methods", "POST");
res.setHeader("Access-Control-Allow-Origin", "*");
// disable caching on both ends
res.setHeader("Cache-Control", "private, no-cache, no-store, must-revalidate");
res.setHeader("Expires", 0);
res.setHeader("Pragma", "no-cache");
if (req.method !== "POST") {
return res.status(405).send(); // 405 Method Not Allowed
}
// start parsing body manually, since the serverless helper functions don't recognize `application/csp-report` and
// `application/reports` as JSON to be parsed:
// https://vercel.com/docs/runtimes#official-runtimes/node-js/node-js-request-and-response-objects/request-body
const body = JSON.parse(await getStream(req));
// default to returning 400 Bad Request for an invalid POST request
let statusCode = 400;
// TODO: add Expect-CT reporting endpoint
if (typeof req.query.csp !== "undefined" && req.headers["content-type"].startsWith("application/csp-report")) {
// send a CSP violation:
// https://docs.report-uri.com/setup/csp/
statusCode = await sendCsp(body, req.headers);
} else if (req.headers["content-type"].startsWith("application/reports")) {
// send a report:
// https://docs.report-uri.com/setup/reporting-api/
statusCode = await sendReport(body, req.headers);
}
return res.status(statusCode).send();
} catch (error) {
console.error(error);
// log error to sentry, give it 2 seconds to finish sending
Sentry.captureException(error);
await Sentry.flush(2000);
return res.status(500).send(); // 500 Internal Server Error
}
};
const sendCsp = async (body, headers) => {
// filter out any last invalid reports (JSON must have at least one csp-report object)
if (Object.hasOwnProperty.call(body, "csp-report")) {
const response = await fetch(`https://${REPORT_URI_SUBDOMAIN}.report-uri.com/r/d/csp/enforce`, {
method: "POST",
headers: {
"Content-Type": "application/csp-report",
"User-Agent": headers["user-agent"],
Origin: headers["origin"],
},
body: JSON.stringify(body),
});
// API returns 201 Created if successful
if (response.status !== 201) {
console.error(`[CSP] ${response.status}: ${await response.text()}`);
}
return response.status;
} else {
return 400; // 400 Bad Request
}
};
const sendReport = async (body, headers) => {
const response = await fetch(`https://${REPORT_URI_SUBDOMAIN}.report-uri.com/a/d/g`, {
method: "POST",
headers: {
"Content-Type": "application/reports+json",
"User-Agent": headers["user-agent"],
Origin: headers["origin"],
},
body: JSON.stringify(body),
});
// API returns 201 Created if successful
if (response.status !== 201) {
console.error(`[REPORT] ${response.status}: ${await response.text()}`);
}
return response.status;
};

View File

@@ -38,6 +38,7 @@
"fast-xml-parser": "^3.21.1",
"faunadb": "^4.4.1",
"get-canonical-url": "^1.0.1",
"get-stream": "^6.0.1",
"html-entities": "^2.3.2",
"lit-html": "^2.0.1",
"modern-normalize": "github:sindresorhus/modern-normalize#1fc6b5a86676b7ac8abc62d04d6080f92debc70f",

View File

@@ -12,11 +12,6 @@
"source": "/apple-touch-icon-precomposed.png",
"destination": "/assets/images/apple-touch-icon.png"
},
{
"source": "/api/csp_wizard/",
"destination": "https://jarvis.report-uri.com/r/d/csp/enforce"
},
{ "source": "/api/report/", "destination": "https://jarvis.report-uri.com/a/d/g" },
{ "source": "/api/mention/", "destination": "https://webmention.io/jarv.is/webmention" },
{ "source": "/api/ping/", "destination": "https://webmention.io/jarv.is/xmlrpc" }
],
@@ -88,7 +83,7 @@
},
{
"key": "Content-Security-Policy",
"value": "default-src 'self'; base-uri 'none'; connect-src 'self' api.github.com hcaptcha.com *.hcaptcha.com platform.twitter.com; font-src 'self'; form-action 'self'; frame-ancestors 'self'; frame-src 'self' jakejarvis.github.io buttons.github.io codepen.io hcaptcha.com *.hcaptcha.com platform.twitter.com www.youtube-nocookie.com; img-src 'self' data: https:; manifest-src 'self'; media-src 'self' data: https:; object-src 'none'; prefetch-src 'self'; script-src 'self' buttons.github.io gist.github.com hcaptcha.com *.hcaptcha.com syndication.twitter.com platform.twitter.com; style-src 'self' 'unsafe-inline' github.githubassets.com; worker-src 'self'; block-all-mixed-content; report-uri https://jarv.is/api/csp_wizard/; report-to default"
"value": "default-src 'self'; base-uri 'none'; connect-src 'self' api.github.com hcaptcha.com *.hcaptcha.com platform.twitter.com; font-src 'self'; form-action 'self'; frame-ancestors 'self'; frame-src 'self' jakejarvis.github.io buttons.github.io codepen.io hcaptcha.com *.hcaptcha.com platform.twitter.com www.youtube-nocookie.com; img-src 'self' data: https:; manifest-src 'self'; media-src 'self' data: https:; object-src 'none'; prefetch-src 'self'; script-src 'self' buttons.github.io gist.github.com hcaptcha.com *.hcaptcha.com syndication.twitter.com platform.twitter.com; style-src 'self' 'unsafe-inline' github.githubassets.com; worker-src 'self'; block-all-mixed-content; report-uri https://jarv.is/api/report/?csp; report-to default"
},
{
"key": "Report-To",

View File

@@ -2855,10 +2855,10 @@ cssesc@^3.0.0:
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
cssnano-preset-default@^5.1.5:
version "5.1.5"
resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.1.5.tgz#6effb7175ee5d296f95330e137587e27ee974d44"
integrity sha512-fF00UI+d3PWkGfMd62geqmoUe5h+LOhGE2GH4Fqq3beNKdCU1LWwLUyIcu4/A72lWv0737cHey5zhhWw3rW0sA==
cssnano-preset-default@^5.1.6:
version "5.1.6"
resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.1.6.tgz#1bdb83be6a6b1fee6dc5e9ec2e61286bcadcc7a6"
integrity sha512-X2nDeNGBXc0486oHjT2vSj+TdeyVsxRvJUxaOH50hOM6vSDLkKd0+59YXpSZRInJ4sNtBOykS4KsPfhdrU/35w==
dependencies:
css-declaration-sorter "^6.0.3"
cssnano-utils "^2.0.1"
@@ -2869,7 +2869,7 @@ cssnano-preset-default@^5.1.5:
postcss-discard-duplicates "^5.0.1"
postcss-discard-empty "^5.0.1"
postcss-discard-overridden "^5.0.1"
postcss-merge-longhand "^5.0.2"
postcss-merge-longhand "^5.0.3"
postcss-merge-rules "^5.0.2"
postcss-minify-font-values "^5.0.1"
postcss-minify-gradients "^5.0.3"
@@ -2896,11 +2896,11 @@ cssnano-utils@^2.0.1:
integrity sha512-i8vLRZTnEH9ubIyfdZCAdIdgnHAUeQeByEeQ2I7oTilvP9oHO6RScpeq3GsFUVqeB8uZgOQ9pw8utofNn32hhQ==
cssnano@^5.0.6:
version "5.0.9"
resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.0.9.tgz#bd03168835c0883c16754085704f57861a32d99c"
integrity sha512-Y4olTKBKsPKl5izpcXHRDiB/1rVdbIDM4qVXgEKBt466kYT42SEEsnCYOQFFXzEkUYV8pJNCII9JKzb8KfDk+g==
version "5.0.10"
resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.0.10.tgz#92207eb7c9c6dc08d318050726f9fad0adf7220b"
integrity sha512-YfNhVJJ04imffOpbPbXP2zjIoByf0m8E2c/s/HnvSvjXgzXMfgopVjAEGvxYOjkOpWuRQDg/OZFjO7WW94Ri8w==
dependencies:
cssnano-preset-default "^5.1.5"
cssnano-preset-default "^5.1.6"
is-resolvable "^1.1.0"
lilconfig "^2.0.3"
yaml "^1.10.2"
@@ -4465,7 +4465,7 @@ get-stream@^5.0.0, get-stream@^5.1.0:
dependencies:
pump "^3.0.0"
get-stream@^6.0.0:
get-stream@^6.0.0, get-stream@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==
@@ -7545,10 +7545,10 @@ postcss-media-query-parser@^0.2.3:
resolved "https://registry.yarnpkg.com/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz#27b39c6f4d94f81b1a73b8f76351c609e5cef244"
integrity sha1-J7Ocb02U+Bsac7j3Y1HGCeXO8kQ=
postcss-merge-longhand@^5.0.2:
version "5.0.2"
resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.0.2.tgz#277ada51d9a7958e8ef8cf263103c9384b322a41"
integrity sha512-BMlg9AXSI5G9TBT0Lo/H3PfUy63P84rVz3BjCFE9e9Y9RXQZD3+h3YO1kgTNsNJy7bBc1YQp8DmSnwLIW5VPcw==
postcss-merge-longhand@^5.0.3:
version "5.0.3"
resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.0.3.tgz#42194a5ffbaa5513edbf606ef79c44958564658b"
integrity sha512-kmB+1TjMTj/bPw6MCDUiqSA5e/x4fvLffiAdthra3a0m2/IjTrWsTmD3FdSskzUjEwkj5ZHBDEbv5dOcqD7CMQ==
dependencies:
css-color-names "^1.0.1"
postcss-value-parser "^4.1.0"