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:
100
api/report.js
Normal file
100
api/report.js
Normal 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;
|
||||
};
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
28
yarn.lock
28
yarn.lock
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user