Remove gist.github.com, gitlab.com from default CSP (#7008)
This commit is contained in:
121
server/routes/embeds.ts
Normal file
121
server/routes/embeds.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
import escape from "escape-html";
|
||||
import { Context, Next } from "koa";
|
||||
|
||||
/**
|
||||
* Resize observer script that sends a message to the parent window when content is resized. Inject
|
||||
* this script into the iframe to receive resize events.
|
||||
*/
|
||||
const resizeObserverScript = (
|
||||
ctx: Context
|
||||
) => `<script nonce="${ctx.state.cspNonce}">
|
||||
const resizeObserver = new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
window.parent.postMessage({ "type": "frame-resized", "value": entry.contentRect.height }, '*');
|
||||
}
|
||||
});
|
||||
resizeObserver.observe(document.body);
|
||||
</script>`;
|
||||
|
||||
/**
|
||||
* Script that checks if the iframe is being loaded in an iframe. If it is not, it redirects to the
|
||||
* origin URL.
|
||||
*/
|
||||
const iframeCheckScript = (
|
||||
ctx: Context
|
||||
) => `<script nonce="${ctx.state.cspNonce}">
|
||||
if (window.self === window.top) {
|
||||
window.location.href = window.location.origin;
|
||||
}
|
||||
</script>`;
|
||||
|
||||
/**
|
||||
* Render an embed for a GitLab or GitHub snippet, injecting the necessary scripts to handle resizing
|
||||
* and iframe checks.
|
||||
*
|
||||
* @param ctx The koa context
|
||||
* @param next The next middleware in the stack
|
||||
* @returns The response body
|
||||
*/
|
||||
export const renderEmbed = async (ctx: Context, next: Next) => {
|
||||
const url = escape(String(ctx.query.url));
|
||||
|
||||
if (!url) {
|
||||
ctx.throw(400, "url is required");
|
||||
}
|
||||
|
||||
let parsed;
|
||||
try {
|
||||
parsed = new URL(url);
|
||||
} catch (err) {
|
||||
ctx.throw(400, "Invalid URL provided");
|
||||
}
|
||||
|
||||
if (
|
||||
parsed.host === "gitlab.com" &&
|
||||
parsed.protocol === "https:" &&
|
||||
ctx.path === "/embeds/gitlab"
|
||||
) {
|
||||
const snippetLink = `${url}.js`;
|
||||
const csp = ctx.response.get("Content-Security-Policy");
|
||||
|
||||
// Inject gitlab.com into the script-src and style-src directives
|
||||
ctx.set(
|
||||
"Content-Security-Policy",
|
||||
csp
|
||||
.replace("script-src", "script-src gitlab.com")
|
||||
.replace("style-src", "style-src gitlab.com")
|
||||
);
|
||||
ctx.set("X-Frame-Options", "sameorigin");
|
||||
|
||||
ctx.type = "html";
|
||||
ctx.body = `
|
||||
<html>
|
||||
<head>
|
||||
<style>body { margin: 0; .gitlab-embed-snippets { margin: 0; } }</style>
|
||||
<base target="_parent">
|
||||
${iframeCheckScript(ctx)}
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/javascript" src="${snippetLink}"></script>
|
||||
${resizeObserverScript(ctx)}
|
||||
</body>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
parsed.host === "gist.github.com" &&
|
||||
parsed.protocol === "https:" &&
|
||||
ctx.path === "/embeds/github"
|
||||
) {
|
||||
const id = parsed.pathname.split("/")[2];
|
||||
const gistLink = `https://gist.github.com/${id}.js`;
|
||||
const csp = ctx.response.get("Content-Security-Policy");
|
||||
|
||||
// Inject GitHub domains into the script-src and style-src directives
|
||||
ctx.set(
|
||||
"Content-Security-Policy",
|
||||
csp
|
||||
.replace("script-src", "script-src gist.github.com")
|
||||
.replace("style-src", "style-src github.githubassets.com")
|
||||
);
|
||||
ctx.set("X-Frame-Options", "sameorigin");
|
||||
|
||||
ctx.type = "html";
|
||||
ctx.body = `
|
||||
<html>
|
||||
<head>
|
||||
<style>*{ font-size:12px; } body { margin: 0; } .gist .blob-wrapper.data { max-height:300px; overflow:auto; }</style>
|
||||
<base target="_parent">
|
||||
${iframeCheckScript(ctx)}
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/javascript" src="${gistLink}"></script>
|
||||
${resizeObserverScript(ctx)}
|
||||
</body>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
return next();
|
||||
};
|
||||
@@ -18,6 +18,7 @@ import { getTeamFromContext } from "@server/utils/passport";
|
||||
import { robotsResponse } from "@server/utils/robots";
|
||||
import apexRedirect from "../middlewares/apexRedirect";
|
||||
import { renderApp, renderShare } from "./app";
|
||||
import { renderEmbed } from "./embeds";
|
||||
import errors from "./errors";
|
||||
|
||||
const koa = new Koa();
|
||||
@@ -128,6 +129,9 @@ router.get("/s/:shareId", shareDomains(), renderShare);
|
||||
router.get("/s/:shareId/doc/:documentSlug", shareDomains(), renderShare);
|
||||
router.get("/s/:shareId/*", shareDomains(), renderShare);
|
||||
|
||||
router.get("/embeds/gitlab", renderEmbed);
|
||||
router.get("/embeds/github", renderEmbed);
|
||||
|
||||
// catch all for application
|
||||
router.get("*", shareDomains(), async (ctx, next) => {
|
||||
if (ctx.state?.rootShare) {
|
||||
|
||||
@@ -24,19 +24,8 @@ import auth from "../routes/auth";
|
||||
|
||||
// Construct scripts CSP based on services in use by this installation
|
||||
const defaultSrc = ["'self'"];
|
||||
const scriptSrc = [
|
||||
"'self'",
|
||||
"gist.github.com",
|
||||
"www.googletagmanager.com",
|
||||
"gitlab.com",
|
||||
];
|
||||
|
||||
const styleSrc = [
|
||||
"'self'",
|
||||
"'unsafe-inline'",
|
||||
"github.githubassets.com",
|
||||
"gitlab.com",
|
||||
];
|
||||
const scriptSrc = ["'self'", "www.googletagmanager.com"];
|
||||
const styleSrc = ["'self'", "'unsafe-inline'"];
|
||||
|
||||
if (env.isCloudHosted) {
|
||||
scriptSrc.push("cdn.zapier.com");
|
||||
|
||||
Reference in New Issue
Block a user