Security hardening and codebase cleanup
Security fixes: - Replace Caesar cipher with electron.safeStorage for account credentials - Validate shell.openExternal URLs (allow only http/https protocols) - Remove rejectUnauthorized:false from all HTTPS calls - Add redirect domain validation to auto-updater - Fix XSS in matchmaker popup (innerHTML → textContent/createTextNode) - Add IPC config key whitelist to prevent arbitrary store access - Credentials never sent to renderer; decrypted on-demand via IPC Optimizations and cleanup: - Simplify onBeforeRequest from double-registration to single handler - Lazy-init matchmaker popup DOM (defer until first use) - Invalidate game config cache immediately on write, not on flush - Remove unused STANDARD_ASSET_RE and KeybindDef exports - Deduplicate Keybind type (import from config.ts) - Replace custom hasOwn wrapper with Object.hasOwn Bug fix: - Stop Krunker's global keydown handler from eating keystrokes in alt manager input fields (stopPropagation) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+62
-35
@@ -39,39 +39,57 @@ function secondsToTimestring(num: number): string {
|
||||
return `${minutes}m ${seconds}s`;
|
||||
}
|
||||
|
||||
// ── Popup DOM (created once, reused) ──
|
||||
// ── Popup DOM (lazy-initialized on first use) ──
|
||||
const POPUP_ID = 'matchmakerPopupContainer';
|
||||
const popupElement = document.createElement('div');
|
||||
popupElement.id = POPUP_ID;
|
||||
|
||||
const popupTitle = document.createElement('div');
|
||||
popupTitle.id = 'matchmakerPopupTitle';
|
||||
popupElement.appendChild(popupTitle);
|
||||
interface PopupDOM {
|
||||
element: HTMLDivElement;
|
||||
title: HTMLDivElement;
|
||||
description: HTMLDivElement;
|
||||
confirmBtn: HTMLDivElement;
|
||||
cancelBtn: HTMLDivElement;
|
||||
}
|
||||
|
||||
const popupDescription = document.createElement('div');
|
||||
popupDescription.id = 'matchmakerPopupDescription';
|
||||
popupElement.appendChild(popupDescription);
|
||||
let _popup: PopupDOM | null = null;
|
||||
|
||||
const popupOptions = document.createElement('div');
|
||||
popupOptions.id = 'matchmakerPopupOptions';
|
||||
function getPopup(): PopupDOM {
|
||||
if (_popup) return _popup;
|
||||
|
||||
const popupConfirmBtn = document.createElement('div');
|
||||
popupConfirmBtn.id = 'matchmakerConfirmButton';
|
||||
popupConfirmBtn.className = 'matchmakerPopupButton bigShadowT';
|
||||
popupConfirmBtn.textContent = 'Join';
|
||||
popupConfirmBtn.setAttribute('onmouseenter', 'playTick()');
|
||||
popupConfirmBtn.addEventListener('click', () => decideMatchmakerDecision(true));
|
||||
const element = document.createElement('div');
|
||||
element.id = POPUP_ID;
|
||||
|
||||
const popupCancelBtn = document.createElement('div');
|
||||
popupCancelBtn.id = 'matchmakerCancelButton';
|
||||
popupCancelBtn.className = 'matchmakerPopupButton bigShadowT';
|
||||
popupCancelBtn.textContent = 'Cancel';
|
||||
popupCancelBtn.setAttribute('onmouseenter', 'playTick()');
|
||||
popupCancelBtn.addEventListener('click', () => decideMatchmakerDecision(false));
|
||||
const title = document.createElement('div');
|
||||
title.id = 'matchmakerPopupTitle';
|
||||
element.appendChild(title);
|
||||
|
||||
popupOptions.appendChild(popupConfirmBtn);
|
||||
popupOptions.appendChild(popupCancelBtn);
|
||||
popupElement.appendChild(popupOptions);
|
||||
const description = document.createElement('div');
|
||||
description.id = 'matchmakerPopupDescription';
|
||||
element.appendChild(description);
|
||||
|
||||
const options = document.createElement('div');
|
||||
options.id = 'matchmakerPopupOptions';
|
||||
|
||||
const confirmBtn = document.createElement('div');
|
||||
confirmBtn.id = 'matchmakerConfirmButton';
|
||||
confirmBtn.className = 'matchmakerPopupButton bigShadowT';
|
||||
confirmBtn.textContent = 'Join';
|
||||
confirmBtn.addEventListener('mouseenter', () => { (window as any).playTick?.(); });
|
||||
confirmBtn.addEventListener('click', () => decideMatchmakerDecision(true));
|
||||
|
||||
const cancelBtn = document.createElement('div');
|
||||
cancelBtn.id = 'matchmakerCancelButton';
|
||||
cancelBtn.className = 'matchmakerPopupButton bigShadowT';
|
||||
cancelBtn.textContent = 'Cancel';
|
||||
cancelBtn.addEventListener('mouseenter', () => { (window as any).playTick?.(); });
|
||||
cancelBtn.addEventListener('click', () => decideMatchmakerDecision(false));
|
||||
|
||||
options.appendChild(confirmBtn);
|
||||
options.appendChild(cancelBtn);
|
||||
element.appendChild(options);
|
||||
|
||||
_popup = { element, title, description, confirmBtn, cancelBtn };
|
||||
return _popup;
|
||||
}
|
||||
|
||||
// ── State ──
|
||||
let currentMatch = '';
|
||||
@@ -86,7 +104,8 @@ function decideMatchmakerDecision(accept: boolean): void {
|
||||
if (accept && currentMatch !== 'none') {
|
||||
window.location.href = `https://krunker.io/?game=${currentMatch}`;
|
||||
} else {
|
||||
if (popupElement.parentNode) popupElement.remove();
|
||||
const popup = getPopup();
|
||||
if (popup.element.parentNode) popup.element.remove();
|
||||
if (currentMatch === 'none' && openServerBrowser && typeof w.openServerWindow === 'function') {
|
||||
w.openServerWindow(0);
|
||||
}
|
||||
@@ -112,24 +131,32 @@ function handleMatchmakerBind(event: KeyboardEvent): void {
|
||||
}
|
||||
|
||||
function createFetchedGamePopup(game: MatchmakerGame): void {
|
||||
const popup = getPopup();
|
||||
const mapIdx = MAP_ICON_INDICES.indexOf(game.map);
|
||||
popupElement.style.backgroundImage = `url(https://assets.krunker.io/img/maps/map_${mapIdx >= 0 ? mapIdx : 0}.png)`;
|
||||
popup.element.style.backgroundImage = `url(https://assets.krunker.io/img/maps/map_${mapIdx >= 0 ? mapIdx : 0}.png)`;
|
||||
|
||||
currentMatch = game.gameID;
|
||||
if (game.gameID === 'none') {
|
||||
popupTitle.innerText = 'No Games Found...';
|
||||
popupDescription.innerHTML = 'Check the server browser to see other lobbies.';
|
||||
popupConfirmBtn.style.display = 'none';
|
||||
popup.title.textContent = 'No Games Found...';
|
||||
popup.description.textContent = 'Check the server browser to see other lobbies.';
|
||||
popup.confirmBtn.style.display = 'none';
|
||||
} else {
|
||||
popupTitle.innerText = 'Game Found!';
|
||||
popup.title.textContent = 'Game Found!';
|
||||
const regionName = MATCHMAKER_REGION_NAMES[game.region] ?? 'Unknown Region';
|
||||
popupDescription.innerHTML = `${game.gamemode} on ${game.map} (${regionName})<br/>${game.playerCount}/${game.playerLimit} Players, ${secondsToTimestring(game.remainingTime)} Left`;
|
||||
popupConfirmBtn.style.display = 'block';
|
||||
popup.description.textContent = '';
|
||||
popup.description.appendChild(document.createTextNode(
|
||||
`${game.gamemode} on ${game.map} (${regionName})`
|
||||
));
|
||||
popup.description.appendChild(document.createElement('br'));
|
||||
popup.description.appendChild(document.createTextNode(
|
||||
`${game.playerCount}/${game.playerLimit} Players, ${secondsToTimestring(game.remainingTime)} Left`
|
||||
));
|
||||
popup.confirmBtn.style.display = 'block';
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', handleMatchmakerBind, true);
|
||||
const uiBase = document.getElementById('uiBase');
|
||||
if (uiBase) uiBase.appendChild(popupElement);
|
||||
if (uiBase) uiBase.appendChild(popup.element);
|
||||
}
|
||||
|
||||
export async function fetchGame(mmConfig: MatchmakerConfig, _con?: SavedConsole): Promise<void> {
|
||||
|
||||
Reference in New Issue
Block a user