Security: calculate connect-source of specialVisualisations by argument, if applicable

This commit is contained in:
Pieter Vander Vennet 2023-11-19 16:31:58 +01:00
parent e4aedc9696
commit 29b1f5be4d
3 changed files with 38 additions and 9 deletions

View file

@ -15,6 +15,7 @@ import { ImmutableStore } from "../src/Logic/UIEventSource"
import * as crypto from "crypto"
import * as eli from "../src/assets/editor-layer-index.json"
import * as eli_global from "../src/assets/global-raster-layers.json"
import ValidationUtils from "../src/Models/ThemeConfig/Conversion/ValidationUtils"
const sharp = require("sharp")
const template = readFileSync("theme.html", "utf8")
@ -264,6 +265,7 @@ async function eliUrls(): Promise<string[]> {
async function generateCsp(
layout: LayoutConfig,
layoutJson: LayoutConfigJson,
options: {
scriptSrcs: string[]
}
@ -275,9 +277,20 @@ async function generateCsp(
Constants.nominatimEndpoint,
"https://api.openstreetmap.org",
"https://pietervdvn.goatcounter.com",
]
.concat(...SpecialVisualizations.specialVisualizations.map((sv) => sv.needsUrls))
.concat(...(await eliUrls()))
].concat(...(await eliUrls()))
const usedSpecialVisualisations = ValidationUtils.getSpecialVisualisationsWithArgs(layoutJson)
for (const usedSpecialVisualisation of usedSpecialVisualisations) {
if (typeof usedSpecialVisualisation === "string") {
continue
}
const neededUrls = usedSpecialVisualisation.func.needsUrls
if (typeof neededUrls === "function") {
apiUrls.push(...neededUrls(usedSpecialVisualisation.args))
} else {
apiUrls.push(...neededUrls)
}
}
const geojsonSources: string[] = layout.layers.map((l) => l.source?.geojsonSource)
const hosts = new Set<string>()
@ -299,6 +312,10 @@ async function generateCsp(
}
}
if (hosts.has("*")) {
throw "* is not allowed as connect-src"
}
const connectSrc = Array.from(hosts).sort()
const newSrcs = connectSrc.filter((newItem) => !previousSrc.has(newItem))
@ -317,7 +334,7 @@ async function generateCsp(
"default-src": "'self'",
"child-src": "'self' blob: ",
"img-src": "* data:", // maplibre depends on 'data:' to load
"connect-src": connectSrc.join(" "),
"connect-src": onnectSrc.join(" "),
"report-to": "https://report.mapcomplete.org/csp",
"worker-src": "'self' blob:", // Vite somehow loads the worker via a 'blob'
"style-src": "'self' 'unsafe-inline'", // unsafe-inline is needed to change the default background pin colours
@ -344,7 +361,13 @@ const removeOtherLanguagesHash = crypto
.update(removeOtherLanguages)
.digest("base64")
async function createLandingPage(layout: LayoutConfig, manifest, whiteIcons, alreadyWritten) {
async function createLandingPage(
layout: LayoutConfig,
layoutJson: LayoutConfigJson,
manifest,
whiteIcons,
alreadyWritten
) {
Locale.language.setData(layout.language[0])
const targetLanguage = layout.language[0]
const ogTitle = Translations.T(layout.title).textFor(targetLanguage).replace(/"/g, '\\"')
@ -428,7 +451,7 @@ async function createLandingPage(layout: LayoutConfig, manifest, whiteIcons, alr
.replace(/<!-- THEME-SPECIFIC -->.*<!-- THEME-SPECIFIC-END-->/s, themeSpecific)
.replace(
/<!-- CSP -->/,
await generateCsp(layout, {
await generateCsp(layout, layoutJson, {
scriptSrcs: [`'sha256-${removeOtherLanguagesHash}'`],
})
)
@ -518,7 +541,13 @@ async function main(): Promise<void> {
writeFile("public/" + manifestLocation, manif, err)
// Create a landing page for the given theme
const landing = await createLandingPage(layout, manifest, whiteIcons, alreadyWritten)
const landing = await createLandingPage(
layout,
layoutConfigJson,
manifest,
whiteIcons,
alreadyWritten
)
writeFile(enc(layout.id) + ".html", landing, err)
await createIndexFor(layout)

View file

@ -88,7 +88,7 @@ export interface SpecialVisualization {
readonly funcName: string
readonly docs: string | BaseUIElement
readonly example?: string
readonly needsUrls: string[]
readonly needsUrls: string[] | ((args: string[]) => string)
/**
* Indicates that this special visualisation will make requests to the 'alLNodesDatabase' and that it thus should be included

View file

@ -1454,7 +1454,7 @@ export default class SpecialVisualizations {
},
],
docs: "Shows events that are happening based on a Giggity URL",
needsUrls: ["*"],
needsUrls: (args) => args[0],
constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,