security: enforce nonce based content policy
This commit is contained in:
@@ -39,6 +39,43 @@ const jsonLd = JSON.stringify({
|
||||
"@context": "https://schema.org",
|
||||
"@graph": jsonLdGraph,
|
||||
}).replaceAll("<", "\\u003c");
|
||||
const configuredFilesDomain = process.env.FILES_DOMAIN || "files.rf4spotter.ru";
|
||||
const filesDomain = /^(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)+$/i.test(configuredFilesDomain)
|
||||
? configuredFilesDomain
|
||||
: "files.rf4spotter.ru";
|
||||
const isLoopback = (hostname: string) => hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]";
|
||||
const safeOrigin = (value: string | undefined, fallback: string) => {
|
||||
try {
|
||||
const url = new URL(value || fallback);
|
||||
return url.protocol === "https:" || (url.protocol === "http:" && isLoopback(url.hostname)) ? url.origin : fallback;
|
||||
} catch {
|
||||
return fallback;
|
||||
}
|
||||
};
|
||||
const apiOrigin = safeOrigin(import.meta.env.PUBLIC_API_URL, siteUrl);
|
||||
const filesFallback = `https://${filesDomain}`;
|
||||
const filesOrigin = (() => {
|
||||
try {
|
||||
const url = new URL(process.env.FILES_ORIGIN || filesFallback);
|
||||
if ((url.protocol === "https:" && url.hostname === filesDomain) || (url.protocol === "http:" && isLoopback(url.hostname))) {
|
||||
return url.origin;
|
||||
}
|
||||
} catch {
|
||||
// Invalid deployment input falls back to the validated production hostname.
|
||||
}
|
||||
return filesFallback;
|
||||
})();
|
||||
const localOrigins = [apiOrigin, filesOrigin].filter((origin) => origin.startsWith("http://"));
|
||||
const cspNonce = crypto.randomUUID().replaceAll("-", "");
|
||||
Astro.response.headers.set("Content-Security-Policy", [
|
||||
"default-src 'self'", "base-uri 'self'", "object-src 'none'", "frame-ancestors 'none'",
|
||||
"form-action 'self'", `connect-src 'self'${apiOrigin === siteUrl ? "" : ` ${apiOrigin}`}`,
|
||||
`img-src 'self' data: ${filesOrigin}`,
|
||||
"font-src 'self'", "media-src 'self'", "manifest-src 'self'",
|
||||
`script-src 'self' 'nonce-${cspNonce}'`, "script-src-attr 'none'",
|
||||
"style-src 'self'", "style-src-attr 'none'",
|
||||
...(localOrigins.length ? [] : ["upgrade-insecure-requests"]),
|
||||
].join("; "));
|
||||
---
|
||||
<!doctype html>
|
||||
<html lang="ru" data-theme={theme === "system" ? undefined : theme}>
|
||||
@@ -70,7 +107,7 @@ const jsonLd = JSON.stringify({
|
||||
<meta name="twitter:description" content={description} />
|
||||
<meta name="twitter:image" content={socialImage} />
|
||||
<meta name="twitter:image:alt" content="Лаймовый поплавок на тёмном озере с координатной сеткой" />
|
||||
<script type="application/ld+json" set:html={jsonLd} is:inline />
|
||||
<script type="application/ld+json" nonce={cspNonce} set:html={jsonLd} is:inline />
|
||||
<title>{title}</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
Reference in New Issue
Block a user