🔒 Fix CSP vulnerability by removing unsafe-inline for styles (#94)

Removes the 'unsafe-inline' directive from the style-src Content
Security Policy to prevent potential Cross-Site Scripting (XSS)
or malicious style injections. Replaces inline style manipulations
(`style.display = "none"`, `style=""`) with class list toggling
using a new `.d-none` utility class.

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
This commit is contained in:
Horizontal42 2026-08-12 22:08:05 +03:00 • committed by GitHub
parent b559c94ffa
commit c7d3cc5493
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 9 additions and 8 deletions

View file

@ -30,7 +30,7 @@
</div> </div>
<div id="statusbar"> <div id="statusbar">
<span id="rates-info" title=""></span> <span id="rates-info" title=""></span>
<span id="market-info" title="" style="display:none"></span> <span id="market-info" title="" class="d-none"></span>
<span class="spacer"></span> <span class="spacer"></span>
<div id="export-wrap"> <div id="export-wrap">
<button id="export-btn" title="Export" aria-label="Export">⤓</button> <button id="export-btn" title="Export" aria-label="Export">⤓</button>

View file

@ -23,7 +23,7 @@
} }
], ],
"security": { "security": {
"csp": "default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; connect-src ipc: http://ipc.localhost https://open.er-api.com https://api.coingecko.com https://query1.finance.yahoo.com https://query2.finance.yahoo.com https://api.frankfurter.dev" "csp": "default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self'; img-src 'self' data:; font-src 'self' data:; connect-src ipc: http://ipc.localhost https://open.er-api.com https://api.coingecko.com https://query1.finance.yahoo.com https://query2.finance.yahoo.com https://api.frankfurter.dev"
} }
}, },
"bundle": { "bundle": {

View file

@ -596,7 +596,7 @@ function applyAllRates(): void {
async function refreshMarket(force = false): Promise<void> { async function refreshMarket(force = false): Promise<void> {
if (!isTauri()) return; if (!isTauri()) return;
const info = $("#market-info") as HTMLElement; const info = $("#market-info") as HTMLElement;
info.style.display = ""; info.classList.remove("d-none");
info.classList.add("spin"); info.classList.add("spin");
info.textContent = t("market"); info.textContent = t("market");
const prices = await fetchMarketData(MARKET_SYMBOLS); const prices = await fetchMarketData(MARKET_SYMBOLS);
@ -636,8 +636,8 @@ async function fetchNeededHistoricalRates(text: string): Promise<void> {
function renderMarketInfo(): void { function renderMarketInfo(): void {
const el = $("#market-info") as HTMLElement; const el = $("#market-info") as HTMLElement;
if (marketFetchedAt === 0) { el.style.display = "none"; return; } if (marketFetchedAt === 0) { el.classList.add("d-none"); return; }
el.style.display = ""; el.classList.remove("d-none");
el.textContent = `${t("market")}: ${agoText(marketFetchedAt)} ↺`; el.textContent = `${t("market")}: ${agoText(marketFetchedAt)} ↺`;
el.title = t("refreshRates"); el.title = t("refreshRates");
} }
@ -661,9 +661,9 @@ function renderTotal(): void {
lbl.textContent = label; lbl.textContent = label;
line.append(lbl, document.createTextNode(total)); line.append(lbl, document.createTextNode(total));
line.dataset.value = total; line.dataset.value = total;
line.style.display = ""; line.classList.remove("d-none");
} else { } else {
line.style.display = "none"; line.classList.add("d-none");
delete line.dataset.value; delete line.dataset.value;
} }
} }
@ -872,7 +872,7 @@ async function bindWindowControls(): Promise<void> {
$("#win-min").addEventListener("click", () => void win.minimize()); $("#win-min").addEventListener("click", () => void win.minimize());
$("#win-close").addEventListener("click", () => void win.hide()); $("#win-close").addEventListener("click", () => void win.hide());
} else { } else {
$("#win-controls").style.display = "none"; $("#win-controls").classList.add("d-none");
} }
} }

View file

@ -654,3 +654,4 @@ body {
} }
.cm-tooltip-autocomplete ul li { font-family: var(--mono); font-size: 13px; color: var(--fg); } .cm-tooltip-autocomplete ul li { font-family: var(--mono); font-size: 13px; color: var(--fg); }
.cm-tooltip-autocomplete ul li[aria-selected] { background: var(--sel); color: var(--fg); } .cm-tooltip-autocomplete ul li[aria-selected] { background: var(--sel); color: var(--fg); }
.d-none { display: none !important; }