merge #69: probe embed signals via GET fallback when HEAD omits them (trinhthiminh650-tech)
# Conflicts: # src/client/builtins/tabs.tsx # src/client/locales.ts # src/client/prefs.ts # src/config.ts # src/index.ts # src/prefs-shared.ts
This commit is contained in:
@@ -26,7 +26,7 @@ import {
|
||||
} from '@deepseek-ai/dsh-client-ui-primitives'
|
||||
import { VscLinkExternal } from 'react-icons/vsc'
|
||||
import { api } from './api.ts'
|
||||
import { embeddabilityOf, normalizeBrowserUrl } from './browser.ts'
|
||||
import { embeddabilityOf, isAllowedLoopbackUrl, normalizeBrowserUrl } from './browser.ts'
|
||||
import { patchTab } from './state.ts'
|
||||
import { SandboxStatusBar } from './SandboxStatusBar.tsx'
|
||||
import { t } from './locales.ts'
|
||||
@@ -43,6 +43,26 @@ import css from './sidebar.module.css'
|
||||
export const BROWSER_IFRAME_SANDBOX =
|
||||
'allow-scripts allow-forms allow-popups allow-downloads allow-modals allow-popups-to-escape-sandbox'
|
||||
|
||||
/** allow-same-origin appended for explicitly allowlisted local addresses. */
|
||||
const BROWSER_IFRAME_SANDBOX_SAME_ORIGIN =
|
||||
`${BROWSER_IFRAME_SANDBOX} allow-same-origin`
|
||||
|
||||
/**
|
||||
* The sandbox tokens for one URL: allowlisted loopback addresses (local dev
|
||||
* servers the user explicitly trusts) additionally get `allow-same-origin`
|
||||
* so Vite/module/HMR pipelines that need a real origin work; every other
|
||||
* site keeps the opaque-origin sandbox. `allow-same-origin` does NOT give
|
||||
* the page access to the GUI — it stays cross-origin to it and to every
|
||||
* other site — but it does give it its OWN origin privileges (localStorage,
|
||||
* fetch without CORS), so it is only granted for the explicit allowlist.
|
||||
*/
|
||||
export function iframeSandboxFor(url: string | undefined, allowedLoopback: string): string | undefined {
|
||||
if (url === undefined) return undefined
|
||||
return isAllowedLoopbackUrl(url, allowedLoopback)
|
||||
? BROWSER_IFRAME_SANDBOX_SAME_ORIGIN
|
||||
: BROWSER_IFRAME_SANDBOX
|
||||
}
|
||||
|
||||
export function BrowserView(props: TabComponentProps) {
|
||||
const { store, tab } = props
|
||||
// The current address (initialized from the persisted tab.path so a
|
||||
@@ -88,7 +108,7 @@ export function BrowserView(props: TabComponentProps) {
|
||||
}
|
||||
|
||||
const navigateTo = (raw: string): void => {
|
||||
const result = normalizeBrowserUrl(raw, window.location.origin)
|
||||
const result = normalizeBrowserUrl(raw, window.location.origin, store.getPrefs().browserAllowedLoopback)
|
||||
if (result.kind === 'ok') {
|
||||
const next = result.url
|
||||
setUrl(next)
|
||||
@@ -210,7 +230,7 @@ export function BrowserView(props: TabComponentProps) {
|
||||
key={`${reloadKey}:${noSandbox ? 'ns' : 'sb'}`}
|
||||
className={css.browserFrame}
|
||||
src={url}
|
||||
sandbox={noSandbox ? undefined : BROWSER_IFRAME_SANDBOX}
|
||||
sandbox={noSandbox ? undefined : iframeSandboxFor(url, store.getPrefs().browserAllowedLoopback)}
|
||||
referrerPolicy="no-referrer"
|
||||
allow=""
|
||||
title={url}
|
||||
|
||||
+50
-2
@@ -68,6 +68,11 @@ export function isLoopbackHostname(hostname: string): boolean {
|
||||
* @param selfOrigin - the GUI's own origin (window.location.origin). The GUI
|
||||
* itself may be browsed in the sidebar (the sandbox keeps it opaque), so it
|
||||
* is let through BEFORE the loopback check — its host is normally loopback.
|
||||
* @param allowedLoopback - comma-separated loopback allowlist from the side
|
||||
* card prefs (`browserAllowedLoopback`): bare hosts (`localhost`,
|
||||
* `127.0.0.1`) allow every port, `host:port` entries allow exactly that
|
||||
* authority. Entries are matched case-insensitively; portless entries match
|
||||
* the host on any port. Empty allowlist keeps the default loopback block.
|
||||
*/
|
||||
/** Schemes that must never reach the iframe, even without `//` (javascript:,
|
||||
* data:, file:, ...). Host:port lookalikes (example.com:8080) are NOT here —
|
||||
@@ -78,7 +83,41 @@ const FORBIDDEN_SCHEMES = new Set([
|
||||
'chrome', 'chrome-extension', 'moz-extension', 'edge', 'opera', 'resource', 'view-source',
|
||||
])
|
||||
|
||||
export function normalizeBrowserUrl(input: string, selfOrigin: string): BrowserNavigateResult {
|
||||
/** Parse the loopback allowlist into a matcher predicate over host:port. */
|
||||
export function parseLoopbackAllowlist(allowlist: string): (host: string, port: string) => boolean {
|
||||
const entries = allowlist.split(',').map(entry => entry.trim().toLowerCase()).filter(entry => entry !== '')
|
||||
const exact = new Set(entries)
|
||||
const hosts = new Set<string>()
|
||||
for (const entry of entries) {
|
||||
if (!entry.includes(':')) hosts.add(entry.replace(/^\[|\]$/g, ''))
|
||||
}
|
||||
return (host, port) => {
|
||||
const key = `${host}:${port}`
|
||||
if (exact.has(key) || exact.has(host)) return true
|
||||
return port !== '' && hosts.has(host)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a loopback URL is explicitly allowlisted by the side card prefs
|
||||
* (`browserAllowedLoopback`). Only allowlisted local addresses may run with
|
||||
* `allow-same-origin` in the sidebar iframe — needed for local dev servers
|
||||
* (Vite etc.) whose module/HMR/fetch pipeline requires a real origin, while
|
||||
* the page stays cross-origin to the GUI and to every other site.
|
||||
*/
|
||||
export function isAllowedLoopbackUrl(url: string, allowlist: string): boolean {
|
||||
if (allowlist.trim() === '') return false
|
||||
let parsed: URL
|
||||
try {
|
||||
parsed = new URL(url)
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
if (!isLoopbackHostname(parsed.hostname)) return false
|
||||
return parseLoopbackAllowlist(allowlist)(parsed.hostname, parsed.port)
|
||||
}
|
||||
|
||||
export function normalizeBrowserUrl(input: string, selfOrigin: string, allowedLoopback = ''): BrowserNavigateResult {
|
||||
const trimmed = input.trim()
|
||||
if (trimmed === '') return { kind: 'invalid' }
|
||||
// Distinguish an explicit scheme from a bare host:port. "example.com:8080"
|
||||
@@ -114,6 +153,15 @@ export function normalizeBrowserUrl(input: string, selfOrigin: string): BrowserN
|
||||
} catch {
|
||||
// Unparsable selfOrigin (never in practice): fall through to the loopback gate.
|
||||
}
|
||||
if (isLoopbackHostname(url.hostname)) return { kind: 'blocked', reason: 'loopback' }
|
||||
if (isLoopbackHostname(url.hostname)) {
|
||||
// An explicit user allowlist (browserAllowedLoopback) can lift the
|
||||
// loopback block for trusted local dev servers. The sandbox still
|
||||
// renders them in an opaque origin — no GUI access, exactly like any
|
||||
// other browsed site.
|
||||
if (allowedLoopback.trim() !== '' && parseLoopbackAllowlist(allowedLoopback)(url.hostname, url.port)) {
|
||||
return { kind: 'ok', url: url.href }
|
||||
}
|
||||
return { kind: 'blocked', reason: 'loopback' }
|
||||
}
|
||||
return { kind: 'ok', url: url.href }
|
||||
}
|
||||
|
||||
@@ -309,6 +309,12 @@ export function builtinTabs(ctx: Context, options: BuiltinTabOptions = {}): read
|
||||
key: 'browserInterceptHttps',
|
||||
title: () => t('settingsBrowserHttpsTitle'),
|
||||
desc: () => t('settingsBrowserHttpsDesc'),
|
||||
}, {
|
||||
key: 'browserAllowedLoopback',
|
||||
type: 'text',
|
||||
title: () => t('settingsBrowserLoopbackTitle'),
|
||||
desc: () => t('settingsBrowserLoopbackDesc'),
|
||||
placeholder: t('settingsBrowserLoopbackPlaceholder'),
|
||||
}],
|
||||
},
|
||||
createTab: (state) => ({
|
||||
|
||||
@@ -261,6 +261,9 @@ export const ar: Record<string, string> = {
|
||||
settingsBrowserHttpDesc: 'عند التفعيل، النقر على رابط HTTP خارجي في المحادثة أو الواجهة يفتح الشريط الجانبي (صفحات الإضافات المعرّفة عن urlTarget تفوز)؛ Ctrl/Cmd+نقر يتجاوز دائماً',
|
||||
settingsBrowserHttpsTitle: 'فتح صفحات HTTPS في الشريط الجانبي',
|
||||
settingsBrowserHttpsDesc: 'عند التفعيل، النقر على رابط HTTPS خارجي في المحادثة أو الواجهة يفتح الشريط الجانبي. معطّل افتراضياً: معظم مواقع HTTPS ترفض التضمين، لذا فالمتصفح الافتراضي للنظام هو الخيار الأنسب',
|
||||
settingsBrowserLoopbackTitle: 'العناوين المحلية المسموح بها',
|
||||
settingsBrowserLoopbackDesc: 'قائمة بيضاء مفصولة بفواصل لعناوين الاسترجاع المحلي (مثل localhost:5174 أو 127.0.0.1:8080) يمكن للمتصفح الجانبي زيارتها؛ فارغة افتراضيًا تمنع جميع العناوين المحلية. تظل صندوق الحماية سارية — لا يمكن للصفحات قراءة بيانات الواجهة',
|
||||
settingsBrowserLoopbackPlaceholder: 'مثال localhost:5174, 127.0.0.1:8080',
|
||||
browserOpenExternal: 'فتح في المتصفح',
|
||||
browserEmbedBlocked: 'رفض {host} التضمين',
|
||||
browserEmbedBlockedDesc: 'يحظر الموقع عرضه داخل صفحات أخرى (X-Frame-Options / frame-ancestors)، لذا لا يمكن تحميله في الشريط الجانبي. افتحه مباشرة في متصفحك بدلاً من ذلك.',
|
||||
|
||||
@@ -246,6 +246,9 @@ export const de: Record<string, string> = {
|
||||
settingsBrowserHttpDesc: 'Wenn aktiviert, öffnet ein Klick auf einen HTTP-Externlink im Chat oder in der Oberfläche die Seitenleiste (Plugin-Seiten mit urlTarget gewinnen); Ctrl/Cmd-Klick umgeht dies immer',
|
||||
settingsBrowserHttpsTitle: 'HTTPS-Seiten in der Seitenleiste öffnen',
|
||||
settingsBrowserHttpsDesc: 'Wenn aktiviert, öffnet ein Klick auf einen HTTPS-Externlink im Chat oder in der Oberfläche die Seitenleiste. Standardmäßig deaktiviert: Die meisten HTTPS-Sites verweigern die Einbettung, der Systembrowser ist der reibungslosere Standard',
|
||||
settingsBrowserLoopbackTitle: 'Erlaubte lokale Adressen',
|
||||
settingsBrowserLoopbackDesc: 'Kommagetrennte Allowlist von Loopback-Adressen (z. B. localhost:5174 oder 127.0.0.1:8080), die der Seitenleisten-Browser besuchen darf; leer blockiert standardmäßig alle lokalen Adressen. Die Sandbox bleibt aktiv — Seiten können keine GUI-Daten lesen',
|
||||
settingsBrowserLoopbackPlaceholder: 'z. B. localhost:5174, 127.0.0.1:8080',
|
||||
browserOpenExternal: 'Im Browser öffnen',
|
||||
browserEmbedBlocked: '{host} hat die Einbettung verweigert',
|
||||
browserEmbedBlockedDesc: 'Die Site verbietet die Anzeige in anderen Seiten (X-Frame-Options / frame-ancestors) und kann daher nicht in der Seitenleiste geladen werden. Öffnen Sie sie stattdessen direkt im Browser.',
|
||||
|
||||
@@ -253,6 +253,9 @@ export const fr: Record<string, string> = {
|
||||
settingsBrowserHttpDesc: 'Une fois activé, cliquer sur un lien externe HTTP du chat ou de l’interface l’ouvre dans la barre latérale (les pages de plugin déclarant urlTarget sont prioritaires) ; Ctrl/Cmd+clic autorise temporairement',
|
||||
settingsBrowserHttpsTitle: 'Ouvrir les pages HTTPS de côté',
|
||||
settingsBrowserHttpsDesc: 'Une fois activé, cliquer sur un lien externe HTTPS du chat ou de l’interface l’ouvre dans la barre latérale. Désactivé par défaut : la plupart des sites HTTPS refusent l’encapsulation, le navigateur système est plus fluide',
|
||||
settingsBrowserLoopbackTitle: 'Adresses locales autorisées',
|
||||
settingsBrowserLoopbackDesc: 'Liste blanche séparée par des virgules d'adresses de bouclage (ex. localhost:5174 ou 127.0.0.1:8080) que le navigateur de la barre latérale peut visiter ; vide bloque toutes les adresses locales par défaut. Le bac à sable s'applique toujours — les pages ne peuvent pas lire les données de l'interface',
|
||||
settingsBrowserLoopbackPlaceholder: 'ex. localhost:5174, 127.0.0.1:8080',
|
||||
browserOpenExternal: 'Ouvrir dans le navigateur',
|
||||
browserEmbedBlocked: '{host} a refusé la demande d’encapsulation',
|
||||
browserEmbedBlockedDesc: 'Ce site interdit son affichage dans d’autres pages (X-Frame-Options / frame-ancestors) et ne peut donc pas être chargé dans la barre latérale. Vous pouvez l’ouvrir directement dans le navigateur',
|
||||
|
||||
@@ -260,6 +260,9 @@ export const hi: Record<string, string> = {
|
||||
settingsBrowserHttpDesc: 'चालू होने पर, चैट या GUI में HTTP बाहरी लिंक क्लिक करने पर साइडबार खुलता है (urlTarget घोषित प्लगइन पृष्ठ जीतते हैं); Ctrl/Cmd+क्लिक हमेशा बायपास करता है',
|
||||
settingsBrowserHttpsTitle: 'HTTPS पृष्ठ साइडबार में खोलें',
|
||||
settingsBrowserHttpsDesc: 'चालू होने पर, चैट या GUI में HTTPS बाहरी लिंक क्लिक करने पर साइडबार खुलता है। डिफ़ॉल्ट रूप से बंद: अधिकांश HTTPS साइटें एम्बेड होने से इनकार करती हैं, सिस्टम ब्राउज़र चिकना डिफ़ॉल्ट है',
|
||||
settingsBrowserLoopbackTitle: 'अनुमत स्थानीय पते',
|
||||
settingsBrowserLoopbackDesc: 'लूपबैक पतों की कॉमा-सेपरेटेड अनुमति सूची (जैसे localhost:5174 या 127.0.0.1:8080) जिन्हें साइडबार ब्राउज़र देख सकता है; खाली डिफ़ॉल्ट रूप से सभी स्थानीय पते ब्लॉक करता है। सैंडबॉक्स लागू रहता है — पेज GUI डेटा नहीं पढ़ सकते',
|
||||
settingsBrowserLoopbackPlaceholder: 'जैसे localhost:5174, 127.0.0.1:8080',
|
||||
browserOpenExternal: 'ब्राउज़र में खोलें',
|
||||
browserEmbedBlocked: '{host} एम्बेड होने से इनकार',
|
||||
browserEmbedBlockedDesc: 'साइट अन्य पृष्ठों के अंदर प्रदर्शित होने से मना करती है (X-Frame-Options / frame-ancestors), इसलिए साइडबार में लोड नहीं हो सकती। इसे अपने ब्राउज़र में सीधे खोलें।',
|
||||
|
||||
@@ -258,6 +258,9 @@ export const id: Record<string, string> = {
|
||||
settingsBrowserHttpDesc: 'Saat aktif, mengeklik tautan eksternal HTTP di obrolan atau GUI membuka sidebar (halaman plugin yang mendeklarasikan urlTarget menang); Ctrl/Cmd+klik selalu menembus',
|
||||
settingsBrowserHttpsTitle: 'Buka halaman HTTPS di sidebar',
|
||||
settingsBrowserHttpsDesc: 'Saat aktif, mengeklik tautan eksternal HTTPS di obrolan atau GUI membuka sidebar. Nonaktif secara default: kebanyakan situs HTTPS menolak untuk disematkan, jadi browser sistem adalah default yang lebih mulus',
|
||||
settingsBrowserLoopbackTitle: 'Alamat lokal yang diizinkan',
|
||||
settingsBrowserLoopbackDesc: 'Daftar putih alamat loopback yang dipisahkan koma (mis. localhost:5174 atau 127.0.0.1:8080) yang dapat dikunjungi browser sidebar; kosong memblokir semua alamat lokal secara default. Sandbox tetap berlaku — halaman tidak dapat membaca data GUI',
|
||||
settingsBrowserLoopbackPlaceholder: 'mis. localhost:5174, 127.0.0.1:8080',
|
||||
browserOpenExternal: 'Buka di browser',
|
||||
browserEmbedBlocked: '{host} menolak untuk disematkan',
|
||||
browserEmbedBlockedDesc: 'Situs melarang ditampilkan di dalam halaman lain (X-Frame-Options / frame-ancestors), sehingga tidak dapat dimuat di sidebar. Buka langsung di browser Anda.',
|
||||
|
||||
@@ -251,6 +251,9 @@ export const it: Record<string, string> = {
|
||||
settingsBrowserHttpDesc: 'Se attivato, cliccando un collegamento HTTP esterno nella chat o nell’interfaccia si apre la barra laterale (le pagine dei plugin che dichiarano urlTarget vincono); Ctrl/Cmd+clic lo aggira sempre',
|
||||
settingsBrowserHttpsTitle: 'Apri le pagine HTTPS nella barra laterale',
|
||||
settingsBrowserHttpsDesc: 'Se attivato, cliccando un collegamento HTTPS esterno nella chat o nell’interfaccia si apre la barra laterale. Disattivato per impostazione predefinita: la maggior parte dei siti HTTPS rifiuta di essere incorporata, per cui il browser di sistema è l’impostazione più fluida',
|
||||
settingsBrowserLoopbackTitle: 'Indirizzi locali consentiti',
|
||||
settingsBrowserLoopbackDesc: 'Allowlist separata da virgole di indirizzi di loopback (es. localhost:5174 o 127.0.0.1:8080) che il browser della barra laterale può visitare; vuoto blocca tutti gli indirizzi locali per impostazione predefinita. La sandbox si applica ancora — le pagine non possono leggere i dati della GUI',
|
||||
settingsBrowserLoopbackPlaceholder: 'es. localhost:5174, 127.0.0.1:8080',
|
||||
browserOpenExternal: 'Apri nel browser',
|
||||
browserEmbedBlocked: '{host} ha rifiutato di essere incorporato',
|
||||
browserEmbedBlockedDesc: 'Il sito vieta di essere mostrato all’interno di altre pagine (X-Frame-Options / frame-ancestors), per cui non può essere caricato nella barra laterale. Lo apra direttamente nel browser.',
|
||||
|
||||
@@ -260,6 +260,9 @@ export const ja: Record<string, string> = {
|
||||
settingsBrowserHttpDesc: 'オンにすると、チャットや GUI の HTTP 外部リンククリック時にサイドバーで開く(urlTarget を宣言したプラグインページが優先)。Ctrl/Cmd+クリックで一時的にバイパス',
|
||||
settingsBrowserHttpsTitle: 'HTTPS ページをサイドバーで開く',
|
||||
settingsBrowserHttpsDesc: 'オンにすると、チャットや GUI の HTTPS 外部リンククリック時にサイドバーで開く。デフォルトオフ:大半の HTTPS サイトは埋め込みを拒否するため、システムブラウザーの方がスムーズ',
|
||||
settingsBrowserLoopbackTitle: '許可されたローカルアドレス',
|
||||
settingsBrowserLoopbackDesc: 'サイドバーブラウザがアクセスできるループバックアドレスのカンマ区切り許可リスト(例:localhost:5174 や 127.0.0.1:8080)。空の場合、デフォルトですべてのローカルアドレスをブロックします。サンドボックスは引き続き適用され、ページは GUI データを読み取れません',
|
||||
settingsBrowserLoopbackPlaceholder: '例 localhost:5174, 127.0.0.1:8080',
|
||||
browserOpenExternal: 'ブラウザーで開く',
|
||||
browserEmbedBlocked: '{host} は埋め込みを拒否しました',
|
||||
browserEmbedBlockedDesc: 'このサイトは X-Frame-Options / frame-ancestors で他ページ内での表示を禁止しているため、サイドバー内で読み込めません。ブラウザーで直接開いてください',
|
||||
|
||||
@@ -252,6 +252,9 @@ export const ko: Record<string, string> = {
|
||||
settingsBrowserHttpDesc: '켜면 채팅이나 인터페이스에서 HTTP 외부 링크를 클릭할 때 사이드바에서 엽니다(urlTarget을 선언한 플러그인 페이지가 우선). Ctrl/Cmd 클릭으로 임시 허용할 수 있습니다',
|
||||
settingsBrowserHttpsTitle: 'HTTPS 페이지를 사이드바에서 열기',
|
||||
settingsBrowserHttpsDesc: '켜면 채팅이나 인터페이스에서 HTTPS 외부 링크를 클릭할 때 사이드바에서 엽니다. 기본 꺼짐: 대부분의 HTTPS 사이트가 임베드를 거부하므로 시스템 브라우저가 더 원활합니다',
|
||||
settingsBrowserLoopbackTitle: '허용된 로컬 주소',
|
||||
settingsBrowserLoopbackDesc: '사이드바 브라우저가 방문할 수 있는 루프백 주소의 쉼표로 구분된 허용 목록(예: localhost:5174 또는 127.0.0.1:8080). 비어 있으면 기본적으로 모든 로컬 주소를 차단합니다. 샌드박스는 계속 적용되며, 페이지는 GUI 데이터를 읽을 수 없습니다',
|
||||
settingsBrowserLoopbackPlaceholder: '예: localhost:5174, 127.0.0.1:8080',
|
||||
browserOpenExternal: '브라우저에서 열기',
|
||||
browserEmbedBlocked: '{host} 가(이) 임베드 요청을 거부했습니다',
|
||||
browserEmbedBlockedDesc: '이 사이트는 X-Frame-Options / frame-ancestors로 다른 페이지에 표시되는 것을 금지하여 사이드바에서 로드할 수 없습니다. 브라우저에서 직접 열 수 있습니다',
|
||||
|
||||
@@ -258,6 +258,9 @@ export const nl: Record<string, string> = {
|
||||
settingsBrowserHttpDesc: 'Indien aan, opent een klik op een externe HTTP-link in de chat of GUI de zijbalk (plugin-pagina\'s die urlTarget declareren winnen); Ctrl/Cmd+klik omzeilt altijd',
|
||||
settingsBrowserHttpsTitle: 'HTTPS-pagina\'s in de zijbalk openen',
|
||||
settingsBrowserHttpsDesc: 'Indien aan, opent een klik op een externe HTTPS-link in de chat of GUI de zijbalk. Standaard uit: de meeste HTTPS-sites weigeren ingebed te worden, dus de systeembrowser is soepeler',
|
||||
settingsBrowserLoopbackTitle: 'Toegestane lokale adressen',
|
||||
settingsBrowserLoopbackDesc: 'Door komma's gescheiden allowlist van loopback-adressen (bijv. localhost:5174 of 127.0.0.1:8080) die de zijbalk-browser kan bezoeken; leeg blokkeert standaard alle lokale adressen. De sandbox blijft van toepassing — pagina's kunnen geen GUI-gegevens lezen',
|
||||
settingsBrowserLoopbackPlaceholder: 'bijv. localhost:5174, 127.0.0.1:8080',
|
||||
browserOpenExternal: 'Openen in browser',
|
||||
browserEmbedBlocked: '{host} weigerde ingebed te worden',
|
||||
browserEmbedBlockedDesc: 'De site verbiedt weergave binnen andere pagina\'s (X-Frame-Options / frame-ancestors), dus kan niet in de zijbalk laden. Open de site direct in uw browser.',
|
||||
|
||||
@@ -262,6 +262,9 @@ export const pl: Record<string, string> = {
|
||||
settingsBrowserHttpDesc: 'Po włączeniu kliknięcie zewnętrznego linku HTTP na czacie lub w interfejsie otwiera panel boczny (strony wtyczek deklarujące urlTarget mają pierwszeństwo); Ctrl/Cmd+klik zawsze pomija',
|
||||
settingsBrowserHttpsTitle: 'Otwieraj strony HTTPS w panelu bocznym',
|
||||
settingsBrowserHttpsDesc: 'Po włączeniu kliknięcie zewnętrznego linku HTTPS na czacie lub w interfejsie otwiera panel boczny. Domyślnie wyłączone: większość stron HTTPS odmawia osadzania, więc przeglądarka systemowa jest płynniejszym domyślnym wyborem',
|
||||
settingsBrowserLoopbackTitle: 'Dozwolone adresy lokalne',
|
||||
settingsBrowserLoopbackDesc: 'Rozdzielana przecinkami lista dozwolonych adresów loopback (np. localhost:5174 lub 127.0.0.1:8080), które może odwiedzać przeglądarka paska bocznego; puste domyślnie blokuje wszystkie adresy lokalne. Piaskownica nadal obowiązuje — strony nie mogą odczytywać danych GUI',
|
||||
settingsBrowserLoopbackPlaceholder: 'np. localhost:5174, 127.0.0.1:8080',
|
||||
browserOpenExternal: 'Otwórz w przeglądarce',
|
||||
browserEmbedBlocked: '{host} odmówił osadzenia',
|
||||
browserEmbedBlockedDesc: 'Witryna zabrania wyświetlania w innych stronach (X-Frame-Options / frame-ancestors), więc nie może się załadować w panelu bocznym. Otwórz ją bezpośrednio w przeglądarce.',
|
||||
|
||||
@@ -243,6 +243,9 @@ export const pt: Record<string, string> = {
|
||||
settingsBrowserHttpDesc: 'Quando ativado, clicar em um link externo HTTP no chat ou na interface abre na barra lateral (páginas de plugins que declaram urlTarget vencem); Ctrl/Cmd+clique sempre ignora',
|
||||
settingsBrowserHttpsTitle: 'Abrir páginas HTTPS na barra lateral',
|
||||
settingsBrowserHttpsDesc: 'Quando ativado, clicar em um link externo HTTPS no chat ou na interface abre na barra lateral. Desativado por padrão: a maioria dos sites HTTPS se recusa a ser incorporada, então o navegador do sistema é o padrão mais suave',
|
||||
settingsBrowserLoopbackTitle: 'Endereços locais permitidos',
|
||||
settingsBrowserLoopbackDesc: 'Lista de permissões separada por vírgulas de endereços de loopback (ex. localhost:5174 ou 127.0.0.1:8080) que o navegador da barra lateral pode visitar; vazio bloqueia todos os endereços locais por padrão. A sandbox ainda se aplica — as páginas não podem ler dados da GUI',
|
||||
settingsBrowserLoopbackPlaceholder: 'ex. localhost:5174, 127.0.0.1:8080',
|
||||
browserOpenExternal: 'Abrir no navegador',
|
||||
browserEmbedBlocked: '{host} se recusou a ser incorporado',
|
||||
browserEmbedBlockedDesc: 'O site proíbe ser exibido dentro de outras páginas (X-Frame-Options / frame-ancestors), então não pode carregar na barra lateral. Abra-o diretamente no navegador.',
|
||||
|
||||
@@ -257,6 +257,9 @@ export const ru: Record<string, string> = {
|
||||
settingsBrowserHttpDesc: 'Если включено, щелчок по HTTP-ссылке в чате или интерфейсе открывает её в боковой панели (страницы плагинов с urlTarget имеют приоритет); Ctrl/Cmd+щелчок всегда пропускает',
|
||||
settingsBrowserHttpsTitle: 'Открывать HTTPS-страницы в боковой панели',
|
||||
settingsBrowserHttpsDesc: 'Если включено, щелчок по HTTPS-ссылке в чате или интерфейсе открывает её в боковой панели. По умолчанию выключено: большинство HTTPS-сайтов отказываются встраиваться, поэтому системный браузер работает плавнее',
|
||||
settingsBrowserLoopbackTitle: 'Разрешённые локальные адреса',
|
||||
settingsBrowserLoopbackDesc: 'Разделённый запятыми список разрешённых loopback-адресов (напр. localhost:5174 или 127.0.0.1:8080), которые может посещать браузер боковой панели; пустое значение по умолчанию блокирует все локальные адреса. Песочница продолжает действовать — страницы не могут читать данные интерфейса',
|
||||
settingsBrowserLoopbackPlaceholder: 'напр. localhost:5174, 127.0.0.1:8080',
|
||||
browserOpenExternal: 'Открыть в браузере',
|
||||
browserEmbedBlocked: '{host} отказал во встраивании',
|
||||
browserEmbedBlockedDesc: 'Сайт запрещает отображение внутри других страниц (X-Frame-Options / frame-ancestors), поэтому его нельзя загрузить в боковой панели. Откройте его напрямую в браузере',
|
||||
|
||||
@@ -243,6 +243,9 @@ export const sv: Record<string, string> = {
|
||||
settingsBrowserHttpDesc: 'När på, klick på en extern HTTP-länk i chatten eller GUI öppnar sidopanelen (pluginsidor som deklarerar urlTarget vinner); Ctrl/Cmd+klick kringgår alltid',
|
||||
settingsBrowserHttpsTitle: 'Öppna HTTPS-sidor i sidopanelen',
|
||||
settingsBrowserHttpsDesc: 'När på, klick på en extern HTTPS-länk i chatten eller GUI öppnar sidopanelen. Av som standard: de flesta HTTPS-sajter vägrar bli inbäddade, så systemwebbläsaren är det smidigare standardvalet',
|
||||
settingsBrowserLoopbackTitle: 'Tillåtna lokala adresser',
|
||||
settingsBrowserLoopbackDesc: 'Kommaseparerad tillåten lista över loopback-adresser (t.ex. localhost:5174 eller 127.0.0.1:8080) som sidofältswebbläsaren kan besöka; tom blockerar alla lokala adresser som standard. Sandlådan gäller fortfarande — sidor kan inte läsa GUI-data',
|
||||
settingsBrowserLoopbackPlaceholder: 't.ex. localhost:5174, 127.0.0.1:8080',
|
||||
browserOpenExternal: 'Öppna i webbläsare',
|
||||
browserEmbedBlocked: '{host} vägrade bli inbäddad',
|
||||
browserEmbedBlockedDesc: 'Sajten förbjuder att visas inuti andra sidor (X-Frame-Options / frame-ancestors), så den kan inte ladda i sidopanelen. Öppna den direkt i er webbläsare i stället.',
|
||||
|
||||
@@ -260,6 +260,9 @@ export const th: Record<string, string> = {
|
||||
settingsBrowserHttpDesc: 'เมื่อเปิด การคลิกลิงก์ HTTP ภายนอกในแชทหรือ GUI จะเปิดแถบด้านข้าง (หน้าปลั๊กอินที่ประกาศ urlTarget win มีความสำคัญก่อน); Ctrl/Cmd+click จะข้ามเสมอ',
|
||||
settingsBrowserHttpsTitle: 'เปิดหน้า HTTPS ในแถบด้านข้าง',
|
||||
settingsBrowserHttpsDesc: 'เมื่อเปิด การคลิกลิงก์ HTTPS ภายนอกในแชทหรือ GUI จะเปิดแถบด้านข้าง ปิดเป็นค่าเริ่มต้น: ไซต์ HTTPS ส่วนใหญ่ปฏิเสธที่จะฝัง ดังนั้นเบราว์เซอร์ระบบจะราบรื่นกว่าเป็นค่าเริ่มต้น',
|
||||
settingsBrowserLoopbackTitle: 'ที่อยู่ภายในเครื่องที่อนุญาต',
|
||||
settingsBrowserLoopbackDesc: 'รายการที่อนุญาตแบบคั่นด้วยจุลภาคของที่อยู่ลูปแบ็ก (เช่น localhost:5174 หรือ 127.0.0.1:8080) ที่เบราว์เซอร์แถบด้านข้างสามารถเยี่ยมชมได้ ค่าว่างจะบล็อกที่อยู่ภายในทั้งหมดโดยค่าเริ่มต้น แซนด์บ็อกซ์ยังคงมีผล — หน้าเว็บไม่สามารถอ่านข้อมูล GUI ได้',
|
||||
settingsBrowserLoopbackPlaceholder: 'เช่น localhost:5174, 127.0.0.1:8080',
|
||||
browserOpenExternal: 'เปิดในเบราว์เซอร์',
|
||||
browserEmbedBlocked: '{host} ปฏิเสธที่จะถูกฝัง',
|
||||
browserEmbedBlockedDesc: 'ไซต์ห้ามไม่ให้แสดงภายในหน้าอื่น (X-Frame-Options / frame-ancestors) จึงไม่สามารถโหลดในแถบด้านข้างได้ เปิดโดยตรงในเบราว์เซอร์ของคุณแทน',
|
||||
|
||||
@@ -260,6 +260,9 @@ export const tr: Record<string, string> = {
|
||||
settingsBrowserHttpDesc: 'Açıkken sohbetteki veya arayüzdeki bir HTTP dış bağlantısına tıklamak kenar çubuğunu açar (urlTarget bildiren eklenti sayfaları öncelikli); Ctrl/Cmd+tıklama her zaman atlatır',
|
||||
settingsBrowserHttpsTitle: 'HTTPS sayfalarını kenar çubuğunda aç',
|
||||
settingsBrowserHttpsDesc: 'Açıkken sohbetteki veya arayüzdeki bir HTTPS dış bağlantısına tıklamak kenar çubuğunu açar. Varsayılan kapalı: çoğu HTTPS sitesi gömülmeyi reddeder, bu yüzden sistem tarayıcısı daha akıcı varsayılandır',
|
||||
settingsBrowserLoopbackTitle: 'İzin verilen yerel adresler',
|
||||
settingsBrowserLoopbackDesc: 'Kenar çubuğu tarayıcısının ziyaret edebileceği geri döngü adreslerinin virgülle ayrılmış izin listesi (ör. localhost:5174 veya 127.0.0.1:8080); boş, varsayılan olarak tüm yerel adresleri engeller. Kum havuzu hala geçerlidir — sayfalar GUI verilerini okuyamaz',
|
||||
settingsBrowserLoopbackPlaceholder: 'ör. localhost:5174, 127.0.0.1:8080',
|
||||
browserOpenExternal: 'Tarayıcıda aç',
|
||||
browserEmbedBlocked: '{host} gömülmeyi reddetti',
|
||||
browserEmbedBlockedDesc: 'Site başka sayfalar içinde gösterilmeyi yasaklıyor (X-Frame-Options / frame-ancestors), bu yüzden kenar çubuğunda yüklenemiyor. Bunun yerine doğrudan tarayıcınızda açın.',
|
||||
|
||||
@@ -260,6 +260,9 @@ export const vi: Record<string, string> = {
|
||||
settingsBrowserHttpDesc: 'Khi bật, nhấp link HTTP ngoài trong chat hoặc UI sẽ mở trong thanh bên (trang plugin khai báo urlTarget ưu tiên); Ctrl/Cmd+nhấp luôn bỏ qua',
|
||||
settingsBrowserHttpsTitle: 'Mở trang HTTPS trong thanh bên',
|
||||
settingsBrowserHttpsDesc: 'Khi bật, nhấp link HTTPS ngoài trong chat hoặc UI sẽ mở trong thanh bên. Mặc định tắt: đa số trang HTTPS từ chối nhúng, trình duyệt hệ thống mượt mà hơn',
|
||||
settingsBrowserLoopbackTitle: 'Địa chỉ cục bộ được phép',
|
||||
settingsBrowserLoopbackDesc: 'Danh sách cho phép phân tách bằng dấu phẩy của địa chỉ loopback (vd. localhost:5174 hoặc 127.0.0.1:8080) mà trình duyệt thanh bên có thể truy cập; để trống sẽ chặn tất cả địa chỉ cục bộ theo mặc định. Hộp cát vẫn áp dụng — các trang không thể đọc dữ liệu GUI',
|
||||
settingsBrowserLoopbackPlaceholder: 'vd. localhost:5174, 127.0.0.1:8080',
|
||||
browserOpenExternal: 'Mở trong trình duyệt',
|
||||
browserEmbedBlocked: '{host} từ chối nhúng',
|
||||
browserEmbedBlockedDesc: 'Trang này cấm hiển thị trong trang khác qua X-Frame-Options / frame-ancestors, không thể tải trong thanh bên. Mở trực tiếp trong trình duyệt',
|
||||
|
||||
@@ -275,6 +275,9 @@ export const zhHK: Record<string, string> = {
|
||||
settingsBrowserHttpDesc: '開啟後,點擊聊天或介面中的 HTTP 外鏈時在側邊欄開啟(宣告了 urlTarget 的插件頁面優先);Ctrl/Cmd 點擊可臨時放行',
|
||||
settingsBrowserHttpsTitle: '側邊開啟HTTPS網頁',
|
||||
settingsBrowserHttpsDesc: '開啟後,點擊聊天或介面中的 HTTPS 外鏈時在側邊欄開啟。預設關閉:多數 HTTPS 網站拒絕被嵌入,走系統瀏覽器更順暢',
|
||||
settingsBrowserLoopbackTitle: '允許訪問的本機地址',
|
||||
settingsBrowserLoopbackDesc: '逗號分隔的本機回環地址白名單(如 localhost:5174 或 127.0.0.1:8080),側邊欄瀏覽器可訪問這些本機服務;預設留空則本機地址全部攔截。沙箱隔離仍然生效,頁面無法讀取介面資料',
|
||||
settingsBrowserLoopbackPlaceholder: '例如 localhost:5174, 127.0.0.1:8080',
|
||||
browserOpenExternal: '在瀏覽器中開啟',
|
||||
browserEmbedBlocked: '{host} 拒絕了嵌入請求',
|
||||
browserEmbedBlockedDesc: '該網站透過 X-Frame-Options / frame-ancestors 禁止在其它頁面中顯示,無法在側邊欄內載入。可在瀏覽器中直接開啟',
|
||||
|
||||
@@ -275,6 +275,9 @@ export const zhMO: Record<string, string> = {
|
||||
settingsBrowserHttpDesc: '開啟後,點擊聊天或介面中的 HTTP 外鏈時在側邊欄開啟(宣告了 urlTarget 的插件頁面優先);Ctrl/Cmd 點擊可臨時放行',
|
||||
settingsBrowserHttpsTitle: '側邊開啟HTTPS網頁',
|
||||
settingsBrowserHttpsDesc: '開啟後,點擊聊天或介面中的 HTTPS 外鏈時在側邊欄開啟。預設關閉:多數 HTTPS 網站拒絕被嵌入,走系統瀏覽器更順暢',
|
||||
settingsBrowserLoopbackTitle: '允許訪問的本機地址',
|
||||
settingsBrowserLoopbackDesc: '逗號分隔的本機回環地址白名單(如 localhost:5174 或 127.0.0.1:8080),側邊欄瀏覽器可訪問這些本機服務;預設留空則本機地址全部攔截。沙箱隔離仍然生效,頁面無法讀取介面資料',
|
||||
settingsBrowserLoopbackPlaceholder: '例如 localhost:5174, 127.0.0.1:8080',
|
||||
browserOpenExternal: '在瀏覽器中開啟',
|
||||
browserEmbedBlocked: '{host} 拒絕了嵌入請求',
|
||||
browserEmbedBlockedDesc: '該網站透過 X-Frame-Options / frame-ancestors 禁止在其它頁面中顯示,無法在側邊欄內載入。可在瀏覽器中直接開啟',
|
||||
|
||||
@@ -275,6 +275,9 @@ export const zhTW: Record<string, string> = {
|
||||
settingsBrowserHttpDesc: '開啟後,點擊聊天或介面中的 HTTP 外鏈時在側邊欄開啟(宣告了 urlTarget 的插件頁面優先);Ctrl/Cmd 點擊可臨時放行',
|
||||
settingsBrowserHttpsTitle: '側邊開啟HTTPS網頁',
|
||||
settingsBrowserHttpsDesc: '開啟後,點擊聊天或介面中的 HTTPS 外鏈時在側邊欄開啟。預設關閉:多數 HTTPS 網站拒絕被嵌入,走系統瀏覽器更順暢',
|
||||
settingsBrowserLoopbackTitle: '允許存取的本機地址',
|
||||
settingsBrowserLoopbackDesc: '逗號分隔的本機回環地址白名單(如 localhost:5174 或 127.0.0.1:8080),側邊欄瀏覽器可存取這些本機服務;預設留空則本機地址全部攔截。沙箱隔離仍然生效,頁面無法讀取介面資料',
|
||||
settingsBrowserLoopbackPlaceholder: '例如 localhost:5174, 127.0.0.1:8080',
|
||||
browserOpenExternal: '在瀏覽器中開啟',
|
||||
browserEmbedBlocked: '{host} 拒絕了嵌入請求',
|
||||
browserEmbedBlockedDesc: '該網站透過 X-Frame-Options / frame-ancestors 禁止在其它頁面中顯示,無法在側邊欄內載入。可在瀏覽器中直接開啟',
|
||||
|
||||
@@ -267,6 +267,9 @@ export const zh = {
|
||||
settingsBrowserHttpDesc: '开启后,点击聊天或界面中的 HTTP 外链时在侧边栏打开(声明了 urlTarget 的插件页面优先);Ctrl/Cmd 点击可临时放行',
|
||||
settingsBrowserHttpsTitle: '侧边打开HTTPS网页',
|
||||
settingsBrowserHttpsDesc: '开启后,点击聊天或界面中的 HTTPS 外链时在侧边栏打开。默认关闭:多数 HTTPS 站点拒绝被嵌入,走系统浏览器更顺畅',
|
||||
settingsBrowserLoopbackTitle: '允许访问的本机地址',
|
||||
settingsBrowserLoopbackDesc: '逗号分隔的本地回环地址白名单(如 localhost:5174 或 127.0.0.1:8080),侧边栏浏览器可访问这些本地服务;默认留空则本机地址全部拦截。沙箱隔离仍然生效,页面无法读取界面数据',
|
||||
settingsBrowserLoopbackPlaceholder: '例如 localhost:5174, 127.0.0.1:8080',
|
||||
browserOpenExternal: '在浏览器中打开',
|
||||
browserEmbedBlocked: '{host} 拒绝了嵌入请求',
|
||||
browserEmbedBlockedDesc: '该站点通过 X-Frame-Options / frame-ancestors 禁止在其它页面中显示,无法在侧边栏内加载。可在浏览器中直接打开',
|
||||
@@ -601,6 +604,9 @@ export const en: Record<keyof typeof zh, string> = {
|
||||
settingsBrowserHttpDesc: 'When on, clicking an HTTP external link in the chat or GUI opens the sidebar (plugin pages declaring urlTarget win); Ctrl/Cmd+click always bypasses',
|
||||
settingsBrowserHttpsTitle: 'Open HTTPS pages in the sidebar',
|
||||
settingsBrowserHttpsDesc: 'When on, clicking an HTTPS external link in the chat or GUI opens the sidebar. Off by default: most HTTPS sites refuse to be embedded, so the system browser is the smoother default',
|
||||
settingsBrowserLoopbackTitle: 'Allowed local addresses',
|
||||
settingsBrowserLoopbackDesc: 'Comma-separated allowlist of loopback addresses (e.g. localhost:5174 or 127.0.0.1:8080) the sidebar browser may visit; empty blocks all local addresses by default. The sandbox still applies — pages cannot read GUI data',
|
||||
settingsBrowserLoopbackPlaceholder: 'e.g. localhost:5174, 127.0.0.1:8080',
|
||||
browserOpenExternal: 'Open in browser',
|
||||
browserEmbedBlocked: '{host} refused to be embedded',
|
||||
browserEmbedBlockedDesc: 'The site forbids being displayed inside other pages (X-Frame-Options / frame-ancestors), so it cannot load in the sidebar. Open it directly in your browser instead.',
|
||||
|
||||
@@ -119,6 +119,9 @@ export function parsePrefs(value: unknown): SidebarPrefs {
|
||||
browserInterceptHttps: typeof record.browserInterceptHttps === 'boolean'
|
||||
? record.browserInterceptHttps
|
||||
: SIDEBAR_PREFS_DEFAULTS.browserInterceptHttps,
|
||||
browserAllowedLoopback: typeof record.browserAllowedLoopback === 'string'
|
||||
? record.browserAllowedLoopback
|
||||
: SIDEBAR_PREFS_DEFAULTS.browserAllowedLoopback,
|
||||
tabsEnabled: booleanMapOf(record.tabsEnabled),
|
||||
viewersEnabled: booleanMapOf(record.viewersEnabled),
|
||||
pluginSettings: pluginSettingsMapOf(record.pluginSettings),
|
||||
|
||||
@@ -140,6 +140,7 @@ export const PrefsSchema: z<SidebarPrefs> = z.object({
|
||||
browserInterceptLinks: z.boolean().default(true),
|
||||
browserInterceptHttp: z.boolean().default(true),
|
||||
browserInterceptHttps: z.boolean().default(false),
|
||||
browserAllowedLoopback: z.string().default(''),
|
||||
// Per-feature enable switches are OPEN maps (any tab/viewer id, built-in or
|
||||
// external): an absent key means enabled, so old documents resolve to {}
|
||||
// (everything on) with no migration. Non-boolean values fail validation.
|
||||
|
||||
+40
-2
@@ -232,6 +232,26 @@ function shellOverridesOf(getSettings: () => SidebarSettingsFace | undefined): {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the browser tab's `browserAllowedLoopback` allowlist into a matcher
|
||||
* over host:port (same contract as the client-side helper in
|
||||
* src/client/browser.ts — kept in sync). Bare hosts (`localhost`,
|
||||
* `127.0.0.1`) match every port; `host:port` entries match exactly.
|
||||
*/
|
||||
function parseLoopbackAllowlist(allowlist: string): (host: string, port: string) => boolean {
|
||||
const entries = allowlist.split(',').map(entry => entry.trim().toLowerCase()).filter(entry => entry !== '')
|
||||
const exact = new Set(entries)
|
||||
const hosts = new Set<string>()
|
||||
for (const entry of entries) {
|
||||
if (!entry.includes(':')) hosts.add(entry.replace(/^\[|\]$/g, ''))
|
||||
}
|
||||
return (host, port) => {
|
||||
const key = `${host}:${port}`
|
||||
if (exact.has(key) || exact.has(host)) return true
|
||||
return port !== '' && hosts.has(host)
|
||||
}
|
||||
}
|
||||
|
||||
function buildApi(
|
||||
ctx: Context,
|
||||
ptyManager: PtyManager | null,
|
||||
@@ -488,9 +508,16 @@ function buildApi(
|
||||
throw new SidebarError('bad-request', 'only http/https urls can be probed', 400)
|
||||
}
|
||||
// Mirror the browser tab's address-bar policy: loopback stays unreachable
|
||||
// from the sidebar, so probing it would leak nothing the tab could use.
|
||||
// from the sidebar (unless the user allowlisted it), so probing it would
|
||||
// leak nothing the tab could use.
|
||||
if (isLoopbackHostname(parsed.hostname)) {
|
||||
throw new SidebarError('bad-request', 'local addresses are not probed', 400)
|
||||
const prefs = getSettings()?.get()?.value as SidebarPrefs | undefined
|
||||
const allowlist = typeof prefs?.browserAllowedLoopback === 'string' ? prefs.browserAllowedLoopback : ''
|
||||
const allowed = allowlist.trim() !== ''
|
||||
&& parseLoopbackAllowlist(allowlist)(parsed.hostname, parsed.port)
|
||||
if (!allowed) {
|
||||
throw new SidebarError('bad-request', 'local addresses are not probed', 400)
|
||||
}
|
||||
}
|
||||
const controller = new AbortController()
|
||||
const timer = setTimeout(() => controller.abort(), 8000)
|
||||
@@ -501,6 +528,17 @@ function buildApi(
|
||||
if (response.status === 405 || response.status === 501) {
|
||||
response = await fetch(parsed, { method: 'GET', redirect: 'follow', signal: controller.signal })
|
||||
}
|
||||
// Some servers (e.g. aliyun consoles) answer HEAD without the
|
||||
// X-Frame-Options / CSP headers that only their GET response
|
||||
// carries. Without those signals the embeddability check below
|
||||
// would wrongly report the site as embeddable and the plain iframe
|
||||
// would surface the browser's misleading "refused to connect".
|
||||
// Retry once as GET when both signals are absent (body discarded).
|
||||
const hasEmbedSignals = response.headers.get('content-security-policy') !== null
|
||||
|| response.headers.get('x-frame-options') !== null
|
||||
if (!hasEmbedSignals && response.status !== 405 && response.status !== 501) {
|
||||
response = await fetch(parsed, { method: 'GET', redirect: 'follow', signal: controller.signal })
|
||||
}
|
||||
const csp = response.headers.get('content-security-policy')
|
||||
const frameAncestors = extractFrameAncestors(csp)
|
||||
const xFrameOptions = response.headers.get('x-frame-options')
|
||||
|
||||
@@ -175,6 +175,16 @@ export interface SidebarPrefs {
|
||||
* tab's own enable switch.
|
||||
*/
|
||||
browserInterceptHttps: boolean
|
||||
/**
|
||||
* Comma-separated allowlist of local (loopback) authorities the browser
|
||||
* tab may navigate to — `localhost`, `127.0.0.1`, `127.0.0.1:5174`, or
|
||||
* host:port pairs. Empty by default: loopback addresses stay blocked so a
|
||||
* browsed page cannot probe local services. Each entry is either a bare
|
||||
* hostname (all ports) or host:port; the GUI's own origin is always
|
||||
* allowed regardless. The iframe sandbox still renders allowed local
|
||||
* pages in an opaque origin, exactly like any other site.
|
||||
*/
|
||||
browserAllowedLoopback: string
|
||||
/**
|
||||
* Per-tab enable switches, keyed by tab descriptor id (`'explorer'`,
|
||||
* `'my-plugin:db'`). An ABSENT key means enabled — only an explicit
|
||||
@@ -247,6 +257,7 @@ export const SIDEBAR_PREFS_DEFAULTS: SidebarPrefs = {
|
||||
browserInterceptLinks: true,
|
||||
browserInterceptHttp: true,
|
||||
browserInterceptHttps: false,
|
||||
browserAllowedLoopback: '',
|
||||
tabsEnabled: {},
|
||||
viewersEnabled: {},
|
||||
pluginSettings: {},
|
||||
|
||||
Reference in New Issue
Block a user