mirror of
https://github.com/avinal/sciezka.git
synced 2026-07-03 23:30:09 +05:30
fix: postMessage security hardening and highlight grouping
Add per-session nonce to all postMessage exchanges between content script and iframe, use targeted origin instead of wildcard, add explicit CSP to manifest. Group consecutive matched characters into single mark elements to fix visual spacing. Assisted-by: Claude Code Signed-off-by: Avinal Kumar <avinal.xlvii@gmail.com>
This commit is contained in:
@@ -13,6 +13,9 @@
|
|||||||
"sessions",
|
"sessions",
|
||||||
"storage"
|
"storage"
|
||||||
],
|
],
|
||||||
|
"content_security_policy": {
|
||||||
|
"extension_pages": "script-src 'self'; object-src 'self'; img-src 'self' data:;"
|
||||||
|
},
|
||||||
"background": {
|
"background": {
|
||||||
"scripts": ["dist/background.js"]
|
"scripts": ["dist/background.js"]
|
||||||
},
|
},
|
||||||
|
|||||||
+9
-3
@@ -1,7 +1,10 @@
|
|||||||
import type { Message } from "./types";
|
import type { Message } from "./types";
|
||||||
|
|
||||||
|
const EXTENSION_ORIGIN = chrome.runtime.getURL("").slice(0, -1);
|
||||||
|
|
||||||
let overlay: HTMLDivElement | null = null;
|
let overlay: HTMLDivElement | null = null;
|
||||||
let iframe: HTMLIFrameElement | null = null;
|
let iframe: HTMLIFrameElement | null = null;
|
||||||
|
let messageNonce: string | null = null;
|
||||||
|
|
||||||
function createOverlay(): void {
|
function createOverlay(): void {
|
||||||
overlay = document.createElement("div");
|
overlay = document.createElement("div");
|
||||||
@@ -21,8 +24,9 @@ function createOverlay(): void {
|
|||||||
transition: background 0.15s ease;
|
transition: background 0.15s ease;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
messageNonce = crypto.randomUUID();
|
||||||
iframe = document.createElement("iframe");
|
iframe = document.createElement("iframe");
|
||||||
iframe.src = chrome.runtime.getURL("sciezka.html");
|
iframe.src = chrome.runtime.getURL("sciezka.html") + "#" + messageNonce;
|
||||||
iframe.style.cssText = `
|
iframe.style.cssText = `
|
||||||
width: 620px;
|
width: 620px;
|
||||||
height: 60px;
|
height: 60px;
|
||||||
@@ -81,6 +85,7 @@ document.addEventListener("keydown", (e) => {
|
|||||||
|
|
||||||
window.addEventListener("message", (event) => {
|
window.addEventListener("message", (event) => {
|
||||||
if (event.source !== iframe?.contentWindow) return;
|
if (event.source !== iframe?.contentWindow) return;
|
||||||
|
if (!event.data?._nonce || event.data._nonce !== messageNonce) return;
|
||||||
|
|
||||||
const data = event.data as Message;
|
const data = event.data as Message;
|
||||||
if (data.type === "closeSaka") {
|
if (data.type === "closeSaka") {
|
||||||
@@ -94,8 +99,9 @@ window.addEventListener("message", (event) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (data.type === "search" || data.type === "action") {
|
if (data.type === "search" || data.type === "action") {
|
||||||
chrome.runtime.sendMessage(data, (response) => {
|
chrome.runtime.sendMessage(data, (response: unknown) => {
|
||||||
iframe?.contentWindow?.postMessage(response, "*");
|
const msg = typeof response === "object" && response ? { ...(response as Record<string, unknown>), _nonce: messageNonce } : response;
|
||||||
|
iframe?.contentWindow?.postMessage(msg, EXTENSION_ORIGIN);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
+21
-13
@@ -11,6 +11,8 @@ const MODE_LABELS: Record<SearchMode, string> = {
|
|||||||
};
|
};
|
||||||
const METHODS: SearchMethod[] = ["fuzzy", "fulltext", "prefix"];
|
const METHODS: SearchMethod[] = ["fuzzy", "fulltext", "prefix"];
|
||||||
|
|
||||||
|
const MESSAGE_NONCE = location.hash.slice(1);
|
||||||
|
|
||||||
let currentMode: SearchMode = "tabs";
|
let currentMode: SearchMode = "tabs";
|
||||||
let currentMethod: SearchMethod = "fuzzy";
|
let currentMethod: SearchMethod = "fuzzy";
|
||||||
let results: SearchResult[] = [];
|
let results: SearchResult[] = [];
|
||||||
@@ -25,17 +27,17 @@ const root = document.getElementById("sciezka-root") as HTMLDivElement;
|
|||||||
|
|
||||||
function notifyResize(): void {
|
function notifyResize(): void {
|
||||||
const height = Math.min(root.scrollHeight, 520);
|
const height = Math.min(root.scrollHeight, 520);
|
||||||
window.parent.postMessage({ type: "resize", height }, "*");
|
window.parent.postMessage({ type: "resize", _nonce: MESSAGE_NONCE, height }, "*");
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendMessage(msg: Message): Promise<unknown> {
|
function sendMessage(msg: Message): Promise<unknown> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
window.parent.postMessage(msg, "*");
|
window.parent.postMessage({ ...msg, _nonce: MESSAGE_NONCE }, "*");
|
||||||
const handler = (event: MessageEvent) => {
|
const handler = (event: MessageEvent) => {
|
||||||
if (event.source === window.parent) {
|
if (event.source !== window.parent) return;
|
||||||
window.removeEventListener("message", handler);
|
if (!event.data?._nonce || event.data._nonce !== MESSAGE_NONCE) return;
|
||||||
resolve(event.data);
|
window.removeEventListener("message", handler);
|
||||||
}
|
resolve(event.data);
|
||||||
};
|
};
|
||||||
window.addEventListener("message", handler);
|
window.addEventListener("message", handler);
|
||||||
});
|
});
|
||||||
@@ -62,11 +64,17 @@ function renderMethodBadge(): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function highlightText(text: string, positions: number[], offset: number): string {
|
function highlightText(text: string, positions: number[], offset: number): string {
|
||||||
const chars = text.split("");
|
|
||||||
const posSet = new Set(positions.map((p) => p - offset).filter((p) => p >= 0 && p < text.length));
|
const posSet = new Set(positions.map((p) => p - offset).filter((p) => p >= 0 && p < text.length));
|
||||||
return chars
|
let result = "";
|
||||||
.map((c, i) => (posSet.has(i) ? `<mark>${escapeHtml(c)}</mark>` : escapeHtml(c)))
|
let inMark = false;
|
||||||
.join("");
|
for (let i = 0; i < text.length; i++) {
|
||||||
|
const matched = posSet.has(i);
|
||||||
|
if (matched && !inMark) { result += "<mark>"; inMark = true; }
|
||||||
|
if (!matched && inMark) { result += "</mark>"; inMark = false; }
|
||||||
|
result += escapeHtml(text[i]);
|
||||||
|
}
|
||||||
|
if (inMark) result += "</mark>";
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function escapeHtml(s: string): string {
|
function escapeHtml(s: string): string {
|
||||||
@@ -157,7 +165,7 @@ function activateResult(index: number): void {
|
|||||||
: { type: "action", action: "open", id: item.url };
|
: { type: "action", action: "open", id: item.url };
|
||||||
|
|
||||||
sendMessage(msg);
|
sendMessage(msg);
|
||||||
window.parent.postMessage({ type: "closeSaka" }, "*");
|
window.parent.postMessage({ type: "closeSaka", _nonce: MESSAGE_NONCE }, "*");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function doSearch(): Promise<void> {
|
async function doSearch(): Promise<void> {
|
||||||
@@ -183,7 +191,7 @@ input.addEventListener("input", () => {
|
|||||||
|
|
||||||
document.addEventListener("keydown", (e) => {
|
document.addEventListener("keydown", (e) => {
|
||||||
if (e.key === "Escape") {
|
if (e.key === "Escape") {
|
||||||
window.parent.postMessage({ type: "closeSaka" }, "*");
|
window.parent.postMessage({ type: "closeSaka", _nonce: MESSAGE_NONCE }, "*");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,7 +217,7 @@ document.addEventListener("keydown", (e) => {
|
|||||||
const result = results[selectedIndex];
|
const result = results[selectedIndex];
|
||||||
if (result && result.item.type !== "tabs") {
|
if (result && result.item.type !== "tabs") {
|
||||||
sendMessage({ type: "action", action: "open", id: result.item.url, newTab: true });
|
sendMessage({ type: "action", action: "open", id: result.item.url, newTab: true });
|
||||||
window.parent.postMessage({ type: "closeSaka" }, "*");
|
window.parent.postMessage({ type: "closeSaka", _nonce: MESSAGE_NONCE }, "*");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
activateResult(selectedIndex);
|
activateResult(selectedIndex);
|
||||||
|
|||||||
Reference in New Issue
Block a user