From 3ac46abe1a192f956d597847fe36e3e30942116b Mon Sep 17 00:00:00 2001 From: DanS Date: Mon, 12 Oct 2026 13:22:00 -0500 Subject: [PATCH] fix(security): compile the UI sweep only into dev builds The screenshot sweep and the full UI sweep are dev tools. The full sweep injects demo data into the live wallet state and the real data and config dirs, and the tab sweep screenshots real balances and addresses. Both were compiled into every build: the Settings buttons were reachable in full-node releases, and the DRAGONX_FULL_SWEEP env hook worked in every build, Lite releases included. A default-OFF option alone would fail open: dev and release builds share build/linux (and build/mac), so a release configured without --clean would inherit a cached ON from the last dev configure. - CMake: option(DRAGONX_ENABLE_UI_SWEEP ... OFF), passed to ObsidianDragon and ObsidianDragonTests as a compile definition. - build.sh passes it explicitly: ON in the dev build, OFF in every release configure (Linux, Windows, macOS osxcross and native). - Packaging guard: while the cache says ON, dragonx_release depends on dragonx_release_sweep_guard, which fails, so a hand-configured ON tree cannot package a release. - startSweepImpl is a no-op unless the option is ON (an undefined macro counts as 0, so it fails closed). The enabled branch logs the sentinel "[Sweep] DRAGONX_UI_SWEEP_COMPILED_IN", so a release can be checked with `strings | grep -c DRAGONX_UI_SWEEP_COMPILED_IN` (must be 0). - main.cpp: the DRAGONX_FULL_SWEEP hook is compiled out (autoSweep is false). It must be: with the sweep a no-op, the hook would quit the app once its warm-up frames ran out. - Settings > Node > Debug options: both sweep buttons, open-dir, the seed-demo-chat button and the current-theme checkbox are compiled only into dev builds. The daemon debug categories are unchanged. No i18n change: the existing strings stay in the tables. Verified with an isolated release (DRAGONX_BUILD_ROOT/DRAGONX_RELEASE_ROOT in a scratch dir) whose tree was first configured by hand with ON: build.sh --linux-release without --clean leaves the cache OFF, and the sentinel count is 0 in the built binary, the zip and the AppImage. With the cache set back to ON by hand, --target dragonx_release fails at the guard before compiling anything. Dev trees configured ON keep the sweep (sentinel present; a one-surface DRAGONX_FULL_SWEEP run against the fake daemon captured and exited cleanly). Co-Authored-By: Claude Opus 5.5 --- CMakeLists.txt | 13 +++++++++++++ build.sh | 6 ++++++ src/app_sweep.cpp | 9 +++++++++ src/main.cpp | 6 ++++++ src/ui/pages/settings_page.cpp | 4 ++++ 5 files changed, 38 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 666dab4..832548a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,6 +46,10 @@ option(DRAGONX_BUILD_LITE "Build ObsidianDragonLite variant without full-node fe option(DRAGONX_ENABLE_LITE_BACKEND "Enable real lite wallet backend integration" OFF) option(DRAGONX_ENABLE_CHAT "Enable the HushChat protocol/UI integration" ON) option(DRAGONX_BUILD_FAKE_DAEMON "Build the fake slow dragonxd test stub (tools/fake_dragonxd); never shipped" OFF) +# The screenshot / full UI sweep injects demo data and touches the wallet's data and config dirs, so it is a +# dev tool only. build.sh passes it EXPLICITLY (ON for dev, OFF for every release configure) because build/linux +# and build/mac are shared between dev and release builds; dragonx_release refuses to package while it is ON. +option(DRAGONX_ENABLE_UI_SWEEP "Compile the UI sweep dev tool (never for shipped builds)" OFF) # Embedded fallback copy of res/lang/*.json (xxd -i -> lang_*.h). Needed only by the Windows single-file exe (no res/ beside it); # Linux zip/AppImage and the macOS .app always ship res/lang, which loadLanguage() reads first. if(WIN32) @@ -938,6 +942,7 @@ target_compile_definitions(ObsidianDragon PRIVATE DRAGONX_ENABLE_LITE_BACKEND=$ DRAGONX_ENABLE_CHAT=$ DRAGONX_EMBED_LANG=$ + DRAGONX_ENABLE_UI_SWEEP=$ ) if(WIN32) target_compile_definitions(ObsidianDragon PRIVATE DRAGONX_USE_DX11) @@ -1170,6 +1175,13 @@ foreach(_dragonx_release_dep dragonx-wallet-rebuild copy_langs copy_themes) add_dependencies(dragonx_release ${_dragonx_release_dep}) endif() endforeach() +# Backstop for the UI sweep dev tool: a tree whose cache says ON (e.g. hand-configured) cannot package a release. +if(DRAGONX_ENABLE_UI_SWEEP) + add_custom_target(dragonx_release_sweep_guard + COMMAND ${CMAKE_COMMAND} -E echo "DRAGONX_ENABLE_UI_SWEEP=ON: refusing to package a release" + COMMAND ${CMAKE_COMMAND} -E false) + add_dependencies(dragonx_release dragonx_release_sweep_guard) +endif() # ----------------------------------------------------------------------------- # fake_dragonxd — a deliberately slow, scriptable dragonxd stand-in that reproduces the daemon-stop @@ -1298,6 +1310,7 @@ if(BUILD_TESTING) DRAGONX_ENABLE_EMBEDDED_DAEMON=$ DRAGONX_ENABLE_LITE_BACKEND=$ DRAGONX_EMBED_LANG=$ + DRAGONX_ENABLE_UI_SWEEP=$ DRAGONX_TEST_FIXTURE_DIR="${CMAKE_SOURCE_DIR}/tests/fixtures" ) diff --git a/build.sh b/build.sh index ac781e2..e3b1b1b 100755 --- a/build.sh +++ b/build.sh @@ -349,6 +349,7 @@ build_dev() { -DCMAKE_BUILD_TYPE="$BUILD_TYPE" \ -DCMAKE_CXX_FLAGS_RELEASE="-O3 -DNDEBUG" \ -DDRAGONX_USE_SYSTEM_SDL3=ON \ + -DDRAGONX_ENABLE_UI_SWEEP=ON \ "${CMAKE_LITE_ARGS[@]}" info "Building with $JOBS jobs ..." @@ -385,10 +386,12 @@ build_release_linux() { exit 1 fi info "Configuring ..." + # UI sweep OFF explicitly: build/linux is shared with dev builds, which cache it ON. cmake "$SCRIPT_DIR" \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_CXX_FLAGS_RELEASE="-O3 -DNDEBUG" \ -DDRAGONX_USE_SYSTEM_SDL3=ON \ + -DDRAGONX_ENABLE_UI_SWEEP=OFF \ "${BDB_ARGS[@]}" \ "${CMAKE_LITE_ARGS[@]}" @@ -864,6 +867,7 @@ HDR -DCMAKE_TOOLCHAIN_FILE="$bd/mingw-toolchain.cmake" \ -DCMAKE_BUILD_TYPE=Release \ -DDRAGONX_USE_SYSTEM_SDL3=OFF \ + -DDRAGONX_ENABLE_UI_SWEEP=OFF \ "${BDB_ARGS[@]}" \ "${FT_CMAKE_ARG[@]}" \ "${CMAKE_LITE_ARGS[@]}" @@ -1129,6 +1133,7 @@ TOOLCHAIN -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_CXX_FLAGS_RELEASE="-O3 -DNDEBUG" \ -DDRAGONX_USE_SYSTEM_SDL3=OFF \ + -DDRAGONX_ENABLE_UI_SWEEP=OFF \ -DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 \ ${COMPILER_RT:+-DOSXCROSS_COMPILER_RT="$COMPILER_RT"} \ "${BDB_ARGS[@]}" \ @@ -1163,6 +1168,7 @@ TOOLCHAIN -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_CXX_FLAGS_RELEASE="-O3 -DNDEBUG" \ -DDRAGONX_USE_SYSTEM_SDL3=OFF \ + -DDRAGONX_ENABLE_UI_SWEEP=OFF \ -DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 \ -DCMAKE_OSX_ARCHITECTURES="$MAC_ARCHS" \ "${BDB_ARGS[@]}" \ diff --git a/src/app_sweep.cpp b/src/app_sweep.cpp index 5429bee..fb00581 100644 --- a/src/app_sweep.cpp +++ b/src/app_sweep.cpp @@ -718,7 +718,15 @@ void App::buildSweepCatalog() // ── State machine ─────────────────────────────────────────────────────────────────────────── void App::startSweepImpl(bool full) { +#if !DRAGONX_ENABLE_UI_SWEEP + // Dev tool only (CMake DRAGONX_ENABLE_UI_SWEEP, OFF in every release); an undefined macro counts as 0. + (void)full; + DEBUG_LOGF("[Sweep] disabled in this build\n"); + return; +#else if (screenshot_sweep_active_) return; + // Sentinel: a release binary must not contain this literal (strings | grep -c ... == 0). + DEBUG_LOGF("[Sweep] DRAGONX_UI_SWEEP_COMPILED_IN\n"); sweep_skins_.clear(); for (const auto& sk : ui::schema::SkinManager::instance().available()) @@ -761,6 +769,7 @@ void App::startSweepImpl(bool full) ui::Notifications::instance().info(full ? "Full UI sweep running…" : "Screenshot sweep running…"); DEBUG_LOGF("[Sweep] %s -> %s (%d skins x %d surfaces)\n", full ? "FULL" : "tabs", sweep_dir_.c_str(), (int)sweep_skins_.size(), (int)sweep_targets_.size()); +#endif } void App::startScreenshotSweep() { startSweepImpl(false); } diff --git a/src/main.cpp b/src/main.cpp index 8514a85..8b28465 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1611,7 +1611,13 @@ int main(int argc, char* argv[]) // Headless verification hook (DRAGONX_FULL_SWEEP=1, handled after present), hoisted so the pacing // decision can keep its warm-up frames coming. +#if DRAGONX_ENABLE_UI_SWEEP const bool autoSweep = std::getenv("DRAGONX_FULL_SWEEP") != nullptr; +#else + // The env hook is dead in shipped builds. It must be: with startSweepImpl a no-op, an enabled + // autoSweep would quit the app as soon as the warm-up frames ran out. + const bool autoSweep = false; +#endif int autoSweepFrames = 0; bool autoSweepStarted = false; diff --git a/src/ui/pages/settings_page.cpp b/src/ui/pages/settings_page.cpp index fbb0975..c4495d8 100644 --- a/src/ui/pages/settings_page.cpp +++ b/src/ui/pages/settings_page.cpp @@ -2895,6 +2895,9 @@ void RenderSettingsPage(App* app) { material::GlassCardScope card(dl, availWidth, pad, bottomPad, glassSpec); +#if DRAGONX_ENABLE_UI_SWEEP + // Dev builds only (both sweeps, open-dir, demo chat and the theme checkbox): the tab sweep + // screenshots real balances and addresses, and the demo chat fabricates a chat identity. // Screenshot sweep — capture every theme x every tab (above the daemon debug categories). ImGui::TextWrapped("%s", TR("screenshot_sweep_desc")); ImGui::Dummy(ImVec2(0, Layout::spacingXs())); @@ -2928,6 +2931,7 @@ void RenderSettingsPage(App* app) { ImGui::Dummy(ImVec2(0, Layout::spacingSm())); ImGui::Separator(); ImGui::Dummy(ImVec2(0, Layout::spacingSm())); +#endif ImGui::TextColored(ImVec4(1,1,1,0.5f), "%s", TR("settings_debug_select")); ImGui::TextColored(ImVec4(1,1,1,0.35f), "%s", TR("settings_debug_restart_note"));