Reduced permissions in the manifest

This commit is contained in:
2026-08-26 17:59:24 +02:00
parent 27f26e615a
commit 7e9bf81bd1
6 changed files with 70 additions and 11 deletions
+6
View File
@@ -1,5 +1,11 @@
# Chat Log # Chat Log
### User
Implement SA-006 permission changes: remove `<all_urls>` 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 ## 2026-08-24
### User ### User
+1
View File
@@ -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. 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. 162. The authenticated session text and sign-in block are still shown together.
163. Use the VIBE directory to log interactions. 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 ## Future entries
Binary file not shown.
+4 -4
View File
@@ -6,11 +6,11 @@
"default_locale": "en-US", "default_locale": "en-US",
"permissions": [ "permissions": [
"activeTab", "activeTab",
"storage", "storage"
"tabs"
], ],
"host_permissions": [ "optional_permissions": [
"<all_urls>" "http://*/*",
"https://*/*"
], ],
"action": { "action": {
"default_title": "__MSG_extensionName__", "default_title": "__MSG_extensionName__",
+38 -4
View File
@@ -13,6 +13,34 @@ const signOutButton = document.getElementById('sign-out');
const t = window.linklogI18n; 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) { function setStatus(message, isError = false) {
statusEl.textContent = message; statusEl.textContent = message;
statusEl.classList.remove('hidden'); statusEl.classList.remove('hidden');
@@ -25,7 +53,7 @@ async function loadSettings() {
backendUrlInput.value = settings.backendUrl || ''; backendUrlInput.value = settings.backendUrl || '';
emailInput.value = settings.email || ''; emailInput.value = settings.email || '';
if (settings.accessToken && settings.backendUrl) { if (settings.accessToken && settings.backendUrl && await hasBackendPermission(settings.backendUrl)) {
try { try {
const response = await fetch( const response = await fetch(
`${settings.backendUrl}/api/auth/me`, `${settings.backendUrl}/api/auth/me`,
@@ -61,7 +89,7 @@ async function clearSession() {
async function saveSettingsAndLogin(event) { async function saveSettingsAndLogin(event) {
event.preventDefault(); event.preventDefault();
const backendUrl = backendUrlInput.value.trim(); const backendUrl = normalizeBackendOrigin(backendUrlInput.value.trim());
const email = emailInput.value.trim(); const email = emailInput.value.trim();
const password = passwordInput.value; const password = passwordInput.value;
const otp = otpInput.value.trim(); const otp = otpInput.value.trim();
@@ -71,6 +99,11 @@ async function saveSettingsAndLogin(event) {
return; return;
} }
if (!(await requestBackendPermission(backendUrl))) {
setStatus(t('unableToLogIn'), true);
return;
}
try { try {
const response = await fetch(`${backendUrl}/api/auth/login`, { const response = await fetch(`${backendUrl}/api/auth/login`, {
method: 'POST', method: 'POST',
@@ -104,8 +137,9 @@ async function saveSettingsAndLogin(event) {
async function signOut() { async function signOut() {
const settings = await browser.storage.local.get(['accessToken']); const settings = await browser.storage.local.get(['accessToken']);
if (settings.accessToken) { const backendUrl = normalizeBackendOrigin(backendUrlInput.value.trim());
await fetch(`${backendUrlInput.value.trim()}/api/auth/logout`, { if (settings.accessToken && await hasBackendPermission(backendUrl)) {
await fetch(`${backendUrl}/api/auth/logout`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token: settings.accessToken }), body: JSON.stringify({ token: settings.accessToken }),
+21 -3
View File
@@ -16,6 +16,24 @@ const authSession = document.getElementById('auth-session');
const t = window.linklogI18n; 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) { function setStatus(message, isError = false) {
statusEl.textContent = message; statusEl.textContent = message;
statusEl.classList.remove('hidden'); statusEl.classList.remove('hidden');
@@ -33,7 +51,7 @@ async function getSettings() {
} }
async function validateSession(settings) { async function validateSession(settings) {
if (!settings.backendUrl || !settings.accessToken) return null; if (!settings.backendUrl || !settings.accessToken || !(await hasBackendPermission(settings.backendUrl))) return null;
try { try {
const response = await fetch(`${settings.backendUrl}/api/auth/me`, { const response = await fetch(`${settings.backendUrl}/api/auth/me`, {
headers: {'Authorization': `Bearer ${settings.accessToken}`}, headers: {'Authorization': `Bearer ${settings.accessToken}`},
@@ -157,7 +175,7 @@ async function populateCurrentTab() {
async function checkExistingLink() { async function checkExistingLink() {
const settings = await getSettings(); 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 { try {
const response = await fetch(`${settings.backendUrl}/api/links/check?${new URLSearchParams({ const response = await fetch(`${settings.backendUrl}/api/links/check?${new URLSearchParams({
title: titleInput.value, title: titleInput.value,
@@ -181,7 +199,7 @@ async function handleSubmit(event) {
const token = settings.accessToken; const token = settings.accessToken;
const backendUrl = settings.backendUrl; const backendUrl = settings.backendUrl;
if (!token || !backendUrl) { if (!token || !backendUrl || !(await hasBackendPermission(backendUrl))) {
setStatus(t('configureAndLogIn'), true); setStatus(t('configureAndLogIn'), true);
browser.runtime.openOptionsPage(); browser.runtime.openOptionsPage();
return; return;