From 7e9bf81bd194ec3a4ace8295913a4b76cc8d0c3b Mon Sep 17 00:00:00 2001 From: Kolkman Date: Wed, 26 Aug 2026 17:59:24 +0200 Subject: [PATCH] Reduced permissions in the manifest --- VIBE/CHAT_LOG.md | 6 +++++ VIBE/PROMPTS.md | 1 + XPI/unsigned/LinkLog-0.1.0.xpi | Bin 49318 -> 49675 bytes webextension/manifest.json | 8 +++---- webextension/options.js | 42 +++++++++++++++++++++++++++++---- webextension/popup.js | 24 ++++++++++++++++--- 6 files changed, 70 insertions(+), 11 deletions(-) diff --git a/VIBE/CHAT_LOG.md b/VIBE/CHAT_LOG.md index 7221178..0d9ca70 100644 --- a/VIBE/CHAT_LOG.md +++ b/VIBE/CHAT_LOG.md @@ -1,5 +1,11 @@ # Chat Log +### User +Implement SA-006 permission changes: remove `` and unnecessary `tabs`, request exact access to the configured self-hosted backend, and keep page capture behind `activeTab`. + +### Assistant outcome +Updated the Firefox extension manifest to use `activeTab` and `storage` only, with Firefox-compatible optional HTTP/HTTPS host permissions. The options login page now normalizes the backend to an origin and requests only that origin during the user-initiated login. The popup checks that exact grant before session validation or link submission, so it does not perform backend calls without permission. + ## 2026-08-24 ### User diff --git a/VIBE/PROMPTS.md b/VIBE/PROMPTS.md index b98e905..3aa52a0 100644 --- a/VIBE/PROMPTS.md +++ b/VIBE/PROMPTS.md @@ -181,6 +181,7 @@ 161. The popup still shows the sign-in block even though the authenticated session text is displayed; show the block only when signed out. 162. The authenticated session text and sign-in block are still shown together. 163. Use the VIBE directory to log interactions. +164. Implement SA-006: remove broad Firefox extension host access and unnecessary tabs permission, request exact configured backend origin access, and keep page capture behind activeTab. ## Future entries diff --git a/XPI/unsigned/LinkLog-0.1.0.xpi b/XPI/unsigned/LinkLog-0.1.0.xpi index 648866ecd43fcc52439c59cda6e815cee6204a82..69c179e002b8bc4e517fdba208585f69788bb09d 100644 GIT binary patch delta 4947 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

jPM=Hi#1`@gybGsex<8fRSa9Xrv(~%N( z#LFr8h%{vO05q1A&_R9aiq6jYt&c1zNbbWxVyg#4x_l!9!S)4NB{Gk3N;rO_9sC?8 z&z;=0#H3o@oCpwzACvzadg;$1rgrAJA@KzwQYiTs;#}DF8@XKeH)36O^cyjG{WlW( zdgM1^)9^QfX$T{x`k!$Fh8rt{A!_5nxL~;{E)f6#(%a2j%s&t_j00m}9Q0JI7c+)G zS7C%QW*BQkf#(ANgtIqFR9Xr%fV+Y*#c@--k@znLmHz`P!f}JjF`YOgP&kGOZv;xm z!12=1f48=Os$a}KfWnBmXyAVuU1EUnqS3|l;}t;whP3r2h!kVdDk=Hz!tb9V>#4v$ z%D@W=wPVaZJQwC;D>nfy3v<*e%KKZO*oL+mN(=zRbihU3_)h_;9L)7L0fIlb@%`pT z#~Bn9U2wT*0f6m)xY&FEqf*F8{P!_`Xz!LaSxsN?<^RR6E5Q)8^Zr*`^tU!A_K9}m z1=sOk+{IS_V2|{6c0{`Ri^KgebnOiPOv;yLx5V*cqbtq-C$&`vU{DQzCAHkncPa19 H?+g47i35Lc diff --git a/webextension/manifest.json b/webextension/manifest.json index e2d42b5..3e9f104 100644 --- a/webextension/manifest.json +++ b/webextension/manifest.json @@ -6,11 +6,11 @@ "default_locale": "en-US", "permissions": [ "activeTab", - "storage", - "tabs" + "storage" ], - "host_permissions": [ - "" + "optional_permissions": [ + "http://*/*", + "https://*/*" ], "action": { "default_title": "__MSG_extensionName__", diff --git a/webextension/options.js b/webextension/options.js index 12480df..0948442 100644 --- a/webextension/options.js +++ b/webextension/options.js @@ -13,6 +13,34 @@ const signOutButton = document.getElementById('sign-out'); const t = window.linklogI18n; +function normalizeBackendOrigin(backendUrl) { + try { + const url = new URL(backendUrl); + if (!['http:', 'https:'].includes(url.protocol)) { + return null; + } + return url.origin; + } catch (error) { + return null; + } +} + +async function hasBackendPermission(backendUrl) { + const origin = normalizeBackendOrigin(backendUrl); + if (!origin) return false; + return browser.permissions.contains({origins: [`${origin}/*`]}); +} + +async function requestBackendPermission(backendUrl) { + const origin = normalizeBackendOrigin(backendUrl); + if (!origin) return false; + try { + return await browser.permissions.request({origins: [`${origin}/*`]}); + } catch (error) { + return false; + } +} + function setStatus(message, isError = false) { statusEl.textContent = message; statusEl.classList.remove('hidden'); @@ -25,7 +53,7 @@ async function loadSettings() { backendUrlInput.value = settings.backendUrl || ''; emailInput.value = settings.email || ''; - if (settings.accessToken && settings.backendUrl) { + if (settings.accessToken && settings.backendUrl && await hasBackendPermission(settings.backendUrl)) { try { const response = await fetch( `${settings.backendUrl}/api/auth/me`, @@ -61,7 +89,7 @@ async function clearSession() { async function saveSettingsAndLogin(event) { event.preventDefault(); - const backendUrl = backendUrlInput.value.trim(); + const backendUrl = normalizeBackendOrigin(backendUrlInput.value.trim()); const email = emailInput.value.trim(); const password = passwordInput.value; const otp = otpInput.value.trim(); @@ -71,6 +99,11 @@ async function saveSettingsAndLogin(event) { return; } + if (!(await requestBackendPermission(backendUrl))) { + setStatus(t('unableToLogIn'), true); + return; + } + try { const response = await fetch(`${backendUrl}/api/auth/login`, { method: 'POST', @@ -104,8 +137,9 @@ async function saveSettingsAndLogin(event) { async function signOut() { const settings = await browser.storage.local.get(['accessToken']); - if (settings.accessToken) { - await fetch(`${backendUrlInput.value.trim()}/api/auth/logout`, { + const backendUrl = normalizeBackendOrigin(backendUrlInput.value.trim()); + if (settings.accessToken && await hasBackendPermission(backendUrl)) { + await fetch(`${backendUrl}/api/auth/logout`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token: settings.accessToken }), diff --git a/webextension/popup.js b/webextension/popup.js index 3a538a4..97761ac 100644 --- a/webextension/popup.js +++ b/webextension/popup.js @@ -16,6 +16,24 @@ const authSession = document.getElementById('auth-session'); const t = window.linklogI18n; +function normalizeBackendOrigin(backendUrl) { + try { + const url = new URL(backendUrl); + if (!['http:', 'https:'].includes(url.protocol)) { + return null; + } + return url.origin; + } catch (error) { + return null; + } +} + +async function hasBackendPermission(backendUrl) { + const origin = normalizeBackendOrigin(backendUrl); + if (!origin) return false; + return browser.permissions.contains({origins: [`${origin}/*`]}); +} + function setStatus(message, isError = false) { statusEl.textContent = message; statusEl.classList.remove('hidden'); @@ -33,7 +51,7 @@ async function getSettings() { } async function validateSession(settings) { - if (!settings.backendUrl || !settings.accessToken) return null; + if (!settings.backendUrl || !settings.accessToken || !(await hasBackendPermission(settings.backendUrl))) return null; try { const response = await fetch(`${settings.backendUrl}/api/auth/me`, { headers: {'Authorization': `Bearer ${settings.accessToken}`}, @@ -157,7 +175,7 @@ async function populateCurrentTab() { async function checkExistingLink() { const settings = await getSettings(); - if (!settings.backendUrl || !settings.accessToken || !titleInput.value || !urlInput.value) return; + if (!settings.backendUrl || !settings.accessToken || !(await hasBackendPermission(settings.backendUrl)) || !titleInput.value || !urlInput.value) return; try { const response = await fetch(`${settings.backendUrl}/api/links/check?${new URLSearchParams({ title: titleInput.value, @@ -181,7 +199,7 @@ async function handleSubmit(event) { const token = settings.accessToken; const backendUrl = settings.backendUrl; - if (!token || !backendUrl) { + if (!token || !backendUrl || !(await hasBackendPermission(backendUrl))) { setStatus(t('configureAndLogIn'), true); browser.runtime.openOptionsPage(); return;