mirror of
https://github.com/vacp2p/linea-monorepo.git
synced 2026-01-09 15:38:06 -05:00
[Feat] CSP Header for BridgeUI (#963)
* add csp header * fix more csp console errors * implement middleware approach for csp headers * empty * streamline csp policy * rm googletagmanager
This commit is contained in:
@@ -10,6 +10,7 @@ import atypTextFont from "@/assets/fonts/atypText";
|
||||
import "./globals.css";
|
||||
import "../scss/app.scss";
|
||||
import FirstVisitModal from "@/components/modal/first-time-visit";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
const metadata: Metadata = {
|
||||
title: "Linea Bridge",
|
||||
@@ -18,6 +19,8 @@ const metadata: Metadata = {
|
||||
};
|
||||
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
const nonce = headers().get("x-nonce") || "";
|
||||
|
||||
return (
|
||||
<html lang="en" data-theme="v2" className={clsx(atypFont.variable, atypTextFont.variable)}>
|
||||
<title>{metadata.title?.toString()}</title>
|
||||
@@ -44,8 +47,13 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
||||
<FirstVisitModal />
|
||||
</body>
|
||||
|
||||
<Script id="usabilla" dangerouslySetInnerHTML={{ __html: usabillaBeScript }} strategy="lazyOnload" />
|
||||
<Script id="gtm" dangerouslySetInnerHTML={{ __html: gtmScript }} strategy="lazyOnload" />
|
||||
<Script
|
||||
id="usabilla"
|
||||
dangerouslySetInnerHTML={{ __html: usabillaBeScript }}
|
||||
strategy="lazyOnload"
|
||||
nonce={nonce}
|
||||
/>
|
||||
<Script id="gtm" dangerouslySetInnerHTML={{ __html: gtmScript }} strategy="lazyOnload" nonce={nonce} />
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -98,8 +98,8 @@ export default function useEVM(): WalletProvider {
|
||||
const connectedWallets: Wallet[] = useMemo(
|
||||
() =>
|
||||
activeConnectors
|
||||
.map((conn) => {
|
||||
const dyn = userWallets.find((w) => true);
|
||||
.map(() => {
|
||||
const dyn = userWallets.find(() => true);
|
||||
if (!dyn) return;
|
||||
return resolveWallet({
|
||||
connection: dyn,
|
||||
|
||||
119
bridge-ui/src/middleware.ts
Normal file
119
bridge-ui/src/middleware.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
// NextJS automatically recognises a single middleware.ts file in the project root - https://nextjs.org/docs/app/building-your-application/routing/middleware#convention
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
export function middleware(request: NextRequest) {
|
||||
const nonce = Buffer.from(crypto.randomUUID()).toString("base64");
|
||||
|
||||
/**
|
||||
* Content Security Policy (CSP) configuration:
|
||||
*
|
||||
* default-src 'self'
|
||||
* - Fallback policy to only allow resources from the same origin, unless overriden by a more specific policy
|
||||
*
|
||||
* script-src-elem 'self' 'nonce-{nonce}' 'strict-dynamic'
|
||||
* - Allow scripts from the same origin, with the provided nonce, and child scripts recursively loaded from a script with nonce
|
||||
*
|
||||
* style-src 'self' 'unsafe-inline'
|
||||
* - Allow styles from the same origin and inline styles.
|
||||
*
|
||||
* img-src
|
||||
* - Control image source
|
||||
* - We allow `https:` here because we cannot sustainably maintain a whitelist for token image sources used by our widgets, especially when new tokens come out everyday and some introduce new image sources
|
||||
*
|
||||
* font-src
|
||||
* - Control font source
|
||||
*
|
||||
* connect-src
|
||||
* - Controls all outbound network requests, including fetch(), WebSockets, EventSource, navigator.sendBeacon
|
||||
*
|
||||
* frame-src
|
||||
* - Control source for iframes
|
||||
*
|
||||
* object-src 'none'
|
||||
* - Disallow object, embed, and applet elements (should not appear in modern frontends)
|
||||
*
|
||||
* base-uri 'self'
|
||||
* - Control <base href="..."> to be from same origin as the page
|
||||
*
|
||||
* form-action 'self'
|
||||
* - Restrict form submissions to the same origin.
|
||||
*
|
||||
* frame-ancestors 'none'
|
||||
* - Disallow this site from being embedded in iframes (similar to X-Frame-Options: DENY).
|
||||
*
|
||||
* block-all-mixed-content
|
||||
* - Block all mixed (HTTP over HTTPS) content.
|
||||
*
|
||||
* upgrade-insecure-requests
|
||||
* - Automatically upgrade HTTP requests to HTTPS.
|
||||
*/
|
||||
const cspHeader = `
|
||||
default-src 'self';
|
||||
script-src 'self' 'nonce-${nonce}' 'strict-dynamic';
|
||||
style-src 'self' 'unsafe-inline';
|
||||
img-src 'self' blob: data: https:;
|
||||
font-src 'self' data: https://cdn.jsdelivr.net;
|
||||
connect-src 'self' https:;
|
||||
frame-src 'self'
|
||||
https://*.walletconnect.com
|
||||
https://buy.onramper.com/;
|
||||
object-src 'none';
|
||||
base-uri 'self';
|
||||
form-action 'self';
|
||||
frame-ancestors 'none';
|
||||
block-all-mixed-content;
|
||||
upgrade-insecure-requests;
|
||||
`;
|
||||
|
||||
/**
|
||||
* Purposely excluded URLs from CSP whitelist because they seem suspicious
|
||||
*
|
||||
* base-uri
|
||||
* - https://d6tizftlrpuof.cloudfront.net/live/
|
||||
*
|
||||
* script-src
|
||||
* - https://snap.licdn.com
|
||||
*/
|
||||
|
||||
// Replace newline characters and spaces
|
||||
const contentSecurityPolicyHeaderValue = cspHeader.replace(/\s{2,}/g, " ").trim();
|
||||
|
||||
const requestHeaders = new Headers(request.headers);
|
||||
// Pass nonce to <Script> elements in layout.tsx to bypass CSP
|
||||
requestHeaders.set("x-nonce", nonce);
|
||||
requestHeaders.set("Content-Security-Policy", contentSecurityPolicyHeaderValue);
|
||||
// Set response headers so that browsers enforce CSP
|
||||
const responseHeaders = new Headers();
|
||||
responseHeaders.set("Content-Security-Policy", contentSecurityPolicyHeaderValue);
|
||||
|
||||
const response = NextResponse.next({
|
||||
request: {
|
||||
headers: requestHeaders,
|
||||
},
|
||||
});
|
||||
response.headers.set("Content-Security-Policy", contentSecurityPolicyHeaderValue);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
// Filter Middleware to run on specific paths - https://nextjs.org/docs/14/app/building-your-application/configuring/content-security-policy#adding-a-nonce-with-middleware
|
||||
export const config = {
|
||||
matcher: [
|
||||
/*
|
||||
* Match all request paths except:
|
||||
* - api (API routes)
|
||||
* - _next/static (static files)
|
||||
* - _next/image (image optimization files)
|
||||
* - favicon.ico (favicon file)
|
||||
* - public folder
|
||||
*/
|
||||
{
|
||||
source: "/((?!api|_next/static|_next/image|favicon.ico|public/).*)",
|
||||
// Skip running Middleware if request includes NextJS prefetch headers
|
||||
missing: [
|
||||
{ type: "header", key: "next-router-prefetch" },
|
||||
{ type: "header", key: "purpose", value: "prefetch" },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -4,4 +4,5 @@ j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
|
||||
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
|
||||
})(window,document,'script','dataLayer','GTM-PPCSK62D');`;
|
||||
|
||||
// The iframe in the noscript tag doesn't execute JavaScript so it doesn't need a CSP nonce
|
||||
export const gtmNoScript = `<iframe src="https://www.googletagmanager.com/ns.html?id=GTM-PPCSK62D" height="0" width="0" style="display:none;visibility:hidden"></iframe>`;
|
||||
|
||||
Reference in New Issue
Block a user