Reduced permissions in the manifest
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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.
@@ -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
@@ -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
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user