fix: harden extension security and plugin reliability

Address audit findings across background handlers, openers,
plugins, and UI: URL allowlists, XSS reductions, popup lifecycle
fixes, plugin dispose/cleanup, cloud sync hardening, global search
mathjs sandbox, and settings storage fixes.
This commit is contained in:
2026-06-17 10:50:26 +09:30
parent 0e696e0175
commit 8a5424c5a4
70 changed files with 1229 additions and 430 deletions
+42 -3
View File
@@ -16,6 +16,24 @@ import { updateAllColors } from "./colors/Manager";
import { delay } from "@/seqta/utils/delay";
let cachedUserInfo: any = null;
let userInfoCacheListenersAttached = false;
export function invalidateCachedUserInfo(): void {
cachedUserInfo = null;
}
function attachUserInfoCacheInvalidation(): void {
if (userInfoCacheListenersAttached || typeof window === "undefined") return;
userInfoCacheListenersAttached = true;
window.addEventListener("pageshow", (event) => {
if (event.persisted) {
invalidateCachedUserInfo();
}
});
}
attachUserInfoCacheInvalidation();
let LightDarkModeSnakeEggButton = 0;
let sidebarAccessibilityObserver: MutationObserver | null = null;
@@ -25,8 +43,10 @@ let sidebarAccessibilityListenersAttached = false;
/** Marks menu rows that are off-screen in the drill stack (CSS blocks clicks). */
const BSPLUS_SIDEBAR_OFFSCREEN = "bsplus-sidebar-offscreen";
export async function getUserInfo() {
if (cachedUserInfo) return cachedUserInfo;
export async function getUserInfo(options?: { validateSession?: boolean }) {
if (cachedUserInfo && !options?.validateSession) {
return cachedUserInfo;
}
try {
const response = await fetch(`${location.origin}/seqta/student/login`, {
@@ -41,7 +61,26 @@ export async function getUserInfo() {
}),
});
cachedUserInfo = (await response.json()).payload;
if (!response.ok) {
throw new Error(`Failed to get user info: HTTP ${response.status}`);
}
const payload = (await response.json()).payload;
if (
cachedUserInfo &&
options?.validateSession &&
payload?.id != null &&
cachedUserInfo.id != null &&
payload.id !== cachedUserInfo.id
) {
console.warn(
"[BetterSEQTA+] Session user changed; invalidating cached user info",
);
invalidateCachedUserInfo();
}
cachedUserInfo = payload;
return cachedUserInfo;
} catch (error) {
console.error("[BetterSEQTA+] Failed to get user info:", error);
+13 -1
View File
@@ -21,6 +21,7 @@ export async function appendBackgroundToUI() {
}
let lastLoadedId: string | null = null;
let lastBlobUrl: string | null = null;
export async function loadBackground() {
if (!isIndexedDBSupported()) {
@@ -36,6 +37,10 @@ export async function loadBackground() {
backgroundContainer.remove();
}
lastLoadedId = null;
if (lastBlobUrl) {
URL.revokeObjectURL(lastBlobUrl);
lastBlobUrl = null;
}
return;
}
@@ -73,12 +78,19 @@ export async function loadBackground() {
mediaContainer.innerHTML = "";
if (lastBlobUrl) {
URL.revokeObjectURL(lastBlobUrl);
lastBlobUrl = null;
}
const mediaElement =
background.type === "video"
? document.createElement("video")
: document.createElement("img");
mediaElement.src = URL.createObjectURL(background.blob);
const blobUrl = URL.createObjectURL(background.blob);
lastBlobUrl = blobUrl;
mediaElement.src = blobUrl;
mediaElement.classList.add("background");
if (mediaElement instanceof HTMLVideoElement) {