diff --git a/dashboard/public/translation/da.json b/dashboard/public/translation/da.json index 0dcdebfb5..8b4f97293 100644 --- a/dashboard/public/translation/da.json +++ b/dashboard/public/translation/da.json @@ -36,9 +36,6 @@ "username": "Brugernavn", "displayName": "Vis navn", "actions": "Foranstaltninger", - "table": { - "date": "Dato" - }, "action": { "reboot": "Genstart", "logs": "Logfiler" @@ -1147,8 +1144,7 @@ "saveAction": "Gem" }, "robots": { - "title": "Robots.txt", - "disableIndexingAction": "Deaktivere indeksering" + "title": "Robots.txt" }, "hstsPreload": "Aktiver HSTS-forudindlæsning for dette websted og alle underdomæner" }, diff --git a/dashboard/public/translation/de.json b/dashboard/public/translation/de.json index 95db03917..75809ca08 100644 --- a/dashboard/public/translation/de.json +++ b/dashboard/public/translation/de.json @@ -45,7 +45,6 @@ "reset": "Zurücksetzen" }, "table": { - "date": "Datum", "version": "Version" }, "actions": "Aktionen", @@ -1244,8 +1243,7 @@ "title": "Content-Security-Policy" }, "robots": { - "title": "robots.txt", - "disableIndexingAction": "Indexierung deaktivieren" + "title": "robots.txt" }, "hstsPreload": "Aktivieren Sie den HSTS-Preload für diese Website und alle Subdomains" }, diff --git a/dashboard/public/translation/en.json b/dashboard/public/translation/en.json index fb0c3bfda..46a9fedd4 100644 --- a/dashboard/public/translation/en.json +++ b/dashboard/public/translation/en.json @@ -35,8 +35,8 @@ "displayName": "Display name", "actions": "Actions", "table": { - "date": "Date", - "version": "Version" + "version": "Version", + "created": "Created" }, "action": { "reboot": "Reboot", @@ -1274,12 +1274,27 @@ "csp": { "description": "Override any CSP headers defined by the app", "title": "Content Security Policy", - "saveAction": "Save" + "saveAction": "Save", + "insertCommonCsp": "Insert common CSP", + "commonPattern": { + "allowEmbedding": "Allow embedding", + "sameOriginEmbedding": "Allow embedding (only subdomains)", + "allowCdnAssets": "Allow CDN assets", + "reportOnly": "Report CSP violations", + "strictBaseline": "Strict baseline" + } }, "robots": { "title": "Robots.txt", - "disableIndexingAction": "Disable indexing", - "description": "By default, bots can index this app" + "description": "By default, bots can index this app", + "commonPattern": { + "allowAll": "Allow all (default)", + "disallowAll": "Disallow all", + "disallowCommonBots": "Disallow common bots", + "disallowAdminPaths": "Disallow admin paths", + "disallowApiPaths": "Disallow API paths" + }, + "insertCommonRobotsTxt": "Insert common robots.txt" }, "hstsPreload": "Enable HSTS Preload (including subdomains)" }, @@ -1401,7 +1416,7 @@ "cron": { "title": "Crontab", "saveAction": "Save", - "addCommonPattern": "Add common pattern", + "addCommonPattern": "Insert common pattern", "commonPattern": { "everyMinute": "Every Minute", "everyHour": "Every Hour", diff --git a/dashboard/public/translation/es.json b/dashboard/public/translation/es.json index 833094434..134c32035 100644 --- a/dashboard/public/translation/es.json +++ b/dashboard/public/translation/es.json @@ -48,7 +48,6 @@ "next": "Siguiente" }, "table": { - "date": "Fecha", "version": "Versión" }, "actions": "Acciones", @@ -854,7 +853,6 @@ }, "security": { "robots": { - "disableIndexingAction": "Desactivar indexado", "title": "Robots.txt" }, "csp": { diff --git a/dashboard/public/translation/fr.json b/dashboard/public/translation/fr.json index 2ffb51667..0c21d30c5 100644 --- a/dashboard/public/translation/fr.json +++ b/dashboard/public/translation/fr.json @@ -31,9 +31,6 @@ "username": "Nom d'utilisateur", "actions": "Actions", "displayName": "Nom affiché", - "table": { - "date": "Date" - }, "action": { "logs": "Journaux", "reboot": "Redémarrer" @@ -620,7 +617,6 @@ "title": "Politique de sécurité du contenu (CSP)" }, "robots": { - "disableIndexingAction": "Désactiver l'indexation", "title": "Robots.txt" }, "hstsPreload": "Activer HSTS pour ce site et tous les sous-domaines" diff --git a/dashboard/public/translation/id.json b/dashboard/public/translation/id.json index 1a55b6b85..566e39cd6 100644 --- a/dashboard/public/translation/id.json +++ b/dashboard/public/translation/id.json @@ -30,7 +30,6 @@ "edit": "Edit" }, "table": { - "date": "Tanggal", "version": "Versi" }, "logout": "Keluar", @@ -1345,7 +1344,6 @@ }, "robots": { "title": "Robots.txt", - "disableIndexingAction": "Nonaktifkan pengindeksan", "description": "Secara bawaan, bot dapat mengindeks aplikasi ini." }, "hstsPreload": "Aktifkan HSTS Preload (termasuk subdomain)" diff --git a/dashboard/public/translation/it.json b/dashboard/public/translation/it.json index 9e48ffee8..122ea1fe1 100644 --- a/dashboard/public/translation/it.json +++ b/dashboard/public/translation/it.json @@ -11,9 +11,6 @@ "logs": "Logs", "reboot": "Riavvia il server" }, - "table": { - "date": "Data" - }, "actions": "Azioni", "displayName": "Nome visualizzato", "username": "Nome utente", @@ -135,8 +132,7 @@ }, "security": { "robots": { - "title": "Robots.txt", - "disableIndexingAction": "Disabilita indicizzazione" + "title": "Robots.txt" }, "csp": { "saveAction": "Salva", diff --git a/dashboard/public/translation/ja.json b/dashboard/public/translation/ja.json index b05674b59..1d6d338ce 100644 --- a/dashboard/public/translation/ja.json +++ b/dashboard/public/translation/ja.json @@ -7,9 +7,6 @@ "logs": "ログ", "reboot": "再起動" }, - "table": { - "date": "日付" - }, "displayName": "表示名", "username": "ユーザー名", "dialog": { diff --git a/dashboard/public/translation/nl.json b/dashboard/public/translation/nl.json index 4ec2315b8..fe52bf5d5 100644 --- a/dashboard/public/translation/nl.json +++ b/dashboard/public/translation/nl.json @@ -34,7 +34,6 @@ "displayName": "Weergavenaam", "actions": "Acties", "table": { - "date": "Datum", "version": "Versie" }, "action": { @@ -827,7 +826,6 @@ }, "robots": { "title": "Robots.txt", - "disableIndexingAction": "Indexering uitschakelen", "description": "Standaard kunnen bots deze app indexeren." }, "hstsPreload": "Schakel HSTS-preload in (inclusief subdomeinen)" diff --git a/dashboard/public/translation/pl.json b/dashboard/public/translation/pl.json index e8e4c5876..885e50e5d 100644 --- a/dashboard/public/translation/pl.json +++ b/dashboard/public/translation/pl.json @@ -36,9 +36,6 @@ "logs": "Logi", "reboot": "Restart" }, - "table": { - "date": "Data" - }, "actions": "Akcje", "displayName": "Wyświetlana nazwa", "username": "Użytkownik", diff --git a/dashboard/public/translation/pt.json b/dashboard/public/translation/pt.json index 314cdf3e5..1d91d3709 100644 --- a/dashboard/public/translation/pt.json +++ b/dashboard/public/translation/pt.json @@ -40,7 +40,6 @@ "username": "Nome de Utilizador", "actions": "Ações", "table": { - "date": "Data", "version": "Versão" }, "action": { @@ -830,7 +829,6 @@ }, "robots": { "title": "Robots.txt", - "disableIndexingAction": "Desativar indexação", "description": "Por predefinição, os robôs podem indexar esta aplicação." } }, diff --git a/dashboard/public/translation/ru.json b/dashboard/public/translation/ru.json index 0bf293e7a..4cdeec8f2 100644 --- a/dashboard/public/translation/ru.json +++ b/dashboard/public/translation/ru.json @@ -40,7 +40,6 @@ "displayName": "Отображаемое имя", "actions": "Действия", "table": { - "date": "Дата", "version": "Версия" }, "action": { @@ -559,7 +558,6 @@ }, "robots": { "title": "Robots.txt", - "disableIndexingAction": "Отключить индексирование", "description": "По умолчанию, роботы могут индексировать это приложение." }, "hstsPreload": "Активировать предзагрузку HSTS (в том числе для поддоменов)" diff --git a/dashboard/public/translation/si.json b/dashboard/public/translation/si.json index 3e1289c9d..a954cfe06 100644 --- a/dashboard/public/translation/si.json +++ b/dashboard/public/translation/si.json @@ -10,9 +10,6 @@ "yes": "ඔව්" }, "username": "පරිශීලක නාමය", - "table": { - "date": "දිනය" - }, "searchPlaceholder": "සොයන්න", "multiselect": { "select": "තෝරන්න" diff --git a/dashboard/public/translation/vi.json b/dashboard/public/translation/vi.json index 5ce91a77e..586dc8f43 100644 --- a/dashboard/public/translation/vi.json +++ b/dashboard/public/translation/vi.json @@ -33,7 +33,6 @@ "username": "Tên đăng nhập", "displayName": "Tên hiển thị", "table": { - "date": "Ngày", "version": "Phiên bản" }, "action": { @@ -1131,7 +1130,6 @@ }, "security": { "robots": { - "disableIndexingAction": "Không cho lên chỉ mục", "title": "File Robots.txt" }, "csp": { diff --git a/dashboard/public/translation/zh_Hans.json b/dashboard/public/translation/zh_Hans.json index a112ea97a..6fd23e139 100644 --- a/dashboard/public/translation/zh_Hans.json +++ b/dashboard/public/translation/zh_Hans.json @@ -183,9 +183,6 @@ "username": "用户名", "displayName": "昵称", "actions": "操作", - "table": { - "date": "日期" - }, "action": { "reboot": "重启", "logs": "日志" @@ -975,8 +972,7 @@ "description": "使用此设置来覆盖应用自带的 CSP header" }, "robots": { - "title": "Robots.txt", - "disableIndexingAction": "禁止爬取" + "title": "Robots.txt" } }, "updates": { diff --git a/dashboard/src/components/app/Security.vue b/dashboard/src/components/app/Security.vue index c3052d850..850794f1d 100644 --- a/dashboard/src/components/app/Security.vue +++ b/dashboard/src/components/app/Security.vue @@ -12,16 +12,16 @@ const props = defineProps([ 'app' ]); const appsModel = AppsModel.create(); -function addRobotsTxtPreset(pattern) { - if (robotsTxt.value) robotsTxt.value += '\n'; - robotsTxt.value += pattern; -} - const busy = ref(false); const robotsTxt = ref(''); const csp = ref(''); const hstsPreload = ref(false); +function addRobotsTxtPreset(pattern) { + if (robotsTxt.value) robotsTxt.value += '\n'; + robotsTxt.value += pattern; +} + const commonRobotsTxtMenu = [ { label: t('app.security.robots.commonPattern.allowAll'), action: () => addRobotsTxtPreset('# Allow all\nUser-agent: *\nDisallow:') }, { label: t('app.security.robots.commonPattern.disallowAll'), action: () => addRobotsTxtPreset('# Disable search engine indexing\n\nUser-agent: *\nDisallow: /') }, @@ -30,6 +30,19 @@ const commonRobotsTxtMenu = [ { label: t('app.security.robots.commonPattern.disallowApiPaths'), action: () => addRobotsTxtPreset('# Disallow API paths\nUser-agent: *\nDisallow: /api/\nDisallow: /v1/\nDisallow: /v2/') }, ]; +function addCspPreset(pattern) { + if (csp.value) csp.value += '\n'; + csp.value += pattern; +} + +const commonCspMenu = [ + { label: t('app.security.csp.commonPattern.allowEmbedding'), action: () => addCspPreset("# Allow embedding from all sites\ndefault-src 'self';\nframe-ancestors 'none';") }, + { label: t('app.security.csp.commonPattern.sameOriginEmbedding'), action: () => addCspPreset("# Allow embedding from subdomains\ndefault-src 'self';\nframe-ancestors 'self';") }, + { label: t('app.security.csp.commonPattern.allowCdnAssets'), action: () => addCspPreset("# Allow CDN assets\ndefault-src 'self';\nscript-src 'self' https://cdn.example.com;\nstyle-src 'self' https://cdn.example.com;\nimg-src 'self' data: https://cdn.example.com;\nfont-src 'self' https://cdn.example.com;\nobject-src 'none';\nframe-ancestors 'none';") }, + { label: t('app.security.csp.commonPattern.reportOnly'), action: () => addCspPreset("# Report violations. A POST request will be sent to URL below\ndefault-src 'self';\nreport-uri /csp-report;") }, + { label: t('app.security.csp.commonPattern.strictBaseline'), action: () => addCspPreset("# Secure CSP that restricts all resources to the same origin\ndefault-src 'self';\nbase-uri 'self';\nobject-src 'none';\nframe-ancestors 'none';\nform-action 'self';\nscript-src 'self';\nstyle-src 'self';\nimg-src 'self' data:;\nfont-src 'self';\nconnect-src 'self';\nmedia-src 'self';\nframe-src 'self';\nworker-src 'self';\nmanifest-src 'self';") }, +]; + async function onSubmit() { busy.value = true; @@ -65,13 +78,16 @@ onMounted(() => {
{{ $t('app.security.robots.description') }}
- + - +
{{ $t('app.security.csp.description') }}
- +
diff --git a/src/apps.js b/src/apps.js index fefac1f1c..e3fb1efe8 100644 --- a/src/apps.js +++ b/src/apps.js @@ -452,7 +452,6 @@ function validateCsp(csp) { if (csp.length > 4096) return new BoxError(BoxError.BAD_FIELD, 'CSP must be less than 4096'); if (csp.includes('"')) return new BoxError(BoxError.BAD_FIELD, 'CSP cannot contains double quotes'); - if (csp.includes('\n')) return new BoxError(BoxError.BAD_FIELD, 'CSP cannot contain newlines'); return null; } diff --git a/src/reverseproxy.js b/src/reverseproxy.js index c7fb5a718..d66edc0c3 100644 --- a/src/reverseproxy.js +++ b/src/reverseproxy.js @@ -477,6 +477,12 @@ async function removeDashboardConfig(subdomain, domain) { await reload(); } +function normalizeCSP(csp) { + const lines = csp.split('\n').map(line => line.trim()).filter(line => line && !line.startsWith('#')); + const statements = lines.map(line => line.endsWith(';') ? line : `${line};`); // semicolon terminate all lines + return statements.join(' ').replace(/\s+/g, ' ').trim(); // merge into single line +} + async function writeAppLocationNginxConfig(app, location, certificatePath) { assert.strictEqual(typeof app, 'object'); assert.strictEqual(typeof location, 'object'); @@ -514,7 +520,7 @@ async function writeAppLocationNginxConfig(app, location, certificatePath) { const reverseProxyConfig = app.reverseProxyConfig || {}; // some of our code uses fake app objects if (reverseProxyConfig.robotsTxt) data.robotsTxtQuoted = JSON.stringify(app.reverseProxyConfig.robotsTxt); if (reverseProxyConfig.csp) { - data.cspQuoted = `"${app.reverseProxyConfig.csp}"`; + data.cspQuoted = `"${normalizeCSP(app.reverseProxyConfig.csp)}"`; data.hideHeaders = [ 'Content-Security-Policy' ]; if (reverseProxyConfig.csp.includes('frame-ancestors ')) data.hideHeaders.push('X-Frame-Options'); }