diff --git a/README.md b/README.md index ece2fb5a..27d49156 100644 --- a/README.md +++ b/README.md @@ -244,7 +244,7 @@ Grist can be configured in many ways. Here are the main environment variables it Variable | Purpose -------- | ------- -ALLOWED_WEBHOOK_DOMAINS | comma-separated list of permitted domains to use in webhooks (e.g. webhook.site,zapier.com) +ALLOWED_WEBHOOK_DOMAINS | comma-separated list of permitted domains to use in webhooks (e.g. webhook.site,zapier.com). You can set this to `*` to allow all domains, but if doing so, we recommend using a carefully locked-down proxy (see `GRIST_HTTPS_PROXY`) if you do not entirely trust users. Otherwise services on your internal network may become vulnerable to manipulation. APP_DOC_URL | doc worker url, set when starting an individual doc worker (other servers will find doc worker urls via redis) APP_HOME_URL | url prefix for home api (home and doc servers need this) APP_STATIC_URL | url prefix for static resources @@ -263,6 +263,7 @@ GRIST_EXPERIMENTAL_PLUGINS | enables experimental plugins GRIST_HIDE_UI_ELEMENTS | comma-separated list of UI features to disable. Allowed names of parts: `helpCenter,billing,templates,multiSite,multiAccounts,sendToDrive,tutorials`. If a part also exists in GRIST_UI_FEATURES, it will still be disabled. GRIST_HOME_INCLUDE_STATIC | if set, home server also serves static resources GRIST_HOST | hostname to use when listening on a port. +GRIST_HTTPS_PROXY | if set, use this proxy for webhook payload delivery. GRIST_ID_PREFIX | for subdomains of form o-*, expect or produce o-${GRIST_ID_PREFIX}*. GRIST_IGNORE_SESSION | if set, Grist will not use a session for authentication. GRIST_INST_DIR | path to Grist instance configuration files, for Grist server. diff --git a/app/server/lib/FlexServer.ts b/app/server/lib/FlexServer.ts index a16daa86..b3088a7f 100644 --- a/app/server/lib/FlexServer.ts +++ b/app/server/lib/FlexServer.ts @@ -1325,6 +1325,22 @@ export class FlexServer implements GristServer { } } + public checkOptionCombinations() { + // Check for some bad combinations we should warn about. + const allowedWebhookDomains = appSettings.section('integrations').flag('allowedWebhookDomains').readString({ + envVar: 'ALLOWED_WEBHOOK_DOMAINS', + }); + const proxy = appSettings.section('integrations').flag('proxy').readString({ + envVar: 'GRIST_HTTPS_PROXY', + }); + // If all webhook targets are accepted, and no proxy is defined, issue + // a warning. This warning can be removed by explicitly setting the proxy + // to the empty string. + if (allowedWebhookDomains === '*' && proxy === undefined) { + log.warn("Setting an ALLOWED_WEBHOOK_DOMAINS wildcard without a GRIST_HTTPS_PROXY exposes your internal network"); + } + } + public async start() { if (this._check('start')) { return; } diff --git a/app/server/lib/Triggers.ts b/app/server/lib/Triggers.ts index 45f0a563..14a0f1d0 100644 --- a/app/server/lib/Triggers.ts +++ b/app/server/lib/Triggers.ts @@ -772,6 +772,17 @@ export function isUrlAllowed(urlString: string) { return false; } + // Support at most https and http. + if (url.protocol !== "https:" && url.protocol !== "http:") { + return false; + } + + // Support a wildcard that allows all domains. + // Allow either https or http if it is set. + if (process.env.ALLOWED_WEBHOOK_DOMAINS === '*') { + return true; + } + // http (no s) is only allowed for localhost for testing. // localhost still needs to be explicitly permitted, and it shouldn't be outside dev if (url.protocol !== "https:" && url.hostname !== "localhost") { diff --git a/app/server/mergedServerMain.ts b/app/server/mergedServerMain.ts index 0aacef81..1396e18b 100644 --- a/app/server/mergedServerMain.ts +++ b/app/server/mergedServerMain.ts @@ -141,6 +141,7 @@ export async function main(port: number, serverTypes: ServerType[], server.finalize(); + server.checkOptionCombinations(); server.summary(); return server; } diff --git a/test/nbrowser/WebhookPage.ts b/test/nbrowser/WebhookPage.ts index 2cb8f638..e88c4f98 100644 --- a/test/nbrowser/WebhookPage.ts +++ b/test/nbrowser/WebhookPage.ts @@ -20,7 +20,7 @@ describe('WebhookPage', function () { before(async function () { oldEnv = new EnvironmentSnapshot(); host = new URL(server.getHost()).host; - process.env.ALLOWED_WEBHOOK_DOMAINS = host; + process.env.ALLOWED_WEBHOOK_DOMAINS = '*'; await server.restart(); session = await gu.session().teamSite.login(); const api = session.createHomeApi();