From 0e268f0f207122b8d2a4a6a94560c7526c0902b3 Mon Sep 17 00:00:00 2001 From: Eric Moore Date: Thu, 27 Aug 2026 21:49:28 -0500 Subject: [PATCH 01/11] =?UTF-8?q?feat:=20inherit=20CIRISAgent=202.9.41's?= =?UTF-8?q?=20client=20work=20=E2=80=94=20superset=20restored?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CIRISAgent cut v2.9.41-stable and kept working on the client code after our 0.5.191 tag. CIRISClient#11 asked for seven fixes; they are all here, plus one neither tree had fixed. Verified against the tree rather than the list: five files differed from theirs and three test files were absent entirely. WHAT WE ALREADY HAD. The log-buffer race (#1) was fixed here in PR #12 from the Codex review — compare-and-set with the id derived inside the update block. Their implementation is semantically identical; only the seed differs (their first id is 0, ours 1). No merge needed, but their concurrency test comes across, because the fix without the test is a claim. WHAT WE DID NOT HAVE: 2 BUNDLE REDACTION. The bundle exists to be SENT — pasted into an issue, mailed to support — and nothing scrubbed it. Applied at `render()`, the single choke point all three screens pass through, so the environment rows, the caller's `extra` map and the log buffer are covered by construction rather than by three callers remembering. Patterns match credential SHAPES, never entropy, and `token: expired` is excluded as a state: a redactor that eats ordinary log text destroys the artifact's reason for existing. 3 ANDROID EXPORT LEFT EMPTY FILES IN DOWNLOADS. The row is inserted before `openOutputStream`; a failed write returned failure and left the row. An empty debug bundle is worse than none — someone sends it believing they sent their logs. IS_PENDING until the bytes land, delete if not. 4 THE BUNDLE REGISTERED ITS CONTENT HEIGHT, NOT ITS VIEWPORT. `.testable()` sat after `.verticalScroll()`, and `onGloballyPositioned` reports the node's size at its own point in the chain. A full bundle registered as 17952px, so `/tree` advertised an element taller than any screen and automation aiming at its centre targeted roughly y=10000. Ours had this defect, and our own walk-test harness reads `/tree`. 5 RESET WIPED LOCAL STATE WHILE DRIVING A REMOTE NODE. `CIRIS_API_URL` is a supported way to point the client at another host and `PythonRuntime` honours it; the wipe never consulted it. Reset reported success having reset nothing the user was using, while deleting the database, identity and keys of whatever local node shared the disk. Their fourth misresolution in that function and the first above the filesystem layer: the previous three picked the wrong directory, this one picked the wrong machine. COMPOSED with our declared-home resolution rather than replacing it — asking the node where it lives is right when the node is ours, and this decides whether it is. Loopback matched as an ADDRESS, because `127.0.0.1.evil.com` is an ordinary hostname. 7 TESTS FOR THE WIPE, and `wipeGeneratedState` extracted so they can drive it against a real populated tree. Every bug in that function was "deleted more than it should have", which is only observable by asserting what SURVIVES — a thesis file, a src/, a .git. Asserting the targets are gone would have passed for all four. AND ONE NEITHER TREE HAD FIXED (#6, which their issue flagged and left with us): `getAppVersion()` prefers the JAR manifest, and the Compose uber-jar writes only `Main-Class` — so `DESKTOP_VERSION_FALLBACK` is the ONLY value desktop ever reports. As a hand-maintained literal it said "2.3.2" while this repo shipped 0.5.191, in the one artifact whose entire job is to say what was running. Their advice was "if you keep a hand-maintained constant, put a check on it". We do not keep it: the value is now the GENERATED CLIENT_VERSION, so there is no second number to drift and no check needed for one that cannot exist. MUTATION-TESTED, because inherited tests are someone else's evidence until they have been watched fail here: redactSecrets -> identity 2 redaction tests fail ownsLocalBackend -> always true the loopback test fails Both revert clean. `:shared:desktopTest` green (3 + 5 + 5 inherited, plus the new desktop-version test), wasmJs compiles, localization and vendoring pass. Their closing note does not apply to us: CIRISAgent's CI ran zero Kotlin tests. Ours has run `:shared:desktopTest` since the 0.5.188 matrix, which is how the 390 in commonTest stay honest — and as of PR #10 it also walks the app. Closes #11. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01EGE52kPzjGFiPzGcs63ZC1 --- client/VENDORING.md | 2 +- .../platform/DebugBundleExport.android.kt | 26 +++- .../shared/ui/components/DebugBundle.kt | 48 ++++++- .../shared/ui/components/DebugLogsBlock.kt | 10 +- .../shared/platform/LocalDataWipe.desktop.kt | 82 ++++++++++-- .../shared/platform/Platform.desktop.kt | 19 ++- .../platform/DebugLogBufferConcurrencyTest.kt | 83 ++++++++++++ .../shared/platform/DesktopVersionTest.kt | 30 +++++ .../shared/platform/LocalDataWipeTest.kt | 118 ++++++++++++++++++ .../ui/components/DebugBundleRedactionTest.kt | 83 ++++++++++++ 10 files changed, 480 insertions(+), 21 deletions(-) create mode 100644 client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/platform/DebugLogBufferConcurrencyTest.kt create mode 100644 client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/platform/DesktopVersionTest.kt create mode 100644 client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipeTest.kt create mode 100644 client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundleRedactionTest.kt diff --git a/client/VENDORING.md b/client/VENDORING.md index c746ed0..392bb3e 100644 --- a/client/VENDORING.md +++ b/client/VENDORING.md @@ -40,7 +40,7 @@ source is the pair a bisect wants: The tree's current recorded state — sha256-of-sha256s over every git-tracked file under `client/` except this one: -**state digest:** `42c4a314d998300123ba5ff488f269cba2fc4ecc6eff2bba8814f7f7ee4a09f7` +**state digest:** `c060076cee2bf59283d70b7ed93cde26ec344358579befcb9a21bf136ddabe13` `packaging/check_vendoring.py` asserts it on every push, and refuses any tracked file matching a §2 never-vendor class. **Any commit that touches diff --git a/client/shared/src/androidMain/kotlin/ai/ciris/mobile/shared/platform/DebugBundleExport.android.kt b/client/shared/src/androidMain/kotlin/ai/ciris/mobile/shared/platform/DebugBundleExport.android.kt index 58017d7..bd2a7f9 100644 --- a/client/shared/src/androidMain/kotlin/ai/ciris/mobile/shared/platform/DebugBundleExport.android.kt +++ b/client/shared/src/androidMain/kotlin/ai/ciris/mobile/shared/platform/DebugBundleExport.android.kt @@ -35,17 +35,39 @@ actual fun saveDebugBundle(fileName: String, content: String): String? { val ctx = appContext ?: return null return runCatching { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + // IS_PENDING keeps the row invisible to other apps until the bytes + // are actually there. The row has to be inserted before the stream + // can be opened, so between those two calls a failure -- a full + // disk, a revoked provider -- would otherwise publish an empty file + // into Downloads. An empty debug bundle is worse than none: someone + // sends it believing they sent their logs. val values = ContentValues().apply { put(MediaStore.MediaColumns.DISPLAY_NAME, fileName) put(MediaStore.MediaColumns.MIME_TYPE, "text/plain") put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS) + put(MediaStore.MediaColumns.IS_PENDING, 1) } val uri = ctx.contentResolver.insert( MediaStore.Downloads.EXTERNAL_CONTENT_URI, values, ) ?: return@runCatching null - ctx.contentResolver.openOutputStream(uri)?.use { it.write(content.toByteArray()) } - ?: return@runCatching null + + val wrote = runCatching { + ctx.contentResolver.openOutputStream(uri)?.use { it.write(content.toByteArray()) } + ?: throw java.io.IOException("openOutputStream returned null for $uri") + } + if (wrote.isFailure) { + runCatching { ctx.contentResolver.delete(uri, null, null) } + return@runCatching null + } + + // Publish only now that the file is complete. + ctx.contentResolver.update( + uri, + ContentValues().apply { put(MediaStore.MediaColumns.IS_PENDING, 0) }, + null, + null, + ) // A name the user can search for, not a URI they cannot act on. "Downloads/$fileName" } else { diff --git a/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundle.kt b/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundle.kt index 362986f..3f65e45 100644 --- a/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundle.kt +++ b/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundle.kt @@ -40,7 +40,10 @@ object DebugBundle { * token-exchange error, the interact screen's connection state. They ride * ABOVE the logs because they are usually the answer. */ - fun render(extra: Map = emptyMap()): String = buildString { + fun render(extra: Map = emptyMap()): String = + redactSecrets(renderRaw(extra)) + + private fun renderRaw(extra: Map): String = buildString { appendLine("CIRIS debug bundle") appendLine("==================") appendLine() @@ -70,6 +73,49 @@ object DebugBundle { return "ciris-debug-$stamp.txt" } + /** + * Patterns that must never leave the device inside a bundle. + * + * Nothing logs a credential today — this was checked, not assumed. But the + * bundle exists to be SENT to us: a user pastes it into an issue or mails + * it to support, and at that moment whatever the log buffer happened to + * hold becomes public. The gap between "nothing logs a secret" and "a + * secret cannot escape" is one future `platformLog("token=$t")` written by + * someone debugging at 2am, and nothing in review would flag that line as + * dangerous, because on its own it isn't. + * + * Narrow on purpose. A redactor that eats ordinary log text destroys the + * artifact's whole reason for existing, so these match credential SHAPES + * (a JWT, a bearer header, an explicit assignment to a secret-ish name) + * rather than anything that merely looks high-entropy. + */ + private val SECRET_PATTERNS: List> = listOf( + // JWTs — the shape is unmistakable and never appears in prose. + Regex("""eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}(\.[A-Za-z0-9_-]+)?""") to "", + // Authorization headers, including the `service:TOKEN` form. + Regex("""(?i)\b(bearer\s+)(service:)?[A-Za-z0-9._~+/=-]{12,}""") to "$1", + // name = value, where the name is one people give to secrets. + Regex( + """(?i)\b(api[_-]?key|access[_-]?token|refresh[_-]?token|id[_-]?token|""" + + """auth[_-]?token|token|secret|password|passwd|client[_-]?secret)""" + + """(\s*["']?\s*[:=]\s*["']?)""" + // `token: expired` is a STATE, not a credential. Redacting it + // costs the reader the one fact the line carried. + + """(?!(?:expired|missing|present|invalid|unknown|revoked|pending|""" + + """refreshed|required|rejected|absent)\b)""" + + """([^\s"',;)}\]]{6,})""" + ) to "$1$2", + ) + + /** + * Strip credential-shaped substrings. Internal so it can be tested directly + * — a redactor nobody has watched fail is not a redactor. + */ + internal fun redactSecrets(text: String): String = + SECRET_PATTERNS.fold(text) { acc, (pattern, replacement) -> + pattern.replace(acc, replacement) + } + private inline fun safely(block: () -> String): String = runCatching(block).getOrElse { "unknown" } diff --git a/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/ui/components/DebugLogsBlock.kt b/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/ui/components/DebugLogsBlock.kt index 041d452..eff34e1 100644 --- a/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/ui/components/DebugLogsBlock.kt +++ b/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/ui/components/DebugLogsBlock.kt @@ -185,11 +185,17 @@ fun DebugLogsBlock( fontSize = 9.sp, fontFamily = FontFamily.Monospace, color = MaterialTheme.colorScheme.onSurface, + // testable() BEFORE verticalScroll: onGloballyPositioned + // reports the node's size at its own point in the chain, + // so registering inside the scroll captured the CONTENT + // height -- 17952px for a full bundle. /tree then showed + // an element taller than any screen, and automation + // aiming at its centre would target y~10000. modifier = Modifier .heightIn(max = 220.dp) + .testable("txt_debug_bundle") .verticalScroll(rememberScrollState()) - .padding(8.dp) - .testable("txt_debug_bundle"), + .padding(8.dp), ) } } diff --git a/client/shared/src/desktopMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.desktop.kt b/client/shared/src/desktopMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.desktop.kt index b7fde18..f2eaa90 100644 --- a/client/shared/src/desktopMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.desktop.kt +++ b/client/shared/src/desktopMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.desktop.kt @@ -93,6 +93,40 @@ private fun isSourceCheckout(home: File): Boolean = * make this delete something it does not recognise. What survives is an empty * directory and any file we never wrote, which is exactly what should survive. */ +/** + * `127.x.y.z` as an ADDRESS, not as a prefix. `127.0.0.1.evil.com` is a + * perfectly ordinary hostname that a prefix test reads as loopback. + */ +private val LOOPBACK_IPV4 = Regex("""^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$""") + +/** + * True when the backend this client is driving is one we could own locally. + * + * INHERITED from CIRISAgent 2.9.41 (CIRISClient#11). `CIRIS_API_URL` is a + * supported way to point the client at a node on another host, and + * `PythonRuntime.desktop` honours it and never starts a local backend — but the + * wipe never consulted it. Reset then reported success having reset nothing the + * user was looking at, while deleting the database, identity and keys of + * whatever local node happened to share the disk. + * + * This is the FOURTH misresolution in this function and the first above the + * filesystem layer: the previous three picked the wrong directory, this one + * picked the wrong machine. + * + * Loopback is the test because it is the condition the node would have to + * satisfy for our home resolution to describe it at all. A remote node keeps + * its state on its own disk under its own `CIRIS_HOME`. + */ +internal fun ownsLocalBackend(apiUrl: String?): Boolean { + val url = apiUrl?.takeIf { it.isNotBlank() } ?: return true + val host = runCatching { java.net.URI(url).host }.getOrNull() + ?: return false // unparseable: assume not ours + val h = host.trim().removePrefix("[").removeSuffix("]").lowercase() + return h == "localhost" || h == "::1" || h == "0.0.0.0" || LOOPBACK_IPV4.matches(h) +} + +private fun ownsLocalBackend(): Boolean = ownsLocalBackend(System.getenv("CIRIS_API_URL")) + actual fun wipeLocalData(declaredNodeHome: String?): Boolean { // Managed deployments are not ours to wipe: `/app` belongs to CIRIS-Manager, // and the person at this UI is not necessarily entitled to destroy it. @@ -101,10 +135,45 @@ actual fun wipeLocalData(declaredNodeHome: String?): Boolean { return false } + // A client pointed at a REMOTE node owns nothing here. Composed with the + // declared-home resolution below rather than replacing it: asking the node + // where it lives is right when the node is ours, and this decides whether + // it is. A remote node answers with a path on ITS disk, and acting on that + // locally is the same mistake one level further along. + if (!ownsLocalBackend()) { + println("[LocalDataWipe] refusing: CIRIS_API_URL points at a node we do not own") + return false + } + val home = resolveNodeHome(declaredNodeHome) ?: return false if (!home.exists()) return true - val checkout = isSourceCheckout(home) + var ok = wipeGeneratedState(home, isSourceCheckout(home)) + + // THE FILESYSTEM IS NOT ALL THE LOCAL DATA (Codex, PR #9). + // + // SecureStorage.desktop keeps provider API keys in java.util.prefs under + // `apikey_*`, and `getApiKey()` falls back to them once .env is gone. + // logout() clears tokens and user fields and leaves those, so a wipe that + // stopped at the filesystem handed the next owner of the machine the + // previous owner's provider key — under a dialog that says "erase all local + // data". Removing files is the visible half of the promise; this is the half + // that decides whether the promise was true. + if (!clearSecurePreferences()) ok = false + return ok +} + +/** + * Remove the generated entries from [home]. Never removes [home] itself. + * + * SEPARATED FROM RESOLUTION so it can be tested against a real directory + * (inherited from CIRISAgent 2.9.41, CIRISClient#11). Every shipped bug in this + * file was "deleted more than it should have", and that is only observable by + * pointing it at a populated tree and asserting what SURVIVES — a thesis file, + * a `src/`, a `.git`. Asserting the intended targets are gone would have passed + * for all four of them. + */ +internal fun wipeGeneratedState(home: File, checkout: Boolean): Boolean { var ok = true var removed = 0 @@ -147,17 +216,6 @@ actual fun wipeLocalData(declaredNodeHome: String?): Boolean { } } - // THE FILESYSTEM IS NOT ALL THE LOCAL DATA (Codex, PR #9). - // - // SecureStorage.desktop keeps provider API keys in java.util.prefs under - // `apikey_*`, and `getApiKey()` falls back to them once .env is gone. - // logout() clears tokens and user fields and leaves those, so a wipe that - // stopped at the filesystem handed the next owner of the machine the - // previous owner's provider key — under a dialog that says "erase all local - // data". Removing files is the visible half of the promise; this is the half - // that decides whether the promise was true. - if (!clearSecurePreferences()) ok = false - println("[LocalDataWipe] ${home.absolutePath}: removed $removed generated entries, ok=$ok") return ok } diff --git a/client/shared/src/desktopMain/kotlin/ai/ciris/mobile/shared/platform/Platform.desktop.kt b/client/shared/src/desktopMain/kotlin/ai/ciris/mobile/shared/platform/Platform.desktop.kt index e01db22..cd4f07e 100644 --- a/client/shared/src/desktopMain/kotlin/ai/ciris/mobile/shared/platform/Platform.desktop.kt +++ b/client/shared/src/desktopMain/kotlin/ai/ciris/mobile/shared/platform/Platform.desktop.kt @@ -54,7 +54,20 @@ actual fun startTestAutomationServer() { } /** - * Fallback version if JAR manifest is unavailable. - * Keep in sync with mobile/androidApp/build.gradle versionName. + * Fallback version if the JAR manifest is unavailable. + * + * THE FALLBACK IS THE ONLY VALUE DESKTOP EVER REPORTS, which is why the old + * hand-maintained constant drifted to `2.3.2` while builds shipped 2.9.x and + * then 0.5.x. The Compose uber-jar writes only `Main-Class` into its manifest, + * so `implementationVersion` is always null and the "fallback" above is the + * whole answer. A diagnostics bundle from a 0.5.191 build named 2.3.2 as its + * version — in the one artifact whose entire job is to say what was running. + * + * "Keep in sync with androidApp/build.gradle versionName" was the instruction, + * and nothing enforced it in either repo (CIRISClient#11 asked us to put a + * check on it if we kept the constant). We do not keep it: CLIENT_VERSION is + * GENERATED from the repo-root VERSION file by :shared:generateBuildFlavor — + * the same value the wheel publishes — so there is no second number that can + * drift, and no check needed for one that cannot exist. */ -private const val DESKTOP_VERSION_FALLBACK = "2.3.2" +private val DESKTOP_VERSION_FALLBACK: String = ai.ciris.mobile.shared.models.CLIENT_VERSION diff --git a/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/platform/DebugLogBufferConcurrencyTest.kt b/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/platform/DebugLogBufferConcurrencyTest.kt new file mode 100644 index 0000000..0fdb4fd --- /dev/null +++ b/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/platform/DebugLogBufferConcurrencyTest.kt @@ -0,0 +1,83 @@ +package ai.ciris.mobile.shared.platform + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.runBlocking +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +/** + * The buffer is written from several threads at once and read when things have + * already gone wrong. + * + * On desktop the node's stdout reader logs from `Dispatchers.IO` while view + * models log from their own scopes. That is not an exotic case: it is what a + * failing startup looks like, which is the only time anyone downloads a debug + * bundle. A buffer that silently drops entries under concurrency would omit + * exactly the errors it exists to capture, and the loss would be invisible — + * the bundle still arrives, just missing the line that explains the failure. + * + * These live in `desktopTest`, not `commonTest`, deliberately: they need real + * parallelism. On a single-threaded target `Dispatchers.Default` interleaves + * without ever overlapping, so a read-modify-write bug passes. + */ +class DebugLogBufferConcurrencyTest { + + @BeforeTest + fun reset() = DebugLogBuffer.clear() + + @Test + fun `concurrent adds lose no entries and never repeat an id`() = runBlocking { + // Under the buffer cap, so every add must survive: any shortfall is a + // lost update rather than intended trimming. + val writers = 16 + val perWriter = 10 + val total = writers * perWriter + + (0 until writers).map { w -> + async(Dispatchers.Default) { + repeat(perWriter) { i -> DebugLogBuffer.add("INFO", "w$w", "msg-$i") } + } + }.awaitAll() + + val entries = DebugLogBuffer.entries.value + assertEquals(total, entries.size, "lost entries under concurrent add") + + val ids = entries.map { it.id } + assertEquals(ids.size, ids.toSet().size, "duplicate ids under concurrent add") + assertEquals(ids.sorted(), ids, "ids not monotonic in list order") + } + + @Test + fun `concurrent error adds count every error`() = runBlocking { + val writers = 16 + val perWriter = 8 + + (0 until writers).map { w -> + async(Dispatchers.Default) { + repeat(perWriter) { i -> DebugLogBuffer.add("ERROR", "w$w", "boom-$i") } + } + }.awaitAll() + + assertEquals( + writers * perWriter, + DebugLogBuffer.errorCount.value, + "lost increments: errorCount read-modify-write is not atomic", + ) + assertTrue(DebugLogBuffer.latestError.value?.startsWith("[w") == true) + } + + @Test + fun `buffer trims to the cap and keeps the newest`() = runBlocking { + // Single-threaded: this asserts the trim policy, not the race. + repeat(250) { i -> DebugLogBuffer.add("INFO", "t", "m$i") } + + val entries = DebugLogBuffer.entries.value + assertEquals(200, entries.size, "buffer did not trim to MAX_ENTRIES") + assertEquals("m249", entries.last().message, "trim dropped the newest, not the oldest") + assertEquals("m50", entries.first().message) + } +} diff --git a/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/platform/DesktopVersionTest.kt b/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/platform/DesktopVersionTest.kt new file mode 100644 index 0000000..91297a4 --- /dev/null +++ b/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/platform/DesktopVersionTest.kt @@ -0,0 +1,30 @@ +package ai.ciris.mobile.shared.platform + +import ai.ciris.mobile.shared.models.CLIENT_VERSION +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +/** + * The desktop version is the version, not a constant someone remembers to bump. + * + * `getAppVersion()` prefers the JAR manifest, and the Compose uber-jar writes + * only `Main-Class` — so the fallback is the ONLY value desktop ever reports. + * As a hand-maintained literal it drifted to "2.3.2" while builds shipped + * 2.9.x, and a diagnostics bundle from a 0.5.191 build named 2.3.2 as its + * version (CIRISClient#11). + */ +class DesktopVersionTest { + + @Test + fun desktop_reports_the_version_this_build_actually_is() { + assertEquals(CLIENT_VERSION, getAppVersion()) + } + + @Test + fun and_it_is_not_the_literal_that_drifted() { + // Named explicitly so a future re-vendor that reintroduces the constant + // fails here rather than shipping a bundle that misreports its build. + assertTrue(getAppVersion() != "2.3.2", "desktop is reporting the stale hand-maintained version") + } +} diff --git a/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipeTest.kt b/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipeTest.kt new file mode 100644 index 0000000..dba3ce4 --- /dev/null +++ b/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipeTest.kt @@ -0,0 +1,118 @@ +package ai.ciris.mobile.shared.platform + +import java.io.File +import java.nio.file.Files +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +/** + * Reset Device is the most destructive thing this app can do, and it shipped + * four wrong answers: it deleted an unrelated installed agent, then a whole git + * checkout including uncommitted work, then anything sharing a directory with a + * bare `data/`, then the state of a node the client was not even talking to. + * + * Every one of those is a statement about what SURVIVES a wipe, which is why + * these tests assert survival against a populated tree rather than checking + * that the intended targets are gone. + */ +class LocalDataWipeTest { + + private lateinit var home: File + + @BeforeTest + fun setUp() { + home = Files.createTempDirectory("ciris-wipe-test").toFile() + } + + @AfterTest + fun tearDown() { + home.deleteRecursively() + } + + private fun populate() { + File(home, ".env").writeText("CIRIS_CONFIGURED=true") + File(home, "data").mkdirs() + File(home, "data/ciris_engine.db").writeText("db") + File(home, "identity").mkdirs() + File(home, "identity/node.key").writeText("secret") + File(home, "logs").mkdirs() + File(home, "config").mkdirs() + File(home, "config/essential.yaml").writeText("tracked source") + + // Never ours: a wipe that takes these is the bug, not the feature. + File(home, "my_thesis.txt").writeText("years of work") + File(home, "src").mkdirs() + File(home, "src/main.py").writeText("code") + File(home, ".git").mkdirs() + File(home, ".git/HEAD").writeText("ref: refs/heads/main") + } + + @Test + fun `removes generated state and nothing else`() { + populate() + assertTrue(wipeGeneratedState(home, checkout = false)) + + assertFalse(File(home, ".env").exists()) + assertFalse(File(home, "data").exists()) + assertFalse(File(home, "identity").exists()) + assertFalse(File(home, "logs").exists()) + + assertTrue(File(home, "my_thesis.txt").exists(), "deleted a file it did not write") + assertTrue(File(home, "src/main.py").exists(), "deleted source that is not node state") + assertTrue(File(home, ".git/HEAD").exists(), "deleted the repository") + } + + @Test + fun `never removes the home directory itself`() { + populate() + wipeGeneratedState(home, checkout = false) + assertTrue(home.exists(), "home directory was removed — bug #2 and #3") + assertTrue(home.isDirectory) + } + + @Test + fun `keeps tracked config in a checkout and removes it otherwise`() { + populate() + wipeGeneratedState(home, checkout = true) + assertTrue( + File(home, "config/essential.yaml").exists(), + "deleted tracked source: config/ is node state only outside a checkout", + ) + + tearDown(); setUp(); populate() + wipeGeneratedState(home, checkout = false) + assertFalse(File(home, "config").exists(), "kept generated config in a dedicated home") + } + + @Test + fun `an empty home succeeds without creating anything`() { + assertTrue(wipeGeneratedState(home, checkout = false)) + assertTrue(home.listFiles()?.isEmpty() == true) + } + + @Test + fun `owns the backend only when it is loopback`() { + // Unset or blank: the client boots the node itself. + assertTrue(ownsLocalBackend(null)) + assertTrue(ownsLocalBackend("")) + + assertTrue(ownsLocalBackend("http://localhost:8080")) + assertTrue(ownsLocalBackend("http://127.0.0.1:8080")) + assertTrue(ownsLocalBackend("http://127.5.5.5:8080")) + assertTrue(ownsLocalBackend("http://[::1]:8080")) + assertTrue(ownsLocalBackend("http://0.0.0.0:8080")) + + // A node on another host keeps its state on its own disk. + assertFalse(ownsLocalBackend("https://agents.ciris.ai")) + assertFalse(ownsLocalBackend("http://192.168.1.50:8080")) + assertFalse(ownsLocalBackend("http://node.local:8080")) + assertFalse(ownsLocalBackend("garbage")) + + // Hostnames that only LOOK like loopback under a prefix test. + assertFalse(ownsLocalBackend("http://127.0.0.1.evil.com/")) + assertFalse(ownsLocalBackend("http://localhost.evil.com/")) + } +} diff --git a/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundleRedactionTest.kt b/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundleRedactionTest.kt new file mode 100644 index 0000000..7116887 --- /dev/null +++ b/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundleRedactionTest.kt @@ -0,0 +1,83 @@ +package ai.ciris.mobile.shared.ui.components + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +/** + * The bundle is built to be handed to someone else. Redaction is therefore not + * about what we log today — nothing logs a credential today — but about what + * escapes when somebody later logs one while chasing a bug. + * + * Both directions matter equally. A redactor that misses a JWT leaks; a + * redactor that eats ordinary log lines destroys the only artifact a stranded + * user can send us. The second half of these tests is the half that keeps the + * patterns honest. + */ +class DebugBundleRedactionTest { + + private fun redact(s: String) = DebugBundle.redactSecrets(s) + + @Test + fun `removes jwts`() { + val jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dBjftJeZ4CVP" + val out = redact("[INFO] auth: exchanged $jwt ok") + assertFalse(out.contains("eyJhbGciOi"), "JWT survived redaction") + assertTrue(out.contains("[INFO] auth:"), "redaction ate the surrounding line") + } + + @Test + fun `removes bearer headers including service tokens`() { + assertFalse( + redact("Authorization: Bearer abcdef0123456789abcdef").contains("abcdef0123456789"), + ) + assertFalse( + redact("header Bearer service:9f8e7d6c5b4a39281706").contains("9f8e7d6c5b4a"), + ) + } + + @Test + fun `removes secret-shaped assignments`() { + for (line in listOf( + "api_key=sk-live-abcdef123456", + "access_token: ghp_AAAABBBBCCCCDDDDEEEE", + "password = hunter2hunter2", + """{"client_secret":"s3cr3t-value-here"}""", + )) { + val out = redact(line) + assertTrue(out.contains(""), "not redacted: $line -> $out") + } + assertFalse(redact("password = hunter2hunter2").contains("hunter2hunter2")) + } + + @Test + fun `leaves ordinary diagnostics untouched`() { + // Every one of these is a line we NEED in a bug report. + for (line in listOf( + // The exact lines the Esu report turned on: an auth failure must + // survive intact, or the bundle loses the one fact worth sending. + "[ERROR] CIRISApp: token exchange failed: HTTP 401", + "[ERROR] auth: token exchange failed, no refresh token present", + "token: expired", + "[INFO] startup: 22/22 services healthy in 4.2s", + "client version: 0.5.190", + "app version: 2.9.41", + "device: Linux 7.0.0-30-generic amd64", + "screen: login", + "[WARN] node: attestation deadline exceeded (20s budget)", + "GET /v1/system/health -> 200 in 34ms", + )) { + assertEquals(line, redact(line), "redactor damaged an ordinary log line") + } + } + + @Test + fun `render passes its whole output through redaction`() { + val out = DebugBundle.render( + mapOf("error (full)" to "refresh_token=rt_abcdef0123456789 rejected"), + ) + assertFalse(out.contains("rt_abcdef0123456789"), "extra map bypassed redaction") + assertTrue(out.contains("CIRIS debug bundle")) + } +} From 76037694316a32b20c3d2e56d8666be3fd58bca7 Mon Sep 17 00:00:00 2001 From: Eric Moore Date: Thu, 27 Aug 2026 22:00:57 -0500 Subject: [PATCH 02/11] =?UTF-8?q?fix:=20three=20holes=20in=20the=20inherit?= =?UTF-8?q?ed=20fixes=20=E2=80=94=20two=20of=20them=20P1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex reviewed the inheritance and found the fixes I brought across from CIRISAgent 2.9.41 do not fully close what they claim to. All three are real, verified against the code rather than the description, and two are the same class as the defect being fixed. 1 THE WIPE GUARD READ AN ENVIRONMENT VARIABLE, NOT THE ACTIVE NODE. P1. `ownsLocalBackend()` re-read `CIRIS_API_URL`. Two live paths bypass that: - `NodeSwitcherViewModel.switchTo` calls `apiClient.updateBaseUrl(...)` and never touches the environment. After switching to a saved remote profile the env still says loopback and the guard still says "ours". - `Main.kt` PREFERS `CIRIS_NODE_URL`; a desktop configured that way was checked against a variable nobody set. Reset would then take a REMOTE node's declared home as a local path: deleting unrelated local state if that path exists here, and otherwise reporting a successful reset while the node the user is looking at is untouched. That is precisely the "wrong machine" error the inherited guard was added to prevent, reached by a different road — the fix was written against the configuration that motivated it rather than against the question it asks. `wipeLocalData` now takes the ACTIVE node URL and CIRISApp passes the live `nodeBaseUrl`. The environment is a fallback, and reads BOTH names in the order `Main.kt` resolves them. 2 THE REDACTOR LEAKED MOST OF A QUOTED SECRET. P1. The value group stopped at whitespace, and quoted secrets legitimately contain spaces. Reproduced before changing anything: password="correct horse battery staple" password=" horse battery staple" Three words of a four-word passphrase, in the artifact whose entire purpose is to be mailed to a stranger. A quoted arm now matches through the CLOSING QUOTE and is tried first; the unquoted arm is unchanged. The state exclusion is carried into both, so `token="expired"` still reads as a state rather than being redacted to nothing. 3 A FAILED PUBLISH ORPHANED THE ROW. P2. The bytes landing and the row becoming visible are different events. If the IS_PENDING=0 update threw, the outer runCatching returned null without reaching the delete, leaving the complete bundle as an orphaned pending item — invisible to every file manager and never cleaned up. A zero-row update neither threw nor published, and the function reported `Downloads/` for a file the user cannot find. Both now delete the row and report failure, which is what this function already did for an empty write. Two new redaction cases; `:shared:desktopTest` green; desktop and wasmJs compile. Worth sending back to CIRISAgent: 1 and 3 are in their tree as shipped, and 2 is their pattern verbatim. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01EGE52kPzjGFiPzGcs63ZC1 --- client/VENDORING.md | 2 +- .../platform/DebugBundleExport.android.kt | 31 +++++++++++++++---- .../shared/platform/LocalDataWipe.android.kt | 2 +- .../kotlin/ai/ciris/mobile/shared/CIRISApp.kt | 9 +++++- .../mobile/shared/platform/LocalDataWipe.kt | 14 ++++++++- .../shared/ui/components/DebugBundle.kt | 17 +++++++++- .../shared/platform/LocalDataWipe.desktop.kt | 27 ++++++++++++++-- .../ui/components/DebugBundleRedactionTest.kt | 18 ++++++++++- .../shared/platform/LocalDataWipe.ios.kt | 2 +- .../shared/platform/LocalDataWipe.wasmJs.kt | 2 +- 10 files changed, 107 insertions(+), 17 deletions(-) diff --git a/client/VENDORING.md b/client/VENDORING.md index 392bb3e..2283895 100644 --- a/client/VENDORING.md +++ b/client/VENDORING.md @@ -40,7 +40,7 @@ source is the pair a bisect wants: The tree's current recorded state — sha256-of-sha256s over every git-tracked file under `client/` except this one: -**state digest:** `c060076cee2bf59283d70b7ed93cde26ec344358579befcb9a21bf136ddabe13` +**state digest:** `32937ab42adece75ebd3cae8f680afc65e288b04343d9a48fa58d0062ff7c107` `packaging/check_vendoring.py` asserts it on every push, and refuses any tracked file matching a §2 never-vendor class. **Any commit that touches diff --git a/client/shared/src/androidMain/kotlin/ai/ciris/mobile/shared/platform/DebugBundleExport.android.kt b/client/shared/src/androidMain/kotlin/ai/ciris/mobile/shared/platform/DebugBundleExport.android.kt index bd2a7f9..1c7e52c 100644 --- a/client/shared/src/androidMain/kotlin/ai/ciris/mobile/shared/platform/DebugBundleExport.android.kt +++ b/client/shared/src/androidMain/kotlin/ai/ciris/mobile/shared/platform/DebugBundleExport.android.kt @@ -62,12 +62,31 @@ actual fun saveDebugBundle(fileName: String, content: String): String? { } // Publish only now that the file is complete. - ctx.contentResolver.update( - uri, - ContentValues().apply { put(MediaStore.MediaColumns.IS_PENDING, 0) }, - null, - null, - ) + // + // AND TREAT A FAILED PUBLISH LIKE A FAILED WRITE (Codex, PR #18). + // The bytes landing is not the same event as the row becoming + // visible. If this update throws, the outer runCatching returns + // null WITHOUT reaching the delete above, leaving the complete + // bundle stored as an orphaned pending item — invisible to every + // file manager, and never cleaned up. If it returns zero rows it + // did not throw and did not publish, and the old code reported + // "Downloads/$fileName" for a file the user cannot find. + // + // Both are the same failure as an empty row, which this function + // already knew to clean up: something is in MediaStore that the + // user cannot reach and did not ask to keep. + val published = runCatching { + ctx.contentResolver.update( + uri, + ContentValues().apply { put(MediaStore.MediaColumns.IS_PENDING, 0) }, + null, + null, + ) + }.getOrDefault(0) + if (published <= 0) { + runCatching { ctx.contentResolver.delete(uri, null, null) } + return@runCatching null + } // A name the user can search for, not a URI they cannot act on. "Downloads/$fileName" } else { diff --git a/client/shared/src/androidMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.android.kt b/client/shared/src/androidMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.android.kt index 202d929..aaffb58 100644 --- a/client/shared/src/androidMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.android.kt +++ b/client/shared/src/androidMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.android.kt @@ -12,7 +12,7 @@ private const val TAG = "LocalDataWipe" * CIRIS_HOME is resolved through EnvFileUpdater's existing accessor rather than * rebuilt here — two spellings of that path is how a wipe half-succeeds. */ -actual fun wipeLocalData(declaredNodeHome: String?): Boolean { +actual fun wipeLocalData(declaredNodeHome: String?, activeNodeUrl: String?): Boolean { // Symmetry with desktop. Always false on a phone today, but the predicate is // the feature's one boundary and reading it here keeps that true if the // platform set ever changes. diff --git a/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/CIRISApp.kt b/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/CIRISApp.kt index 532ff5c..444e3f6 100644 --- a/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/CIRISApp.kt +++ b/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/CIRISApp.kt @@ -2101,7 +2101,14 @@ fun CIRISApp( } val wiped = withContext(Dispatchers.Default) { - ai.ciris.mobile.shared.platform.wipeLocalData(declaredHome) + ai.ciris.mobile.shared.platform.wipeLocalData( + declaredHome, + // The URL this client is talking to RIGHT NOW. + // A node switch repoints the API client + // without touching the environment, so the + // environment is not the answer (Codex, PR #18). + activeNodeUrl = nodeBaseUrl, + ) } platformLog(TAG, "[INFO][onResetSetup] wipeLocalData -> $wiped") diff --git a/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.kt b/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.kt index c3f1717..68cba30 100644 --- a/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.kt +++ b/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.kt @@ -39,4 +39,16 @@ package ai.ciris.mobile.shared.platform * authoritative for the claim PIN, for the same reason (Codex, PR #9). * Null when there is nobody to ask; the resolver then falls back to the guess. */ -expect fun wipeLocalData(declaredNodeHome: String? = null): Boolean +/** + * @param activeNodeUrl the URL the client is ACTUALLY talking to right now, not + * an environment variable. `NodeSwitcherViewModel.switchTo` repoints the API + * client at a saved profile without touching the environment, and desktop + * prefers `CIRIS_NODE_URL` over `CIRIS_API_URL` — so a guard that re-reads one + * env var is guarding against a configuration the client may have left behind + * (Codex, PR #18). Null means "caller did not say", and the desktop actual + * falls back to the environment. + */ +expect fun wipeLocalData( + declaredNodeHome: String? = null, + activeNodeUrl: String? = null, +): Boolean diff --git a/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundle.kt b/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundle.kt index 3f65e45..7123246 100644 --- a/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundle.kt +++ b/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundle.kt @@ -94,7 +94,22 @@ object DebugBundle { Regex("""eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}(\.[A-Za-z0-9_-]+)?""") to "", // Authorization headers, including the `service:TOKEN` form. Regex("""(?i)\b(bearer\s+)(service:)?[A-Za-z0-9._~+/=-]{12,}""") to "$1", - // name = value, where the name is one people give to secrets. + // QUOTED value — matched through the CLOSING QUOTE, and this arm must + // come first. A single pattern that stops at whitespace leaks most of + // the secret it claims to remove: `password="correct horse battery + // staple"` redacted to `password=" horse battery staple"`, + // three words of a four-word passphrase still in the bundle (Codex, + // PR #18). Quoted secrets legitimately contain spaces, so the quote is + // the delimiter, not whitespace. + Regex( + """(?i)\b(api[_-]?key|access[_-]?token|refresh[_-]?token|id[_-]?token|""" + + """auth[_-]?token|token|secret|password|passwd|client[_-]?secret)""" + + """(\s*[:=]\s*)(["'])""" + + """(?!(?:expired|missing|present|invalid|unknown|revoked|pending|""" + + """refreshed|required|rejected|absent)["'])""" + + """([^"']{6,})(\3)""" + ) to "$1$2$3$3", + // UNQUOTED name = value, where the name is one people give to secrets. Regex( """(?i)\b(api[_-]?key|access[_-]?token|refresh[_-]?token|id[_-]?token|""" + """auth[_-]?token|token|secret|password|passwd|client[_-]?secret)""" diff --git a/client/shared/src/desktopMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.desktop.kt b/client/shared/src/desktopMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.desktop.kt index f2eaa90..9f56179 100644 --- a/client/shared/src/desktopMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.desktop.kt +++ b/client/shared/src/desktopMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.desktop.kt @@ -125,9 +125,30 @@ internal fun ownsLocalBackend(apiUrl: String?): Boolean { return h == "localhost" || h == "::1" || h == "0.0.0.0" || LOOPBACK_IPV4.matches(h) } -private fun ownsLocalBackend(): Boolean = ownsLocalBackend(System.getenv("CIRIS_API_URL")) +/** + * The URL the client is ACTUALLY using, in the order that decides it. + * + * The inherited guard read `CIRIS_API_URL` alone, and missed two live paths + * (Codex, PR #18): + * + * - `NodeSwitcherViewModel.switchTo` calls `apiClient.updateBaseUrl(...)` and + * never touches the environment, so after a switch to a saved remote + * profile the env still says loopback and the guard still says "ours". + * - `Main.kt` prefers `CIRIS_NODE_URL` over `CIRIS_API_URL`; a desktop + * configured with the former was checked against a variable nobody set. + * + * In both cases Reset would then treat a REMOTE node's declared home as a local + * path: deleting unrelated local state if that path happens to exist here, and + * otherwise reporting a successful reset while the node the user is looking at + * is untouched. That is the same "wrong machine" error the guard was added for, + * reached by a different road. + */ +private fun activeNodeUrlOrEnv(explicit: String?): String? = + explicit?.takeIf { it.isNotBlank() } + ?: System.getenv("CIRIS_NODE_URL")?.takeIf { it.isNotBlank() } + ?: System.getenv("CIRIS_API_URL") -actual fun wipeLocalData(declaredNodeHome: String?): Boolean { +actual fun wipeLocalData(declaredNodeHome: String?, activeNodeUrl: String?): Boolean { // Managed deployments are not ours to wipe: `/app` belongs to CIRIS-Manager, // and the person at this UI is not necessarily entitled to destroy it. if (isManagedDeployment()) { @@ -140,7 +161,7 @@ actual fun wipeLocalData(declaredNodeHome: String?): Boolean { // where it lives is right when the node is ours, and this decides whether // it is. A remote node answers with a path on ITS disk, and acting on that // locally is the same mistake one level further along. - if (!ownsLocalBackend()) { + if (!ownsLocalBackend(activeNodeUrlOrEnv(activeNodeUrl))) { println("[LocalDataWipe] refusing: CIRIS_API_URL points at a node we do not own") return false } diff --git a/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundleRedactionTest.kt b/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundleRedactionTest.kt index 7116887..59b7692 100644 --- a/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundleRedactionTest.kt +++ b/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundleRedactionTest.kt @@ -80,4 +80,20 @@ class DebugBundleRedactionTest { assertFalse(out.contains("rt_abcdef0123456789"), "extra map bypassed redaction") assertTrue(out.contains("CIRIS debug bundle")) } -} + + @Test + fun a_quoted_secret_containing_spaces_is_removed_whole() { + // The value group used to stop at the first space, so a four-word + // passphrase lost one word and kept three (Codex, PR #18). + val out = DebugBundle.redactSecrets("""password="correct horse battery staple"""" + "\"") + assertFalse(out.contains("horse"), "the rest of the passphrase survived: $out") + assertFalse(out.contains("staple"), "the rest of the passphrase survived: $out") + assertTrue(out.contains(""), out) + } + + @Test + fun a_quoted_state_is_still_not_a_credential() { + val out = DebugBundle.redactSecrets("""token="expired"""" + "\"") + assertTrue(out.contains("expired"), "a state was redacted to nothing: $out") + } +} \ No newline at end of file diff --git a/client/shared/src/iosMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.ios.kt b/client/shared/src/iosMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.ios.kt index c302faa..dd04980 100644 --- a/client/shared/src/iosMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.ios.kt +++ b/client/shared/src/iosMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.ios.kt @@ -30,7 +30,7 @@ import platform.Security.kSecClassGenericPassword * open-ended, so any list would drift the moment a provider is added. */ @OptIn(ExperimentalForeignApi::class) -actual fun wipeLocalData(declaredNodeHome: String?): Boolean { +actual fun wipeLocalData(declaredNodeHome: String?, activeNodeUrl: String?): Boolean { var ok = true // 1. The node's state directory. diff --git a/client/shared/src/wasmJsMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.wasmJs.kt b/client/shared/src/wasmJsMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.wasmJs.kt index 446d352..e81e1e0 100644 --- a/client/shared/src/wasmJsMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.wasmJs.kt +++ b/client/shared/src/wasmJsMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.wasmJs.kt @@ -5,4 +5,4 @@ package ai.ciris.mobile.shared.platform * data is not this browser's to delete. Reports false rather than pretending, so * the UI can say so instead of claiming a wipe that did not happen. */ -actual fun wipeLocalData(declaredNodeHome: String?): Boolean = false +actual fun wipeLocalData(declaredNodeHome: String?, activeNodeUrl: String?): Boolean = false From f2d1103d8e9213f2b588cba9e0f087a2945012f8 Mon Sep 17 00:00:00 2001 From: Eric Moore Date: Thu, 27 Aug 2026 22:09:31 -0500 Subject: [PATCH 03/11] fix: both round-one fixes were written against the example, not the question MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex reviewed my fixes for its own findings and both were wrong, in the same way, and it is the same way the inherited fixes were wrong: written against the case that motivated them rather than against the question being asked. 1 I PASSED THE STALE URL WHILE SAYING I PASSED THE CURRENT ONE. P1. The fix threaded an `activeNodeUrl` through `wipeLocalData` and CIRISApp passed `nodeBaseUrl` — an immutable CIRISApp PARAMETER, fixed for the composition. `switchTo` mutates `apiClient.baseUrl` through `updateBaseUrl` and never touches the parameter. So after a switch to a remote profile the guard still read loopback and still said "ours": the whole fix did nothing for the exact case it was written for. Worse than not fixing it, because the line carried a comment asserting it passed the URL the client was talking to right now. A reader checking this would have read the claim and moved on. Now `apiClient.baseUrl`. 2 ONE QUOTED ARM CANNOT COVER TWO DELIMITERS. P1. The arm excluded BOTH quote characters from the value, so a double-quoted secret containing an apostrophe never matched: password="correct horse's battery staple" unchanged, no match and fell through to the unquoted arm, which stops at whitespace — leaking everything after the first word again. That is the defect I had just fixed, reappearing through the character I chose to exclude. One arm per delimiter, each excluding only its own, so the opposite quote can appear inside the value — which it may, and does in ordinary English. `SECRET_NAMES` and `NOT_A_STATE` are now single definitions shared by all three arms, because three copies of an alternation is a drift waiting to happen and this file now has three arms. Four redaction cases in total for the quoting rules, and both delimiters are tested carrying the other. `:shared:desktopTest` green, desktop compiles. The pattern across both rounds is worth naming: a fix aimed at the reproduction passes the reproduction. The question was never "does this handle the example" but "what does this guard actually read" and "what does this pattern actually delimit". Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01EGE52kPzjGFiPzGcs63ZC1 --- client/VENDORING.md | 2 +- .../kotlin/ai/ciris/mobile/shared/CIRISApp.kt | 19 +++++-- .../shared/ui/components/DebugBundle.kt | 55 +++++++++++-------- .../ui/components/DebugBundleRedactionTest.kt | 17 ++++++ 4 files changed, 64 insertions(+), 29 deletions(-) diff --git a/client/VENDORING.md b/client/VENDORING.md index 2283895..e082c36 100644 --- a/client/VENDORING.md +++ b/client/VENDORING.md @@ -40,7 +40,7 @@ source is the pair a bisect wants: The tree's current recorded state — sha256-of-sha256s over every git-tracked file under `client/` except this one: -**state digest:** `32937ab42adece75ebd3cae8f680afc65e288b04343d9a48fa58d0062ff7c107` +**state digest:** `eaddcbf4a29bac06687344ce292edb2cde43a70619e970bc67ec38a3cd63f428` `packaging/check_vendoring.py` asserts it on every push, and refuses any tracked file matching a §2 never-vendor class. **Any commit that touches diff --git a/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/CIRISApp.kt b/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/CIRISApp.kt index 444e3f6..23f3706 100644 --- a/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/CIRISApp.kt +++ b/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/CIRISApp.kt @@ -2103,11 +2103,20 @@ fun CIRISApp( val wiped = withContext(Dispatchers.Default) { ai.ciris.mobile.shared.platform.wipeLocalData( declaredHome, - // The URL this client is talking to RIGHT NOW. - // A node switch repoints the API client - // without touching the environment, so the - // environment is not the answer (Codex, PR #18). - activeNodeUrl = nodeBaseUrl, + // `apiClient.baseUrl`, NOT `nodeBaseUrl`. + // + // I wrote this line claiming to pass the + // current URL and passed the stale one + // (Codex, PR #18). `nodeBaseUrl` is an + // immutable CIRISApp PARAMETER, fixed for + // the composition; `switchTo` mutates + // `apiClient.baseUrl` via updateBaseUrl and + // never touches the parameter. So after a + // switch to a remote profile this still + // read loopback — the guard accepted it, + // and the whole fix did nothing for the + // exact case it was written for. + activeNodeUrl = apiClient.baseUrl, ) } platformLog(TAG, "[INFO][onResetSetup] wipeLocalData -> $wiped") diff --git a/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundle.kt b/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundle.kt index 7123246..988b86a 100644 --- a/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundle.kt +++ b/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundle.kt @@ -89,36 +89,45 @@ object DebugBundle { * (a JWT, a bearer header, an explicit assignment to a secret-ish name) * rather than anything that merely looks high-entropy. */ + /** Names people give to secrets. One definition, so the arms cannot drift. */ + private const val SECRET_NAMES = + """api[_-]?key|access[_-]?token|refresh[_-]?token|id[_-]?token|""" + + """auth[_-]?token|token|secret|password|passwd|client[_-]?secret""" + + /** + * `token: expired` is a STATE, not a credential. Redacting it costs the + * reader the one fact the line carried. + */ + private const val NOT_A_STATE = + """expired|missing|present|invalid|unknown|revoked|pending|""" + + """refreshed|required|rejected|absent""" + private val SECRET_PATTERNS: List> = listOf( // JWTs — the shape is unmistakable and never appears in prose. Regex("""eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}(\.[A-Za-z0-9_-]+)?""") to "", // Authorization headers, including the `service:TOKEN` form. Regex("""(?i)\b(bearer\s+)(service:)?[A-Za-z0-9._~+/=-]{12,}""") to "$1", - // QUOTED value — matched through the CLOSING QUOTE, and this arm must - // come first. A single pattern that stops at whitespace leaks most of - // the secret it claims to remove: `password="correct horse battery - // staple"` redacted to `password=" horse battery staple"`, - // three words of a four-word passphrase still in the bundle (Codex, - // PR #18). Quoted secrets legitimately contain spaces, so the quote is - // the delimiter, not whitespace. + // DOUBLE-quoted value. The value class excludes ONLY this delimiter. + // + // A single quoted arm written as `[^"']` fails the moment a + // double-quoted secret contains an apostrophe — `password="correct + // horse's battery staple"` did not match at all, and fell through to + // the unquoted arm, which redacted the prefix and left the rest in the + // bundle (Codex, PR #18). One delimiter per arm is the only way the + // opposite quote can appear inside the value, which it may. + // + // `["]` rather than a bare quote: a raw string cannot end with one. + Regex( + """(?i)\b($SECRET_NAMES)(\s*[:=]\s*)["](?!(?:$NOT_A_STATE)["])([^"]{6,})["]""" + ) to "\$1\$2\"\"", + // SINGLE-quoted value, same rule, mirrored. Regex( - """(?i)\b(api[_-]?key|access[_-]?token|refresh[_-]?token|id[_-]?token|""" - + """auth[_-]?token|token|secret|password|passwd|client[_-]?secret)""" - + """(\s*[:=]\s*)(["'])""" - + """(?!(?:expired|missing|present|invalid|unknown|revoked|pending|""" - + """refreshed|required|rejected|absent)["'])""" - + """([^"']{6,})(\3)""" - ) to "$1$2$3$3", - // UNQUOTED name = value, where the name is one people give to secrets. + """(?i)\b($SECRET_NAMES)(\s*[:=]\s*)['](?!(?:$NOT_A_STATE)['])([^']{6,})[']""" + ) to "\$1\$2''", + // UNQUOTED name = value. Whitespace really is the delimiter here. Regex( - """(?i)\b(api[_-]?key|access[_-]?token|refresh[_-]?token|id[_-]?token|""" - + """auth[_-]?token|token|secret|password|passwd|client[_-]?secret)""" - + """(\s*["']?\s*[:=]\s*["']?)""" - // `token: expired` is a STATE, not a credential. Redacting it - // costs the reader the one fact the line carried. - + """(?!(?:expired|missing|present|invalid|unknown|revoked|pending|""" - + """refreshed|required|rejected|absent)\b)""" - + """([^\s"',;)}\]]{6,})""" + """(?i)\b($SECRET_NAMES)(\s*["']?\s*[:=]\s*["']?)""" + + """(?!(?:$NOT_A_STATE)\b)([^\s"',;)}\]]{6,})""" ) to "$1$2", ) diff --git a/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundleRedactionTest.kt b/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundleRedactionTest.kt index 59b7692..3b58bd4 100644 --- a/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundleRedactionTest.kt +++ b/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundleRedactionTest.kt @@ -96,4 +96,21 @@ class DebugBundleRedactionTest { val out = DebugBundle.redactSecrets("""token="expired"""" + "\"") assertTrue(out.contains("expired"), "a state was redacted to nothing: $out") } + + @Test + fun a_double_quoted_secret_may_contain_an_apostrophe() { + // A single arm excluding BOTH quote types never matched this, so it + // fell through to the unquoted arm and leaked the rest (Codex, PR #18). + val out = DebugBundle.redactSecrets("password=\"correct horse's battery staple\"") + assertFalse(out.contains("battery"), "the credential survived: $out") + assertFalse(out.contains("staple"), "the credential survived: $out") + assertTrue(out.contains(""), out) + } + + @Test + fun a_single_quoted_secret_may_contain_a_double_quote() { + val out = DebugBundle.redactSecrets("password='he said \"open sesame\" twice'") + assertFalse(out.contains("sesame"), "the credential survived: $out") + assertTrue(out.contains(""), out) + } } \ No newline at end of file From c94ab8411b09a3cf1c6f1db2da114e7d3a4b7ea9 Mon Sep 17 00:00:00 2001 From: Eric Moore Date: Thu, 27 Aug 2026 22:24:50 -0500 Subject: [PATCH 04/11] fix: the redactor was blind to the names this codebase actually uses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Round three, and one finding changes what the redaction is FOR. COMPOUND NAMES. P1, and the important one. `\b(api[_-]?key|password|...)` cannot fire inside `llm_api_key`: `_` is a word character, so there is no boundary before `api`. The spellings this codebase uses for credentials are all compound — llm_api_key 7 sites system_admin_password 4 backup_llm_api_key 3 owner_password 1 — so the pattern was blind to exactly the names a leak here would wear. I told this repo's owner earlier that the redactor guards a hypothetical future log line. That was true, and it guarded it INCORRECTLY: the 2am mistake would be written `platformLog("llm_api_key=$key")`, because that is what this code calls the thing, and none of it would have matched. A shape matcher with a blind spot aligned to the local naming convention is worse than none, because it reports having looked. The name may now carry a prefix, with the delimiter still required IMMEDIATELY after it — which is what keeps `llm_api_key_set=true` readable: no split of that string ends in a credential name followed by `=`. Tested in both directions. ESCAPED DELIMITERS. P1. `[^"]` reads the `\"` in `{"password":"correct \"horse\" battery staple"}` as the closing quote and stops there. The value now consumes escapes as part of itself. Fixing that surfaced a second miss in the same input: in JSON the NAME is quoted too, so the delimiter run is `":"` and the pattern had no room for the name's own closing quote. Both are in, and the JSON form is the case in the test. EXPANDED IPv6 LOOPBACK. P2, with the worst consequence in the flow. `::1` and `0:0:0:0:0:0:0:1` are one address and `URI.host` returns whichever was typed, so a desktop configured with the expanded form was classified REMOTE and refused — after reset had already shut the local runtime down. The operator is left logged out, node stopped, nothing erased. Now the STACK decides what loopback means (`InetAddress.isLoopbackAddress || isAnyLocalAddress`), gated behind a hex-colons-dots test so a hostname is never resolved: a DNS lookup here would be slow and would also let a remote name answer "loopback". 12 redaction cases and 7 wipe cases, both directions each — what must be redacted, and what must survive. THREE ROUNDS, EIGHT DEFECTS, ALL IN ONE REGEX. That is the argument against shape matching making an artifact safe, and it should be recorded rather than smoothed over: every round found a credential form the previous round's pattern missed, and there is no reason to believe this round is the last. The control that would actually prevent the leak is on the logging side, where it is decidable — this remains defense in depth and should not be described as more. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01EGE52kPzjGFiPzGcs63ZC1 --- client/VENDORING.md | 2 +- .../shared/ui/components/DebugBundle.kt | 27 ++++++++++++-- .../shared/platform/LocalDataWipe.desktop.kt | 26 +++++++++++++- .../shared/platform/LocalDataWipeTest.kt | 22 +++++++++++- .../ui/components/DebugBundleRedactionTest.kt | 35 +++++++++++++++++++ 5 files changed, 106 insertions(+), 6 deletions(-) diff --git a/client/VENDORING.md b/client/VENDORING.md index e082c36..52bd4ab 100644 --- a/client/VENDORING.md +++ b/client/VENDORING.md @@ -40,7 +40,7 @@ source is the pair a bisect wants: The tree's current recorded state — sha256-of-sha256s over every git-tracked file under `client/` except this one: -**state digest:** `eaddcbf4a29bac06687344ce292edb2cde43a70619e970bc67ec38a3cd63f428` +**state digest:** `9d2db2dbe3245ebb490dd76054e59435528ba6ed1dbd668bef8bebfad6fac147` `packaging/check_vendoring.py` asserts it on every push, and refuses any tracked file matching a §2 never-vendor class. **Any commit that touches diff --git a/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundle.kt b/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundle.kt index 988b86a..7028021 100644 --- a/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundle.kt +++ b/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundle.kt @@ -89,6 +89,22 @@ object DebugBundle { * (a JWT, a bearer header, an explicit assignment to a secret-ish name) * rather than anything that merely looks high-entropy. */ + /** + * A credential name, optionally CARRYING A PREFIX. + * + * `\b(api[_-]?key)` cannot fire inside `llm_api_key`: `_` is a word + * character, so there is no boundary before `api`. The names this codebase + * actually uses are the compound ones — `llm_api_key` (7 sites), + * `system_admin_password`, `backup_llm_api_key`, `owner_password` — so the + * pattern was blind to exactly the spellings a leak here would wear + * (Codex, PR #18). + * + * `[a-z0-9_]*` before the name, and the delimiter still required + * IMMEDIATELY after it — which is what keeps `llm_api_key_set=true` out: + * no split of that string ends in a credential name followed by `=`. + */ + private const val NAME_PREFIX = """[a-z0-9_]*""" + /** Names people give to secrets. One definition, so the arms cannot drift. */ private const val SECRET_NAMES = """api[_-]?key|access[_-]?token|refresh[_-]?token|id[_-]?token|""" + @@ -117,16 +133,21 @@ object DebugBundle { // opposite quote can appear inside the value, which it may. // // `["]` rather than a bare quote: a raw string cannot end with one. + // + // The value consumes ESCAPED characters as part of itself. `[^"]` reads + // the `\"` in `{"password":"correct \"horse\" battery staple"}` as the + // closing delimiter and stops there, leaving the rest of the passphrase + // in the bundle — the same leak again, through the escape this time. Regex( - """(?i)\b($SECRET_NAMES)(\s*[:=]\s*)["](?!(?:$NOT_A_STATE)["])([^"]{6,})["]""" + """(?i)\b($NAME_PREFIX(?:$SECRET_NAMES))(["']?\s*[:=]\s*)["](?!(?:$NOT_A_STATE)["])((?:[^"\\]|\\.){6,})["]""" ) to "\$1\$2\"\"", // SINGLE-quoted value, same rule, mirrored. Regex( - """(?i)\b($SECRET_NAMES)(\s*[:=]\s*)['](?!(?:$NOT_A_STATE)['])([^']{6,})[']""" + """(?i)\b($NAME_PREFIX(?:$SECRET_NAMES))(["']?\s*[:=]\s*)['](?!(?:$NOT_A_STATE)['])((?:[^'\\]|\\.){6,})[']""" ) to "\$1\$2''", // UNQUOTED name = value. Whitespace really is the delimiter here. Regex( - """(?i)\b($SECRET_NAMES)(\s*["']?\s*[:=]\s*["']?)""" + + """(?i)\b($NAME_PREFIX(?:$SECRET_NAMES))(\s*["']?\s*[:=]\s*["']?)""" + """(?!(?:$NOT_A_STATE)\b)([^\s"',;)}\]]{6,})""" ) to "$1$2", ) diff --git a/client/shared/src/desktopMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.desktop.kt b/client/shared/src/desktopMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.desktop.kt index 9f56179..9b2a734 100644 --- a/client/shared/src/desktopMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.desktop.kt +++ b/client/shared/src/desktopMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.desktop.kt @@ -99,6 +99,9 @@ private fun isSourceCheckout(home: File): Boolean = */ private val LOOPBACK_IPV4 = Regex("""^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$""") +/** Hex, colons and dots only — an address literal, never a name to resolve. */ +private val LOOPS_LIKE_AN_IP = Regex("""^[0-9a-f:.]+$""") + /** * True when the backend this client is driving is one we could own locally. * @@ -122,7 +125,28 @@ internal fun ownsLocalBackend(apiUrl: String?): Boolean { val host = runCatching { java.net.URI(url).host }.getOrNull() ?: return false // unparseable: assume not ours val h = host.trim().removePrefix("[").removeSuffix("]").lowercase() - return h == "localhost" || h == "::1" || h == "0.0.0.0" || LOOPBACK_IPV4.matches(h) + if (h == "localhost") return true + + // ASK THE STACK, DO NOT SPELL OUT THE ADDRESSES. + // + // `::1` and `0:0:0:0:0:0:0:1` are the same address, and `URI.host` returns + // whichever spelling was typed — so a desktop configured with the expanded + // form was classified REMOTE and refused (Codex, PR #18). The consequence is + // the worst ordering in this flow: reset has already shut the local runtime + // down before this predicate runs, so a false negative leaves the operator + // logged out, the node stopped, and nothing erased. + // + // Only for things that already look like an address, so this never resolves + // a hostname — a DNS lookup here would be both slow and a way to make a + // remote name answer "loopback". + if (LOOPS_LIKE_AN_IP.matches(h)) { + val verdict = runCatching { + val addr = java.net.InetAddress.getByName(h) + addr.isLoopbackAddress || addr.isAnyLocalAddress + }.getOrNull() + if (verdict != null) return verdict + } + return LOOPBACK_IPV4.matches(h) } /** diff --git a/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipeTest.kt b/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipeTest.kt index dba3ce4..b9ddf1a 100644 --- a/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipeTest.kt +++ b/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipeTest.kt @@ -115,4 +115,24 @@ class LocalDataWipeTest { assertFalse(ownsLocalBackend("http://127.0.0.1.evil.com/")) assertFalse(ownsLocalBackend("http://localhost.evil.com/")) } -} + + @Test + fun expanded_ipv6_loopback_is_still_loopback() { + // `::1` and `0:0:0:0:0:0:0:1` are one address; URI.host returns the + // spelling that was typed. A false negative here is the worst case in + // the flow: reset has already stopped the runtime, so the operator is + // logged out with nothing erased (Codex, PR #18). + assertTrue(ownsLocalBackend("http://[0:0:0:0:0:0:0:1]:8080")) + assertTrue(ownsLocalBackend("http://[::1]:8080")) + assertTrue(ownsLocalBackend("http://127.0.0.1:4243")) + assertTrue(ownsLocalBackend("http://localhost:4243")) + } + + @Test + fun a_remote_host_is_still_remote_and_is_never_resolved() { + assertFalse(ownsLocalBackend("http://192.168.50.8:4243")) + assertFalse(ownsLocalBackend("http://node.example.com:4243")) + // A name that merely BEGINS with a loopback literal is an ordinary host. + assertFalse(ownsLocalBackend("http://127.0.0.1.evil.com:4243")) + } +} \ No newline at end of file diff --git a/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundleRedactionTest.kt b/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundleRedactionTest.kt index 3b58bd4..8f91f32 100644 --- a/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundleRedactionTest.kt +++ b/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundleRedactionTest.kt @@ -113,4 +113,39 @@ class DebugBundleRedactionTest { assertFalse(out.contains("sesame"), "the credential survived: $out") assertTrue(out.contains(""), out) } + + @Test + fun compound_credential_names_are_the_ones_this_codebase_uses() { + // llm_api_key appears 7 times in the models; \b cannot fire after an + // underscore, so the pattern was blind to exactly these spellings + // (Codex, PR #18). + for (line in listOf( + "llm_api_key=sk-abcdef123456", + "owner_password=hunter2hunter2", + "system_admin_password=hunter2hunter2", + "backup_llm_api_key=sk-abcdef123456", + )) { + val out = DebugBundle.redactSecrets(line) + assertTrue(out.contains(""), "not redacted: $out") + assertFalse(out.contains("sk-abcdef123456"), "credential survived: $out") + assertFalse(out.contains("hunter2hunter2"), "credential survived: $out") + } + } + + @Test + fun a_status_flag_is_not_a_credential() { + // `llm_api_key_set=true` is a boolean about configuration. Redacting it + // costs the reader the fact the line carried. + val out = DebugBundle.redactSecrets("llm_api_key_set=true") + assertTrue(out.contains("true"), "a status flag was redacted: $out") + } + + @Test + fun an_escaped_delimiter_does_not_end_the_value() { + val out = DebugBundle.redactSecrets( + "{\"password\":\"correct \\\"horse\\\" battery staple\"}" + ) + assertFalse(out.contains("battery"), "the credential survived: $out") + assertFalse(out.contains("staple"), "the credential survived: $out") + } } \ No newline at end of file From 43ac4ae24bddc3c2bd49ef322f016d9006e5166b Mon Sep 17 00:00:00 2001 From: Eric Moore Date: Thu, 27 Aug 2026 22:42:59 -0500 Subject: [PATCH 05/11] fix: the redactor crashed on web, ignored short secrets, and reset stopped nodes it would not wipe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Round four. Three findings, and one of them is a live crash rather than a leak. `(?i)` IS INVALID IN ECMAScript. P1. On wasmJs Kotlin's Regex is backed by JS regular expressions, which reject the inline flag — so building SECRET_PATTERNS threw at object initialization. `DebugBundle.environment()` touches the object, and that is what "Open a GitHub issue" calls on a failure panel: the button on the screen you reach WHEN SOMETHING IS ALREADY WRONG crashed before it could open the report. Inherited verbatim from CIRISAgent, where nothing builds wasm. Now `RegexOption.IGNORE_CASE`, which every target honours. SHORT VALUES WERE NOT REDACTED. P1. The quantifier was `{6,}`, so `client_secret="abc"` and `password=12345` were exported whole. The NAME is the shape check — that is the entire premise of matching on names — and length was never doing work the name was not already doing. Relaxed to `+`, which makes the state list load-bearing for the first time: `token: ok` and `password: none` are now long enough to match, so the exclusions grew to cover them and both directions are tested. RESET STOPPED THE NODE IT THEN REFUSED TO WIPE. P2. `wipeLocalData` correctly declines when the client is driving a remote node, and the reset flow called `pythonRuntime.shutdown()` BEFORE it. So on a remote profile the app killed an unrelated local node, logged the operator out, and declined to wipe — reporting failure for having done the right thing, having already done a wrong one. The refusal was right and its position was not. `ownsLocalNode(activeNodeUrl)` is now an expect/actual so the question can be asked from common code before anything is stopped: desktop delegates to the real loopback check, Android and iOS answer true (the node is in-process, there is no other machine to confuse it with), and web answers false (no local runtime, so no local state to erase). 15 redaction cases. Both directions on every rule. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01EGE52kPzjGFiPzGcs63ZC1 --- client/VENDORING.md | 2 +- .../shared/platform/LocalDataWipe.android.kt | 4 +++ .../kotlin/ai/ciris/mobile/shared/CIRISApp.kt | 22 ++++++++++++++ .../mobile/shared/platform/LocalDataWipe.kt | 14 +++++++++ .../shared/ui/components/DebugBundle.kt | 19 ++++++++---- .../shared/platform/LocalDataWipe.desktop.kt | 4 +++ .../ui/components/DebugBundleRedactionTest.kt | 30 +++++++++++++++++++ .../shared/platform/LocalDataWipe.ios.kt | 4 +++ .../shared/platform/LocalDataWipe.wasmJs.kt | 4 +++ 9 files changed, 96 insertions(+), 7 deletions(-) diff --git a/client/VENDORING.md b/client/VENDORING.md index 52bd4ab..216f47c 100644 --- a/client/VENDORING.md +++ b/client/VENDORING.md @@ -40,7 +40,7 @@ source is the pair a bisect wants: The tree's current recorded state — sha256-of-sha256s over every git-tracked file under `client/` except this one: -**state digest:** `9d2db2dbe3245ebb490dd76054e59435528ba6ed1dbd668bef8bebfad6fac147` +**state digest:** `15cb6499d91ccec385241d55fdece5177ecb9100d20b6a2957cc8d58c9943bac` `packaging/check_vendoring.py` asserts it on every push, and refuses any tracked file matching a §2 never-vendor class. **Any commit that touches diff --git a/client/shared/src/androidMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.android.kt b/client/shared/src/androidMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.android.kt index aaffb58..39ffd35 100644 --- a/client/shared/src/androidMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.android.kt +++ b/client/shared/src/androidMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.android.kt @@ -81,3 +81,7 @@ private var wipeContext: Context? = null fun initLocalDataWipe(context: Context) { wipeContext = context.applicationContext } + + +/** Android runs its node in-process; there is no other machine to confuse it with. */ +actual fun ownsLocalNode(activeNodeUrl: String?): Boolean = true diff --git a/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/CIRISApp.kt b/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/CIRISApp.kt index 23f3706..c6b3c5d 100644 --- a/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/CIRISApp.kt +++ b/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/CIRISApp.kt @@ -2081,6 +2081,28 @@ fun CIRISApp( // ANR before any feedback appears, which reads as the reset hanging // rather than working. coroutineScope.launch { + // OWNERSHIP FIRST, BEFORE ANYTHING IS STOPPED OR DELETED + // (Codex, PR #18). + // + // `wipeLocalData` refuses when the client is driving a + // REMOTE node, and that refusal was correct and badly + // placed: the shutdown below already ran, so a remote + // profile meant this killed an unrelated local node, + // logged the operator out, and then declined to wipe — + // the worst of both, and it reported failure for having + // done the right thing. + // + // Ask before touching anything. + if (!ai.ciris.mobile.shared.platform.ownsLocalNode(apiClient.baseUrl)) { + platformLog(TAG, "[WARN][onResetSetup] refusing: ${apiClient.baseUrl} is not a node this device owns") + // The surface the reset dialog already reports on. + loginErrorMessage = + "This client is connected to a node on another machine. " + + "Reset erases local node data, and there is none here " + + "for that node — reset it from the machine it runs on." + return@launch + } + // STOP THE NODE BEFORE DELETING ITS LIVE FILES (Codex, PR #9). // This used to run after the wipe. On Windows an open SQLite or // log handle makes deletion fail outright; on Unix it succeeds diff --git a/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.kt b/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.kt index 68cba30..578e81f 100644 --- a/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.kt +++ b/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.kt @@ -52,3 +52,17 @@ expect fun wipeLocalData( declaredNodeHome: String? = null, activeNodeUrl: String? = null, ): Boolean + + +/** + * Can this device wipe the node it is currently talking to? + * + * ASKED BEFORE ANYTHING IS DESTROYED. `wipeLocalData` already refuses when the + * client is driving a remote node, but the reset flow stops the local runtime + * FIRST — so a remote profile meant the app killed an unrelated local node, + * logged the operator out, and then declined to wipe anything (Codex, PR #18). + * The refusal was right and its position was not. + * + * @param activeNodeUrl the URL the client is actually using right now. + */ +expect fun ownsLocalNode(activeNodeUrl: String?): Boolean diff --git a/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundle.kt b/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundle.kt index 7028021..01fcb18 100644 --- a/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundle.kt +++ b/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundle.kt @@ -116,13 +116,17 @@ object DebugBundle { */ private const val NOT_A_STATE = """expired|missing|present|invalid|unknown|revoked|pending|""" + - """refreshed|required|rejected|absent""" + """refreshed|required|rejected|absent|ok|set|unset|none|null|""" + + """true|false|yes|no|enabled|disabled|empty|blank|valid""" private val SECRET_PATTERNS: List> = listOf( // JWTs — the shape is unmistakable and never appears in prose. Regex("""eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}(\.[A-Za-z0-9_-]+)?""") to "", // Authorization headers, including the `service:TOKEN` form. - Regex("""(?i)\b(bearer\s+)(service:)?[A-Za-z0-9._~+/=-]{12,}""") to "$1", + Regex( + """\b(bearer\s+)(service:)?[A-Za-z0-9._~+/=-]{12,}""", + RegexOption.IGNORE_CASE, + ) to "$1", // DOUBLE-quoted value. The value class excludes ONLY this delimiter. // // A single quoted arm written as `[^"']` fails the moment a @@ -139,16 +143,19 @@ object DebugBundle { // closing delimiter and stops there, leaving the rest of the passphrase // in the bundle — the same leak again, through the escape this time. Regex( - """(?i)\b($NAME_PREFIX(?:$SECRET_NAMES))(["']?\s*[:=]\s*)["](?!(?:$NOT_A_STATE)["])((?:[^"\\]|\\.){6,})["]""" + """\b($NAME_PREFIX(?:$SECRET_NAMES))(["']?\s*[:=]\s*)["](?!(?:$NOT_A_STATE)["])((?:[^"\\]|\\.)+)["]""", + RegexOption.IGNORE_CASE, ) to "\$1\$2\"\"", // SINGLE-quoted value, same rule, mirrored. Regex( - """(?i)\b($NAME_PREFIX(?:$SECRET_NAMES))(["']?\s*[:=]\s*)['](?!(?:$NOT_A_STATE)['])((?:[^'\\]|\\.){6,})[']""" + """\b($NAME_PREFIX(?:$SECRET_NAMES))(["']?\s*[:=]\s*)['](?!(?:$NOT_A_STATE)['])((?:[^'\\]|\\.)+)[']""", + RegexOption.IGNORE_CASE, ) to "\$1\$2''", // UNQUOTED name = value. Whitespace really is the delimiter here. Regex( - """(?i)\b($NAME_PREFIX(?:$SECRET_NAMES))(\s*["']?\s*[:=]\s*["']?)""" + - """(?!(?:$NOT_A_STATE)\b)([^\s"',;)}\]]{6,})""" + """\b($NAME_PREFIX(?:$SECRET_NAMES))(\s*["']?\s*[:=]\s*["']?)""" + + """(?!(?:$NOT_A_STATE)\b)([^\s"',;)}\]]+)""", + RegexOption.IGNORE_CASE, ) to "$1$2", ) diff --git a/client/shared/src/desktopMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.desktop.kt b/client/shared/src/desktopMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.desktop.kt index 9b2a734..238f45b 100644 --- a/client/shared/src/desktopMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.desktop.kt +++ b/client/shared/src/desktopMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.desktop.kt @@ -289,3 +289,7 @@ private fun clearSecurePreferences(): Boolean = runCatching { println("[LocalDataWipe] could not clear secure preferences: ${e.message}") false } + + +actual fun ownsLocalNode(activeNodeUrl: String?): Boolean = + ownsLocalBackend(activeNodeUrlOrEnv(activeNodeUrl)) diff --git a/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundleRedactionTest.kt b/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundleRedactionTest.kt index 8f91f32..b5b93e1 100644 --- a/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundleRedactionTest.kt +++ b/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundleRedactionTest.kt @@ -148,4 +148,34 @@ class DebugBundleRedactionTest { assertFalse(out.contains("battery"), "the credential survived: $out") assertFalse(out.contains("staple"), "the credential survived: $out") } + + @Test + fun a_short_credential_is_still_a_credential() { + // The value quantifier was {6,}, so anything shorter was exported whole + // (Codex, PR #18). The NAME is the shape check; length was never it. + for (line in listOf("client_secret=\"abc\"", "password=12345", "token=x1")) { + val out = DebugBundle.redactSecrets(line) + assertTrue(out.contains(""), "not redacted: $out") + } + } + + @Test + fun a_short_status_is_still_a_status() { + // Relaxing the quantifier made the state list load-bearing for words + // that are now long enough to match. + for (line in listOf("token: ok", "llm_api_key=unset", "password: none", "secret=null")) { + val out = DebugBundle.redactSecrets(line) + assertFalse(out.contains(""), "a status was redacted: $out") + } + } + + @Test + fun the_patterns_construct_without_inline_flags() { + // `(?i)` is invalid in ECMAScript, so on wasmJs building these threw at + // object init — and `environment()` touches the object, so "Open a + // GitHub issue" on a failure panel crashed before it could open + // (Codex, PR #18). Constructing and using them is the assertion. + assertTrue(DebugBundle.redactSecrets("API_KEY=sk-abcdef").contains("")) + assertTrue(DebugBundle.redactSecrets("Bearer AbCdEf0123456789").contains("")) + } } \ No newline at end of file diff --git a/client/shared/src/iosMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.ios.kt b/client/shared/src/iosMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.ios.kt index dd04980..194b65b 100644 --- a/client/shared/src/iosMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.ios.kt +++ b/client/shared/src/iosMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.ios.kt @@ -67,3 +67,7 @@ actual fun wipeLocalData(declaredNodeHome: String?, activeNodeUrl: String?): Boo return ok } + + +/** iOS runs its node in-process; there is no other machine to confuse it with. */ +actual fun ownsLocalNode(activeNodeUrl: String?): Boolean = true diff --git a/client/shared/src/wasmJsMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.wasmJs.kt b/client/shared/src/wasmJsMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.wasmJs.kt index e81e1e0..ced1498 100644 --- a/client/shared/src/wasmJsMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.wasmJs.kt +++ b/client/shared/src/wasmJsMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.wasmJs.kt @@ -6,3 +6,7 @@ package ai.ciris.mobile.shared.platform * the UI can say so instead of claiming a wipe that did not happen. */ actual fun wipeLocalData(declaredNodeHome: String?, activeNodeUrl: String?): Boolean = false + + +/** Web has no local runtime at all, so there is never local node state to erase. */ +actual fun ownsLocalNode(activeNodeUrl: String?): Boolean = false From bc1920f6a3ee1b1f180cd0065c0a4008aba1d1be Mon Sep 17 00:00:00 2001 From: Eric Moore Date: Thu, 27 Aug 2026 23:01:13 -0500 Subject: [PATCH 06/11] fix(reset): name the button after what it does, and delete the machinery that compensated for not doing so MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reset device -> Reset local device Reset device? -> Reset this device? and the body now says a node you are connected to on another machine is not affected. THAT IS THE ENTIRE FIX, AND IT REPLACES FOUR ROUNDS OF MINE. Codex found that Reset wiped local state while the client was driving a remote node; I inherited CIRISAgent's guard for it, and then spent four review rounds making the guard correct — an env-var read that missed `CIRIS_NODE_URL`, a stale `nodeBaseUrl` passed while a comment claimed otherwise, an IPv6 spelling that classified a local node as remote, an ordering that stopped the runtime before refusing, and mobile actuals I hardcoded to `true` that would have deleted the on-device node. Every one of those was a real defect in a mechanism that should not exist. The button was not picking the wrong machine. It was failing to say which machine it meant. Naming it dissolves all of it: removed ownsLocalNode expect + four actuals removed isLoopbackUrl, LOOPBACK_IPV4, activeNodeUrlOrEnv, ownsLocalBackend removed the activeNodeUrl parameter threaded through wipeLocalData removed the pre-shutdown refusal in CIRISApp removed four tests that existed only to check the above AND THE FEATURE IS A CONVENIENCE, NOT A BOUNDARY. Anyone who can press this button can uninstall and reinstall, or delete the directory by hand. It exists so someone who forgot a password or wants to change the main login does not have to. A guard that refuses is protecting nothing — the same person achieves the same result with a file manager, slower. We do NOT inherit CIRISAgent's fix #5 for this. Deliberate, and recorded here rather than left as a silent gap: their guard answers a question the label now answers better. One redaction fix rides along, because the test that caught it was already failing: the state exemption's terminator set omitted the quote characters that the unquoted arm absorbs into its delimiter group, so `token="expired"` was redacted as though `expired` were a credential. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01EGE52kPzjGFiPzGcs63ZC1 --- client/VENDORING.md | 2 +- .../src/main/assets/localization/en.json | 6 +- .../src/main/resources/localization/en.json | 6 +- client/iosApp/iosApp/localization/en.json | 6 +- .../shared/platform/LocalDataWipe.android.kt | 4 +- .../kotlin/ai/ciris/mobile/shared/CIRISApp.kt | 36 ------- .../mobile/shared/platform/LocalDataWipe.kt | 28 +----- .../shared/ui/components/DebugBundle.kt | 16 +++- .../shared/platform/LocalDataWipe.desktop.kt | 94 +------------------ .../resources/localization/en.json | 6 +- .../shared/platform/LocalDataWipeTest.kt | 41 +------- .../ui/components/DebugBundleRedactionTest.kt | 33 ++++++- .../shared/platform/LocalDataWipe.ios.kt | 4 +- .../shared/platform/LocalDataWipe.wasmJs.kt | 4 +- 14 files changed, 64 insertions(+), 222 deletions(-) diff --git a/client/VENDORING.md b/client/VENDORING.md index 216f47c..a984821 100644 --- a/client/VENDORING.md +++ b/client/VENDORING.md @@ -40,7 +40,7 @@ source is the pair a bisect wants: The tree's current recorded state — sha256-of-sha256s over every git-tracked file under `client/` except this one: -**state digest:** `15cb6499d91ccec385241d55fdece5177ecb9100d20b6a2957cc8d58c9943bac` +**state digest:** `a5569b62d82ea1d8ce2a14f49f2044fc99d1c4d929362004a95002ddac36091d` `packaging/check_vendoring.py` asserts it on every push, and refuses any tracked file matching a §2 never-vendor class. **Any commit that touches diff --git a/client/androidApp/src/main/assets/localization/en.json b/client/androidApp/src/main/assets/localization/en.json index 8561878..a640c47 100644 --- a/client/androidApp/src/main/assets/localization/en.json +++ b/client/androidApp/src/main/assets/localization/en.json @@ -1928,10 +1928,10 @@ "login_owner_hint": "Last signed in: {owner}", "login_password_label": "Password", "login_privacy": "View Privacy Policy", - "login_reset_device": "Reset device", + "login_reset_device": "Reset local device", "login_reset_device_cancel": "Cancel", - "login_reset_device_confirm_body": "This will erase all local data and return to the setup wizard. This cannot be undone.", - "login_reset_device_confirm_title": "Reset device?", + "login_reset_device_confirm_body": "This erases the node data stored on THIS device and returns to the setup wizard. A node you are connected to on another machine is not affected. This cannot be undone.", + "login_reset_device_confirm_title": "Reset this device?", "login_setup_complete_relogin": "Setup is complete. Your node restarted, so please log back in with the same credentials you used to complete setup.", "login_signin_apple": "Sign in with Apple", "login_signin_google": "Sign in with Google", diff --git a/client/desktopApp/src/main/resources/localization/en.json b/client/desktopApp/src/main/resources/localization/en.json index 8561878..a640c47 100644 --- a/client/desktopApp/src/main/resources/localization/en.json +++ b/client/desktopApp/src/main/resources/localization/en.json @@ -1928,10 +1928,10 @@ "login_owner_hint": "Last signed in: {owner}", "login_password_label": "Password", "login_privacy": "View Privacy Policy", - "login_reset_device": "Reset device", + "login_reset_device": "Reset local device", "login_reset_device_cancel": "Cancel", - "login_reset_device_confirm_body": "This will erase all local data and return to the setup wizard. This cannot be undone.", - "login_reset_device_confirm_title": "Reset device?", + "login_reset_device_confirm_body": "This erases the node data stored on THIS device and returns to the setup wizard. A node you are connected to on another machine is not affected. This cannot be undone.", + "login_reset_device_confirm_title": "Reset this device?", "login_setup_complete_relogin": "Setup is complete. Your node restarted, so please log back in with the same credentials you used to complete setup.", "login_signin_apple": "Sign in with Apple", "login_signin_google": "Sign in with Google", diff --git a/client/iosApp/iosApp/localization/en.json b/client/iosApp/iosApp/localization/en.json index 8561878..a640c47 100644 --- a/client/iosApp/iosApp/localization/en.json +++ b/client/iosApp/iosApp/localization/en.json @@ -1928,10 +1928,10 @@ "login_owner_hint": "Last signed in: {owner}", "login_password_label": "Password", "login_privacy": "View Privacy Policy", - "login_reset_device": "Reset device", + "login_reset_device": "Reset local device", "login_reset_device_cancel": "Cancel", - "login_reset_device_confirm_body": "This will erase all local data and return to the setup wizard. This cannot be undone.", - "login_reset_device_confirm_title": "Reset device?", + "login_reset_device_confirm_body": "This erases the node data stored on THIS device and returns to the setup wizard. A node you are connected to on another machine is not affected. This cannot be undone.", + "login_reset_device_confirm_title": "Reset this device?", "login_setup_complete_relogin": "Setup is complete. Your node restarted, so please log back in with the same credentials you used to complete setup.", "login_signin_apple": "Sign in with Apple", "login_signin_google": "Sign in with Google", diff --git a/client/shared/src/androidMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.android.kt b/client/shared/src/androidMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.android.kt index 39ffd35..e427eb5 100644 --- a/client/shared/src/androidMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.android.kt +++ b/client/shared/src/androidMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.android.kt @@ -12,7 +12,7 @@ private const val TAG = "LocalDataWipe" * CIRIS_HOME is resolved through EnvFileUpdater's existing accessor rather than * rebuilt here — two spellings of that path is how a wipe half-succeeds. */ -actual fun wipeLocalData(declaredNodeHome: String?, activeNodeUrl: String?): Boolean { +actual fun wipeLocalData(declaredNodeHome: String?): Boolean { // Symmetry with desktop. Always false on a phone today, but the predicate is // the feature's one boundary and reading it here keeps that true if the // platform set ever changes. @@ -83,5 +83,3 @@ fun initLocalDataWipe(context: Context) { } -/** Android runs its node in-process; there is no other machine to confuse it with. */ -actual fun ownsLocalNode(activeNodeUrl: String?): Boolean = true diff --git a/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/CIRISApp.kt b/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/CIRISApp.kt index c6b3c5d..deecad1 100644 --- a/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/CIRISApp.kt +++ b/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/CIRISApp.kt @@ -2081,28 +2081,6 @@ fun CIRISApp( // ANR before any feedback appears, which reads as the reset hanging // rather than working. coroutineScope.launch { - // OWNERSHIP FIRST, BEFORE ANYTHING IS STOPPED OR DELETED - // (Codex, PR #18). - // - // `wipeLocalData` refuses when the client is driving a - // REMOTE node, and that refusal was correct and badly - // placed: the shutdown below already ran, so a remote - // profile meant this killed an unrelated local node, - // logged the operator out, and then declined to wipe — - // the worst of both, and it reported failure for having - // done the right thing. - // - // Ask before touching anything. - if (!ai.ciris.mobile.shared.platform.ownsLocalNode(apiClient.baseUrl)) { - platformLog(TAG, "[WARN][onResetSetup] refusing: ${apiClient.baseUrl} is not a node this device owns") - // The surface the reset dialog already reports on. - loginErrorMessage = - "This client is connected to a node on another machine. " + - "Reset erases local node data, and there is none here " + - "for that node — reset it from the machine it runs on." - return@launch - } - // STOP THE NODE BEFORE DELETING ITS LIVE FILES (Codex, PR #9). // This used to run after the wipe. On Windows an open SQLite or // log handle makes deletion fail outright; on Unix it succeeds @@ -2125,20 +2103,6 @@ fun CIRISApp( val wiped = withContext(Dispatchers.Default) { ai.ciris.mobile.shared.platform.wipeLocalData( declaredHome, - // `apiClient.baseUrl`, NOT `nodeBaseUrl`. - // - // I wrote this line claiming to pass the - // current URL and passed the stale one - // (Codex, PR #18). `nodeBaseUrl` is an - // immutable CIRISApp PARAMETER, fixed for - // the composition; `switchTo` mutates - // `apiClient.baseUrl` via updateBaseUrl and - // never touches the parameter. So after a - // switch to a remote profile this still - // read loopback — the guard accepted it, - // and the whole fix did nothing for the - // exact case it was written for. - activeNodeUrl = apiClient.baseUrl, ) } platformLog(TAG, "[INFO][onResetSetup] wipeLocalData -> $wiped") diff --git a/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.kt b/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.kt index 578e81f..c3f1717 100644 --- a/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.kt +++ b/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.kt @@ -39,30 +39,4 @@ package ai.ciris.mobile.shared.platform * authoritative for the claim PIN, for the same reason (Codex, PR #9). * Null when there is nobody to ask; the resolver then falls back to the guess. */ -/** - * @param activeNodeUrl the URL the client is ACTUALLY talking to right now, not - * an environment variable. `NodeSwitcherViewModel.switchTo` repoints the API - * client at a saved profile without touching the environment, and desktop - * prefers `CIRIS_NODE_URL` over `CIRIS_API_URL` — so a guard that re-reads one - * env var is guarding against a configuration the client may have left behind - * (Codex, PR #18). Null means "caller did not say", and the desktop actual - * falls back to the environment. - */ -expect fun wipeLocalData( - declaredNodeHome: String? = null, - activeNodeUrl: String? = null, -): Boolean - - -/** - * Can this device wipe the node it is currently talking to? - * - * ASKED BEFORE ANYTHING IS DESTROYED. `wipeLocalData` already refuses when the - * client is driving a remote node, but the reset flow stops the local runtime - * FIRST — so a remote profile meant the app killed an unrelated local node, - * logged the operator out, and then declined to wipe anything (Codex, PR #18). - * The refusal was right and its position was not. - * - * @param activeNodeUrl the URL the client is actually using right now. - */ -expect fun ownsLocalNode(activeNodeUrl: String?): Boolean +expect fun wipeLocalData(declaredNodeHome: String? = null): Boolean diff --git a/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundle.kt b/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundle.kt index 01fcb18..2a3a3fa 100644 --- a/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundle.kt +++ b/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundle.kt @@ -123,8 +123,13 @@ object DebugBundle { // JWTs — the shape is unmistakable and never appears in prose. Regex("""eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}(\.[A-Za-z0-9_-]+)?""") to "", // Authorization headers, including the `service:TOKEN` form. + // + // No length floor. `Bearer abc123` is a credential exactly as much as a + // 40-character one, and the explicit marker is the whole shape check — + // I removed the analogous minimum from the assignment arms last round + // and left this one behind (Codex, PR #18). Regex( - """\b(bearer\s+)(service:)?[A-Za-z0-9._~+/=-]{12,}""", + """\b(bearer\s+)(service:)?[A-Za-z0-9._~+/=-]+""", RegexOption.IGNORE_CASE, ) to "$1", // DOUBLE-quoted value. The value class excludes ONLY this delimiter. @@ -154,7 +159,14 @@ object DebugBundle { // UNQUOTED name = value. Whitespace really is the delimiter here. Regex( """\b($NAME_PREFIX(?:$SECRET_NAMES))(\s*["']?\s*[:=]\s*["']?)""" + - """(?!(?:$NOT_A_STATE)\b)([^\s"',;)}\]]+)""", + // THE STATE MUST BE THE ENTIRE VALUE. `\b` alone matches the + // boundary INSIDE `valid-secret-123`, `no-way-this-leaks` and + // `expired-value`, so the lookahead suppressed the match and + // exported the credential whole — a leak my own relaxed + // quantifier created, since these values were previously too + // short-circuited to reach it (Codex, PR #18). The quoted arms + // already required the closing delimiter; this one must too. + """(?!(?:$NOT_A_STATE)(?:["'\s,;)}\]]|${'$'}))([^\s"',;)}\]]+)""", RegexOption.IGNORE_CASE, ) to "$1$2", ) diff --git a/client/shared/src/desktopMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.desktop.kt b/client/shared/src/desktopMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.desktop.kt index 238f45b..6f2ce24 100644 --- a/client/shared/src/desktopMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.desktop.kt +++ b/client/shared/src/desktopMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.desktop.kt @@ -93,103 +93,13 @@ private fun isSourceCheckout(home: File): Boolean = * make this delete something it does not recognise. What survives is an empty * directory and any file we never wrote, which is exactly what should survive. */ -/** - * `127.x.y.z` as an ADDRESS, not as a prefix. `127.0.0.1.evil.com` is a - * perfectly ordinary hostname that a prefix test reads as loopback. - */ -private val LOOPBACK_IPV4 = Regex("""^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$""") - -/** Hex, colons and dots only — an address literal, never a name to resolve. */ -private val LOOPS_LIKE_AN_IP = Regex("""^[0-9a-f:.]+$""") - -/** - * True when the backend this client is driving is one we could own locally. - * - * INHERITED from CIRISAgent 2.9.41 (CIRISClient#11). `CIRIS_API_URL` is a - * supported way to point the client at a node on another host, and - * `PythonRuntime.desktop` honours it and never starts a local backend — but the - * wipe never consulted it. Reset then reported success having reset nothing the - * user was looking at, while deleting the database, identity and keys of - * whatever local node happened to share the disk. - * - * This is the FOURTH misresolution in this function and the first above the - * filesystem layer: the previous three picked the wrong directory, this one - * picked the wrong machine. - * - * Loopback is the test because it is the condition the node would have to - * satisfy for our home resolution to describe it at all. A remote node keeps - * its state on its own disk under its own `CIRIS_HOME`. - */ -internal fun ownsLocalBackend(apiUrl: String?): Boolean { - val url = apiUrl?.takeIf { it.isNotBlank() } ?: return true - val host = runCatching { java.net.URI(url).host }.getOrNull() - ?: return false // unparseable: assume not ours - val h = host.trim().removePrefix("[").removeSuffix("]").lowercase() - if (h == "localhost") return true - - // ASK THE STACK, DO NOT SPELL OUT THE ADDRESSES. - // - // `::1` and `0:0:0:0:0:0:0:1` are the same address, and `URI.host` returns - // whichever spelling was typed — so a desktop configured with the expanded - // form was classified REMOTE and refused (Codex, PR #18). The consequence is - // the worst ordering in this flow: reset has already shut the local runtime - // down before this predicate runs, so a false negative leaves the operator - // logged out, the node stopped, and nothing erased. - // - // Only for things that already look like an address, so this never resolves - // a hostname — a DNS lookup here would be both slow and a way to make a - // remote name answer "loopback". - if (LOOPS_LIKE_AN_IP.matches(h)) { - val verdict = runCatching { - val addr = java.net.InetAddress.getByName(h) - addr.isLoopbackAddress || addr.isAnyLocalAddress - }.getOrNull() - if (verdict != null) return verdict - } - return LOOPBACK_IPV4.matches(h) -} - -/** - * The URL the client is ACTUALLY using, in the order that decides it. - * - * The inherited guard read `CIRIS_API_URL` alone, and missed two live paths - * (Codex, PR #18): - * - * - `NodeSwitcherViewModel.switchTo` calls `apiClient.updateBaseUrl(...)` and - * never touches the environment, so after a switch to a saved remote - * profile the env still says loopback and the guard still says "ours". - * - `Main.kt` prefers `CIRIS_NODE_URL` over `CIRIS_API_URL`; a desktop - * configured with the former was checked against a variable nobody set. - * - * In both cases Reset would then treat a REMOTE node's declared home as a local - * path: deleting unrelated local state if that path happens to exist here, and - * otherwise reporting a successful reset while the node the user is looking at - * is untouched. That is the same "wrong machine" error the guard was added for, - * reached by a different road. - */ -private fun activeNodeUrlOrEnv(explicit: String?): String? = - explicit?.takeIf { it.isNotBlank() } - ?: System.getenv("CIRIS_NODE_URL")?.takeIf { it.isNotBlank() } - ?: System.getenv("CIRIS_API_URL") - -actual fun wipeLocalData(declaredNodeHome: String?, activeNodeUrl: String?): Boolean { +actual fun wipeLocalData(declaredNodeHome: String?): Boolean { // Managed deployments are not ours to wipe: `/app` belongs to CIRIS-Manager, // and the person at this UI is not necessarily entitled to destroy it. if (isManagedDeployment()) { println("[LocalDataWipe] refusing: CIRIS-Manager-managed deployment (/app)") return false } - - // A client pointed at a REMOTE node owns nothing here. Composed with the - // declared-home resolution below rather than replacing it: asking the node - // where it lives is right when the node is ours, and this decides whether - // it is. A remote node answers with a path on ITS disk, and acting on that - // locally is the same mistake one level further along. - if (!ownsLocalBackend(activeNodeUrlOrEnv(activeNodeUrl))) { - println("[LocalDataWipe] refusing: CIRIS_API_URL points at a node we do not own") - return false - } - val home = resolveNodeHome(declaredNodeHome) ?: return false if (!home.exists()) return true @@ -291,5 +201,3 @@ private fun clearSecurePreferences(): Boolean = runCatching { } -actual fun ownsLocalNode(activeNodeUrl: String?): Boolean = - ownsLocalBackend(activeNodeUrlOrEnv(activeNodeUrl)) diff --git a/client/shared/src/desktopMain/resources/localization/en.json b/client/shared/src/desktopMain/resources/localization/en.json index 8561878..a640c47 100644 --- a/client/shared/src/desktopMain/resources/localization/en.json +++ b/client/shared/src/desktopMain/resources/localization/en.json @@ -1928,10 +1928,10 @@ "login_owner_hint": "Last signed in: {owner}", "login_password_label": "Password", "login_privacy": "View Privacy Policy", - "login_reset_device": "Reset device", + "login_reset_device": "Reset local device", "login_reset_device_cancel": "Cancel", - "login_reset_device_confirm_body": "This will erase all local data and return to the setup wizard. This cannot be undone.", - "login_reset_device_confirm_title": "Reset device?", + "login_reset_device_confirm_body": "This erases the node data stored on THIS device and returns to the setup wizard. A node you are connected to on another machine is not affected. This cannot be undone.", + "login_reset_device_confirm_title": "Reset this device?", "login_setup_complete_relogin": "Setup is complete. Your node restarted, so please log back in with the same credentials you used to complete setup.", "login_signin_apple": "Sign in with Apple", "login_signin_google": "Sign in with Google", diff --git a/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipeTest.kt b/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipeTest.kt index b9ddf1a..f358758 100644 --- a/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipeTest.kt +++ b/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipeTest.kt @@ -93,46 +93,7 @@ class LocalDataWipeTest { assertTrue(home.listFiles()?.isEmpty() == true) } - @Test - fun `owns the backend only when it is loopback`() { - // Unset or blank: the client boots the node itself. - assertTrue(ownsLocalBackend(null)) - assertTrue(ownsLocalBackend("")) - - assertTrue(ownsLocalBackend("http://localhost:8080")) - assertTrue(ownsLocalBackend("http://127.0.0.1:8080")) - assertTrue(ownsLocalBackend("http://127.5.5.5:8080")) - assertTrue(ownsLocalBackend("http://[::1]:8080")) - assertTrue(ownsLocalBackend("http://0.0.0.0:8080")) - - // A node on another host keeps its state on its own disk. - assertFalse(ownsLocalBackend("https://agents.ciris.ai")) - assertFalse(ownsLocalBackend("http://192.168.1.50:8080")) - assertFalse(ownsLocalBackend("http://node.local:8080")) - assertFalse(ownsLocalBackend("garbage")) - - // Hostnames that only LOOK like loopback under a prefix test. - assertFalse(ownsLocalBackend("http://127.0.0.1.evil.com/")) - assertFalse(ownsLocalBackend("http://localhost.evil.com/")) - } - @Test - fun expanded_ipv6_loopback_is_still_loopback() { - // `::1` and `0:0:0:0:0:0:0:1` are one address; URI.host returns the - // spelling that was typed. A false negative here is the worst case in - // the flow: reset has already stopped the runtime, so the operator is - // logged out with nothing erased (Codex, PR #18). - assertTrue(ownsLocalBackend("http://[0:0:0:0:0:0:0:1]:8080")) - assertTrue(ownsLocalBackend("http://[::1]:8080")) - assertTrue(ownsLocalBackend("http://127.0.0.1:4243")) - assertTrue(ownsLocalBackend("http://localhost:4243")) - } - @Test - fun a_remote_host_is_still_remote_and_is_never_resolved() { - assertFalse(ownsLocalBackend("http://192.168.50.8:4243")) - assertFalse(ownsLocalBackend("http://node.example.com:4243")) - // A name that merely BEGINS with a loopback literal is an ordinary host. - assertFalse(ownsLocalBackend("http://127.0.0.1.evil.com:4243")) - } + } \ No newline at end of file diff --git a/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundleRedactionTest.kt b/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundleRedactionTest.kt index b5b93e1..c8ca0fc 100644 --- a/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundleRedactionTest.kt +++ b/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundleRedactionTest.kt @@ -85,7 +85,7 @@ class DebugBundleRedactionTest { fun a_quoted_secret_containing_spaces_is_removed_whole() { // The value group used to stop at the first space, so a four-word // passphrase lost one word and kept three (Codex, PR #18). - val out = DebugBundle.redactSecrets("""password="correct horse battery staple"""" + "\"") + val out = DebugBundle.redactSecrets("password=\"correct horse battery staple\"") assertFalse(out.contains("horse"), "the rest of the passphrase survived: $out") assertFalse(out.contains("staple"), "the rest of the passphrase survived: $out") assertTrue(out.contains(""), out) @@ -93,7 +93,7 @@ class DebugBundleRedactionTest { @Test fun a_quoted_state_is_still_not_a_credential() { - val out = DebugBundle.redactSecrets("""token="expired"""" + "\"") + val out = DebugBundle.redactSecrets("token=\"expired\"") assertTrue(out.contains("expired"), "a state was redacted to nothing: $out") } @@ -178,4 +178,33 @@ class DebugBundleRedactionTest { assertTrue(DebugBundle.redactSecrets("API_KEY=sk-abcdef").contains("")) assertTrue(DebugBundle.redactSecrets("Bearer AbCdEf0123456789").contains("")) } + + @Test + fun a_state_word_is_only_a_state_when_it_is_the_whole_value() { + // `\b` matched INSIDE these, so the lookahead suppressed the match and + // exported the credential whole — a leak the relaxed quantifier created + // (Codex, PR #18). + for (line in listOf( + "password=valid-secret-123", + "password=no-way-this-leaks", + "token=expired-value", + "llm_api_key=set-me-to-something", + )) { + val out = DebugBundle.redactSecrets(line) + assertTrue(out.contains(""), "not redacted: $out") + } + // and the genuine states still survive + for (line in listOf("password=valid", "token=expired", "llm_api_key=set")) { + assertFalse(DebugBundle.redactSecrets(line).contains(""), line) + } + } + + @Test + fun a_short_bearer_token_is_still_a_bearer_token() { + for (line in listOf("Authorization: Bearer abc123", "Bearer service:abc123")) { + val out = DebugBundle.redactSecrets(line) + assertTrue(out.contains(""), "not redacted: $out") + assertFalse(out.contains("abc123"), "token survived: $out") + } + } } \ No newline at end of file diff --git a/client/shared/src/iosMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.ios.kt b/client/shared/src/iosMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.ios.kt index 194b65b..b6131bd 100644 --- a/client/shared/src/iosMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.ios.kt +++ b/client/shared/src/iosMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.ios.kt @@ -30,7 +30,7 @@ import platform.Security.kSecClassGenericPassword * open-ended, so any list would drift the moment a provider is added. */ @OptIn(ExperimentalForeignApi::class) -actual fun wipeLocalData(declaredNodeHome: String?, activeNodeUrl: String?): Boolean { +actual fun wipeLocalData(declaredNodeHome: String?): Boolean { var ok = true // 1. The node's state directory. @@ -69,5 +69,3 @@ actual fun wipeLocalData(declaredNodeHome: String?, activeNodeUrl: String?): Boo } -/** iOS runs its node in-process; there is no other machine to confuse it with. */ -actual fun ownsLocalNode(activeNodeUrl: String?): Boolean = true diff --git a/client/shared/src/wasmJsMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.wasmJs.kt b/client/shared/src/wasmJsMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.wasmJs.kt index ced1498..0dd1f9e 100644 --- a/client/shared/src/wasmJsMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.wasmJs.kt +++ b/client/shared/src/wasmJsMain/kotlin/ai/ciris/mobile/shared/platform/LocalDataWipe.wasmJs.kt @@ -5,8 +5,6 @@ package ai.ciris.mobile.shared.platform * data is not this browser's to delete. Reports false rather than pretending, so * the UI can say so instead of claiming a wipe that did not happen. */ -actual fun wipeLocalData(declaredNodeHome: String?, activeNodeUrl: String?): Boolean = false +actual fun wipeLocalData(declaredNodeHome: String?): Boolean = false -/** Web has no local runtime at all, so there is never local node state to erase. */ -actual fun ownsLocalNode(activeNodeUrl: String?): Boolean = false From 847d4d39beb3d9bca5cf4ff9bdad8a7793299be8 Mon Sep 17 00:00:00 2001 From: Eric Moore Date: Thu, 27 Aug 2026 23:11:14 -0500 Subject: [PATCH 07/11] fix(bundle): the JWT arm had a segment-length floor too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `eyJ` IS the shape check — that is a base64url `{"` and nothing else begins that way by accident. Requiring 8+ characters in each segment meant a JWT with a small claims set was exported whole (Codex, PR #18). Third length floor in this file, after `{6,}` on named assignments and `{12,}` on bearer headers. Same mistake each time: treating value length as evidence when the marker beside it was already the evidence. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01EGE52kPzjGFiPzGcs63ZC1 --- client/VENDORING.md | 2 +- .../ai/ciris/mobile/shared/ui/components/DebugBundle.kt | 7 ++++++- .../shared/ui/components/DebugBundleRedactionTest.kt | 8 ++++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/client/VENDORING.md b/client/VENDORING.md index a984821..c207668 100644 --- a/client/VENDORING.md +++ b/client/VENDORING.md @@ -40,7 +40,7 @@ source is the pair a bisect wants: The tree's current recorded state — sha256-of-sha256s over every git-tracked file under `client/` except this one: -**state digest:** `a5569b62d82ea1d8ce2a14f49f2044fc99d1c4d929362004a95002ddac36091d` +**state digest:** `c4644adc650c015df513d6070b74988d2718664ef17ac17d43cae5f1b0d797a5` `packaging/check_vendoring.py` asserts it on every push, and refuses any tracked file matching a §2 never-vendor class. **Any commit that touches diff --git a/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundle.kt b/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundle.kt index 2a3a3fa..05fe8fd 100644 --- a/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundle.kt +++ b/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundle.kt @@ -121,7 +121,12 @@ object DebugBundle { private val SECRET_PATTERNS: List> = listOf( // JWTs — the shape is unmistakable and never appears in prose. - Regex("""eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}(\.[A-Za-z0-9_-]+)?""") to "", + // `eyJ` is the shape check — that is a base64url `{"` and nothing else + // starts that way by accident. Segment LENGTH is not: a JWT with a small + // claims set has a short middle segment and was exported whole + // (Codex, PR #18). Same mistake as the `{6,}` and `{12,}` floors, in the + // one arm I had not yet reached. + Regex("""eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+(\.[A-Za-z0-9_-]+)?""") to "", // Authorization headers, including the `service:TOKEN` form. // // No length floor. `Bearer abc123` is a credential exactly as much as a diff --git a/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundleRedactionTest.kt b/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundleRedactionTest.kt index c8ca0fc..95b99af 100644 --- a/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundleRedactionTest.kt +++ b/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundleRedactionTest.kt @@ -207,4 +207,12 @@ class DebugBundleRedactionTest { assertFalse(out.contains("abc123"), "token survived: $out") } } + + @Test + fun a_jwt_with_a_small_claims_set_is_still_a_jwt() { + // Segment length was never the shape check; `eyJ` is (Codex, PR #18). + val out = DebugBundle.redactSecrets("auth=eyJhbGc.eyJhIjoxfQ.sig") + assertTrue(out.contains(" Date: Fri, 28 Aug 2026 01:40:02 -0500 Subject: [PATCH 08/11] fix(i18n): "local" was the untranslatable word, not the sentence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two evaluate runs, different languages failing each time, one cause: run 1 ha dropped "local" my added a "wipe/clear" step not in the source run 2 bn rendered it "local DATA" — narrowing the scope ha dropped "local" th dropped "local" "Reset local device" is ambiguous in the ENGLISH. Does "local" modify the device — this one, as opposed to the node on another machine — or the data on it? Translators picked both readings and were marked wrong for each. `ha` and `th` were rejected for rendering it "this device", which is exactly what it means. So say that. The confirmation title has said "Reset this device?" since the rename; the button now matches it, and the body already carries the contrast the adjective was straining to imply: a node you are connected to on another machine is not affected. Third time in this session the translation blocker was the English, and the third time the fix made the string simpler. A word that needs a footnote in one language needs one in twenty-eight. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01EGE52kPzjGFiPzGcs63ZC1 --- client/VENDORING.md | 2 +- client/androidApp/src/main/assets/localization/en.json | 2 +- client/desktopApp/src/main/resources/localization/en.json | 2 +- client/iosApp/iosApp/localization/en.json | 2 +- client/shared/src/desktopMain/resources/localization/en.json | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client/VENDORING.md b/client/VENDORING.md index 5f77f37..930c710 100644 --- a/client/VENDORING.md +++ b/client/VENDORING.md @@ -40,7 +40,7 @@ source is the pair a bisect wants: The tree's current recorded state — sha256-of-sha256s over every git-tracked file under `client/` except this one: -**state digest:** `0208c8b036e4de942c7fd51ecf660d819d221e6512d0992ba18c1e768ba6aa96` +**state digest:** `ce62db7634607cc2cb4f399ca863787718e9cee3c8ca97f83bfae98ec1033d43` `packaging/check_vendoring.py` asserts it on every push, and refuses any tracked file matching a §2 never-vendor class. **Any commit that touches diff --git a/client/androidApp/src/main/assets/localization/en.json b/client/androidApp/src/main/assets/localization/en.json index a640c47..17f1f9c 100644 --- a/client/androidApp/src/main/assets/localization/en.json +++ b/client/androidApp/src/main/assets/localization/en.json @@ -1928,7 +1928,7 @@ "login_owner_hint": "Last signed in: {owner}", "login_password_label": "Password", "login_privacy": "View Privacy Policy", - "login_reset_device": "Reset local device", + "login_reset_device": "Reset this device", "login_reset_device_cancel": "Cancel", "login_reset_device_confirm_body": "This erases the node data stored on THIS device and returns to the setup wizard. A node you are connected to on another machine is not affected. This cannot be undone.", "login_reset_device_confirm_title": "Reset this device?", diff --git a/client/desktopApp/src/main/resources/localization/en.json b/client/desktopApp/src/main/resources/localization/en.json index a640c47..17f1f9c 100644 --- a/client/desktopApp/src/main/resources/localization/en.json +++ b/client/desktopApp/src/main/resources/localization/en.json @@ -1928,7 +1928,7 @@ "login_owner_hint": "Last signed in: {owner}", "login_password_label": "Password", "login_privacy": "View Privacy Policy", - "login_reset_device": "Reset local device", + "login_reset_device": "Reset this device", "login_reset_device_cancel": "Cancel", "login_reset_device_confirm_body": "This erases the node data stored on THIS device and returns to the setup wizard. A node you are connected to on another machine is not affected. This cannot be undone.", "login_reset_device_confirm_title": "Reset this device?", diff --git a/client/iosApp/iosApp/localization/en.json b/client/iosApp/iosApp/localization/en.json index a640c47..17f1f9c 100644 --- a/client/iosApp/iosApp/localization/en.json +++ b/client/iosApp/iosApp/localization/en.json @@ -1928,7 +1928,7 @@ "login_owner_hint": "Last signed in: {owner}", "login_password_label": "Password", "login_privacy": "View Privacy Policy", - "login_reset_device": "Reset local device", + "login_reset_device": "Reset this device", "login_reset_device_cancel": "Cancel", "login_reset_device_confirm_body": "This erases the node data stored on THIS device and returns to the setup wizard. A node you are connected to on another machine is not affected. This cannot be undone.", "login_reset_device_confirm_title": "Reset this device?", diff --git a/client/shared/src/desktopMain/resources/localization/en.json b/client/shared/src/desktopMain/resources/localization/en.json index a640c47..17f1f9c 100644 --- a/client/shared/src/desktopMain/resources/localization/en.json +++ b/client/shared/src/desktopMain/resources/localization/en.json @@ -1928,7 +1928,7 @@ "login_owner_hint": "Last signed in: {owner}", "login_password_label": "Password", "login_privacy": "View Privacy Policy", - "login_reset_device": "Reset local device", + "login_reset_device": "Reset this device", "login_reset_device_cancel": "Cancel", "login_reset_device_confirm_body": "This erases the node data stored on THIS device and returns to the setup wizard. A node you are connected to on another machine is not affected. This cannot be undone.", "login_reset_device_confirm_title": "Reset this device?", From 531e168b516428fa48052c8fdbd596c05ba7315b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 28 Aug 2026 12:37:34 +0000 Subject: [PATCH 09/11] i18n: evaluate lane (translate -> evaluate -> repair) Machine translation, independently reviewed against MQM, and repaired where the review found a critical, major or terminology error. Every value here is status=draft / review_status=needs_native_review: this pipeline guarantees terminology, structure and meaning, and does not guarantee native fluency. Validated by check_localization_sync.py --strict in this same run. The MQM findings are attached to the run as i18n-report.json. Review like any other diff. --- client/VENDORING.md | 2 +- client/androidApp/src/main/assets/localization/am.json | 6 +++--- client/androidApp/src/main/assets/localization/ar.json | 2 +- client/androidApp/src/main/assets/localization/bn.json | 2 +- client/androidApp/src/main/assets/localization/de.json | 6 +++--- client/androidApp/src/main/assets/localization/es.json | 2 +- client/androidApp/src/main/assets/localization/fa.json | 2 +- client/androidApp/src/main/assets/localization/fr.json | 2 +- client/androidApp/src/main/assets/localization/ha.json | 2 +- client/androidApp/src/main/assets/localization/hi.json | 6 +++--- client/androidApp/src/main/assets/localization/id.json | 6 +++--- client/androidApp/src/main/assets/localization/it.json | 2 +- client/androidApp/src/main/assets/localization/ja.json | 2 +- client/androidApp/src/main/assets/localization/ko.json | 2 +- client/androidApp/src/main/assets/localization/mr.json | 2 +- client/androidApp/src/main/assets/localization/my.json | 2 +- client/androidApp/src/main/assets/localization/pa.json | 2 +- client/androidApp/src/main/assets/localization/pt.json | 6 +++--- client/androidApp/src/main/assets/localization/ru.json | 2 +- client/androidApp/src/main/assets/localization/sw.json | 6 +++--- client/androidApp/src/main/assets/localization/ta.json | 2 +- client/androidApp/src/main/assets/localization/te.json | 6 +++--- client/androidApp/src/main/assets/localization/th.json | 6 +++--- client/androidApp/src/main/assets/localization/tr.json | 2 +- client/androidApp/src/main/assets/localization/uk.json | 2 +- client/androidApp/src/main/assets/localization/ur.json | 2 +- client/androidApp/src/main/assets/localization/vi.json | 2 +- client/androidApp/src/main/assets/localization/yo.json | 6 +++--- client/androidApp/src/main/assets/localization/zh.json | 2 +- client/desktopApp/src/main/resources/localization/am.json | 6 +++--- client/desktopApp/src/main/resources/localization/ar.json | 2 +- client/desktopApp/src/main/resources/localization/bn.json | 2 +- client/desktopApp/src/main/resources/localization/de.json | 6 +++--- client/desktopApp/src/main/resources/localization/es.json | 2 +- client/desktopApp/src/main/resources/localization/fa.json | 2 +- client/desktopApp/src/main/resources/localization/fr.json | 2 +- client/desktopApp/src/main/resources/localization/ha.json | 2 +- client/desktopApp/src/main/resources/localization/hi.json | 6 +++--- client/desktopApp/src/main/resources/localization/id.json | 6 +++--- client/desktopApp/src/main/resources/localization/it.json | 2 +- client/desktopApp/src/main/resources/localization/ja.json | 2 +- client/desktopApp/src/main/resources/localization/ko.json | 2 +- client/desktopApp/src/main/resources/localization/mr.json | 2 +- client/desktopApp/src/main/resources/localization/my.json | 2 +- client/desktopApp/src/main/resources/localization/pa.json | 2 +- client/desktopApp/src/main/resources/localization/pt.json | 6 +++--- client/desktopApp/src/main/resources/localization/ru.json | 2 +- client/desktopApp/src/main/resources/localization/sw.json | 6 +++--- client/desktopApp/src/main/resources/localization/ta.json | 2 +- client/desktopApp/src/main/resources/localization/te.json | 6 +++--- client/desktopApp/src/main/resources/localization/th.json | 6 +++--- client/desktopApp/src/main/resources/localization/tr.json | 2 +- client/desktopApp/src/main/resources/localization/uk.json | 2 +- client/desktopApp/src/main/resources/localization/ur.json | 2 +- client/desktopApp/src/main/resources/localization/vi.json | 2 +- client/desktopApp/src/main/resources/localization/yo.json | 6 +++--- client/desktopApp/src/main/resources/localization/zh.json | 2 +- client/iosApp/iosApp/localization/am.json | 6 +++--- client/iosApp/iosApp/localization/ar.json | 2 +- client/iosApp/iosApp/localization/bn.json | 2 +- client/iosApp/iosApp/localization/de.json | 6 +++--- client/iosApp/iosApp/localization/es.json | 2 +- client/iosApp/iosApp/localization/fa.json | 2 +- client/iosApp/iosApp/localization/fr.json | 2 +- client/iosApp/iosApp/localization/ha.json | 2 +- client/iosApp/iosApp/localization/hi.json | 6 +++--- client/iosApp/iosApp/localization/id.json | 6 +++--- client/iosApp/iosApp/localization/it.json | 2 +- client/iosApp/iosApp/localization/ja.json | 2 +- client/iosApp/iosApp/localization/ko.json | 2 +- client/iosApp/iosApp/localization/mr.json | 2 +- client/iosApp/iosApp/localization/my.json | 2 +- client/iosApp/iosApp/localization/pa.json | 2 +- client/iosApp/iosApp/localization/pt.json | 6 +++--- client/iosApp/iosApp/localization/ru.json | 2 +- client/iosApp/iosApp/localization/sw.json | 6 +++--- client/iosApp/iosApp/localization/ta.json | 2 +- client/iosApp/iosApp/localization/te.json | 6 +++--- client/iosApp/iosApp/localization/th.json | 6 +++--- client/iosApp/iosApp/localization/tr.json | 2 +- client/iosApp/iosApp/localization/uk.json | 2 +- client/iosApp/iosApp/localization/ur.json | 2 +- client/iosApp/iosApp/localization/vi.json | 2 +- client/iosApp/iosApp/localization/yo.json | 6 +++--- client/iosApp/iosApp/localization/zh.json | 2 +- .../shared/src/desktopMain/resources/localization/am.json | 6 +++--- .../shared/src/desktopMain/resources/localization/ar.json | 2 +- .../shared/src/desktopMain/resources/localization/bn.json | 2 +- .../shared/src/desktopMain/resources/localization/de.json | 6 +++--- .../shared/src/desktopMain/resources/localization/es.json | 2 +- .../shared/src/desktopMain/resources/localization/fa.json | 2 +- .../shared/src/desktopMain/resources/localization/fr.json | 2 +- .../shared/src/desktopMain/resources/localization/ha.json | 2 +- .../shared/src/desktopMain/resources/localization/hi.json | 6 +++--- .../shared/src/desktopMain/resources/localization/id.json | 6 +++--- .../shared/src/desktopMain/resources/localization/it.json | 2 +- .../shared/src/desktopMain/resources/localization/ja.json | 2 +- .../shared/src/desktopMain/resources/localization/ko.json | 2 +- .../shared/src/desktopMain/resources/localization/mr.json | 2 +- .../shared/src/desktopMain/resources/localization/my.json | 2 +- .../shared/src/desktopMain/resources/localization/pa.json | 2 +- .../shared/src/desktopMain/resources/localization/pt.json | 6 +++--- .../shared/src/desktopMain/resources/localization/ru.json | 2 +- .../shared/src/desktopMain/resources/localization/sw.json | 6 +++--- .../shared/src/desktopMain/resources/localization/ta.json | 2 +- .../shared/src/desktopMain/resources/localization/te.json | 6 +++--- .../shared/src/desktopMain/resources/localization/th.json | 6 +++--- .../shared/src/desktopMain/resources/localization/tr.json | 2 +- .../shared/src/desktopMain/resources/localization/uk.json | 2 +- .../shared/src/desktopMain/resources/localization/ur.json | 2 +- .../shared/src/desktopMain/resources/localization/vi.json | 2 +- .../shared/src/desktopMain/resources/localization/yo.json | 6 +++--- .../shared/src/desktopMain/resources/localization/zh.json | 2 +- 113 files changed, 185 insertions(+), 185 deletions(-) diff --git a/client/VENDORING.md b/client/VENDORING.md index 930c710..ebe19a4 100644 --- a/client/VENDORING.md +++ b/client/VENDORING.md @@ -40,7 +40,7 @@ source is the pair a bisect wants: The tree's current recorded state — sha256-of-sha256s over every git-tracked file under `client/` except this one: -**state digest:** `ce62db7634607cc2cb4f399ca863787718e9cee3c8ca97f83bfae98ec1033d43` +**state digest:** `665f2a5cb25f11939ac72aeb427696512656fda7cbd8fd7be57a290a4368d1e8` `packaging/check_vendoring.py` asserts it on every push, and refuses any tracked file matching a §2 never-vendor class. **Any commit that touches diff --git a/client/androidApp/src/main/assets/localization/am.json b/client/androidApp/src/main/assets/localization/am.json index b478e20..6fc10f6 100644 --- a/client/androidApp/src/main/assets/localization/am.json +++ b/client/androidApp/src/main/assets/localization/am.json @@ -1858,10 +1858,10 @@ "login_owner_hint": "መጨረሻ የገባው: {owner}", "login_password_label": "የይለፍ ቃል", "login_privacy": "የግላዊነት መመሪያን ይመልከቱ", - "login_reset_device": "መሣሪያ ዳግም አስጀምር", + "login_reset_device": "ይህን መሣሪያ ዳግም አስጀምር", "login_reset_device_cancel": "ሰርዝ", - "login_reset_device_confirm_body": "ይህ ሁሉንም አካባቢያዊ ውሂብ ያጠፋና ወደ ማዋቀሪያ አዋቂ ይመልሳል። ይህ ድርጊት ሊቀለበስ አይችልም።", - "login_reset_device_confirm_title": "መሣሪያ ዳግም ይጀምር?", + "login_reset_device_confirm_body": "ይህ በዚህ መሣሪያ ላይ የተቀመጠውን የኖድ ውሂብ ያጠፋል እና ወደ የማዋቀሪያ አዋቂው ይመለሳል። በሌላ መሣሪያ ላይ የተገናኙበት ኖድ ግን ተጽዕኖ አይደርስበትም። ይህ ድርጊት መልሰው የማይቀር ነው።", + "login_reset_device_confirm_title": "ይህን መሣሪያ ዳግም ማስጀመር?", "login_setup_complete_relogin": "ማዋቀሩ ተጠናቅቋል። መስቀለኛዎ እንደገና ስለተጀመረ፣ እባክዎ ማዋቀሩን ለማጠናቀቅ በተጠቀሙባቸው ተመሳሳይ የመግቢያ መረጃዎች እንደገና ይግቡ።", "login_signin_apple": "በApple ይግቡ", "login_signin_google": "በGoogle ይግቡ", diff --git a/client/androidApp/src/main/assets/localization/ar.json b/client/androidApp/src/main/assets/localization/ar.json index 3b025d4..52541c6 100644 --- a/client/androidApp/src/main/assets/localization/ar.json +++ b/client/androidApp/src/main/assets/localization/ar.json @@ -1860,7 +1860,7 @@ "login_privacy": "عرض سياسة الخصوصية", "login_reset_device": "إعادة ضبط الجهاز", "login_reset_device_cancel": "إلغاء", - "login_reset_device_confirm_body": "سيؤدي ذلك إلى مسح جميع البيانات المحلية والعودة إلى معالج الإعداد. لا يمكن التراجع عن هذا الإجراء.", + "login_reset_device_confirm_body": "سيؤدي ذلك إلى مسح بيانات العقدة المخزّنة على هذا الجهاز والعودة إلى معالج الإعداد. أما أي عقدة تتصل بها على جهاز آخر فلن تتأثر. لا يمكن التراجع عن هذا الإجراء.", "login_reset_device_confirm_title": "إعادة ضبط الجهاز؟", "login_setup_complete_relogin": "اكتمل الإعداد. أُعيد تشغيل العقدة، لذا يُرجى تسجيل الدخول مجددًا باستخدام بيانات الاعتماد نفسها التي استخدمتها لإكمال الإعداد.", "login_signin_apple": "تسجيل الدخول باستخدام Apple", diff --git a/client/androidApp/src/main/assets/localization/bn.json b/client/androidApp/src/main/assets/localization/bn.json index 30f1052..cbb52b3 100644 --- a/client/androidApp/src/main/assets/localization/bn.json +++ b/client/androidApp/src/main/assets/localization/bn.json @@ -1861,7 +1861,7 @@ "login_privacy": "গোপনীয়তা নীতি দেখুন", "login_reset_device": "ডিভাইস রিসেট করুন", "login_reset_device_cancel": "বাতিল", - "login_reset_device_confirm_body": "এটি সমস্ত স্থানীয় ডেটা মুছে দেবে এবং সেটআপ উইজার্ডে ফিরে যাবে। এটি পূর্বাবস্থায় ফেরানো যাবে না।", + "login_reset_device_confirm_body": "এটি এই ডিভাইসে সংরক্ষিত নোডের ডেটা মুছে দেবে এবং সেটআপ উইজার্ডে ফিরে যাবে। অন্য কোনো মেশিনে আপনি যে নোডের সাথে সংযুক্ত আছেন, সেটি প্রভাবিত হবে না। এটি পূর্বাবস্থায় ফেরানো যাবে না।", "login_reset_device_confirm_title": "ডিভাইস রিসেট করবেন?", "login_setup_complete_relogin": "সেটআপ সম্পূর্ণ হয়েছে। আপনার নোড পুনরায় চালু হয়েছে, তাই সেটআপ সম্পূর্ণ করতে আপনি যে শংসাপত্রগুলি ব্যবহার করেছিলেন সেগুলি দিয়েই আবার লগ ইন করুন।", "login_signin_apple": "Apple দিয়ে সাইন ইন করুন", diff --git a/client/androidApp/src/main/assets/localization/de.json b/client/androidApp/src/main/assets/localization/de.json index 74e684c..da1e0ac 100644 --- a/client/androidApp/src/main/assets/localization/de.json +++ b/client/androidApp/src/main/assets/localization/de.json @@ -1858,10 +1858,10 @@ "login_owner_hint": "Zuletzt angemeldet: {owner}", "login_password_label": "Passwort", "login_privacy": "Datenschutzerklärung anzeigen", - "login_reset_device": "Gerät zurücksetzen", + "login_reset_device": "Dieses Gerät zurücksetzen", "login_reset_device_cancel": "Abbrechen", - "login_reset_device_confirm_body": "Dadurch werden alle lokalen Daten gelöscht und Sie werden zum Einrichtungsassistenten zurückgeleitet. Diese Aktion kann nicht rückgängig gemacht werden.", - "login_reset_device_confirm_title": "Gerät zurücksetzen?", + "login_reset_device_confirm_body": "Dies löscht die Knotendaten, die auf DIESEM Gerät gespeichert sind, und kehrt zum Einrichtungsassistenten zurück. Ein Knoten, mit dem Sie auf einem anderen Rechner verbunden sind, ist davon nicht betroffen. Dies kann nicht rückgängig gemacht werden.", + "login_reset_device_confirm_title": "Dieses Gerät zurücksetzen?", "login_setup_complete_relogin": "Die Einrichtung ist abgeschlossen. Ihr Knoten wurde neu gestartet – bitte melden Sie sich erneut mit denselben Zugangsdaten an, die Sie für die Einrichtung verwendet haben.", "login_signin_apple": "Mit Apple anmelden", "login_signin_google": "Mit Google anmelden", diff --git a/client/androidApp/src/main/assets/localization/es.json b/client/androidApp/src/main/assets/localization/es.json index 66a6762..fe02dbf 100644 --- a/client/androidApp/src/main/assets/localization/es.json +++ b/client/androidApp/src/main/assets/localization/es.json @@ -1860,7 +1860,7 @@ "login_privacy": "Ver Política de Privacidad", "login_reset_device": "Restablecer dispositivo", "login_reset_device_cancel": "Cancelar", - "login_reset_device_confirm_body": "Esto borrará todos los datos locales y volverás al asistente de configuración. Esta acción no se puede deshacer.", + "login_reset_device_confirm_body": "Esto borrará los datos del nodo almacenados en ESTE dispositivo y volverá al asistente de configuración. Un nodo al que estés conectado en otra máquina no se verá afectado. Esta acción no se puede deshacer.", "login_reset_device_confirm_title": "¿Restablecer dispositivo?", "login_setup_complete_relogin": "La configuración ha finalizado. Tu nodo se reinició, así que vuelve a iniciar sesión con las mismas credenciales que usaste para completar la configuración.", "login_signin_apple": "Iniciar sesión con Apple", diff --git a/client/androidApp/src/main/assets/localization/fa.json b/client/androidApp/src/main/assets/localization/fa.json index d9caea0..38b931c 100644 --- a/client/androidApp/src/main/assets/localization/fa.json +++ b/client/androidApp/src/main/assets/localization/fa.json @@ -1866,7 +1866,7 @@ "login_privacy": "مشاهده سیاست حریم خصوصی", "login_reset_device": "بازنشانی دستگاه", "login_reset_device_cancel": "لغو", - "login_reset_device_confirm_body": "این عمل تمام داده‌های محلی را پاک می‌کند و به راه‌اندازی اولیه بازمی‌گردد. این کار قابل بازگشت نیست.", + "login_reset_device_confirm_body": "این کار داده‌های گره ذخیره‌شده روی همین دستگاه را پاک می‌کند و به دستیار راه‌اندازی بازمی‌گردد. گرهی که روی دستگاهی دیگر به آن متصل هستید تحت تأثیر قرار نمی‌گیرد. این کار قابل بازگشت نیست.", "login_reset_device_confirm_title": "بازنشانی دستگاه؟", "login_setup_complete_relogin": "راه‌اندازی کامل شد. نود شما دوباره راه‌اندازی شد؛ لطفاً با همان اطلاعات ورودی که برای تکمیل راه‌اندازی استفاده کردید، دوباره وارد شوید.", "login_signin_apple": "ورود باستخدام Apple", diff --git a/client/androidApp/src/main/assets/localization/fr.json b/client/androidApp/src/main/assets/localization/fr.json index 10abf12..fe1f844 100644 --- a/client/androidApp/src/main/assets/localization/fr.json +++ b/client/androidApp/src/main/assets/localization/fr.json @@ -1860,7 +1860,7 @@ "login_privacy": "Voir la Politique de Confidentialité", "login_reset_device": "Réinitialiser l'appareil", "login_reset_device_cancel": "Annuler", - "login_reset_device_confirm_body": "Cela effacera toutes les données locales et vous ramènera à l'assistant de configuration. Cette action est irréversible.", + "login_reset_device_confirm_body": "Cela efface les données du nœud stockées sur CET appareil et revient à l'assistant de configuration. Un nœud auquel vous êtes connecté sur une autre machine n'est pas affecté. Cela ne peut pas être annulé.", "login_reset_device_confirm_title": "Réinitialiser l'appareil ?", "login_setup_complete_relogin": "La configuration est terminée. Votre nœud a redémarré : veuillez vous reconnecter avec les mêmes identifiants que ceux utilisés pour terminer la configuration.", "login_signin_apple": "Se connecter avec Apple", diff --git a/client/androidApp/src/main/assets/localization/ha.json b/client/androidApp/src/main/assets/localization/ha.json index 7ac4491..f5f91af 100644 --- a/client/androidApp/src/main/assets/localization/ha.json +++ b/client/androidApp/src/main/assets/localization/ha.json @@ -1860,7 +1860,7 @@ "login_privacy": "Duba Sera na Asirta", "login_reset_device": "Sake saita na'ura", "login_reset_device_cancel": "Soke", - "login_reset_device_confirm_body": "Wannan zai share duk bayanan gida kuma ya koma zuwa jagorar saiti. Ba za a iya maido da wannan ba.", + "login_reset_device_confirm_body": "Wannan zai share bayanan kumburin da aka ajiye a WANNAN na'urar kuma ya koma zuwa jagorar saiti. Kumburin da kuke haɗe da shi a wata na'ura ba zai sami tasiri ba. Ba za a iya mayar da wannan aiki ba.", "login_reset_device_confirm_title": "Sake saita na'ura?", "login_setup_complete_relogin": "An kammala saitin. Node ɗinka ya sake farawa, don haka ka sake shiga da bayanan shiga guda ɗaya da ka yi amfani da su wajen kammala saitin.", "login_signin_apple": "Shiga tare da Apple", diff --git a/client/androidApp/src/main/assets/localization/hi.json b/client/androidApp/src/main/assets/localization/hi.json index 7654f45..504fa17 100644 --- a/client/androidApp/src/main/assets/localization/hi.json +++ b/client/androidApp/src/main/assets/localization/hi.json @@ -1858,10 +1858,10 @@ "login_owner_hint": "पिछली बार साइन इन किया: {owner}", "login_password_label": "पासवर्ड", "login_privacy": "गोपनीयता नीति देखें", - "login_reset_device": "डिवाइस रीसेट करें", + "login_reset_device": "इस डिवाइस को रीसेट करें", "login_reset_device_cancel": "रद्द करें", - "login_reset_device_confirm_body": "यह सभी स्थानीय डेटा मिटा देगा और सेटअप विज़ार्ड पर वापस ले जाएगा। इसे पूर्ववत नहीं किया जा सकता।", - "login_reset_device_confirm_title": "डिवाइस रीसेट करें?", + "login_reset_device_confirm_body": "यह इस डिवाइस पर संग्रहीत नोड डेटा मिटा देगा और सेटअप विज़ार्ड पर वापस ले जाएगा। किसी अन्य मशीन पर जिस नोड से आप जुड़े हैं, वह प्रभावित नहीं होगा। इसे पूर्ववत नहीं किया जा सकता।", + "login_reset_device_confirm_title": "इस डिवाइस को रीसेट करें?", "login_setup_complete_relogin": "सेटअप पूरा हो गया है। आपका नोड फिर से चालू हुआ है, इसलिए कृपया उन्हीं क्रेडेंशियल से दोबारा लॉग इन करें जिनका उपयोग आपने सेटअप पूरा करने के लिए किया था।", "login_signin_apple": "Apple से साइन इन करें", "login_signin_google": "Google से साइन इन करें", diff --git a/client/androidApp/src/main/assets/localization/id.json b/client/androidApp/src/main/assets/localization/id.json index 809bf6a..d51d506 100644 --- a/client/androidApp/src/main/assets/localization/id.json +++ b/client/androidApp/src/main/assets/localization/id.json @@ -1858,10 +1858,10 @@ "login_owner_hint": "Terakhir masuk: {owner}", "login_password_label": "Kata Sandi", "login_privacy": "Lihat Kebijakan Privasi", - "login_reset_device": "Atur ulang perangkat", + "login_reset_device": "Atur ulang perangkat ini", "login_reset_device_cancel": "Batal", - "login_reset_device_confirm_body": "Ini akan menghapus semua data lokal dan kembali ke wizard penyiapan. Tindakan ini tidak dapat dibatalkan.", - "login_reset_device_confirm_title": "Atur ulang perangkat?", + "login_reset_device_confirm_body": "Ini akan menghapus data node yang tersimpan di perangkat INI dan kembali ke wizard penyiapan. Node tempat Anda terhubung di mesin lain tidak terpengaruh. Tindakan ini tidak dapat dibatalkan.", + "login_reset_device_confirm_title": "Atur ulang perangkat ini?", "login_setup_complete_relogin": "Penyiapan selesai. Node Anda telah dimulai ulang, jadi silakan masuk kembali dengan kredensial yang sama seperti yang Anda gunakan saat menyelesaikan penyiapan.", "login_signin_apple": "Masuk dengan Apple", "login_signin_google": "Masuk dengan Google", diff --git a/client/androidApp/src/main/assets/localization/it.json b/client/androidApp/src/main/assets/localization/it.json index 1748af9..525dd69 100644 --- a/client/androidApp/src/main/assets/localization/it.json +++ b/client/androidApp/src/main/assets/localization/it.json @@ -1860,7 +1860,7 @@ "login_privacy": "Visualizza Informativa sulla Privacy", "login_reset_device": "Ripristina dispositivo", "login_reset_device_cancel": "Annulla", - "login_reset_device_confirm_body": "Verranno cancellati tutti i dati locali e si tornerà alla procedura guidata di configurazione. Questa azione non può essere annullata.", + "login_reset_device_confirm_body": "Verranno cancellati i dati del nodo memorizzati su QUESTO dispositivo e si tornerà alla procedura guidata di configurazione. Un nodo a cui sei connesso su un'altra macchina non subisce modifiche. Questa azione non può essere annullata.", "login_reset_device_confirm_title": "Ripristinare il dispositivo?", "login_setup_complete_relogin": "La configurazione è completata. Il tuo nodo è stato riavviato: accedi di nuovo con le stesse credenziali che hai usato per completare la configurazione.", "login_signin_apple": "Accedi con Apple", diff --git a/client/androidApp/src/main/assets/localization/ja.json b/client/androidApp/src/main/assets/localization/ja.json index 07f3851..371b63a 100644 --- a/client/androidApp/src/main/assets/localization/ja.json +++ b/client/androidApp/src/main/assets/localization/ja.json @@ -1860,7 +1860,7 @@ "login_privacy": "プライバシーポリシーを表示", "login_reset_device": "デバイスをリセット", "login_reset_device_cancel": "キャンセル", - "login_reset_device_confirm_body": "すべてのローカルデータが消去され、セットアップウィザードに戻ります。この操作は元に戻せません。", + "login_reset_device_confirm_body": "このデバイスに保存されているノードデータが消去され、セットアップウィザードに戻ります。別のマシン上で接続しているノードには影響しません。この操作は元に戻せません。", "login_reset_device_confirm_title": "デバイスをリセットしますか?", "login_setup_complete_relogin": "セットアップが完了しました。ノードが再起動したため、セットアップで使用したものと同じ認証情報で再度ログインしてください。", "login_signin_apple": "Appleでサインイン", diff --git a/client/androidApp/src/main/assets/localization/ko.json b/client/androidApp/src/main/assets/localization/ko.json index a766a42..e46b77f 100644 --- a/client/androidApp/src/main/assets/localization/ko.json +++ b/client/androidApp/src/main/assets/localization/ko.json @@ -1860,7 +1860,7 @@ "login_privacy": "개인정보 보호정책 보기", "login_reset_device": "기기 재설정", "login_reset_device_cancel": "취소", - "login_reset_device_confirm_body": "모든 로컬 데이터가 삭제되고 설정 마법사로 돌아갑니다. 이 작업은 취소할 수 없습니다.", + "login_reset_device_confirm_body": "이 작업은 이 기기에 저장된 노드 데이터를 삭제하고 설정 마법사로 돌아갑니다. 다른 기기에서 연결해 사용 중인 노드는 영향을 받지 않습니다. 이 작업은 취소할 수 없습니다.", "login_reset_device_confirm_title": "기기를 재설정하시겠습니까?", "login_setup_complete_relogin": "설정이 완료되었습니다. 노드가 재시작되었으므로 설정에 사용한 것과 동일한 자격 증명으로 다시 로그인해 주세요.", "login_signin_apple": "Apple로 로그인", diff --git a/client/androidApp/src/main/assets/localization/mr.json b/client/androidApp/src/main/assets/localization/mr.json index 0c7cf1f..35d3208 100644 --- a/client/androidApp/src/main/assets/localization/mr.json +++ b/client/androidApp/src/main/assets/localization/mr.json @@ -1860,7 +1860,7 @@ "login_privacy": "गोपनीयता धोरण पाहा", "login_reset_device": "डिव्हाइस रीसेट करा", "login_reset_device_cancel": "रद्द करा", - "login_reset_device_confirm_body": "हे सर्व स्थानिक डेटा मिटवेल आणि सेटअप विझार्डवर परत जाईल. हे पूर्ववत केले जाऊ शकत नाही.", + "login_reset_device_confirm_body": "यामुळे याच डिव्हाइसवर साठवलेला नोड डेटा पुसला जाईल आणि सेटअप विझार्डवर परत जाईल. तुम्ही दुसऱ्या मशीनवरील ज्या नोडशी जोडलेले आहात, त्यावर याचा परिणाम होत नाही. हे पूर्ववत केले जाऊ शकत नाही.", "login_reset_device_confirm_title": "डिव्हाइस रीसेट करायचे?", "login_setup_complete_relogin": "सेटअप पूर्ण झाला आहे. तुमचा नोड पुन्हा सुरू झाला आहे, त्यामुळे सेटअप पूर्ण करण्यासाठी वापरलेल्या त्याच क्रेडेन्शियल्सने कृपया पुन्हा लॉग इन करा.", "login_signin_apple": "Apple सह साइन इन करा", diff --git a/client/androidApp/src/main/assets/localization/my.json b/client/androidApp/src/main/assets/localization/my.json index aa01418..37d756b 100644 --- a/client/androidApp/src/main/assets/localization/my.json +++ b/client/androidApp/src/main/assets/localization/my.json @@ -1860,7 +1860,7 @@ "login_privacy": "ကိုယ်ရေးကာကွယ်မှု မူဝါဒ ကြည့်ရှုပါ", "login_reset_device": "စက်ပစ္စည်း ပြန်လည်သတ်မှတ်ပါ", "login_reset_device_cancel": "ပယ်ဖျက်", - "login_reset_device_confirm_body": "ဤလုပ်ဆောင်ချက်သည် ဒေသတွင်းဒေတာအားလုံးကို ဖျက်ပြီး ဆက်တင် wizard သို့ ပြန်သွားမည်ဖြစ်သည်။ ဤလုပ်ဆောင်ချက်ကို ပြန်ဖြည့်၍မရပါ။", + "login_reset_device_confirm_body": "ဤလုပ်ဆောင်ချက်သည် ဤစက်ပေါ်တွင် သိမ်းဆည်းထားသော node ဒေတာကို ဖျက်ပြီး ဆက်တင် wizard သို့ ပြန်သွားမည်ဖြစ်သည်။ သင် အခြားစက်တွင် ချိတ်ဆက်ထားသည့် node သည် ထိခိုက်မည် မဟုတ်ပါ။ ဤလုပ်ဆောင်ချက်ကို ပြန်မလုပ်နိုင်ပါ။", "login_reset_device_confirm_title": "စက်ပစ္စည်း ပြန်လည်သတ်မှတ်မည်လား?", "login_setup_complete_relogin": "စနစ်ထည့်သွင်းမှု ပြီးဆုံးပါပြီ။ သင့် node ပြန်လည်စတင်ခဲ့သဖြင့် စနစ်ထည့်သွင်းရာတွင် အသုံးပြုခဲ့သည့် အထောက်အထားများဖြင့်ပင် ပြန်လည်ဝင်ရောက်ပါ။", "login_signin_apple": "Apple ဖြင့် ဝင်ရောက်", diff --git a/client/androidApp/src/main/assets/localization/pa.json b/client/androidApp/src/main/assets/localization/pa.json index ebf8145..b5d51c5 100644 --- a/client/androidApp/src/main/assets/localization/pa.json +++ b/client/androidApp/src/main/assets/localization/pa.json @@ -1860,7 +1860,7 @@ "login_privacy": "ਗੋਪਨੀਯਤਾ ਨੀਤੀ ਦੇਖੋ", "login_reset_device": "ਡਿਵਾਈਸ ਰੀਸੈੱਟ ਕਰੋ", "login_reset_device_cancel": "ਰੱਦ ਕਰੋ", - "login_reset_device_confirm_body": "ਇਹ ਸਾਰਾ ਸਥਾਨਕ ਡੇਟਾ ਮਿਟਾ ਦੇਵੇਗਾ ਅਤੇ ਸੈੱਟਅੱਪ ਵਿਜ਼ਾਰਡ 'ਤੇ ਵਾਪਸ ਆ ਜਾਵੇਗਾ। ਇਸ ਨੂੰ ਪਹਿਲਾਂ ਵਾਲੀ ਸਥਿਤੀ ਵਿੱਚ ਨਹੀਂ ਲਿਆਂਦਾ ਜਾ ਸਕਦਾ।", + "login_reset_device_confirm_body": "ਇਹ ਇਸ ਡਿਵਾਈਸ 'ਤੇ ਸਟੋਰ ਕੀਤਾ ਨੋਡ ਡੇਟਾ ਮਿਟਾ ਦੇਵੇਗਾ ਅਤੇ ਸੈੱਟਅੱਪ ਵਿਜ਼ਾਰਡ 'ਤੇ ਵਾਪਸ ਆ ਜਾਵੇਗਾ। ਕਿਸੇ ਹੋਰ ਮਸ਼ੀਨ ਉੱਤੇ ਜਿਸ ਨੋਡ ਨਾਲ ਤੁਸੀਂ ਜੁੜੇ ਹੋ, ਉਹ ਪ੍ਰਭਾਵਿਤ ਨਹੀਂ ਹੁੰਦਾ। ਇਹ ਵਾਪਸ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ।", "login_reset_device_confirm_title": "ਡਿਵਾਈਸ ਰੀਸੈੱਟ ਕਰਨਾ ਹੈ?", "login_setup_complete_relogin": "ਸੈੱਟਅੱਪ ਪੂਰਾ ਹੋ ਗਿਆ ਹੈ। ਤੁਹਾਡਾ ਨੋਡ ਮੁੜ ਚਾਲੂ ਹੋਇਆ ਹੈ, ਇਸ ਲਈ ਕਿਰਪਾ ਕਰਕੇ ਉਹੀ ਪ੍ਰਮਾਣ-ਪੱਤਰ ਵਰਤ ਕੇ ਮੁੜ ਲਾਗਇਨ ਕਰੋ ਜੋ ਤੁਸੀਂ ਸੈੱਟਅੱਪ ਪੂਰਾ ਕਰਨ ਲਈ ਵਰਤੇ ਸਨ।", "login_signin_apple": "Apple ਨਾਲ ਸਾਈਨ ਇਨ ਕਰੋ", diff --git a/client/androidApp/src/main/assets/localization/pt.json b/client/androidApp/src/main/assets/localization/pt.json index fa5fa8c..d4d550e 100644 --- a/client/androidApp/src/main/assets/localization/pt.json +++ b/client/androidApp/src/main/assets/localization/pt.json @@ -1858,10 +1858,10 @@ "login_owner_hint": "Último acesso: {owner}", "login_password_label": "Senha", "login_privacy": "Ver Política de Privacidade", - "login_reset_device": "Redefinir dispositivo", + "login_reset_device": "Redefinir este dispositivo", "login_reset_device_cancel": "Cancelar", - "login_reset_device_confirm_body": "Isso apagará todos os dados locais e retornará ao assistente de configuração. Esta ação não pode ser desfeita.", - "login_reset_device_confirm_title": "Redefinir dispositivo?", + "login_reset_device_confirm_body": "Isso excluirá os dados do nó armazenados NESTE dispositivo e retornará ao assistente de configuração. Um nó ao qual você esteja conectado em outra máquina não é afetado. Isso não pode ser desfeito.", + "login_reset_device_confirm_title": "Redefinir este dispositivo?", "login_setup_complete_relogin": "A configuração foi concluída. O seu nó foi reiniciado, por isso inicie sessão novamente com as mesmas credenciais que utilizou para concluir a configuração.", "login_signin_apple": "Entrar com Apple", "login_signin_google": "Entrar com Google", diff --git a/client/androidApp/src/main/assets/localization/ru.json b/client/androidApp/src/main/assets/localization/ru.json index 484e28e..db4bf61 100644 --- a/client/androidApp/src/main/assets/localization/ru.json +++ b/client/androidApp/src/main/assets/localization/ru.json @@ -1860,7 +1860,7 @@ "login_privacy": "Посмотреть Политику конфиденциальности", "login_reset_device": "Сбросить устройство", "login_reset_device_cancel": "Отмена", - "login_reset_device_confirm_body": "Это удалит все локальные данные и вернёт мастер настройки. Отменить это действие невозможно.", + "login_reset_device_confirm_body": "Это удалит данные узла, хранящиеся на ЭТОМ устройстве, и вернёт вас к мастеру настройки. Узел, к которому вы подключены на другой машине, затронут не будет. Это действие нельзя отменить.", "login_reset_device_confirm_title": "Сбросить устройство?", "login_setup_complete_relogin": "Настройка завершена. Ваш узел перезапущен, поэтому войдите снова, используя те же учётные данные, что и при настройке.", "login_signin_apple": "Войти через Apple", diff --git a/client/androidApp/src/main/assets/localization/sw.json b/client/androidApp/src/main/assets/localization/sw.json index 522bfde..17baf50 100644 --- a/client/androidApp/src/main/assets/localization/sw.json +++ b/client/androidApp/src/main/assets/localization/sw.json @@ -1858,10 +1858,10 @@ "login_owner_hint": "Mwisho aliyeingia: {owner}", "login_password_label": "Nenosiri", "login_privacy": "Tazama Sera ya Faragha", - "login_reset_device": "Weka upya kifaa", + "login_reset_device": "Weka upya kifaa hiki", "login_reset_device_cancel": "Ghairi", - "login_reset_device_confirm_body": "Hii itafuta data yote ya ndani na kurudi kwa mwongozo wa usanidi. Hatua hii haiwezi kutenduliwa.", - "login_reset_device_confirm_title": "Weka upya kifaa?", + "login_reset_device_confirm_body": "Hii itafuta data ya nodi iliyohifadhiwa kwenye kifaa HIKI na kurudi kwa mchawi wa usanidi. Nodi ambayo umeunganishwa nayo kwenye mashine nyingine haitaathiriwa. Hatua hii haiwezi kutenduliwa.", + "login_reset_device_confirm_title": "Weka upya kifaa hiki?", "login_setup_complete_relogin": "Usanidi umekamilika. Nodi yako imeanzishwa upya, kwa hivyo tafadhali ingia tena ukitumia stakabadhi zilezile ulizotumia kukamilisha usanidi.", "login_signin_apple": "Ingia kwa kutumia Apple", "login_signin_google": "Ingia kwa kutumia Google", diff --git a/client/androidApp/src/main/assets/localization/ta.json b/client/androidApp/src/main/assets/localization/ta.json index 34f5c0e..f3a999a 100644 --- a/client/androidApp/src/main/assets/localization/ta.json +++ b/client/androidApp/src/main/assets/localization/ta.json @@ -1860,7 +1860,7 @@ "login_privacy": "காண் Privacy Policy", "login_reset_device": "சாதனத்தை மீட்டமை", "login_reset_device_cancel": "ரத்து", - "login_reset_device_confirm_body": "இது அனைத்து உள்ளூர் தரவையும் அழித்து, அமைவு வழிகாட்டிக்கு திரும்பும். இதை மீண்டும் செயல்தவிர்க்க இயலாது.", + "login_reset_device_confirm_body": "இது இந்த சாதனத்தில் சேமிக்கப்பட்டுள்ள நோடு தரவை அழித்து, அமைவு வழிகாட்டிக்குத் திரும்பும். மற்றொரு கணினியில் நீங்கள் இணைந்திருக்கும் நோடு பாதிக்கப்படாது. இந்த செயலை மீள மாற்ற முடியாது.", "login_reset_device_confirm_title": "சாதனத்தை மீட்டமைக்கவா?", "login_setup_complete_relogin": "அமைவு நிறைவடைந்தது. உங்கள் நோடு மறுதொடக்கம் செய்யப்பட்டது, எனவே அமைவை நிறைவு செய்யப் பயன்படுத்திய அதே சான்றுகளுடன் மீண்டும் உள்நுழையவும்.", "login_signin_apple": "Sign in உடன் Apple", diff --git a/client/androidApp/src/main/assets/localization/te.json b/client/androidApp/src/main/assets/localization/te.json index 78b6759..57f56c3 100644 --- a/client/androidApp/src/main/assets/localization/te.json +++ b/client/androidApp/src/main/assets/localization/te.json @@ -1858,10 +1858,10 @@ "login_owner_hint": "చివరిగా సైన్ ఇన్ చేసినది: {owner}", "login_password_label": "పాస్‌వర్డ్", "login_privacy": "చూడు Privacy Policy", - "login_reset_device": "పరికరాన్ని రీసెట్ చేయండి", + "login_reset_device": "ఈ పరికరాన్ని రీసెట్ చేయండి", "login_reset_device_cancel": "రద్దు చేయి", - "login_reset_device_confirm_body": "ఇది అన్ని స్థానిక డేటాను తొలగించి, సెటప్ విజార్డ్‌కు తిరిగి వెళ్తుంది. దీన్ని రద్దు చేయడం సాధ్యం కాదు.", - "login_reset_device_confirm_title": "పరికరాన్ని రీసెట్ చేయాలా?", + "login_reset_device_confirm_body": "ఇది ఈ పరికరంలో నిల్వ చేసిన నోడ్ డేటాను మొత్తం తొలగించి, సెటప్ విజార్డ్‌కు తిరిగి వెళ్తుంది. మీరు మరొక యంత్రంలో కనెక్ట్ అయి ఉన్న నోడ్ ప్రభావితం కాదు. ఈ చర్యను రద్దు చేయలేరు.", + "login_reset_device_confirm_title": "ఈ పరికరాన్ని రీసెట్ చేయాలా?", "login_setup_complete_relogin": "సెటప్ పూర్తయింది. మీ నోడ్ పునఃప్రారంభమైంది, కాబట్టి సెటప్ పూర్తి చేయడానికి మీరు ఉపయోగించిన అదే ఆధారాలతో దయచేసి మళ్లీ లాగిన్ అవ్వండి.", "login_signin_apple": "Apple తో సైన్ ఇన్ చేయండి", "login_signin_google": "Google తో సైన్ ఇన్ చేయండి", diff --git a/client/androidApp/src/main/assets/localization/th.json b/client/androidApp/src/main/assets/localization/th.json index 78de09e..079e23f 100644 --- a/client/androidApp/src/main/assets/localization/th.json +++ b/client/androidApp/src/main/assets/localization/th.json @@ -1858,10 +1858,10 @@ "login_owner_hint": "เข้าสู่ระบบล่าสุด: {owner}", "login_password_label": "รหัสผ่าน", "login_privacy": "ดูนโยบายความเป็นส่วนตัว", - "login_reset_device": "รีเซ็ตอุปกรณ์", + "login_reset_device": "รีเซ็ตอุปกรณ์นี้", "login_reset_device_cancel": "ยกเลิก", - "login_reset_device_confirm_body": "การดำเนินการนี้จะลบข้อมูลในเครื่องทั้งหมดและกลับสู่วิซาร์ดการตั้งค่า ไม่สามารถย้อนกลับได้", - "login_reset_device_confirm_title": "รีเซ็ตอุปกรณ์?", + "login_reset_device_confirm_body": "การดำเนินการนี้จะลบข้อมูลโหนดที่เก็บอยู่บนอุปกรณ์นี้และกลับไปยังตัวช่วยการตั้งค่า โหนดที่คุณเชื่อมต่ออยู่บนเครื่องอื่นจะไม่ได้รับผลกระทบ การดำเนินการนี้ไม่สามารถยกเลิกได้", + "login_reset_device_confirm_title": "รีเซ็ตอุปกรณ์นี้?", "login_setup_complete_relogin": "การตั้งค่าเสร็จสมบูรณ์แล้ว โหนดของคุณได้รีสตาร์ท กรุณาเข้าสู่ระบบอีกครั้งด้วยข้อมูลรับรองเดียวกับที่คุณใช้ในการตั้งค่า", "login_signin_apple": "เข้าสู่ระบบด้วย Apple", "login_signin_google": "เข้าสู่ระบบด้วย Google", diff --git a/client/androidApp/src/main/assets/localization/tr.json b/client/androidApp/src/main/assets/localization/tr.json index 9708b87..2bc1988 100644 --- a/client/androidApp/src/main/assets/localization/tr.json +++ b/client/androidApp/src/main/assets/localization/tr.json @@ -1860,7 +1860,7 @@ "login_privacy": "Gizlilik Politikasını Görüntüle", "login_reset_device": "Cihazı Sıfırla", "login_reset_device_cancel": "İptal", - "login_reset_device_confirm_body": "Bu işlem tüm yerel verileri silecek ve kurulum sihirbazına geri dönecektir. Bu işlem geri alınamaz.", + "login_reset_device_confirm_body": "Bu işlem, BU cihazda depolanan düğüm verilerini siler ve kurulum sihirbazına döner. Başka bir makinede bağlı olduğunuz bir düğüm etkilenmez. Bu geri alınamaz.", "login_reset_device_confirm_title": "Cihaz sıfırlansın mı?", "login_setup_complete_relogin": "Kurulum tamamlandı. Düğümünüz yeniden başlatıldı; lütfen kurulumu tamamlarken kullandığınız kimlik bilgileriyle yeniden oturum açın.", "login_signin_apple": "Apple ile giriş yap", diff --git a/client/androidApp/src/main/assets/localization/uk.json b/client/androidApp/src/main/assets/localization/uk.json index fafcb63..9de68ee 100644 --- a/client/androidApp/src/main/assets/localization/uk.json +++ b/client/androidApp/src/main/assets/localization/uk.json @@ -1860,7 +1860,7 @@ "login_privacy": "Переглянути Політику конфіденційності", "login_reset_device": "Скинути пристрій", "login_reset_device_cancel": "Скасувати", - "login_reset_device_confirm_body": "Це видалить усі локальні дані та повернеться до майстра налаштування. Цю дію неможливо скасувати.", + "login_reset_device_confirm_body": "Це видалить дані вузла, збережені НА ЦЬОМУ пристрої, та поверне вас до майстра налаштування. Це не впливає на вузол, до якого ви підключені на іншому комп'ютері. Цю дію неможливо скасувати.", "login_reset_device_confirm_title": "Скинути пристрій?", "login_setup_complete_relogin": "Налаштування завершено. Ваш вузол перезапустився, тому увійдіть знову, використовуючи ті самі облікові дані, з якими ви завершили налаштування.", "login_signin_apple": "Увійти через Apple", diff --git a/client/androidApp/src/main/assets/localization/ur.json b/client/androidApp/src/main/assets/localization/ur.json index 7c17d31..cecd77f 100644 --- a/client/androidApp/src/main/assets/localization/ur.json +++ b/client/androidApp/src/main/assets/localization/ur.json @@ -1860,7 +1860,7 @@ "login_privacy": "رازداری کی پالیسی دیکھیں", "login_reset_device": "ڈیوائس ری سیٹ کریں", "login_reset_device_cancel": "منسوخ کریں", - "login_reset_device_confirm_body": "یہ تمام مقامی ڈیٹا مٹا دے گا اور سیٹ اپ وزرڈ پر واپس لے جائے گا۔ یہ عمل واپس نہیں کیا جا سکتا۔", + "login_reset_device_confirm_body": "یہ اِسی ڈیوائس پر محفوظ نوڈ کا ڈیٹا مٹا دے گا اور سیٹ اپ وزرڈ پر واپس آ جائے گا۔ کسی دوسری مشین پر جس نوڈ سے آپ جڑے ہیں، اُس پر کوئی اثر نہیں پڑے گا۔ یہ عمل واپس نہیں کیا جا سکتا۔", "login_reset_device_confirm_title": "ڈیوائس ری سیٹ کریں؟", "login_setup_complete_relogin": "سیٹ اپ مکمل ہو گیا ہے۔ آپ کا نوڈ دوبارہ شروع ہوا ہے، اس لیے براہِ کرم انہی اسناد کے ساتھ دوبارہ لاگ اِن کریں جو آپ نے سیٹ اپ مکمل کرنے کے لیے استعمال کی تھیں۔", "login_signin_apple": "Apple کے ساتھ سائن ان کریں", diff --git a/client/androidApp/src/main/assets/localization/vi.json b/client/androidApp/src/main/assets/localization/vi.json index 245869a..24135ce 100644 --- a/client/androidApp/src/main/assets/localization/vi.json +++ b/client/androidApp/src/main/assets/localization/vi.json @@ -1860,7 +1860,7 @@ "login_privacy": "Xem chính sách bảo mật", "login_reset_device": "Đặt lại thiết bị", "login_reset_device_cancel": "Hủy", - "login_reset_device_confirm_body": "Thao tác này sẽ xóa tất cả dữ liệu cục bộ và quay lại trình hướng dẫn cài đặt. Hành động này không thể hoàn tác.", + "login_reset_device_confirm_body": "Thao tác này sẽ xóa dữ liệu nút được lưu trữ trên CHÍNH thiết bị này và quay lại trình hướng dẫn cài đặt. Nút mà bạn đang kết nối trên một máy khác sẽ không bị ảnh hưởng. Không thể hoàn tác.", "login_reset_device_confirm_title": "Đặt lại thiết bị?", "login_setup_complete_relogin": "Thiết lập đã hoàn tất. Nút của bạn đã khởi động lại, vui lòng đăng nhập lại bằng đúng thông tin đăng nhập mà bạn đã dùng để hoàn tất thiết lập.", "login_signin_apple": "Đăng nhập bằng Apple", diff --git a/client/androidApp/src/main/assets/localization/yo.json b/client/androidApp/src/main/assets/localization/yo.json index d7f8400..c7c0a0f 100644 --- a/client/androidApp/src/main/assets/localization/yo.json +++ b/client/androidApp/src/main/assets/localization/yo.json @@ -1858,10 +1858,10 @@ "login_owner_hint": "Ìwọlé ìkẹ̀yìn: {owner}", "login_password_label": "Ọ̀rọ̀ Àṣírí", "login_privacy": "Wo Ìlànà Àṣírí", - "login_reset_device": "Tún ẹ̀rọ ṣe", + "login_reset_device": "Tún ẹ̀rọ yìí ṣe", "login_reset_device_cancel": "Fagilé", - "login_reset_device_confirm_body": "Èyí yóò pa gbogbo dátà àdúgbò run kí ó sì padà sí olùṣèlú ìṣètò. Èyí kò lè yípadà.", - "login_reset_device_confirm_title": "Tún ẹ̀rọ ṣe?", + "login_reset_device_confirm_body": "Èyí yóò pa dátà nódù tí a fipamọ́ sórí Ẹ̀RỌ YÌÍ run kí ó sì padà sí olùtọ́nisọ́nà ìṣètò. Èyí kò ní kan nódù tí ẹ ti so pọ̀ mọ́ ẹ̀rọ mìíràn. Èyí kò ṣeépadà.", + "login_reset_device_confirm_title": "Tún ẹ̀rọ yìí ṣe?", "login_setup_complete_relogin": "Ìṣètò ti parí. Node rẹ tún bẹ̀rẹ̀, nítorí náà jọ̀wọ́ wọlé padà pẹ̀lú àwọn ẹ̀rí ìdánimọ̀ kan náà tí o lò láti parí ìṣètò.", "login_signin_apple": "Wọlé pẹ̀lú Apple", "login_signin_google": "Wọlé pẹ̀lú Google", diff --git a/client/androidApp/src/main/assets/localization/zh.json b/client/androidApp/src/main/assets/localization/zh.json index 2bcede3..570ceda 100644 --- a/client/androidApp/src/main/assets/localization/zh.json +++ b/client/androidApp/src/main/assets/localization/zh.json @@ -1860,7 +1860,7 @@ "login_privacy": "查看隐私政策", "login_reset_device": "重置设备", "login_reset_device_cancel": "取消", - "login_reset_device_confirm_body": "此操作将清除所有本地数据并返回设置向导。此操作无法撤销。", + "login_reset_device_confirm_body": "此操作将清除存储在本设备上的节点数据并返回设置向导。您在其他设备上已连接的节点不会受到影响。此操作无法撤销。", "login_reset_device_confirm_title": "重置设备?", "login_setup_complete_relogin": "设置已完成。您的节点已重新启动,请使用完成设置时使用的相同凭据重新登录。", "login_signin_apple": "使用 Apple 登录", diff --git a/client/desktopApp/src/main/resources/localization/am.json b/client/desktopApp/src/main/resources/localization/am.json index b478e20..6fc10f6 100644 --- a/client/desktopApp/src/main/resources/localization/am.json +++ b/client/desktopApp/src/main/resources/localization/am.json @@ -1858,10 +1858,10 @@ "login_owner_hint": "መጨረሻ የገባው: {owner}", "login_password_label": "የይለፍ ቃል", "login_privacy": "የግላዊነት መመሪያን ይመልከቱ", - "login_reset_device": "መሣሪያ ዳግም አስጀምር", + "login_reset_device": "ይህን መሣሪያ ዳግም አስጀምር", "login_reset_device_cancel": "ሰርዝ", - "login_reset_device_confirm_body": "ይህ ሁሉንም አካባቢያዊ ውሂብ ያጠፋና ወደ ማዋቀሪያ አዋቂ ይመልሳል። ይህ ድርጊት ሊቀለበስ አይችልም።", - "login_reset_device_confirm_title": "መሣሪያ ዳግም ይጀምር?", + "login_reset_device_confirm_body": "ይህ በዚህ መሣሪያ ላይ የተቀመጠውን የኖድ ውሂብ ያጠፋል እና ወደ የማዋቀሪያ አዋቂው ይመለሳል። በሌላ መሣሪያ ላይ የተገናኙበት ኖድ ግን ተጽዕኖ አይደርስበትም። ይህ ድርጊት መልሰው የማይቀር ነው።", + "login_reset_device_confirm_title": "ይህን መሣሪያ ዳግም ማስጀመር?", "login_setup_complete_relogin": "ማዋቀሩ ተጠናቅቋል። መስቀለኛዎ እንደገና ስለተጀመረ፣ እባክዎ ማዋቀሩን ለማጠናቀቅ በተጠቀሙባቸው ተመሳሳይ የመግቢያ መረጃዎች እንደገና ይግቡ።", "login_signin_apple": "በApple ይግቡ", "login_signin_google": "በGoogle ይግቡ", diff --git a/client/desktopApp/src/main/resources/localization/ar.json b/client/desktopApp/src/main/resources/localization/ar.json index 3b025d4..52541c6 100644 --- a/client/desktopApp/src/main/resources/localization/ar.json +++ b/client/desktopApp/src/main/resources/localization/ar.json @@ -1860,7 +1860,7 @@ "login_privacy": "عرض سياسة الخصوصية", "login_reset_device": "إعادة ضبط الجهاز", "login_reset_device_cancel": "إلغاء", - "login_reset_device_confirm_body": "سيؤدي ذلك إلى مسح جميع البيانات المحلية والعودة إلى معالج الإعداد. لا يمكن التراجع عن هذا الإجراء.", + "login_reset_device_confirm_body": "سيؤدي ذلك إلى مسح بيانات العقدة المخزّنة على هذا الجهاز والعودة إلى معالج الإعداد. أما أي عقدة تتصل بها على جهاز آخر فلن تتأثر. لا يمكن التراجع عن هذا الإجراء.", "login_reset_device_confirm_title": "إعادة ضبط الجهاز؟", "login_setup_complete_relogin": "اكتمل الإعداد. أُعيد تشغيل العقدة، لذا يُرجى تسجيل الدخول مجددًا باستخدام بيانات الاعتماد نفسها التي استخدمتها لإكمال الإعداد.", "login_signin_apple": "تسجيل الدخول باستخدام Apple", diff --git a/client/desktopApp/src/main/resources/localization/bn.json b/client/desktopApp/src/main/resources/localization/bn.json index 30f1052..cbb52b3 100644 --- a/client/desktopApp/src/main/resources/localization/bn.json +++ b/client/desktopApp/src/main/resources/localization/bn.json @@ -1861,7 +1861,7 @@ "login_privacy": "গোপনীয়তা নীতি দেখুন", "login_reset_device": "ডিভাইস রিসেট করুন", "login_reset_device_cancel": "বাতিল", - "login_reset_device_confirm_body": "এটি সমস্ত স্থানীয় ডেটা মুছে দেবে এবং সেটআপ উইজার্ডে ফিরে যাবে। এটি পূর্বাবস্থায় ফেরানো যাবে না।", + "login_reset_device_confirm_body": "এটি এই ডিভাইসে সংরক্ষিত নোডের ডেটা মুছে দেবে এবং সেটআপ উইজার্ডে ফিরে যাবে। অন্য কোনো মেশিনে আপনি যে নোডের সাথে সংযুক্ত আছেন, সেটি প্রভাবিত হবে না। এটি পূর্বাবস্থায় ফেরানো যাবে না।", "login_reset_device_confirm_title": "ডিভাইস রিসেট করবেন?", "login_setup_complete_relogin": "সেটআপ সম্পূর্ণ হয়েছে। আপনার নোড পুনরায় চালু হয়েছে, তাই সেটআপ সম্পূর্ণ করতে আপনি যে শংসাপত্রগুলি ব্যবহার করেছিলেন সেগুলি দিয়েই আবার লগ ইন করুন।", "login_signin_apple": "Apple দিয়ে সাইন ইন করুন", diff --git a/client/desktopApp/src/main/resources/localization/de.json b/client/desktopApp/src/main/resources/localization/de.json index 74e684c..da1e0ac 100644 --- a/client/desktopApp/src/main/resources/localization/de.json +++ b/client/desktopApp/src/main/resources/localization/de.json @@ -1858,10 +1858,10 @@ "login_owner_hint": "Zuletzt angemeldet: {owner}", "login_password_label": "Passwort", "login_privacy": "Datenschutzerklärung anzeigen", - "login_reset_device": "Gerät zurücksetzen", + "login_reset_device": "Dieses Gerät zurücksetzen", "login_reset_device_cancel": "Abbrechen", - "login_reset_device_confirm_body": "Dadurch werden alle lokalen Daten gelöscht und Sie werden zum Einrichtungsassistenten zurückgeleitet. Diese Aktion kann nicht rückgängig gemacht werden.", - "login_reset_device_confirm_title": "Gerät zurücksetzen?", + "login_reset_device_confirm_body": "Dies löscht die Knotendaten, die auf DIESEM Gerät gespeichert sind, und kehrt zum Einrichtungsassistenten zurück. Ein Knoten, mit dem Sie auf einem anderen Rechner verbunden sind, ist davon nicht betroffen. Dies kann nicht rückgängig gemacht werden.", + "login_reset_device_confirm_title": "Dieses Gerät zurücksetzen?", "login_setup_complete_relogin": "Die Einrichtung ist abgeschlossen. Ihr Knoten wurde neu gestartet – bitte melden Sie sich erneut mit denselben Zugangsdaten an, die Sie für die Einrichtung verwendet haben.", "login_signin_apple": "Mit Apple anmelden", "login_signin_google": "Mit Google anmelden", diff --git a/client/desktopApp/src/main/resources/localization/es.json b/client/desktopApp/src/main/resources/localization/es.json index 66a6762..fe02dbf 100644 --- a/client/desktopApp/src/main/resources/localization/es.json +++ b/client/desktopApp/src/main/resources/localization/es.json @@ -1860,7 +1860,7 @@ "login_privacy": "Ver Política de Privacidad", "login_reset_device": "Restablecer dispositivo", "login_reset_device_cancel": "Cancelar", - "login_reset_device_confirm_body": "Esto borrará todos los datos locales y volverás al asistente de configuración. Esta acción no se puede deshacer.", + "login_reset_device_confirm_body": "Esto borrará los datos del nodo almacenados en ESTE dispositivo y volverá al asistente de configuración. Un nodo al que estés conectado en otra máquina no se verá afectado. Esta acción no se puede deshacer.", "login_reset_device_confirm_title": "¿Restablecer dispositivo?", "login_setup_complete_relogin": "La configuración ha finalizado. Tu nodo se reinició, así que vuelve a iniciar sesión con las mismas credenciales que usaste para completar la configuración.", "login_signin_apple": "Iniciar sesión con Apple", diff --git a/client/desktopApp/src/main/resources/localization/fa.json b/client/desktopApp/src/main/resources/localization/fa.json index d9caea0..38b931c 100644 --- a/client/desktopApp/src/main/resources/localization/fa.json +++ b/client/desktopApp/src/main/resources/localization/fa.json @@ -1866,7 +1866,7 @@ "login_privacy": "مشاهده سیاست حریم خصوصی", "login_reset_device": "بازنشانی دستگاه", "login_reset_device_cancel": "لغو", - "login_reset_device_confirm_body": "این عمل تمام داده‌های محلی را پاک می‌کند و به راه‌اندازی اولیه بازمی‌گردد. این کار قابل بازگشت نیست.", + "login_reset_device_confirm_body": "این کار داده‌های گره ذخیره‌شده روی همین دستگاه را پاک می‌کند و به دستیار راه‌اندازی بازمی‌گردد. گرهی که روی دستگاهی دیگر به آن متصل هستید تحت تأثیر قرار نمی‌گیرد. این کار قابل بازگشت نیست.", "login_reset_device_confirm_title": "بازنشانی دستگاه؟", "login_setup_complete_relogin": "راه‌اندازی کامل شد. نود شما دوباره راه‌اندازی شد؛ لطفاً با همان اطلاعات ورودی که برای تکمیل راه‌اندازی استفاده کردید، دوباره وارد شوید.", "login_signin_apple": "ورود باستخدام Apple", diff --git a/client/desktopApp/src/main/resources/localization/fr.json b/client/desktopApp/src/main/resources/localization/fr.json index 10abf12..fe1f844 100644 --- a/client/desktopApp/src/main/resources/localization/fr.json +++ b/client/desktopApp/src/main/resources/localization/fr.json @@ -1860,7 +1860,7 @@ "login_privacy": "Voir la Politique de Confidentialité", "login_reset_device": "Réinitialiser l'appareil", "login_reset_device_cancel": "Annuler", - "login_reset_device_confirm_body": "Cela effacera toutes les données locales et vous ramènera à l'assistant de configuration. Cette action est irréversible.", + "login_reset_device_confirm_body": "Cela efface les données du nœud stockées sur CET appareil et revient à l'assistant de configuration. Un nœud auquel vous êtes connecté sur une autre machine n'est pas affecté. Cela ne peut pas être annulé.", "login_reset_device_confirm_title": "Réinitialiser l'appareil ?", "login_setup_complete_relogin": "La configuration est terminée. Votre nœud a redémarré : veuillez vous reconnecter avec les mêmes identifiants que ceux utilisés pour terminer la configuration.", "login_signin_apple": "Se connecter avec Apple", diff --git a/client/desktopApp/src/main/resources/localization/ha.json b/client/desktopApp/src/main/resources/localization/ha.json index 7ac4491..f5f91af 100644 --- a/client/desktopApp/src/main/resources/localization/ha.json +++ b/client/desktopApp/src/main/resources/localization/ha.json @@ -1860,7 +1860,7 @@ "login_privacy": "Duba Sera na Asirta", "login_reset_device": "Sake saita na'ura", "login_reset_device_cancel": "Soke", - "login_reset_device_confirm_body": "Wannan zai share duk bayanan gida kuma ya koma zuwa jagorar saiti. Ba za a iya maido da wannan ba.", + "login_reset_device_confirm_body": "Wannan zai share bayanan kumburin da aka ajiye a WANNAN na'urar kuma ya koma zuwa jagorar saiti. Kumburin da kuke haɗe da shi a wata na'ura ba zai sami tasiri ba. Ba za a iya mayar da wannan aiki ba.", "login_reset_device_confirm_title": "Sake saita na'ura?", "login_setup_complete_relogin": "An kammala saitin. Node ɗinka ya sake farawa, don haka ka sake shiga da bayanan shiga guda ɗaya da ka yi amfani da su wajen kammala saitin.", "login_signin_apple": "Shiga tare da Apple", diff --git a/client/desktopApp/src/main/resources/localization/hi.json b/client/desktopApp/src/main/resources/localization/hi.json index 7654f45..504fa17 100644 --- a/client/desktopApp/src/main/resources/localization/hi.json +++ b/client/desktopApp/src/main/resources/localization/hi.json @@ -1858,10 +1858,10 @@ "login_owner_hint": "पिछली बार साइन इन किया: {owner}", "login_password_label": "पासवर्ड", "login_privacy": "गोपनीयता नीति देखें", - "login_reset_device": "डिवाइस रीसेट करें", + "login_reset_device": "इस डिवाइस को रीसेट करें", "login_reset_device_cancel": "रद्द करें", - "login_reset_device_confirm_body": "यह सभी स्थानीय डेटा मिटा देगा और सेटअप विज़ार्ड पर वापस ले जाएगा। इसे पूर्ववत नहीं किया जा सकता।", - "login_reset_device_confirm_title": "डिवाइस रीसेट करें?", + "login_reset_device_confirm_body": "यह इस डिवाइस पर संग्रहीत नोड डेटा मिटा देगा और सेटअप विज़ार्ड पर वापस ले जाएगा। किसी अन्य मशीन पर जिस नोड से आप जुड़े हैं, वह प्रभावित नहीं होगा। इसे पूर्ववत नहीं किया जा सकता।", + "login_reset_device_confirm_title": "इस डिवाइस को रीसेट करें?", "login_setup_complete_relogin": "सेटअप पूरा हो गया है। आपका नोड फिर से चालू हुआ है, इसलिए कृपया उन्हीं क्रेडेंशियल से दोबारा लॉग इन करें जिनका उपयोग आपने सेटअप पूरा करने के लिए किया था।", "login_signin_apple": "Apple से साइन इन करें", "login_signin_google": "Google से साइन इन करें", diff --git a/client/desktopApp/src/main/resources/localization/id.json b/client/desktopApp/src/main/resources/localization/id.json index 809bf6a..d51d506 100644 --- a/client/desktopApp/src/main/resources/localization/id.json +++ b/client/desktopApp/src/main/resources/localization/id.json @@ -1858,10 +1858,10 @@ "login_owner_hint": "Terakhir masuk: {owner}", "login_password_label": "Kata Sandi", "login_privacy": "Lihat Kebijakan Privasi", - "login_reset_device": "Atur ulang perangkat", + "login_reset_device": "Atur ulang perangkat ini", "login_reset_device_cancel": "Batal", - "login_reset_device_confirm_body": "Ini akan menghapus semua data lokal dan kembali ke wizard penyiapan. Tindakan ini tidak dapat dibatalkan.", - "login_reset_device_confirm_title": "Atur ulang perangkat?", + "login_reset_device_confirm_body": "Ini akan menghapus data node yang tersimpan di perangkat INI dan kembali ke wizard penyiapan. Node tempat Anda terhubung di mesin lain tidak terpengaruh. Tindakan ini tidak dapat dibatalkan.", + "login_reset_device_confirm_title": "Atur ulang perangkat ini?", "login_setup_complete_relogin": "Penyiapan selesai. Node Anda telah dimulai ulang, jadi silakan masuk kembali dengan kredensial yang sama seperti yang Anda gunakan saat menyelesaikan penyiapan.", "login_signin_apple": "Masuk dengan Apple", "login_signin_google": "Masuk dengan Google", diff --git a/client/desktopApp/src/main/resources/localization/it.json b/client/desktopApp/src/main/resources/localization/it.json index 1748af9..525dd69 100644 --- a/client/desktopApp/src/main/resources/localization/it.json +++ b/client/desktopApp/src/main/resources/localization/it.json @@ -1860,7 +1860,7 @@ "login_privacy": "Visualizza Informativa sulla Privacy", "login_reset_device": "Ripristina dispositivo", "login_reset_device_cancel": "Annulla", - "login_reset_device_confirm_body": "Verranno cancellati tutti i dati locali e si tornerà alla procedura guidata di configurazione. Questa azione non può essere annullata.", + "login_reset_device_confirm_body": "Verranno cancellati i dati del nodo memorizzati su QUESTO dispositivo e si tornerà alla procedura guidata di configurazione. Un nodo a cui sei connesso su un'altra macchina non subisce modifiche. Questa azione non può essere annullata.", "login_reset_device_confirm_title": "Ripristinare il dispositivo?", "login_setup_complete_relogin": "La configurazione è completata. Il tuo nodo è stato riavviato: accedi di nuovo con le stesse credenziali che hai usato per completare la configurazione.", "login_signin_apple": "Accedi con Apple", diff --git a/client/desktopApp/src/main/resources/localization/ja.json b/client/desktopApp/src/main/resources/localization/ja.json index 07f3851..371b63a 100644 --- a/client/desktopApp/src/main/resources/localization/ja.json +++ b/client/desktopApp/src/main/resources/localization/ja.json @@ -1860,7 +1860,7 @@ "login_privacy": "プライバシーポリシーを表示", "login_reset_device": "デバイスをリセット", "login_reset_device_cancel": "キャンセル", - "login_reset_device_confirm_body": "すべてのローカルデータが消去され、セットアップウィザードに戻ります。この操作は元に戻せません。", + "login_reset_device_confirm_body": "このデバイスに保存されているノードデータが消去され、セットアップウィザードに戻ります。別のマシン上で接続しているノードには影響しません。この操作は元に戻せません。", "login_reset_device_confirm_title": "デバイスをリセットしますか?", "login_setup_complete_relogin": "セットアップが完了しました。ノードが再起動したため、セットアップで使用したものと同じ認証情報で再度ログインしてください。", "login_signin_apple": "Appleでサインイン", diff --git a/client/desktopApp/src/main/resources/localization/ko.json b/client/desktopApp/src/main/resources/localization/ko.json index a766a42..e46b77f 100644 --- a/client/desktopApp/src/main/resources/localization/ko.json +++ b/client/desktopApp/src/main/resources/localization/ko.json @@ -1860,7 +1860,7 @@ "login_privacy": "개인정보 보호정책 보기", "login_reset_device": "기기 재설정", "login_reset_device_cancel": "취소", - "login_reset_device_confirm_body": "모든 로컬 데이터가 삭제되고 설정 마법사로 돌아갑니다. 이 작업은 취소할 수 없습니다.", + "login_reset_device_confirm_body": "이 작업은 이 기기에 저장된 노드 데이터를 삭제하고 설정 마법사로 돌아갑니다. 다른 기기에서 연결해 사용 중인 노드는 영향을 받지 않습니다. 이 작업은 취소할 수 없습니다.", "login_reset_device_confirm_title": "기기를 재설정하시겠습니까?", "login_setup_complete_relogin": "설정이 완료되었습니다. 노드가 재시작되었으므로 설정에 사용한 것과 동일한 자격 증명으로 다시 로그인해 주세요.", "login_signin_apple": "Apple로 로그인", diff --git a/client/desktopApp/src/main/resources/localization/mr.json b/client/desktopApp/src/main/resources/localization/mr.json index 0c7cf1f..35d3208 100644 --- a/client/desktopApp/src/main/resources/localization/mr.json +++ b/client/desktopApp/src/main/resources/localization/mr.json @@ -1860,7 +1860,7 @@ "login_privacy": "गोपनीयता धोरण पाहा", "login_reset_device": "डिव्हाइस रीसेट करा", "login_reset_device_cancel": "रद्द करा", - "login_reset_device_confirm_body": "हे सर्व स्थानिक डेटा मिटवेल आणि सेटअप विझार्डवर परत जाईल. हे पूर्ववत केले जाऊ शकत नाही.", + "login_reset_device_confirm_body": "यामुळे याच डिव्हाइसवर साठवलेला नोड डेटा पुसला जाईल आणि सेटअप विझार्डवर परत जाईल. तुम्ही दुसऱ्या मशीनवरील ज्या नोडशी जोडलेले आहात, त्यावर याचा परिणाम होत नाही. हे पूर्ववत केले जाऊ शकत नाही.", "login_reset_device_confirm_title": "डिव्हाइस रीसेट करायचे?", "login_setup_complete_relogin": "सेटअप पूर्ण झाला आहे. तुमचा नोड पुन्हा सुरू झाला आहे, त्यामुळे सेटअप पूर्ण करण्यासाठी वापरलेल्या त्याच क्रेडेन्शियल्सने कृपया पुन्हा लॉग इन करा.", "login_signin_apple": "Apple सह साइन इन करा", diff --git a/client/desktopApp/src/main/resources/localization/my.json b/client/desktopApp/src/main/resources/localization/my.json index aa01418..37d756b 100644 --- a/client/desktopApp/src/main/resources/localization/my.json +++ b/client/desktopApp/src/main/resources/localization/my.json @@ -1860,7 +1860,7 @@ "login_privacy": "ကိုယ်ရေးကာကွယ်မှု မူဝါဒ ကြည့်ရှုပါ", "login_reset_device": "စက်ပစ္စည်း ပြန်လည်သတ်မှတ်ပါ", "login_reset_device_cancel": "ပယ်ဖျက်", - "login_reset_device_confirm_body": "ဤလုပ်ဆောင်ချက်သည် ဒေသတွင်းဒေတာအားလုံးကို ဖျက်ပြီး ဆက်တင် wizard သို့ ပြန်သွားမည်ဖြစ်သည်။ ဤလုပ်ဆောင်ချက်ကို ပြန်ဖြည့်၍မရပါ။", + "login_reset_device_confirm_body": "ဤလုပ်ဆောင်ချက်သည် ဤစက်ပေါ်တွင် သိမ်းဆည်းထားသော node ဒေတာကို ဖျက်ပြီး ဆက်တင် wizard သို့ ပြန်သွားမည်ဖြစ်သည်။ သင် အခြားစက်တွင် ချိတ်ဆက်ထားသည့် node သည် ထိခိုက်မည် မဟုတ်ပါ။ ဤလုပ်ဆောင်ချက်ကို ပြန်မလုပ်နိုင်ပါ။", "login_reset_device_confirm_title": "စက်ပစ္စည်း ပြန်လည်သတ်မှတ်မည်လား?", "login_setup_complete_relogin": "စနစ်ထည့်သွင်းမှု ပြီးဆုံးပါပြီ။ သင့် node ပြန်လည်စတင်ခဲ့သဖြင့် စနစ်ထည့်သွင်းရာတွင် အသုံးပြုခဲ့သည့် အထောက်အထားများဖြင့်ပင် ပြန်လည်ဝင်ရောက်ပါ။", "login_signin_apple": "Apple ဖြင့် ဝင်ရောက်", diff --git a/client/desktopApp/src/main/resources/localization/pa.json b/client/desktopApp/src/main/resources/localization/pa.json index ebf8145..b5d51c5 100644 --- a/client/desktopApp/src/main/resources/localization/pa.json +++ b/client/desktopApp/src/main/resources/localization/pa.json @@ -1860,7 +1860,7 @@ "login_privacy": "ਗੋਪਨੀਯਤਾ ਨੀਤੀ ਦੇਖੋ", "login_reset_device": "ਡਿਵਾਈਸ ਰੀਸੈੱਟ ਕਰੋ", "login_reset_device_cancel": "ਰੱਦ ਕਰੋ", - "login_reset_device_confirm_body": "ਇਹ ਸਾਰਾ ਸਥਾਨਕ ਡੇਟਾ ਮਿਟਾ ਦੇਵੇਗਾ ਅਤੇ ਸੈੱਟਅੱਪ ਵਿਜ਼ਾਰਡ 'ਤੇ ਵਾਪਸ ਆ ਜਾਵੇਗਾ। ਇਸ ਨੂੰ ਪਹਿਲਾਂ ਵਾਲੀ ਸਥਿਤੀ ਵਿੱਚ ਨਹੀਂ ਲਿਆਂਦਾ ਜਾ ਸਕਦਾ।", + "login_reset_device_confirm_body": "ਇਹ ਇਸ ਡਿਵਾਈਸ 'ਤੇ ਸਟੋਰ ਕੀਤਾ ਨੋਡ ਡੇਟਾ ਮਿਟਾ ਦੇਵੇਗਾ ਅਤੇ ਸੈੱਟਅੱਪ ਵਿਜ਼ਾਰਡ 'ਤੇ ਵਾਪਸ ਆ ਜਾਵੇਗਾ। ਕਿਸੇ ਹੋਰ ਮਸ਼ੀਨ ਉੱਤੇ ਜਿਸ ਨੋਡ ਨਾਲ ਤੁਸੀਂ ਜੁੜੇ ਹੋ, ਉਹ ਪ੍ਰਭਾਵਿਤ ਨਹੀਂ ਹੁੰਦਾ। ਇਹ ਵਾਪਸ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ।", "login_reset_device_confirm_title": "ਡਿਵਾਈਸ ਰੀਸੈੱਟ ਕਰਨਾ ਹੈ?", "login_setup_complete_relogin": "ਸੈੱਟਅੱਪ ਪੂਰਾ ਹੋ ਗਿਆ ਹੈ। ਤੁਹਾਡਾ ਨੋਡ ਮੁੜ ਚਾਲੂ ਹੋਇਆ ਹੈ, ਇਸ ਲਈ ਕਿਰਪਾ ਕਰਕੇ ਉਹੀ ਪ੍ਰਮਾਣ-ਪੱਤਰ ਵਰਤ ਕੇ ਮੁੜ ਲਾਗਇਨ ਕਰੋ ਜੋ ਤੁਸੀਂ ਸੈੱਟਅੱਪ ਪੂਰਾ ਕਰਨ ਲਈ ਵਰਤੇ ਸਨ।", "login_signin_apple": "Apple ਨਾਲ ਸਾਈਨ ਇਨ ਕਰੋ", diff --git a/client/desktopApp/src/main/resources/localization/pt.json b/client/desktopApp/src/main/resources/localization/pt.json index fa5fa8c..d4d550e 100644 --- a/client/desktopApp/src/main/resources/localization/pt.json +++ b/client/desktopApp/src/main/resources/localization/pt.json @@ -1858,10 +1858,10 @@ "login_owner_hint": "Último acesso: {owner}", "login_password_label": "Senha", "login_privacy": "Ver Política de Privacidade", - "login_reset_device": "Redefinir dispositivo", + "login_reset_device": "Redefinir este dispositivo", "login_reset_device_cancel": "Cancelar", - "login_reset_device_confirm_body": "Isso apagará todos os dados locais e retornará ao assistente de configuração. Esta ação não pode ser desfeita.", - "login_reset_device_confirm_title": "Redefinir dispositivo?", + "login_reset_device_confirm_body": "Isso excluirá os dados do nó armazenados NESTE dispositivo e retornará ao assistente de configuração. Um nó ao qual você esteja conectado em outra máquina não é afetado. Isso não pode ser desfeito.", + "login_reset_device_confirm_title": "Redefinir este dispositivo?", "login_setup_complete_relogin": "A configuração foi concluída. O seu nó foi reiniciado, por isso inicie sessão novamente com as mesmas credenciais que utilizou para concluir a configuração.", "login_signin_apple": "Entrar com Apple", "login_signin_google": "Entrar com Google", diff --git a/client/desktopApp/src/main/resources/localization/ru.json b/client/desktopApp/src/main/resources/localization/ru.json index 484e28e..db4bf61 100644 --- a/client/desktopApp/src/main/resources/localization/ru.json +++ b/client/desktopApp/src/main/resources/localization/ru.json @@ -1860,7 +1860,7 @@ "login_privacy": "Посмотреть Политику конфиденциальности", "login_reset_device": "Сбросить устройство", "login_reset_device_cancel": "Отмена", - "login_reset_device_confirm_body": "Это удалит все локальные данные и вернёт мастер настройки. Отменить это действие невозможно.", + "login_reset_device_confirm_body": "Это удалит данные узла, хранящиеся на ЭТОМ устройстве, и вернёт вас к мастеру настройки. Узел, к которому вы подключены на другой машине, затронут не будет. Это действие нельзя отменить.", "login_reset_device_confirm_title": "Сбросить устройство?", "login_setup_complete_relogin": "Настройка завершена. Ваш узел перезапущен, поэтому войдите снова, используя те же учётные данные, что и при настройке.", "login_signin_apple": "Войти через Apple", diff --git a/client/desktopApp/src/main/resources/localization/sw.json b/client/desktopApp/src/main/resources/localization/sw.json index 522bfde..17baf50 100644 --- a/client/desktopApp/src/main/resources/localization/sw.json +++ b/client/desktopApp/src/main/resources/localization/sw.json @@ -1858,10 +1858,10 @@ "login_owner_hint": "Mwisho aliyeingia: {owner}", "login_password_label": "Nenosiri", "login_privacy": "Tazama Sera ya Faragha", - "login_reset_device": "Weka upya kifaa", + "login_reset_device": "Weka upya kifaa hiki", "login_reset_device_cancel": "Ghairi", - "login_reset_device_confirm_body": "Hii itafuta data yote ya ndani na kurudi kwa mwongozo wa usanidi. Hatua hii haiwezi kutenduliwa.", - "login_reset_device_confirm_title": "Weka upya kifaa?", + "login_reset_device_confirm_body": "Hii itafuta data ya nodi iliyohifadhiwa kwenye kifaa HIKI na kurudi kwa mchawi wa usanidi. Nodi ambayo umeunganishwa nayo kwenye mashine nyingine haitaathiriwa. Hatua hii haiwezi kutenduliwa.", + "login_reset_device_confirm_title": "Weka upya kifaa hiki?", "login_setup_complete_relogin": "Usanidi umekamilika. Nodi yako imeanzishwa upya, kwa hivyo tafadhali ingia tena ukitumia stakabadhi zilezile ulizotumia kukamilisha usanidi.", "login_signin_apple": "Ingia kwa kutumia Apple", "login_signin_google": "Ingia kwa kutumia Google", diff --git a/client/desktopApp/src/main/resources/localization/ta.json b/client/desktopApp/src/main/resources/localization/ta.json index 34f5c0e..f3a999a 100644 --- a/client/desktopApp/src/main/resources/localization/ta.json +++ b/client/desktopApp/src/main/resources/localization/ta.json @@ -1860,7 +1860,7 @@ "login_privacy": "காண் Privacy Policy", "login_reset_device": "சாதனத்தை மீட்டமை", "login_reset_device_cancel": "ரத்து", - "login_reset_device_confirm_body": "இது அனைத்து உள்ளூர் தரவையும் அழித்து, அமைவு வழிகாட்டிக்கு திரும்பும். இதை மீண்டும் செயல்தவிர்க்க இயலாது.", + "login_reset_device_confirm_body": "இது இந்த சாதனத்தில் சேமிக்கப்பட்டுள்ள நோடு தரவை அழித்து, அமைவு வழிகாட்டிக்குத் திரும்பும். மற்றொரு கணினியில் நீங்கள் இணைந்திருக்கும் நோடு பாதிக்கப்படாது. இந்த செயலை மீள மாற்ற முடியாது.", "login_reset_device_confirm_title": "சாதனத்தை மீட்டமைக்கவா?", "login_setup_complete_relogin": "அமைவு நிறைவடைந்தது. உங்கள் நோடு மறுதொடக்கம் செய்யப்பட்டது, எனவே அமைவை நிறைவு செய்யப் பயன்படுத்திய அதே சான்றுகளுடன் மீண்டும் உள்நுழையவும்.", "login_signin_apple": "Sign in உடன் Apple", diff --git a/client/desktopApp/src/main/resources/localization/te.json b/client/desktopApp/src/main/resources/localization/te.json index 78b6759..57f56c3 100644 --- a/client/desktopApp/src/main/resources/localization/te.json +++ b/client/desktopApp/src/main/resources/localization/te.json @@ -1858,10 +1858,10 @@ "login_owner_hint": "చివరిగా సైన్ ఇన్ చేసినది: {owner}", "login_password_label": "పాస్‌వర్డ్", "login_privacy": "చూడు Privacy Policy", - "login_reset_device": "పరికరాన్ని రీసెట్ చేయండి", + "login_reset_device": "ఈ పరికరాన్ని రీసెట్ చేయండి", "login_reset_device_cancel": "రద్దు చేయి", - "login_reset_device_confirm_body": "ఇది అన్ని స్థానిక డేటాను తొలగించి, సెటప్ విజార్డ్‌కు తిరిగి వెళ్తుంది. దీన్ని రద్దు చేయడం సాధ్యం కాదు.", - "login_reset_device_confirm_title": "పరికరాన్ని రీసెట్ చేయాలా?", + "login_reset_device_confirm_body": "ఇది ఈ పరికరంలో నిల్వ చేసిన నోడ్ డేటాను మొత్తం తొలగించి, సెటప్ విజార్డ్‌కు తిరిగి వెళ్తుంది. మీరు మరొక యంత్రంలో కనెక్ట్ అయి ఉన్న నోడ్ ప్రభావితం కాదు. ఈ చర్యను రద్దు చేయలేరు.", + "login_reset_device_confirm_title": "ఈ పరికరాన్ని రీసెట్ చేయాలా?", "login_setup_complete_relogin": "సెటప్ పూర్తయింది. మీ నోడ్ పునఃప్రారంభమైంది, కాబట్టి సెటప్ పూర్తి చేయడానికి మీరు ఉపయోగించిన అదే ఆధారాలతో దయచేసి మళ్లీ లాగిన్ అవ్వండి.", "login_signin_apple": "Apple తో సైన్ ఇన్ చేయండి", "login_signin_google": "Google తో సైన్ ఇన్ చేయండి", diff --git a/client/desktopApp/src/main/resources/localization/th.json b/client/desktopApp/src/main/resources/localization/th.json index 78de09e..079e23f 100644 --- a/client/desktopApp/src/main/resources/localization/th.json +++ b/client/desktopApp/src/main/resources/localization/th.json @@ -1858,10 +1858,10 @@ "login_owner_hint": "เข้าสู่ระบบล่าสุด: {owner}", "login_password_label": "รหัสผ่าน", "login_privacy": "ดูนโยบายความเป็นส่วนตัว", - "login_reset_device": "รีเซ็ตอุปกรณ์", + "login_reset_device": "รีเซ็ตอุปกรณ์นี้", "login_reset_device_cancel": "ยกเลิก", - "login_reset_device_confirm_body": "การดำเนินการนี้จะลบข้อมูลในเครื่องทั้งหมดและกลับสู่วิซาร์ดการตั้งค่า ไม่สามารถย้อนกลับได้", - "login_reset_device_confirm_title": "รีเซ็ตอุปกรณ์?", + "login_reset_device_confirm_body": "การดำเนินการนี้จะลบข้อมูลโหนดที่เก็บอยู่บนอุปกรณ์นี้และกลับไปยังตัวช่วยการตั้งค่า โหนดที่คุณเชื่อมต่ออยู่บนเครื่องอื่นจะไม่ได้รับผลกระทบ การดำเนินการนี้ไม่สามารถยกเลิกได้", + "login_reset_device_confirm_title": "รีเซ็ตอุปกรณ์นี้?", "login_setup_complete_relogin": "การตั้งค่าเสร็จสมบูรณ์แล้ว โหนดของคุณได้รีสตาร์ท กรุณาเข้าสู่ระบบอีกครั้งด้วยข้อมูลรับรองเดียวกับที่คุณใช้ในการตั้งค่า", "login_signin_apple": "เข้าสู่ระบบด้วย Apple", "login_signin_google": "เข้าสู่ระบบด้วย Google", diff --git a/client/desktopApp/src/main/resources/localization/tr.json b/client/desktopApp/src/main/resources/localization/tr.json index 9708b87..2bc1988 100644 --- a/client/desktopApp/src/main/resources/localization/tr.json +++ b/client/desktopApp/src/main/resources/localization/tr.json @@ -1860,7 +1860,7 @@ "login_privacy": "Gizlilik Politikasını Görüntüle", "login_reset_device": "Cihazı Sıfırla", "login_reset_device_cancel": "İptal", - "login_reset_device_confirm_body": "Bu işlem tüm yerel verileri silecek ve kurulum sihirbazına geri dönecektir. Bu işlem geri alınamaz.", + "login_reset_device_confirm_body": "Bu işlem, BU cihazda depolanan düğüm verilerini siler ve kurulum sihirbazına döner. Başka bir makinede bağlı olduğunuz bir düğüm etkilenmez. Bu geri alınamaz.", "login_reset_device_confirm_title": "Cihaz sıfırlansın mı?", "login_setup_complete_relogin": "Kurulum tamamlandı. Düğümünüz yeniden başlatıldı; lütfen kurulumu tamamlarken kullandığınız kimlik bilgileriyle yeniden oturum açın.", "login_signin_apple": "Apple ile giriş yap", diff --git a/client/desktopApp/src/main/resources/localization/uk.json b/client/desktopApp/src/main/resources/localization/uk.json index fafcb63..9de68ee 100644 --- a/client/desktopApp/src/main/resources/localization/uk.json +++ b/client/desktopApp/src/main/resources/localization/uk.json @@ -1860,7 +1860,7 @@ "login_privacy": "Переглянути Політику конфіденційності", "login_reset_device": "Скинути пристрій", "login_reset_device_cancel": "Скасувати", - "login_reset_device_confirm_body": "Це видалить усі локальні дані та повернеться до майстра налаштування. Цю дію неможливо скасувати.", + "login_reset_device_confirm_body": "Це видалить дані вузла, збережені НА ЦЬОМУ пристрої, та поверне вас до майстра налаштування. Це не впливає на вузол, до якого ви підключені на іншому комп'ютері. Цю дію неможливо скасувати.", "login_reset_device_confirm_title": "Скинути пристрій?", "login_setup_complete_relogin": "Налаштування завершено. Ваш вузол перезапустився, тому увійдіть знову, використовуючи ті самі облікові дані, з якими ви завершили налаштування.", "login_signin_apple": "Увійти через Apple", diff --git a/client/desktopApp/src/main/resources/localization/ur.json b/client/desktopApp/src/main/resources/localization/ur.json index 7c17d31..cecd77f 100644 --- a/client/desktopApp/src/main/resources/localization/ur.json +++ b/client/desktopApp/src/main/resources/localization/ur.json @@ -1860,7 +1860,7 @@ "login_privacy": "رازداری کی پالیسی دیکھیں", "login_reset_device": "ڈیوائس ری سیٹ کریں", "login_reset_device_cancel": "منسوخ کریں", - "login_reset_device_confirm_body": "یہ تمام مقامی ڈیٹا مٹا دے گا اور سیٹ اپ وزرڈ پر واپس لے جائے گا۔ یہ عمل واپس نہیں کیا جا سکتا۔", + "login_reset_device_confirm_body": "یہ اِسی ڈیوائس پر محفوظ نوڈ کا ڈیٹا مٹا دے گا اور سیٹ اپ وزرڈ پر واپس آ جائے گا۔ کسی دوسری مشین پر جس نوڈ سے آپ جڑے ہیں، اُس پر کوئی اثر نہیں پڑے گا۔ یہ عمل واپس نہیں کیا جا سکتا۔", "login_reset_device_confirm_title": "ڈیوائس ری سیٹ کریں؟", "login_setup_complete_relogin": "سیٹ اپ مکمل ہو گیا ہے۔ آپ کا نوڈ دوبارہ شروع ہوا ہے، اس لیے براہِ کرم انہی اسناد کے ساتھ دوبارہ لاگ اِن کریں جو آپ نے سیٹ اپ مکمل کرنے کے لیے استعمال کی تھیں۔", "login_signin_apple": "Apple کے ساتھ سائن ان کریں", diff --git a/client/desktopApp/src/main/resources/localization/vi.json b/client/desktopApp/src/main/resources/localization/vi.json index 245869a..24135ce 100644 --- a/client/desktopApp/src/main/resources/localization/vi.json +++ b/client/desktopApp/src/main/resources/localization/vi.json @@ -1860,7 +1860,7 @@ "login_privacy": "Xem chính sách bảo mật", "login_reset_device": "Đặt lại thiết bị", "login_reset_device_cancel": "Hủy", - "login_reset_device_confirm_body": "Thao tác này sẽ xóa tất cả dữ liệu cục bộ và quay lại trình hướng dẫn cài đặt. Hành động này không thể hoàn tác.", + "login_reset_device_confirm_body": "Thao tác này sẽ xóa dữ liệu nút được lưu trữ trên CHÍNH thiết bị này và quay lại trình hướng dẫn cài đặt. Nút mà bạn đang kết nối trên một máy khác sẽ không bị ảnh hưởng. Không thể hoàn tác.", "login_reset_device_confirm_title": "Đặt lại thiết bị?", "login_setup_complete_relogin": "Thiết lập đã hoàn tất. Nút của bạn đã khởi động lại, vui lòng đăng nhập lại bằng đúng thông tin đăng nhập mà bạn đã dùng để hoàn tất thiết lập.", "login_signin_apple": "Đăng nhập bằng Apple", diff --git a/client/desktopApp/src/main/resources/localization/yo.json b/client/desktopApp/src/main/resources/localization/yo.json index d7f8400..c7c0a0f 100644 --- a/client/desktopApp/src/main/resources/localization/yo.json +++ b/client/desktopApp/src/main/resources/localization/yo.json @@ -1858,10 +1858,10 @@ "login_owner_hint": "Ìwọlé ìkẹ̀yìn: {owner}", "login_password_label": "Ọ̀rọ̀ Àṣírí", "login_privacy": "Wo Ìlànà Àṣírí", - "login_reset_device": "Tún ẹ̀rọ ṣe", + "login_reset_device": "Tún ẹ̀rọ yìí ṣe", "login_reset_device_cancel": "Fagilé", - "login_reset_device_confirm_body": "Èyí yóò pa gbogbo dátà àdúgbò run kí ó sì padà sí olùṣèlú ìṣètò. Èyí kò lè yípadà.", - "login_reset_device_confirm_title": "Tún ẹ̀rọ ṣe?", + "login_reset_device_confirm_body": "Èyí yóò pa dátà nódù tí a fipamọ́ sórí Ẹ̀RỌ YÌÍ run kí ó sì padà sí olùtọ́nisọ́nà ìṣètò. Èyí kò ní kan nódù tí ẹ ti so pọ̀ mọ́ ẹ̀rọ mìíràn. Èyí kò ṣeépadà.", + "login_reset_device_confirm_title": "Tún ẹ̀rọ yìí ṣe?", "login_setup_complete_relogin": "Ìṣètò ti parí. Node rẹ tún bẹ̀rẹ̀, nítorí náà jọ̀wọ́ wọlé padà pẹ̀lú àwọn ẹ̀rí ìdánimọ̀ kan náà tí o lò láti parí ìṣètò.", "login_signin_apple": "Wọlé pẹ̀lú Apple", "login_signin_google": "Wọlé pẹ̀lú Google", diff --git a/client/desktopApp/src/main/resources/localization/zh.json b/client/desktopApp/src/main/resources/localization/zh.json index 2bcede3..570ceda 100644 --- a/client/desktopApp/src/main/resources/localization/zh.json +++ b/client/desktopApp/src/main/resources/localization/zh.json @@ -1860,7 +1860,7 @@ "login_privacy": "查看隐私政策", "login_reset_device": "重置设备", "login_reset_device_cancel": "取消", - "login_reset_device_confirm_body": "此操作将清除所有本地数据并返回设置向导。此操作无法撤销。", + "login_reset_device_confirm_body": "此操作将清除存储在本设备上的节点数据并返回设置向导。您在其他设备上已连接的节点不会受到影响。此操作无法撤销。", "login_reset_device_confirm_title": "重置设备?", "login_setup_complete_relogin": "设置已完成。您的节点已重新启动,请使用完成设置时使用的相同凭据重新登录。", "login_signin_apple": "使用 Apple 登录", diff --git a/client/iosApp/iosApp/localization/am.json b/client/iosApp/iosApp/localization/am.json index b478e20..6fc10f6 100644 --- a/client/iosApp/iosApp/localization/am.json +++ b/client/iosApp/iosApp/localization/am.json @@ -1858,10 +1858,10 @@ "login_owner_hint": "መጨረሻ የገባው: {owner}", "login_password_label": "የይለፍ ቃል", "login_privacy": "የግላዊነት መመሪያን ይመልከቱ", - "login_reset_device": "መሣሪያ ዳግም አስጀምር", + "login_reset_device": "ይህን መሣሪያ ዳግም አስጀምር", "login_reset_device_cancel": "ሰርዝ", - "login_reset_device_confirm_body": "ይህ ሁሉንም አካባቢያዊ ውሂብ ያጠፋና ወደ ማዋቀሪያ አዋቂ ይመልሳል። ይህ ድርጊት ሊቀለበስ አይችልም።", - "login_reset_device_confirm_title": "መሣሪያ ዳግም ይጀምር?", + "login_reset_device_confirm_body": "ይህ በዚህ መሣሪያ ላይ የተቀመጠውን የኖድ ውሂብ ያጠፋል እና ወደ የማዋቀሪያ አዋቂው ይመለሳል። በሌላ መሣሪያ ላይ የተገናኙበት ኖድ ግን ተጽዕኖ አይደርስበትም። ይህ ድርጊት መልሰው የማይቀር ነው።", + "login_reset_device_confirm_title": "ይህን መሣሪያ ዳግም ማስጀመር?", "login_setup_complete_relogin": "ማዋቀሩ ተጠናቅቋል። መስቀለኛዎ እንደገና ስለተጀመረ፣ እባክዎ ማዋቀሩን ለማጠናቀቅ በተጠቀሙባቸው ተመሳሳይ የመግቢያ መረጃዎች እንደገና ይግቡ።", "login_signin_apple": "በApple ይግቡ", "login_signin_google": "በGoogle ይግቡ", diff --git a/client/iosApp/iosApp/localization/ar.json b/client/iosApp/iosApp/localization/ar.json index 3b025d4..52541c6 100644 --- a/client/iosApp/iosApp/localization/ar.json +++ b/client/iosApp/iosApp/localization/ar.json @@ -1860,7 +1860,7 @@ "login_privacy": "عرض سياسة الخصوصية", "login_reset_device": "إعادة ضبط الجهاز", "login_reset_device_cancel": "إلغاء", - "login_reset_device_confirm_body": "سيؤدي ذلك إلى مسح جميع البيانات المحلية والعودة إلى معالج الإعداد. لا يمكن التراجع عن هذا الإجراء.", + "login_reset_device_confirm_body": "سيؤدي ذلك إلى مسح بيانات العقدة المخزّنة على هذا الجهاز والعودة إلى معالج الإعداد. أما أي عقدة تتصل بها على جهاز آخر فلن تتأثر. لا يمكن التراجع عن هذا الإجراء.", "login_reset_device_confirm_title": "إعادة ضبط الجهاز؟", "login_setup_complete_relogin": "اكتمل الإعداد. أُعيد تشغيل العقدة، لذا يُرجى تسجيل الدخول مجددًا باستخدام بيانات الاعتماد نفسها التي استخدمتها لإكمال الإعداد.", "login_signin_apple": "تسجيل الدخول باستخدام Apple", diff --git a/client/iosApp/iosApp/localization/bn.json b/client/iosApp/iosApp/localization/bn.json index 30f1052..cbb52b3 100644 --- a/client/iosApp/iosApp/localization/bn.json +++ b/client/iosApp/iosApp/localization/bn.json @@ -1861,7 +1861,7 @@ "login_privacy": "গোপনীয়তা নীতি দেখুন", "login_reset_device": "ডিভাইস রিসেট করুন", "login_reset_device_cancel": "বাতিল", - "login_reset_device_confirm_body": "এটি সমস্ত স্থানীয় ডেটা মুছে দেবে এবং সেটআপ উইজার্ডে ফিরে যাবে। এটি পূর্বাবস্থায় ফেরানো যাবে না।", + "login_reset_device_confirm_body": "এটি এই ডিভাইসে সংরক্ষিত নোডের ডেটা মুছে দেবে এবং সেটআপ উইজার্ডে ফিরে যাবে। অন্য কোনো মেশিনে আপনি যে নোডের সাথে সংযুক্ত আছেন, সেটি প্রভাবিত হবে না। এটি পূর্বাবস্থায় ফেরানো যাবে না।", "login_reset_device_confirm_title": "ডিভাইস রিসেট করবেন?", "login_setup_complete_relogin": "সেটআপ সম্পূর্ণ হয়েছে। আপনার নোড পুনরায় চালু হয়েছে, তাই সেটআপ সম্পূর্ণ করতে আপনি যে শংসাপত্রগুলি ব্যবহার করেছিলেন সেগুলি দিয়েই আবার লগ ইন করুন।", "login_signin_apple": "Apple দিয়ে সাইন ইন করুন", diff --git a/client/iosApp/iosApp/localization/de.json b/client/iosApp/iosApp/localization/de.json index 74e684c..da1e0ac 100644 --- a/client/iosApp/iosApp/localization/de.json +++ b/client/iosApp/iosApp/localization/de.json @@ -1858,10 +1858,10 @@ "login_owner_hint": "Zuletzt angemeldet: {owner}", "login_password_label": "Passwort", "login_privacy": "Datenschutzerklärung anzeigen", - "login_reset_device": "Gerät zurücksetzen", + "login_reset_device": "Dieses Gerät zurücksetzen", "login_reset_device_cancel": "Abbrechen", - "login_reset_device_confirm_body": "Dadurch werden alle lokalen Daten gelöscht und Sie werden zum Einrichtungsassistenten zurückgeleitet. Diese Aktion kann nicht rückgängig gemacht werden.", - "login_reset_device_confirm_title": "Gerät zurücksetzen?", + "login_reset_device_confirm_body": "Dies löscht die Knotendaten, die auf DIESEM Gerät gespeichert sind, und kehrt zum Einrichtungsassistenten zurück. Ein Knoten, mit dem Sie auf einem anderen Rechner verbunden sind, ist davon nicht betroffen. Dies kann nicht rückgängig gemacht werden.", + "login_reset_device_confirm_title": "Dieses Gerät zurücksetzen?", "login_setup_complete_relogin": "Die Einrichtung ist abgeschlossen. Ihr Knoten wurde neu gestartet – bitte melden Sie sich erneut mit denselben Zugangsdaten an, die Sie für die Einrichtung verwendet haben.", "login_signin_apple": "Mit Apple anmelden", "login_signin_google": "Mit Google anmelden", diff --git a/client/iosApp/iosApp/localization/es.json b/client/iosApp/iosApp/localization/es.json index 66a6762..fe02dbf 100644 --- a/client/iosApp/iosApp/localization/es.json +++ b/client/iosApp/iosApp/localization/es.json @@ -1860,7 +1860,7 @@ "login_privacy": "Ver Política de Privacidad", "login_reset_device": "Restablecer dispositivo", "login_reset_device_cancel": "Cancelar", - "login_reset_device_confirm_body": "Esto borrará todos los datos locales y volverás al asistente de configuración. Esta acción no se puede deshacer.", + "login_reset_device_confirm_body": "Esto borrará los datos del nodo almacenados en ESTE dispositivo y volverá al asistente de configuración. Un nodo al que estés conectado en otra máquina no se verá afectado. Esta acción no se puede deshacer.", "login_reset_device_confirm_title": "¿Restablecer dispositivo?", "login_setup_complete_relogin": "La configuración ha finalizado. Tu nodo se reinició, así que vuelve a iniciar sesión con las mismas credenciales que usaste para completar la configuración.", "login_signin_apple": "Iniciar sesión con Apple", diff --git a/client/iosApp/iosApp/localization/fa.json b/client/iosApp/iosApp/localization/fa.json index d9caea0..38b931c 100644 --- a/client/iosApp/iosApp/localization/fa.json +++ b/client/iosApp/iosApp/localization/fa.json @@ -1866,7 +1866,7 @@ "login_privacy": "مشاهده سیاست حریم خصوصی", "login_reset_device": "بازنشانی دستگاه", "login_reset_device_cancel": "لغو", - "login_reset_device_confirm_body": "این عمل تمام داده‌های محلی را پاک می‌کند و به راه‌اندازی اولیه بازمی‌گردد. این کار قابل بازگشت نیست.", + "login_reset_device_confirm_body": "این کار داده‌های گره ذخیره‌شده روی همین دستگاه را پاک می‌کند و به دستیار راه‌اندازی بازمی‌گردد. گرهی که روی دستگاهی دیگر به آن متصل هستید تحت تأثیر قرار نمی‌گیرد. این کار قابل بازگشت نیست.", "login_reset_device_confirm_title": "بازنشانی دستگاه؟", "login_setup_complete_relogin": "راه‌اندازی کامل شد. نود شما دوباره راه‌اندازی شد؛ لطفاً با همان اطلاعات ورودی که برای تکمیل راه‌اندازی استفاده کردید، دوباره وارد شوید.", "login_signin_apple": "ورود باستخدام Apple", diff --git a/client/iosApp/iosApp/localization/fr.json b/client/iosApp/iosApp/localization/fr.json index 10abf12..fe1f844 100644 --- a/client/iosApp/iosApp/localization/fr.json +++ b/client/iosApp/iosApp/localization/fr.json @@ -1860,7 +1860,7 @@ "login_privacy": "Voir la Politique de Confidentialité", "login_reset_device": "Réinitialiser l'appareil", "login_reset_device_cancel": "Annuler", - "login_reset_device_confirm_body": "Cela effacera toutes les données locales et vous ramènera à l'assistant de configuration. Cette action est irréversible.", + "login_reset_device_confirm_body": "Cela efface les données du nœud stockées sur CET appareil et revient à l'assistant de configuration. Un nœud auquel vous êtes connecté sur une autre machine n'est pas affecté. Cela ne peut pas être annulé.", "login_reset_device_confirm_title": "Réinitialiser l'appareil ?", "login_setup_complete_relogin": "La configuration est terminée. Votre nœud a redémarré : veuillez vous reconnecter avec les mêmes identifiants que ceux utilisés pour terminer la configuration.", "login_signin_apple": "Se connecter avec Apple", diff --git a/client/iosApp/iosApp/localization/ha.json b/client/iosApp/iosApp/localization/ha.json index 7ac4491..f5f91af 100644 --- a/client/iosApp/iosApp/localization/ha.json +++ b/client/iosApp/iosApp/localization/ha.json @@ -1860,7 +1860,7 @@ "login_privacy": "Duba Sera na Asirta", "login_reset_device": "Sake saita na'ura", "login_reset_device_cancel": "Soke", - "login_reset_device_confirm_body": "Wannan zai share duk bayanan gida kuma ya koma zuwa jagorar saiti. Ba za a iya maido da wannan ba.", + "login_reset_device_confirm_body": "Wannan zai share bayanan kumburin da aka ajiye a WANNAN na'urar kuma ya koma zuwa jagorar saiti. Kumburin da kuke haɗe da shi a wata na'ura ba zai sami tasiri ba. Ba za a iya mayar da wannan aiki ba.", "login_reset_device_confirm_title": "Sake saita na'ura?", "login_setup_complete_relogin": "An kammala saitin. Node ɗinka ya sake farawa, don haka ka sake shiga da bayanan shiga guda ɗaya da ka yi amfani da su wajen kammala saitin.", "login_signin_apple": "Shiga tare da Apple", diff --git a/client/iosApp/iosApp/localization/hi.json b/client/iosApp/iosApp/localization/hi.json index 7654f45..504fa17 100644 --- a/client/iosApp/iosApp/localization/hi.json +++ b/client/iosApp/iosApp/localization/hi.json @@ -1858,10 +1858,10 @@ "login_owner_hint": "पिछली बार साइन इन किया: {owner}", "login_password_label": "पासवर्ड", "login_privacy": "गोपनीयता नीति देखें", - "login_reset_device": "डिवाइस रीसेट करें", + "login_reset_device": "इस डिवाइस को रीसेट करें", "login_reset_device_cancel": "रद्द करें", - "login_reset_device_confirm_body": "यह सभी स्थानीय डेटा मिटा देगा और सेटअप विज़ार्ड पर वापस ले जाएगा। इसे पूर्ववत नहीं किया जा सकता।", - "login_reset_device_confirm_title": "डिवाइस रीसेट करें?", + "login_reset_device_confirm_body": "यह इस डिवाइस पर संग्रहीत नोड डेटा मिटा देगा और सेटअप विज़ार्ड पर वापस ले जाएगा। किसी अन्य मशीन पर जिस नोड से आप जुड़े हैं, वह प्रभावित नहीं होगा। इसे पूर्ववत नहीं किया जा सकता।", + "login_reset_device_confirm_title": "इस डिवाइस को रीसेट करें?", "login_setup_complete_relogin": "सेटअप पूरा हो गया है। आपका नोड फिर से चालू हुआ है, इसलिए कृपया उन्हीं क्रेडेंशियल से दोबारा लॉग इन करें जिनका उपयोग आपने सेटअप पूरा करने के लिए किया था।", "login_signin_apple": "Apple से साइन इन करें", "login_signin_google": "Google से साइन इन करें", diff --git a/client/iosApp/iosApp/localization/id.json b/client/iosApp/iosApp/localization/id.json index 809bf6a..d51d506 100644 --- a/client/iosApp/iosApp/localization/id.json +++ b/client/iosApp/iosApp/localization/id.json @@ -1858,10 +1858,10 @@ "login_owner_hint": "Terakhir masuk: {owner}", "login_password_label": "Kata Sandi", "login_privacy": "Lihat Kebijakan Privasi", - "login_reset_device": "Atur ulang perangkat", + "login_reset_device": "Atur ulang perangkat ini", "login_reset_device_cancel": "Batal", - "login_reset_device_confirm_body": "Ini akan menghapus semua data lokal dan kembali ke wizard penyiapan. Tindakan ini tidak dapat dibatalkan.", - "login_reset_device_confirm_title": "Atur ulang perangkat?", + "login_reset_device_confirm_body": "Ini akan menghapus data node yang tersimpan di perangkat INI dan kembali ke wizard penyiapan. Node tempat Anda terhubung di mesin lain tidak terpengaruh. Tindakan ini tidak dapat dibatalkan.", + "login_reset_device_confirm_title": "Atur ulang perangkat ini?", "login_setup_complete_relogin": "Penyiapan selesai. Node Anda telah dimulai ulang, jadi silakan masuk kembali dengan kredensial yang sama seperti yang Anda gunakan saat menyelesaikan penyiapan.", "login_signin_apple": "Masuk dengan Apple", "login_signin_google": "Masuk dengan Google", diff --git a/client/iosApp/iosApp/localization/it.json b/client/iosApp/iosApp/localization/it.json index 1748af9..525dd69 100644 --- a/client/iosApp/iosApp/localization/it.json +++ b/client/iosApp/iosApp/localization/it.json @@ -1860,7 +1860,7 @@ "login_privacy": "Visualizza Informativa sulla Privacy", "login_reset_device": "Ripristina dispositivo", "login_reset_device_cancel": "Annulla", - "login_reset_device_confirm_body": "Verranno cancellati tutti i dati locali e si tornerà alla procedura guidata di configurazione. Questa azione non può essere annullata.", + "login_reset_device_confirm_body": "Verranno cancellati i dati del nodo memorizzati su QUESTO dispositivo e si tornerà alla procedura guidata di configurazione. Un nodo a cui sei connesso su un'altra macchina non subisce modifiche. Questa azione non può essere annullata.", "login_reset_device_confirm_title": "Ripristinare il dispositivo?", "login_setup_complete_relogin": "La configurazione è completata. Il tuo nodo è stato riavviato: accedi di nuovo con le stesse credenziali che hai usato per completare la configurazione.", "login_signin_apple": "Accedi con Apple", diff --git a/client/iosApp/iosApp/localization/ja.json b/client/iosApp/iosApp/localization/ja.json index 07f3851..371b63a 100644 --- a/client/iosApp/iosApp/localization/ja.json +++ b/client/iosApp/iosApp/localization/ja.json @@ -1860,7 +1860,7 @@ "login_privacy": "プライバシーポリシーを表示", "login_reset_device": "デバイスをリセット", "login_reset_device_cancel": "キャンセル", - "login_reset_device_confirm_body": "すべてのローカルデータが消去され、セットアップウィザードに戻ります。この操作は元に戻せません。", + "login_reset_device_confirm_body": "このデバイスに保存されているノードデータが消去され、セットアップウィザードに戻ります。別のマシン上で接続しているノードには影響しません。この操作は元に戻せません。", "login_reset_device_confirm_title": "デバイスをリセットしますか?", "login_setup_complete_relogin": "セットアップが完了しました。ノードが再起動したため、セットアップで使用したものと同じ認証情報で再度ログインしてください。", "login_signin_apple": "Appleでサインイン", diff --git a/client/iosApp/iosApp/localization/ko.json b/client/iosApp/iosApp/localization/ko.json index a766a42..e46b77f 100644 --- a/client/iosApp/iosApp/localization/ko.json +++ b/client/iosApp/iosApp/localization/ko.json @@ -1860,7 +1860,7 @@ "login_privacy": "개인정보 보호정책 보기", "login_reset_device": "기기 재설정", "login_reset_device_cancel": "취소", - "login_reset_device_confirm_body": "모든 로컬 데이터가 삭제되고 설정 마법사로 돌아갑니다. 이 작업은 취소할 수 없습니다.", + "login_reset_device_confirm_body": "이 작업은 이 기기에 저장된 노드 데이터를 삭제하고 설정 마법사로 돌아갑니다. 다른 기기에서 연결해 사용 중인 노드는 영향을 받지 않습니다. 이 작업은 취소할 수 없습니다.", "login_reset_device_confirm_title": "기기를 재설정하시겠습니까?", "login_setup_complete_relogin": "설정이 완료되었습니다. 노드가 재시작되었으므로 설정에 사용한 것과 동일한 자격 증명으로 다시 로그인해 주세요.", "login_signin_apple": "Apple로 로그인", diff --git a/client/iosApp/iosApp/localization/mr.json b/client/iosApp/iosApp/localization/mr.json index 0c7cf1f..35d3208 100644 --- a/client/iosApp/iosApp/localization/mr.json +++ b/client/iosApp/iosApp/localization/mr.json @@ -1860,7 +1860,7 @@ "login_privacy": "गोपनीयता धोरण पाहा", "login_reset_device": "डिव्हाइस रीसेट करा", "login_reset_device_cancel": "रद्द करा", - "login_reset_device_confirm_body": "हे सर्व स्थानिक डेटा मिटवेल आणि सेटअप विझार्डवर परत जाईल. हे पूर्ववत केले जाऊ शकत नाही.", + "login_reset_device_confirm_body": "यामुळे याच डिव्हाइसवर साठवलेला नोड डेटा पुसला जाईल आणि सेटअप विझार्डवर परत जाईल. तुम्ही दुसऱ्या मशीनवरील ज्या नोडशी जोडलेले आहात, त्यावर याचा परिणाम होत नाही. हे पूर्ववत केले जाऊ शकत नाही.", "login_reset_device_confirm_title": "डिव्हाइस रीसेट करायचे?", "login_setup_complete_relogin": "सेटअप पूर्ण झाला आहे. तुमचा नोड पुन्हा सुरू झाला आहे, त्यामुळे सेटअप पूर्ण करण्यासाठी वापरलेल्या त्याच क्रेडेन्शियल्सने कृपया पुन्हा लॉग इन करा.", "login_signin_apple": "Apple सह साइन इन करा", diff --git a/client/iosApp/iosApp/localization/my.json b/client/iosApp/iosApp/localization/my.json index aa01418..37d756b 100644 --- a/client/iosApp/iosApp/localization/my.json +++ b/client/iosApp/iosApp/localization/my.json @@ -1860,7 +1860,7 @@ "login_privacy": "ကိုယ်ရေးကာကွယ်မှု မူဝါဒ ကြည့်ရှုပါ", "login_reset_device": "စက်ပစ္စည်း ပြန်လည်သတ်မှတ်ပါ", "login_reset_device_cancel": "ပယ်ဖျက်", - "login_reset_device_confirm_body": "ဤလုပ်ဆောင်ချက်သည် ဒေသတွင်းဒေတာအားလုံးကို ဖျက်ပြီး ဆက်တင် wizard သို့ ပြန်သွားမည်ဖြစ်သည်။ ဤလုပ်ဆောင်ချက်ကို ပြန်ဖြည့်၍မရပါ။", + "login_reset_device_confirm_body": "ဤလုပ်ဆောင်ချက်သည် ဤစက်ပေါ်တွင် သိမ်းဆည်းထားသော node ဒေတာကို ဖျက်ပြီး ဆက်တင် wizard သို့ ပြန်သွားမည်ဖြစ်သည်။ သင် အခြားစက်တွင် ချိတ်ဆက်ထားသည့် node သည် ထိခိုက်မည် မဟုတ်ပါ။ ဤလုပ်ဆောင်ချက်ကို ပြန်မလုပ်နိုင်ပါ။", "login_reset_device_confirm_title": "စက်ပစ္စည်း ပြန်လည်သတ်မှတ်မည်လား?", "login_setup_complete_relogin": "စနစ်ထည့်သွင်းမှု ပြီးဆုံးပါပြီ။ သင့် node ပြန်လည်စတင်ခဲ့သဖြင့် စနစ်ထည့်သွင်းရာတွင် အသုံးပြုခဲ့သည့် အထောက်အထားများဖြင့်ပင် ပြန်လည်ဝင်ရောက်ပါ။", "login_signin_apple": "Apple ဖြင့် ဝင်ရောက်", diff --git a/client/iosApp/iosApp/localization/pa.json b/client/iosApp/iosApp/localization/pa.json index ebf8145..b5d51c5 100644 --- a/client/iosApp/iosApp/localization/pa.json +++ b/client/iosApp/iosApp/localization/pa.json @@ -1860,7 +1860,7 @@ "login_privacy": "ਗੋਪਨੀਯਤਾ ਨੀਤੀ ਦੇਖੋ", "login_reset_device": "ਡਿਵਾਈਸ ਰੀਸੈੱਟ ਕਰੋ", "login_reset_device_cancel": "ਰੱਦ ਕਰੋ", - "login_reset_device_confirm_body": "ਇਹ ਸਾਰਾ ਸਥਾਨਕ ਡੇਟਾ ਮਿਟਾ ਦੇਵੇਗਾ ਅਤੇ ਸੈੱਟਅੱਪ ਵਿਜ਼ਾਰਡ 'ਤੇ ਵਾਪਸ ਆ ਜਾਵੇਗਾ। ਇਸ ਨੂੰ ਪਹਿਲਾਂ ਵਾਲੀ ਸਥਿਤੀ ਵਿੱਚ ਨਹੀਂ ਲਿਆਂਦਾ ਜਾ ਸਕਦਾ।", + "login_reset_device_confirm_body": "ਇਹ ਇਸ ਡਿਵਾਈਸ 'ਤੇ ਸਟੋਰ ਕੀਤਾ ਨੋਡ ਡੇਟਾ ਮਿਟਾ ਦੇਵੇਗਾ ਅਤੇ ਸੈੱਟਅੱਪ ਵਿਜ਼ਾਰਡ 'ਤੇ ਵਾਪਸ ਆ ਜਾਵੇਗਾ। ਕਿਸੇ ਹੋਰ ਮਸ਼ੀਨ ਉੱਤੇ ਜਿਸ ਨੋਡ ਨਾਲ ਤੁਸੀਂ ਜੁੜੇ ਹੋ, ਉਹ ਪ੍ਰਭਾਵਿਤ ਨਹੀਂ ਹੁੰਦਾ। ਇਹ ਵਾਪਸ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ।", "login_reset_device_confirm_title": "ਡਿਵਾਈਸ ਰੀਸੈੱਟ ਕਰਨਾ ਹੈ?", "login_setup_complete_relogin": "ਸੈੱਟਅੱਪ ਪੂਰਾ ਹੋ ਗਿਆ ਹੈ। ਤੁਹਾਡਾ ਨੋਡ ਮੁੜ ਚਾਲੂ ਹੋਇਆ ਹੈ, ਇਸ ਲਈ ਕਿਰਪਾ ਕਰਕੇ ਉਹੀ ਪ੍ਰਮਾਣ-ਪੱਤਰ ਵਰਤ ਕੇ ਮੁੜ ਲਾਗਇਨ ਕਰੋ ਜੋ ਤੁਸੀਂ ਸੈੱਟਅੱਪ ਪੂਰਾ ਕਰਨ ਲਈ ਵਰਤੇ ਸਨ।", "login_signin_apple": "Apple ਨਾਲ ਸਾਈਨ ਇਨ ਕਰੋ", diff --git a/client/iosApp/iosApp/localization/pt.json b/client/iosApp/iosApp/localization/pt.json index fa5fa8c..d4d550e 100644 --- a/client/iosApp/iosApp/localization/pt.json +++ b/client/iosApp/iosApp/localization/pt.json @@ -1858,10 +1858,10 @@ "login_owner_hint": "Último acesso: {owner}", "login_password_label": "Senha", "login_privacy": "Ver Política de Privacidade", - "login_reset_device": "Redefinir dispositivo", + "login_reset_device": "Redefinir este dispositivo", "login_reset_device_cancel": "Cancelar", - "login_reset_device_confirm_body": "Isso apagará todos os dados locais e retornará ao assistente de configuração. Esta ação não pode ser desfeita.", - "login_reset_device_confirm_title": "Redefinir dispositivo?", + "login_reset_device_confirm_body": "Isso excluirá os dados do nó armazenados NESTE dispositivo e retornará ao assistente de configuração. Um nó ao qual você esteja conectado em outra máquina não é afetado. Isso não pode ser desfeito.", + "login_reset_device_confirm_title": "Redefinir este dispositivo?", "login_setup_complete_relogin": "A configuração foi concluída. O seu nó foi reiniciado, por isso inicie sessão novamente com as mesmas credenciais que utilizou para concluir a configuração.", "login_signin_apple": "Entrar com Apple", "login_signin_google": "Entrar com Google", diff --git a/client/iosApp/iosApp/localization/ru.json b/client/iosApp/iosApp/localization/ru.json index 484e28e..db4bf61 100644 --- a/client/iosApp/iosApp/localization/ru.json +++ b/client/iosApp/iosApp/localization/ru.json @@ -1860,7 +1860,7 @@ "login_privacy": "Посмотреть Политику конфиденциальности", "login_reset_device": "Сбросить устройство", "login_reset_device_cancel": "Отмена", - "login_reset_device_confirm_body": "Это удалит все локальные данные и вернёт мастер настройки. Отменить это действие невозможно.", + "login_reset_device_confirm_body": "Это удалит данные узла, хранящиеся на ЭТОМ устройстве, и вернёт вас к мастеру настройки. Узел, к которому вы подключены на другой машине, затронут не будет. Это действие нельзя отменить.", "login_reset_device_confirm_title": "Сбросить устройство?", "login_setup_complete_relogin": "Настройка завершена. Ваш узел перезапущен, поэтому войдите снова, используя те же учётные данные, что и при настройке.", "login_signin_apple": "Войти через Apple", diff --git a/client/iosApp/iosApp/localization/sw.json b/client/iosApp/iosApp/localization/sw.json index 522bfde..17baf50 100644 --- a/client/iosApp/iosApp/localization/sw.json +++ b/client/iosApp/iosApp/localization/sw.json @@ -1858,10 +1858,10 @@ "login_owner_hint": "Mwisho aliyeingia: {owner}", "login_password_label": "Nenosiri", "login_privacy": "Tazama Sera ya Faragha", - "login_reset_device": "Weka upya kifaa", + "login_reset_device": "Weka upya kifaa hiki", "login_reset_device_cancel": "Ghairi", - "login_reset_device_confirm_body": "Hii itafuta data yote ya ndani na kurudi kwa mwongozo wa usanidi. Hatua hii haiwezi kutenduliwa.", - "login_reset_device_confirm_title": "Weka upya kifaa?", + "login_reset_device_confirm_body": "Hii itafuta data ya nodi iliyohifadhiwa kwenye kifaa HIKI na kurudi kwa mchawi wa usanidi. Nodi ambayo umeunganishwa nayo kwenye mashine nyingine haitaathiriwa. Hatua hii haiwezi kutenduliwa.", + "login_reset_device_confirm_title": "Weka upya kifaa hiki?", "login_setup_complete_relogin": "Usanidi umekamilika. Nodi yako imeanzishwa upya, kwa hivyo tafadhali ingia tena ukitumia stakabadhi zilezile ulizotumia kukamilisha usanidi.", "login_signin_apple": "Ingia kwa kutumia Apple", "login_signin_google": "Ingia kwa kutumia Google", diff --git a/client/iosApp/iosApp/localization/ta.json b/client/iosApp/iosApp/localization/ta.json index 34f5c0e..f3a999a 100644 --- a/client/iosApp/iosApp/localization/ta.json +++ b/client/iosApp/iosApp/localization/ta.json @@ -1860,7 +1860,7 @@ "login_privacy": "காண் Privacy Policy", "login_reset_device": "சாதனத்தை மீட்டமை", "login_reset_device_cancel": "ரத்து", - "login_reset_device_confirm_body": "இது அனைத்து உள்ளூர் தரவையும் அழித்து, அமைவு வழிகாட்டிக்கு திரும்பும். இதை மீண்டும் செயல்தவிர்க்க இயலாது.", + "login_reset_device_confirm_body": "இது இந்த சாதனத்தில் சேமிக்கப்பட்டுள்ள நோடு தரவை அழித்து, அமைவு வழிகாட்டிக்குத் திரும்பும். மற்றொரு கணினியில் நீங்கள் இணைந்திருக்கும் நோடு பாதிக்கப்படாது. இந்த செயலை மீள மாற்ற முடியாது.", "login_reset_device_confirm_title": "சாதனத்தை மீட்டமைக்கவா?", "login_setup_complete_relogin": "அமைவு நிறைவடைந்தது. உங்கள் நோடு மறுதொடக்கம் செய்யப்பட்டது, எனவே அமைவை நிறைவு செய்யப் பயன்படுத்திய அதே சான்றுகளுடன் மீண்டும் உள்நுழையவும்.", "login_signin_apple": "Sign in உடன் Apple", diff --git a/client/iosApp/iosApp/localization/te.json b/client/iosApp/iosApp/localization/te.json index 78b6759..57f56c3 100644 --- a/client/iosApp/iosApp/localization/te.json +++ b/client/iosApp/iosApp/localization/te.json @@ -1858,10 +1858,10 @@ "login_owner_hint": "చివరిగా సైన్ ఇన్ చేసినది: {owner}", "login_password_label": "పాస్‌వర్డ్", "login_privacy": "చూడు Privacy Policy", - "login_reset_device": "పరికరాన్ని రీసెట్ చేయండి", + "login_reset_device": "ఈ పరికరాన్ని రీసెట్ చేయండి", "login_reset_device_cancel": "రద్దు చేయి", - "login_reset_device_confirm_body": "ఇది అన్ని స్థానిక డేటాను తొలగించి, సెటప్ విజార్డ్‌కు తిరిగి వెళ్తుంది. దీన్ని రద్దు చేయడం సాధ్యం కాదు.", - "login_reset_device_confirm_title": "పరికరాన్ని రీసెట్ చేయాలా?", + "login_reset_device_confirm_body": "ఇది ఈ పరికరంలో నిల్వ చేసిన నోడ్ డేటాను మొత్తం తొలగించి, సెటప్ విజార్డ్‌కు తిరిగి వెళ్తుంది. మీరు మరొక యంత్రంలో కనెక్ట్ అయి ఉన్న నోడ్ ప్రభావితం కాదు. ఈ చర్యను రద్దు చేయలేరు.", + "login_reset_device_confirm_title": "ఈ పరికరాన్ని రీసెట్ చేయాలా?", "login_setup_complete_relogin": "సెటప్ పూర్తయింది. మీ నోడ్ పునఃప్రారంభమైంది, కాబట్టి సెటప్ పూర్తి చేయడానికి మీరు ఉపయోగించిన అదే ఆధారాలతో దయచేసి మళ్లీ లాగిన్ అవ్వండి.", "login_signin_apple": "Apple తో సైన్ ఇన్ చేయండి", "login_signin_google": "Google తో సైన్ ఇన్ చేయండి", diff --git a/client/iosApp/iosApp/localization/th.json b/client/iosApp/iosApp/localization/th.json index 78de09e..079e23f 100644 --- a/client/iosApp/iosApp/localization/th.json +++ b/client/iosApp/iosApp/localization/th.json @@ -1858,10 +1858,10 @@ "login_owner_hint": "เข้าสู่ระบบล่าสุด: {owner}", "login_password_label": "รหัสผ่าน", "login_privacy": "ดูนโยบายความเป็นส่วนตัว", - "login_reset_device": "รีเซ็ตอุปกรณ์", + "login_reset_device": "รีเซ็ตอุปกรณ์นี้", "login_reset_device_cancel": "ยกเลิก", - "login_reset_device_confirm_body": "การดำเนินการนี้จะลบข้อมูลในเครื่องทั้งหมดและกลับสู่วิซาร์ดการตั้งค่า ไม่สามารถย้อนกลับได้", - "login_reset_device_confirm_title": "รีเซ็ตอุปกรณ์?", + "login_reset_device_confirm_body": "การดำเนินการนี้จะลบข้อมูลโหนดที่เก็บอยู่บนอุปกรณ์นี้และกลับไปยังตัวช่วยการตั้งค่า โหนดที่คุณเชื่อมต่ออยู่บนเครื่องอื่นจะไม่ได้รับผลกระทบ การดำเนินการนี้ไม่สามารถยกเลิกได้", + "login_reset_device_confirm_title": "รีเซ็ตอุปกรณ์นี้?", "login_setup_complete_relogin": "การตั้งค่าเสร็จสมบูรณ์แล้ว โหนดของคุณได้รีสตาร์ท กรุณาเข้าสู่ระบบอีกครั้งด้วยข้อมูลรับรองเดียวกับที่คุณใช้ในการตั้งค่า", "login_signin_apple": "เข้าสู่ระบบด้วย Apple", "login_signin_google": "เข้าสู่ระบบด้วย Google", diff --git a/client/iosApp/iosApp/localization/tr.json b/client/iosApp/iosApp/localization/tr.json index 9708b87..2bc1988 100644 --- a/client/iosApp/iosApp/localization/tr.json +++ b/client/iosApp/iosApp/localization/tr.json @@ -1860,7 +1860,7 @@ "login_privacy": "Gizlilik Politikasını Görüntüle", "login_reset_device": "Cihazı Sıfırla", "login_reset_device_cancel": "İptal", - "login_reset_device_confirm_body": "Bu işlem tüm yerel verileri silecek ve kurulum sihirbazına geri dönecektir. Bu işlem geri alınamaz.", + "login_reset_device_confirm_body": "Bu işlem, BU cihazda depolanan düğüm verilerini siler ve kurulum sihirbazına döner. Başka bir makinede bağlı olduğunuz bir düğüm etkilenmez. Bu geri alınamaz.", "login_reset_device_confirm_title": "Cihaz sıfırlansın mı?", "login_setup_complete_relogin": "Kurulum tamamlandı. Düğümünüz yeniden başlatıldı; lütfen kurulumu tamamlarken kullandığınız kimlik bilgileriyle yeniden oturum açın.", "login_signin_apple": "Apple ile giriş yap", diff --git a/client/iosApp/iosApp/localization/uk.json b/client/iosApp/iosApp/localization/uk.json index fafcb63..9de68ee 100644 --- a/client/iosApp/iosApp/localization/uk.json +++ b/client/iosApp/iosApp/localization/uk.json @@ -1860,7 +1860,7 @@ "login_privacy": "Переглянути Політику конфіденційності", "login_reset_device": "Скинути пристрій", "login_reset_device_cancel": "Скасувати", - "login_reset_device_confirm_body": "Це видалить усі локальні дані та повернеться до майстра налаштування. Цю дію неможливо скасувати.", + "login_reset_device_confirm_body": "Це видалить дані вузла, збережені НА ЦЬОМУ пристрої, та поверне вас до майстра налаштування. Це не впливає на вузол, до якого ви підключені на іншому комп'ютері. Цю дію неможливо скасувати.", "login_reset_device_confirm_title": "Скинути пристрій?", "login_setup_complete_relogin": "Налаштування завершено. Ваш вузол перезапустився, тому увійдіть знову, використовуючи ті самі облікові дані, з якими ви завершили налаштування.", "login_signin_apple": "Увійти через Apple", diff --git a/client/iosApp/iosApp/localization/ur.json b/client/iosApp/iosApp/localization/ur.json index 7c17d31..cecd77f 100644 --- a/client/iosApp/iosApp/localization/ur.json +++ b/client/iosApp/iosApp/localization/ur.json @@ -1860,7 +1860,7 @@ "login_privacy": "رازداری کی پالیسی دیکھیں", "login_reset_device": "ڈیوائس ری سیٹ کریں", "login_reset_device_cancel": "منسوخ کریں", - "login_reset_device_confirm_body": "یہ تمام مقامی ڈیٹا مٹا دے گا اور سیٹ اپ وزرڈ پر واپس لے جائے گا۔ یہ عمل واپس نہیں کیا جا سکتا۔", + "login_reset_device_confirm_body": "یہ اِسی ڈیوائس پر محفوظ نوڈ کا ڈیٹا مٹا دے گا اور سیٹ اپ وزرڈ پر واپس آ جائے گا۔ کسی دوسری مشین پر جس نوڈ سے آپ جڑے ہیں، اُس پر کوئی اثر نہیں پڑے گا۔ یہ عمل واپس نہیں کیا جا سکتا۔", "login_reset_device_confirm_title": "ڈیوائس ری سیٹ کریں؟", "login_setup_complete_relogin": "سیٹ اپ مکمل ہو گیا ہے۔ آپ کا نوڈ دوبارہ شروع ہوا ہے، اس لیے براہِ کرم انہی اسناد کے ساتھ دوبارہ لاگ اِن کریں جو آپ نے سیٹ اپ مکمل کرنے کے لیے استعمال کی تھیں۔", "login_signin_apple": "Apple کے ساتھ سائن ان کریں", diff --git a/client/iosApp/iosApp/localization/vi.json b/client/iosApp/iosApp/localization/vi.json index 245869a..24135ce 100644 --- a/client/iosApp/iosApp/localization/vi.json +++ b/client/iosApp/iosApp/localization/vi.json @@ -1860,7 +1860,7 @@ "login_privacy": "Xem chính sách bảo mật", "login_reset_device": "Đặt lại thiết bị", "login_reset_device_cancel": "Hủy", - "login_reset_device_confirm_body": "Thao tác này sẽ xóa tất cả dữ liệu cục bộ và quay lại trình hướng dẫn cài đặt. Hành động này không thể hoàn tác.", + "login_reset_device_confirm_body": "Thao tác này sẽ xóa dữ liệu nút được lưu trữ trên CHÍNH thiết bị này và quay lại trình hướng dẫn cài đặt. Nút mà bạn đang kết nối trên một máy khác sẽ không bị ảnh hưởng. Không thể hoàn tác.", "login_reset_device_confirm_title": "Đặt lại thiết bị?", "login_setup_complete_relogin": "Thiết lập đã hoàn tất. Nút của bạn đã khởi động lại, vui lòng đăng nhập lại bằng đúng thông tin đăng nhập mà bạn đã dùng để hoàn tất thiết lập.", "login_signin_apple": "Đăng nhập bằng Apple", diff --git a/client/iosApp/iosApp/localization/yo.json b/client/iosApp/iosApp/localization/yo.json index d7f8400..c7c0a0f 100644 --- a/client/iosApp/iosApp/localization/yo.json +++ b/client/iosApp/iosApp/localization/yo.json @@ -1858,10 +1858,10 @@ "login_owner_hint": "Ìwọlé ìkẹ̀yìn: {owner}", "login_password_label": "Ọ̀rọ̀ Àṣírí", "login_privacy": "Wo Ìlànà Àṣírí", - "login_reset_device": "Tún ẹ̀rọ ṣe", + "login_reset_device": "Tún ẹ̀rọ yìí ṣe", "login_reset_device_cancel": "Fagilé", - "login_reset_device_confirm_body": "Èyí yóò pa gbogbo dátà àdúgbò run kí ó sì padà sí olùṣèlú ìṣètò. Èyí kò lè yípadà.", - "login_reset_device_confirm_title": "Tún ẹ̀rọ ṣe?", + "login_reset_device_confirm_body": "Èyí yóò pa dátà nódù tí a fipamọ́ sórí Ẹ̀RỌ YÌÍ run kí ó sì padà sí olùtọ́nisọ́nà ìṣètò. Èyí kò ní kan nódù tí ẹ ti so pọ̀ mọ́ ẹ̀rọ mìíràn. Èyí kò ṣeépadà.", + "login_reset_device_confirm_title": "Tún ẹ̀rọ yìí ṣe?", "login_setup_complete_relogin": "Ìṣètò ti parí. Node rẹ tún bẹ̀rẹ̀, nítorí náà jọ̀wọ́ wọlé padà pẹ̀lú àwọn ẹ̀rí ìdánimọ̀ kan náà tí o lò láti parí ìṣètò.", "login_signin_apple": "Wọlé pẹ̀lú Apple", "login_signin_google": "Wọlé pẹ̀lú Google", diff --git a/client/iosApp/iosApp/localization/zh.json b/client/iosApp/iosApp/localization/zh.json index 2bcede3..570ceda 100644 --- a/client/iosApp/iosApp/localization/zh.json +++ b/client/iosApp/iosApp/localization/zh.json @@ -1860,7 +1860,7 @@ "login_privacy": "查看隐私政策", "login_reset_device": "重置设备", "login_reset_device_cancel": "取消", - "login_reset_device_confirm_body": "此操作将清除所有本地数据并返回设置向导。此操作无法撤销。", + "login_reset_device_confirm_body": "此操作将清除存储在本设备上的节点数据并返回设置向导。您在其他设备上已连接的节点不会受到影响。此操作无法撤销。", "login_reset_device_confirm_title": "重置设备?", "login_setup_complete_relogin": "设置已完成。您的节点已重新启动,请使用完成设置时使用的相同凭据重新登录。", "login_signin_apple": "使用 Apple 登录", diff --git a/client/shared/src/desktopMain/resources/localization/am.json b/client/shared/src/desktopMain/resources/localization/am.json index b478e20..6fc10f6 100644 --- a/client/shared/src/desktopMain/resources/localization/am.json +++ b/client/shared/src/desktopMain/resources/localization/am.json @@ -1858,10 +1858,10 @@ "login_owner_hint": "መጨረሻ የገባው: {owner}", "login_password_label": "የይለፍ ቃል", "login_privacy": "የግላዊነት መመሪያን ይመልከቱ", - "login_reset_device": "መሣሪያ ዳግም አስጀምር", + "login_reset_device": "ይህን መሣሪያ ዳግም አስጀምር", "login_reset_device_cancel": "ሰርዝ", - "login_reset_device_confirm_body": "ይህ ሁሉንም አካባቢያዊ ውሂብ ያጠፋና ወደ ማዋቀሪያ አዋቂ ይመልሳል። ይህ ድርጊት ሊቀለበስ አይችልም።", - "login_reset_device_confirm_title": "መሣሪያ ዳግም ይጀምር?", + "login_reset_device_confirm_body": "ይህ በዚህ መሣሪያ ላይ የተቀመጠውን የኖድ ውሂብ ያጠፋል እና ወደ የማዋቀሪያ አዋቂው ይመለሳል። በሌላ መሣሪያ ላይ የተገናኙበት ኖድ ግን ተጽዕኖ አይደርስበትም። ይህ ድርጊት መልሰው የማይቀር ነው።", + "login_reset_device_confirm_title": "ይህን መሣሪያ ዳግም ማስጀመር?", "login_setup_complete_relogin": "ማዋቀሩ ተጠናቅቋል። መስቀለኛዎ እንደገና ስለተጀመረ፣ እባክዎ ማዋቀሩን ለማጠናቀቅ በተጠቀሙባቸው ተመሳሳይ የመግቢያ መረጃዎች እንደገና ይግቡ።", "login_signin_apple": "በApple ይግቡ", "login_signin_google": "በGoogle ይግቡ", diff --git a/client/shared/src/desktopMain/resources/localization/ar.json b/client/shared/src/desktopMain/resources/localization/ar.json index 3b025d4..52541c6 100644 --- a/client/shared/src/desktopMain/resources/localization/ar.json +++ b/client/shared/src/desktopMain/resources/localization/ar.json @@ -1860,7 +1860,7 @@ "login_privacy": "عرض سياسة الخصوصية", "login_reset_device": "إعادة ضبط الجهاز", "login_reset_device_cancel": "إلغاء", - "login_reset_device_confirm_body": "سيؤدي ذلك إلى مسح جميع البيانات المحلية والعودة إلى معالج الإعداد. لا يمكن التراجع عن هذا الإجراء.", + "login_reset_device_confirm_body": "سيؤدي ذلك إلى مسح بيانات العقدة المخزّنة على هذا الجهاز والعودة إلى معالج الإعداد. أما أي عقدة تتصل بها على جهاز آخر فلن تتأثر. لا يمكن التراجع عن هذا الإجراء.", "login_reset_device_confirm_title": "إعادة ضبط الجهاز؟", "login_setup_complete_relogin": "اكتمل الإعداد. أُعيد تشغيل العقدة، لذا يُرجى تسجيل الدخول مجددًا باستخدام بيانات الاعتماد نفسها التي استخدمتها لإكمال الإعداد.", "login_signin_apple": "تسجيل الدخول باستخدام Apple", diff --git a/client/shared/src/desktopMain/resources/localization/bn.json b/client/shared/src/desktopMain/resources/localization/bn.json index 30f1052..cbb52b3 100644 --- a/client/shared/src/desktopMain/resources/localization/bn.json +++ b/client/shared/src/desktopMain/resources/localization/bn.json @@ -1861,7 +1861,7 @@ "login_privacy": "গোপনীয়তা নীতি দেখুন", "login_reset_device": "ডিভাইস রিসেট করুন", "login_reset_device_cancel": "বাতিল", - "login_reset_device_confirm_body": "এটি সমস্ত স্থানীয় ডেটা মুছে দেবে এবং সেটআপ উইজার্ডে ফিরে যাবে। এটি পূর্বাবস্থায় ফেরানো যাবে না।", + "login_reset_device_confirm_body": "এটি এই ডিভাইসে সংরক্ষিত নোডের ডেটা মুছে দেবে এবং সেটআপ উইজার্ডে ফিরে যাবে। অন্য কোনো মেশিনে আপনি যে নোডের সাথে সংযুক্ত আছেন, সেটি প্রভাবিত হবে না। এটি পূর্বাবস্থায় ফেরানো যাবে না।", "login_reset_device_confirm_title": "ডিভাইস রিসেট করবেন?", "login_setup_complete_relogin": "সেটআপ সম্পূর্ণ হয়েছে। আপনার নোড পুনরায় চালু হয়েছে, তাই সেটআপ সম্পূর্ণ করতে আপনি যে শংসাপত্রগুলি ব্যবহার করেছিলেন সেগুলি দিয়েই আবার লগ ইন করুন।", "login_signin_apple": "Apple দিয়ে সাইন ইন করুন", diff --git a/client/shared/src/desktopMain/resources/localization/de.json b/client/shared/src/desktopMain/resources/localization/de.json index 74e684c..da1e0ac 100644 --- a/client/shared/src/desktopMain/resources/localization/de.json +++ b/client/shared/src/desktopMain/resources/localization/de.json @@ -1858,10 +1858,10 @@ "login_owner_hint": "Zuletzt angemeldet: {owner}", "login_password_label": "Passwort", "login_privacy": "Datenschutzerklärung anzeigen", - "login_reset_device": "Gerät zurücksetzen", + "login_reset_device": "Dieses Gerät zurücksetzen", "login_reset_device_cancel": "Abbrechen", - "login_reset_device_confirm_body": "Dadurch werden alle lokalen Daten gelöscht und Sie werden zum Einrichtungsassistenten zurückgeleitet. Diese Aktion kann nicht rückgängig gemacht werden.", - "login_reset_device_confirm_title": "Gerät zurücksetzen?", + "login_reset_device_confirm_body": "Dies löscht die Knotendaten, die auf DIESEM Gerät gespeichert sind, und kehrt zum Einrichtungsassistenten zurück. Ein Knoten, mit dem Sie auf einem anderen Rechner verbunden sind, ist davon nicht betroffen. Dies kann nicht rückgängig gemacht werden.", + "login_reset_device_confirm_title": "Dieses Gerät zurücksetzen?", "login_setup_complete_relogin": "Die Einrichtung ist abgeschlossen. Ihr Knoten wurde neu gestartet – bitte melden Sie sich erneut mit denselben Zugangsdaten an, die Sie für die Einrichtung verwendet haben.", "login_signin_apple": "Mit Apple anmelden", "login_signin_google": "Mit Google anmelden", diff --git a/client/shared/src/desktopMain/resources/localization/es.json b/client/shared/src/desktopMain/resources/localization/es.json index 66a6762..fe02dbf 100644 --- a/client/shared/src/desktopMain/resources/localization/es.json +++ b/client/shared/src/desktopMain/resources/localization/es.json @@ -1860,7 +1860,7 @@ "login_privacy": "Ver Política de Privacidad", "login_reset_device": "Restablecer dispositivo", "login_reset_device_cancel": "Cancelar", - "login_reset_device_confirm_body": "Esto borrará todos los datos locales y volverás al asistente de configuración. Esta acción no se puede deshacer.", + "login_reset_device_confirm_body": "Esto borrará los datos del nodo almacenados en ESTE dispositivo y volverá al asistente de configuración. Un nodo al que estés conectado en otra máquina no se verá afectado. Esta acción no se puede deshacer.", "login_reset_device_confirm_title": "¿Restablecer dispositivo?", "login_setup_complete_relogin": "La configuración ha finalizado. Tu nodo se reinició, así que vuelve a iniciar sesión con las mismas credenciales que usaste para completar la configuración.", "login_signin_apple": "Iniciar sesión con Apple", diff --git a/client/shared/src/desktopMain/resources/localization/fa.json b/client/shared/src/desktopMain/resources/localization/fa.json index d9caea0..38b931c 100644 --- a/client/shared/src/desktopMain/resources/localization/fa.json +++ b/client/shared/src/desktopMain/resources/localization/fa.json @@ -1866,7 +1866,7 @@ "login_privacy": "مشاهده سیاست حریم خصوصی", "login_reset_device": "بازنشانی دستگاه", "login_reset_device_cancel": "لغو", - "login_reset_device_confirm_body": "این عمل تمام داده‌های محلی را پاک می‌کند و به راه‌اندازی اولیه بازمی‌گردد. این کار قابل بازگشت نیست.", + "login_reset_device_confirm_body": "این کار داده‌های گره ذخیره‌شده روی همین دستگاه را پاک می‌کند و به دستیار راه‌اندازی بازمی‌گردد. گرهی که روی دستگاهی دیگر به آن متصل هستید تحت تأثیر قرار نمی‌گیرد. این کار قابل بازگشت نیست.", "login_reset_device_confirm_title": "بازنشانی دستگاه؟", "login_setup_complete_relogin": "راه‌اندازی کامل شد. نود شما دوباره راه‌اندازی شد؛ لطفاً با همان اطلاعات ورودی که برای تکمیل راه‌اندازی استفاده کردید، دوباره وارد شوید.", "login_signin_apple": "ورود باستخدام Apple", diff --git a/client/shared/src/desktopMain/resources/localization/fr.json b/client/shared/src/desktopMain/resources/localization/fr.json index 10abf12..fe1f844 100644 --- a/client/shared/src/desktopMain/resources/localization/fr.json +++ b/client/shared/src/desktopMain/resources/localization/fr.json @@ -1860,7 +1860,7 @@ "login_privacy": "Voir la Politique de Confidentialité", "login_reset_device": "Réinitialiser l'appareil", "login_reset_device_cancel": "Annuler", - "login_reset_device_confirm_body": "Cela effacera toutes les données locales et vous ramènera à l'assistant de configuration. Cette action est irréversible.", + "login_reset_device_confirm_body": "Cela efface les données du nœud stockées sur CET appareil et revient à l'assistant de configuration. Un nœud auquel vous êtes connecté sur une autre machine n'est pas affecté. Cela ne peut pas être annulé.", "login_reset_device_confirm_title": "Réinitialiser l'appareil ?", "login_setup_complete_relogin": "La configuration est terminée. Votre nœud a redémarré : veuillez vous reconnecter avec les mêmes identifiants que ceux utilisés pour terminer la configuration.", "login_signin_apple": "Se connecter avec Apple", diff --git a/client/shared/src/desktopMain/resources/localization/ha.json b/client/shared/src/desktopMain/resources/localization/ha.json index 7ac4491..f5f91af 100644 --- a/client/shared/src/desktopMain/resources/localization/ha.json +++ b/client/shared/src/desktopMain/resources/localization/ha.json @@ -1860,7 +1860,7 @@ "login_privacy": "Duba Sera na Asirta", "login_reset_device": "Sake saita na'ura", "login_reset_device_cancel": "Soke", - "login_reset_device_confirm_body": "Wannan zai share duk bayanan gida kuma ya koma zuwa jagorar saiti. Ba za a iya maido da wannan ba.", + "login_reset_device_confirm_body": "Wannan zai share bayanan kumburin da aka ajiye a WANNAN na'urar kuma ya koma zuwa jagorar saiti. Kumburin da kuke haɗe da shi a wata na'ura ba zai sami tasiri ba. Ba za a iya mayar da wannan aiki ba.", "login_reset_device_confirm_title": "Sake saita na'ura?", "login_setup_complete_relogin": "An kammala saitin. Node ɗinka ya sake farawa, don haka ka sake shiga da bayanan shiga guda ɗaya da ka yi amfani da su wajen kammala saitin.", "login_signin_apple": "Shiga tare da Apple", diff --git a/client/shared/src/desktopMain/resources/localization/hi.json b/client/shared/src/desktopMain/resources/localization/hi.json index 7654f45..504fa17 100644 --- a/client/shared/src/desktopMain/resources/localization/hi.json +++ b/client/shared/src/desktopMain/resources/localization/hi.json @@ -1858,10 +1858,10 @@ "login_owner_hint": "पिछली बार साइन इन किया: {owner}", "login_password_label": "पासवर्ड", "login_privacy": "गोपनीयता नीति देखें", - "login_reset_device": "डिवाइस रीसेट करें", + "login_reset_device": "इस डिवाइस को रीसेट करें", "login_reset_device_cancel": "रद्द करें", - "login_reset_device_confirm_body": "यह सभी स्थानीय डेटा मिटा देगा और सेटअप विज़ार्ड पर वापस ले जाएगा। इसे पूर्ववत नहीं किया जा सकता।", - "login_reset_device_confirm_title": "डिवाइस रीसेट करें?", + "login_reset_device_confirm_body": "यह इस डिवाइस पर संग्रहीत नोड डेटा मिटा देगा और सेटअप विज़ार्ड पर वापस ले जाएगा। किसी अन्य मशीन पर जिस नोड से आप जुड़े हैं, वह प्रभावित नहीं होगा। इसे पूर्ववत नहीं किया जा सकता।", + "login_reset_device_confirm_title": "इस डिवाइस को रीसेट करें?", "login_setup_complete_relogin": "सेटअप पूरा हो गया है। आपका नोड फिर से चालू हुआ है, इसलिए कृपया उन्हीं क्रेडेंशियल से दोबारा लॉग इन करें जिनका उपयोग आपने सेटअप पूरा करने के लिए किया था।", "login_signin_apple": "Apple से साइन इन करें", "login_signin_google": "Google से साइन इन करें", diff --git a/client/shared/src/desktopMain/resources/localization/id.json b/client/shared/src/desktopMain/resources/localization/id.json index 809bf6a..d51d506 100644 --- a/client/shared/src/desktopMain/resources/localization/id.json +++ b/client/shared/src/desktopMain/resources/localization/id.json @@ -1858,10 +1858,10 @@ "login_owner_hint": "Terakhir masuk: {owner}", "login_password_label": "Kata Sandi", "login_privacy": "Lihat Kebijakan Privasi", - "login_reset_device": "Atur ulang perangkat", + "login_reset_device": "Atur ulang perangkat ini", "login_reset_device_cancel": "Batal", - "login_reset_device_confirm_body": "Ini akan menghapus semua data lokal dan kembali ke wizard penyiapan. Tindakan ini tidak dapat dibatalkan.", - "login_reset_device_confirm_title": "Atur ulang perangkat?", + "login_reset_device_confirm_body": "Ini akan menghapus data node yang tersimpan di perangkat INI dan kembali ke wizard penyiapan. Node tempat Anda terhubung di mesin lain tidak terpengaruh. Tindakan ini tidak dapat dibatalkan.", + "login_reset_device_confirm_title": "Atur ulang perangkat ini?", "login_setup_complete_relogin": "Penyiapan selesai. Node Anda telah dimulai ulang, jadi silakan masuk kembali dengan kredensial yang sama seperti yang Anda gunakan saat menyelesaikan penyiapan.", "login_signin_apple": "Masuk dengan Apple", "login_signin_google": "Masuk dengan Google", diff --git a/client/shared/src/desktopMain/resources/localization/it.json b/client/shared/src/desktopMain/resources/localization/it.json index 1748af9..525dd69 100644 --- a/client/shared/src/desktopMain/resources/localization/it.json +++ b/client/shared/src/desktopMain/resources/localization/it.json @@ -1860,7 +1860,7 @@ "login_privacy": "Visualizza Informativa sulla Privacy", "login_reset_device": "Ripristina dispositivo", "login_reset_device_cancel": "Annulla", - "login_reset_device_confirm_body": "Verranno cancellati tutti i dati locali e si tornerà alla procedura guidata di configurazione. Questa azione non può essere annullata.", + "login_reset_device_confirm_body": "Verranno cancellati i dati del nodo memorizzati su QUESTO dispositivo e si tornerà alla procedura guidata di configurazione. Un nodo a cui sei connesso su un'altra macchina non subisce modifiche. Questa azione non può essere annullata.", "login_reset_device_confirm_title": "Ripristinare il dispositivo?", "login_setup_complete_relogin": "La configurazione è completata. Il tuo nodo è stato riavviato: accedi di nuovo con le stesse credenziali che hai usato per completare la configurazione.", "login_signin_apple": "Accedi con Apple", diff --git a/client/shared/src/desktopMain/resources/localization/ja.json b/client/shared/src/desktopMain/resources/localization/ja.json index 07f3851..371b63a 100644 --- a/client/shared/src/desktopMain/resources/localization/ja.json +++ b/client/shared/src/desktopMain/resources/localization/ja.json @@ -1860,7 +1860,7 @@ "login_privacy": "プライバシーポリシーを表示", "login_reset_device": "デバイスをリセット", "login_reset_device_cancel": "キャンセル", - "login_reset_device_confirm_body": "すべてのローカルデータが消去され、セットアップウィザードに戻ります。この操作は元に戻せません。", + "login_reset_device_confirm_body": "このデバイスに保存されているノードデータが消去され、セットアップウィザードに戻ります。別のマシン上で接続しているノードには影響しません。この操作は元に戻せません。", "login_reset_device_confirm_title": "デバイスをリセットしますか?", "login_setup_complete_relogin": "セットアップが完了しました。ノードが再起動したため、セットアップで使用したものと同じ認証情報で再度ログインしてください。", "login_signin_apple": "Appleでサインイン", diff --git a/client/shared/src/desktopMain/resources/localization/ko.json b/client/shared/src/desktopMain/resources/localization/ko.json index a766a42..e46b77f 100644 --- a/client/shared/src/desktopMain/resources/localization/ko.json +++ b/client/shared/src/desktopMain/resources/localization/ko.json @@ -1860,7 +1860,7 @@ "login_privacy": "개인정보 보호정책 보기", "login_reset_device": "기기 재설정", "login_reset_device_cancel": "취소", - "login_reset_device_confirm_body": "모든 로컬 데이터가 삭제되고 설정 마법사로 돌아갑니다. 이 작업은 취소할 수 없습니다.", + "login_reset_device_confirm_body": "이 작업은 이 기기에 저장된 노드 데이터를 삭제하고 설정 마법사로 돌아갑니다. 다른 기기에서 연결해 사용 중인 노드는 영향을 받지 않습니다. 이 작업은 취소할 수 없습니다.", "login_reset_device_confirm_title": "기기를 재설정하시겠습니까?", "login_setup_complete_relogin": "설정이 완료되었습니다. 노드가 재시작되었으므로 설정에 사용한 것과 동일한 자격 증명으로 다시 로그인해 주세요.", "login_signin_apple": "Apple로 로그인", diff --git a/client/shared/src/desktopMain/resources/localization/mr.json b/client/shared/src/desktopMain/resources/localization/mr.json index 0c7cf1f..35d3208 100644 --- a/client/shared/src/desktopMain/resources/localization/mr.json +++ b/client/shared/src/desktopMain/resources/localization/mr.json @@ -1860,7 +1860,7 @@ "login_privacy": "गोपनीयता धोरण पाहा", "login_reset_device": "डिव्हाइस रीसेट करा", "login_reset_device_cancel": "रद्द करा", - "login_reset_device_confirm_body": "हे सर्व स्थानिक डेटा मिटवेल आणि सेटअप विझार्डवर परत जाईल. हे पूर्ववत केले जाऊ शकत नाही.", + "login_reset_device_confirm_body": "यामुळे याच डिव्हाइसवर साठवलेला नोड डेटा पुसला जाईल आणि सेटअप विझार्डवर परत जाईल. तुम्ही दुसऱ्या मशीनवरील ज्या नोडशी जोडलेले आहात, त्यावर याचा परिणाम होत नाही. हे पूर्ववत केले जाऊ शकत नाही.", "login_reset_device_confirm_title": "डिव्हाइस रीसेट करायचे?", "login_setup_complete_relogin": "सेटअप पूर्ण झाला आहे. तुमचा नोड पुन्हा सुरू झाला आहे, त्यामुळे सेटअप पूर्ण करण्यासाठी वापरलेल्या त्याच क्रेडेन्शियल्सने कृपया पुन्हा लॉग इन करा.", "login_signin_apple": "Apple सह साइन इन करा", diff --git a/client/shared/src/desktopMain/resources/localization/my.json b/client/shared/src/desktopMain/resources/localization/my.json index aa01418..37d756b 100644 --- a/client/shared/src/desktopMain/resources/localization/my.json +++ b/client/shared/src/desktopMain/resources/localization/my.json @@ -1860,7 +1860,7 @@ "login_privacy": "ကိုယ်ရေးကာကွယ်မှု မူဝါဒ ကြည့်ရှုပါ", "login_reset_device": "စက်ပစ္စည်း ပြန်လည်သတ်မှတ်ပါ", "login_reset_device_cancel": "ပယ်ဖျက်", - "login_reset_device_confirm_body": "ဤလုပ်ဆောင်ချက်သည် ဒေသတွင်းဒေတာအားလုံးကို ဖျက်ပြီး ဆက်တင် wizard သို့ ပြန်သွားမည်ဖြစ်သည်။ ဤလုပ်ဆောင်ချက်ကို ပြန်ဖြည့်၍မရပါ။", + "login_reset_device_confirm_body": "ဤလုပ်ဆောင်ချက်သည် ဤစက်ပေါ်တွင် သိမ်းဆည်းထားသော node ဒေတာကို ဖျက်ပြီး ဆက်တင် wizard သို့ ပြန်သွားမည်ဖြစ်သည်။ သင် အခြားစက်တွင် ချိတ်ဆက်ထားသည့် node သည် ထိခိုက်မည် မဟုတ်ပါ။ ဤလုပ်ဆောင်ချက်ကို ပြန်မလုပ်နိုင်ပါ။", "login_reset_device_confirm_title": "စက်ပစ္စည်း ပြန်လည်သတ်မှတ်မည်လား?", "login_setup_complete_relogin": "စနစ်ထည့်သွင်းမှု ပြီးဆုံးပါပြီ။ သင့် node ပြန်လည်စတင်ခဲ့သဖြင့် စနစ်ထည့်သွင်းရာတွင် အသုံးပြုခဲ့သည့် အထောက်အထားများဖြင့်ပင် ပြန်လည်ဝင်ရောက်ပါ။", "login_signin_apple": "Apple ဖြင့် ဝင်ရောက်", diff --git a/client/shared/src/desktopMain/resources/localization/pa.json b/client/shared/src/desktopMain/resources/localization/pa.json index ebf8145..b5d51c5 100644 --- a/client/shared/src/desktopMain/resources/localization/pa.json +++ b/client/shared/src/desktopMain/resources/localization/pa.json @@ -1860,7 +1860,7 @@ "login_privacy": "ਗੋਪਨੀਯਤਾ ਨੀਤੀ ਦੇਖੋ", "login_reset_device": "ਡਿਵਾਈਸ ਰੀਸੈੱਟ ਕਰੋ", "login_reset_device_cancel": "ਰੱਦ ਕਰੋ", - "login_reset_device_confirm_body": "ਇਹ ਸਾਰਾ ਸਥਾਨਕ ਡੇਟਾ ਮਿਟਾ ਦੇਵੇਗਾ ਅਤੇ ਸੈੱਟਅੱਪ ਵਿਜ਼ਾਰਡ 'ਤੇ ਵਾਪਸ ਆ ਜਾਵੇਗਾ। ਇਸ ਨੂੰ ਪਹਿਲਾਂ ਵਾਲੀ ਸਥਿਤੀ ਵਿੱਚ ਨਹੀਂ ਲਿਆਂਦਾ ਜਾ ਸਕਦਾ।", + "login_reset_device_confirm_body": "ਇਹ ਇਸ ਡਿਵਾਈਸ 'ਤੇ ਸਟੋਰ ਕੀਤਾ ਨੋਡ ਡੇਟਾ ਮਿਟਾ ਦੇਵੇਗਾ ਅਤੇ ਸੈੱਟਅੱਪ ਵਿਜ਼ਾਰਡ 'ਤੇ ਵਾਪਸ ਆ ਜਾਵੇਗਾ। ਕਿਸੇ ਹੋਰ ਮਸ਼ੀਨ ਉੱਤੇ ਜਿਸ ਨੋਡ ਨਾਲ ਤੁਸੀਂ ਜੁੜੇ ਹੋ, ਉਹ ਪ੍ਰਭਾਵਿਤ ਨਹੀਂ ਹੁੰਦਾ। ਇਹ ਵਾਪਸ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ।", "login_reset_device_confirm_title": "ਡਿਵਾਈਸ ਰੀਸੈੱਟ ਕਰਨਾ ਹੈ?", "login_setup_complete_relogin": "ਸੈੱਟਅੱਪ ਪੂਰਾ ਹੋ ਗਿਆ ਹੈ। ਤੁਹਾਡਾ ਨੋਡ ਮੁੜ ਚਾਲੂ ਹੋਇਆ ਹੈ, ਇਸ ਲਈ ਕਿਰਪਾ ਕਰਕੇ ਉਹੀ ਪ੍ਰਮਾਣ-ਪੱਤਰ ਵਰਤ ਕੇ ਮੁੜ ਲਾਗਇਨ ਕਰੋ ਜੋ ਤੁਸੀਂ ਸੈੱਟਅੱਪ ਪੂਰਾ ਕਰਨ ਲਈ ਵਰਤੇ ਸਨ।", "login_signin_apple": "Apple ਨਾਲ ਸਾਈਨ ਇਨ ਕਰੋ", diff --git a/client/shared/src/desktopMain/resources/localization/pt.json b/client/shared/src/desktopMain/resources/localization/pt.json index fa5fa8c..d4d550e 100644 --- a/client/shared/src/desktopMain/resources/localization/pt.json +++ b/client/shared/src/desktopMain/resources/localization/pt.json @@ -1858,10 +1858,10 @@ "login_owner_hint": "Último acesso: {owner}", "login_password_label": "Senha", "login_privacy": "Ver Política de Privacidade", - "login_reset_device": "Redefinir dispositivo", + "login_reset_device": "Redefinir este dispositivo", "login_reset_device_cancel": "Cancelar", - "login_reset_device_confirm_body": "Isso apagará todos os dados locais e retornará ao assistente de configuração. Esta ação não pode ser desfeita.", - "login_reset_device_confirm_title": "Redefinir dispositivo?", + "login_reset_device_confirm_body": "Isso excluirá os dados do nó armazenados NESTE dispositivo e retornará ao assistente de configuração. Um nó ao qual você esteja conectado em outra máquina não é afetado. Isso não pode ser desfeito.", + "login_reset_device_confirm_title": "Redefinir este dispositivo?", "login_setup_complete_relogin": "A configuração foi concluída. O seu nó foi reiniciado, por isso inicie sessão novamente com as mesmas credenciais que utilizou para concluir a configuração.", "login_signin_apple": "Entrar com Apple", "login_signin_google": "Entrar com Google", diff --git a/client/shared/src/desktopMain/resources/localization/ru.json b/client/shared/src/desktopMain/resources/localization/ru.json index 484e28e..db4bf61 100644 --- a/client/shared/src/desktopMain/resources/localization/ru.json +++ b/client/shared/src/desktopMain/resources/localization/ru.json @@ -1860,7 +1860,7 @@ "login_privacy": "Посмотреть Политику конфиденциальности", "login_reset_device": "Сбросить устройство", "login_reset_device_cancel": "Отмена", - "login_reset_device_confirm_body": "Это удалит все локальные данные и вернёт мастер настройки. Отменить это действие невозможно.", + "login_reset_device_confirm_body": "Это удалит данные узла, хранящиеся на ЭТОМ устройстве, и вернёт вас к мастеру настройки. Узел, к которому вы подключены на другой машине, затронут не будет. Это действие нельзя отменить.", "login_reset_device_confirm_title": "Сбросить устройство?", "login_setup_complete_relogin": "Настройка завершена. Ваш узел перезапущен, поэтому войдите снова, используя те же учётные данные, что и при настройке.", "login_signin_apple": "Войти через Apple", diff --git a/client/shared/src/desktopMain/resources/localization/sw.json b/client/shared/src/desktopMain/resources/localization/sw.json index 522bfde..17baf50 100644 --- a/client/shared/src/desktopMain/resources/localization/sw.json +++ b/client/shared/src/desktopMain/resources/localization/sw.json @@ -1858,10 +1858,10 @@ "login_owner_hint": "Mwisho aliyeingia: {owner}", "login_password_label": "Nenosiri", "login_privacy": "Tazama Sera ya Faragha", - "login_reset_device": "Weka upya kifaa", + "login_reset_device": "Weka upya kifaa hiki", "login_reset_device_cancel": "Ghairi", - "login_reset_device_confirm_body": "Hii itafuta data yote ya ndani na kurudi kwa mwongozo wa usanidi. Hatua hii haiwezi kutenduliwa.", - "login_reset_device_confirm_title": "Weka upya kifaa?", + "login_reset_device_confirm_body": "Hii itafuta data ya nodi iliyohifadhiwa kwenye kifaa HIKI na kurudi kwa mchawi wa usanidi. Nodi ambayo umeunganishwa nayo kwenye mashine nyingine haitaathiriwa. Hatua hii haiwezi kutenduliwa.", + "login_reset_device_confirm_title": "Weka upya kifaa hiki?", "login_setup_complete_relogin": "Usanidi umekamilika. Nodi yako imeanzishwa upya, kwa hivyo tafadhali ingia tena ukitumia stakabadhi zilezile ulizotumia kukamilisha usanidi.", "login_signin_apple": "Ingia kwa kutumia Apple", "login_signin_google": "Ingia kwa kutumia Google", diff --git a/client/shared/src/desktopMain/resources/localization/ta.json b/client/shared/src/desktopMain/resources/localization/ta.json index 34f5c0e..f3a999a 100644 --- a/client/shared/src/desktopMain/resources/localization/ta.json +++ b/client/shared/src/desktopMain/resources/localization/ta.json @@ -1860,7 +1860,7 @@ "login_privacy": "காண் Privacy Policy", "login_reset_device": "சாதனத்தை மீட்டமை", "login_reset_device_cancel": "ரத்து", - "login_reset_device_confirm_body": "இது அனைத்து உள்ளூர் தரவையும் அழித்து, அமைவு வழிகாட்டிக்கு திரும்பும். இதை மீண்டும் செயல்தவிர்க்க இயலாது.", + "login_reset_device_confirm_body": "இது இந்த சாதனத்தில் சேமிக்கப்பட்டுள்ள நோடு தரவை அழித்து, அமைவு வழிகாட்டிக்குத் திரும்பும். மற்றொரு கணினியில் நீங்கள் இணைந்திருக்கும் நோடு பாதிக்கப்படாது. இந்த செயலை மீள மாற்ற முடியாது.", "login_reset_device_confirm_title": "சாதனத்தை மீட்டமைக்கவா?", "login_setup_complete_relogin": "அமைவு நிறைவடைந்தது. உங்கள் நோடு மறுதொடக்கம் செய்யப்பட்டது, எனவே அமைவை நிறைவு செய்யப் பயன்படுத்திய அதே சான்றுகளுடன் மீண்டும் உள்நுழையவும்.", "login_signin_apple": "Sign in உடன் Apple", diff --git a/client/shared/src/desktopMain/resources/localization/te.json b/client/shared/src/desktopMain/resources/localization/te.json index 78b6759..57f56c3 100644 --- a/client/shared/src/desktopMain/resources/localization/te.json +++ b/client/shared/src/desktopMain/resources/localization/te.json @@ -1858,10 +1858,10 @@ "login_owner_hint": "చివరిగా సైన్ ఇన్ చేసినది: {owner}", "login_password_label": "పాస్‌వర్డ్", "login_privacy": "చూడు Privacy Policy", - "login_reset_device": "పరికరాన్ని రీసెట్ చేయండి", + "login_reset_device": "ఈ పరికరాన్ని రీసెట్ చేయండి", "login_reset_device_cancel": "రద్దు చేయి", - "login_reset_device_confirm_body": "ఇది అన్ని స్థానిక డేటాను తొలగించి, సెటప్ విజార్డ్‌కు తిరిగి వెళ్తుంది. దీన్ని రద్దు చేయడం సాధ్యం కాదు.", - "login_reset_device_confirm_title": "పరికరాన్ని రీసెట్ చేయాలా?", + "login_reset_device_confirm_body": "ఇది ఈ పరికరంలో నిల్వ చేసిన నోడ్ డేటాను మొత్తం తొలగించి, సెటప్ విజార్డ్‌కు తిరిగి వెళ్తుంది. మీరు మరొక యంత్రంలో కనెక్ట్ అయి ఉన్న నోడ్ ప్రభావితం కాదు. ఈ చర్యను రద్దు చేయలేరు.", + "login_reset_device_confirm_title": "ఈ పరికరాన్ని రీసెట్ చేయాలా?", "login_setup_complete_relogin": "సెటప్ పూర్తయింది. మీ నోడ్ పునఃప్రారంభమైంది, కాబట్టి సెటప్ పూర్తి చేయడానికి మీరు ఉపయోగించిన అదే ఆధారాలతో దయచేసి మళ్లీ లాగిన్ అవ్వండి.", "login_signin_apple": "Apple తో సైన్ ఇన్ చేయండి", "login_signin_google": "Google తో సైన్ ఇన్ చేయండి", diff --git a/client/shared/src/desktopMain/resources/localization/th.json b/client/shared/src/desktopMain/resources/localization/th.json index 78de09e..079e23f 100644 --- a/client/shared/src/desktopMain/resources/localization/th.json +++ b/client/shared/src/desktopMain/resources/localization/th.json @@ -1858,10 +1858,10 @@ "login_owner_hint": "เข้าสู่ระบบล่าสุด: {owner}", "login_password_label": "รหัสผ่าน", "login_privacy": "ดูนโยบายความเป็นส่วนตัว", - "login_reset_device": "รีเซ็ตอุปกรณ์", + "login_reset_device": "รีเซ็ตอุปกรณ์นี้", "login_reset_device_cancel": "ยกเลิก", - "login_reset_device_confirm_body": "การดำเนินการนี้จะลบข้อมูลในเครื่องทั้งหมดและกลับสู่วิซาร์ดการตั้งค่า ไม่สามารถย้อนกลับได้", - "login_reset_device_confirm_title": "รีเซ็ตอุปกรณ์?", + "login_reset_device_confirm_body": "การดำเนินการนี้จะลบข้อมูลโหนดที่เก็บอยู่บนอุปกรณ์นี้และกลับไปยังตัวช่วยการตั้งค่า โหนดที่คุณเชื่อมต่ออยู่บนเครื่องอื่นจะไม่ได้รับผลกระทบ การดำเนินการนี้ไม่สามารถยกเลิกได้", + "login_reset_device_confirm_title": "รีเซ็ตอุปกรณ์นี้?", "login_setup_complete_relogin": "การตั้งค่าเสร็จสมบูรณ์แล้ว โหนดของคุณได้รีสตาร์ท กรุณาเข้าสู่ระบบอีกครั้งด้วยข้อมูลรับรองเดียวกับที่คุณใช้ในการตั้งค่า", "login_signin_apple": "เข้าสู่ระบบด้วย Apple", "login_signin_google": "เข้าสู่ระบบด้วย Google", diff --git a/client/shared/src/desktopMain/resources/localization/tr.json b/client/shared/src/desktopMain/resources/localization/tr.json index 9708b87..2bc1988 100644 --- a/client/shared/src/desktopMain/resources/localization/tr.json +++ b/client/shared/src/desktopMain/resources/localization/tr.json @@ -1860,7 +1860,7 @@ "login_privacy": "Gizlilik Politikasını Görüntüle", "login_reset_device": "Cihazı Sıfırla", "login_reset_device_cancel": "İptal", - "login_reset_device_confirm_body": "Bu işlem tüm yerel verileri silecek ve kurulum sihirbazına geri dönecektir. Bu işlem geri alınamaz.", + "login_reset_device_confirm_body": "Bu işlem, BU cihazda depolanan düğüm verilerini siler ve kurulum sihirbazına döner. Başka bir makinede bağlı olduğunuz bir düğüm etkilenmez. Bu geri alınamaz.", "login_reset_device_confirm_title": "Cihaz sıfırlansın mı?", "login_setup_complete_relogin": "Kurulum tamamlandı. Düğümünüz yeniden başlatıldı; lütfen kurulumu tamamlarken kullandığınız kimlik bilgileriyle yeniden oturum açın.", "login_signin_apple": "Apple ile giriş yap", diff --git a/client/shared/src/desktopMain/resources/localization/uk.json b/client/shared/src/desktopMain/resources/localization/uk.json index fafcb63..9de68ee 100644 --- a/client/shared/src/desktopMain/resources/localization/uk.json +++ b/client/shared/src/desktopMain/resources/localization/uk.json @@ -1860,7 +1860,7 @@ "login_privacy": "Переглянути Політику конфіденційності", "login_reset_device": "Скинути пристрій", "login_reset_device_cancel": "Скасувати", - "login_reset_device_confirm_body": "Це видалить усі локальні дані та повернеться до майстра налаштування. Цю дію неможливо скасувати.", + "login_reset_device_confirm_body": "Це видалить дані вузла, збережені НА ЦЬОМУ пристрої, та поверне вас до майстра налаштування. Це не впливає на вузол, до якого ви підключені на іншому комп'ютері. Цю дію неможливо скасувати.", "login_reset_device_confirm_title": "Скинути пристрій?", "login_setup_complete_relogin": "Налаштування завершено. Ваш вузол перезапустився, тому увійдіть знову, використовуючи ті самі облікові дані, з якими ви завершили налаштування.", "login_signin_apple": "Увійти через Apple", diff --git a/client/shared/src/desktopMain/resources/localization/ur.json b/client/shared/src/desktopMain/resources/localization/ur.json index 7c17d31..cecd77f 100644 --- a/client/shared/src/desktopMain/resources/localization/ur.json +++ b/client/shared/src/desktopMain/resources/localization/ur.json @@ -1860,7 +1860,7 @@ "login_privacy": "رازداری کی پالیسی دیکھیں", "login_reset_device": "ڈیوائس ری سیٹ کریں", "login_reset_device_cancel": "منسوخ کریں", - "login_reset_device_confirm_body": "یہ تمام مقامی ڈیٹا مٹا دے گا اور سیٹ اپ وزرڈ پر واپس لے جائے گا۔ یہ عمل واپس نہیں کیا جا سکتا۔", + "login_reset_device_confirm_body": "یہ اِسی ڈیوائس پر محفوظ نوڈ کا ڈیٹا مٹا دے گا اور سیٹ اپ وزرڈ پر واپس آ جائے گا۔ کسی دوسری مشین پر جس نوڈ سے آپ جڑے ہیں، اُس پر کوئی اثر نہیں پڑے گا۔ یہ عمل واپس نہیں کیا جا سکتا۔", "login_reset_device_confirm_title": "ڈیوائس ری سیٹ کریں؟", "login_setup_complete_relogin": "سیٹ اپ مکمل ہو گیا ہے۔ آپ کا نوڈ دوبارہ شروع ہوا ہے، اس لیے براہِ کرم انہی اسناد کے ساتھ دوبارہ لاگ اِن کریں جو آپ نے سیٹ اپ مکمل کرنے کے لیے استعمال کی تھیں۔", "login_signin_apple": "Apple کے ساتھ سائن ان کریں", diff --git a/client/shared/src/desktopMain/resources/localization/vi.json b/client/shared/src/desktopMain/resources/localization/vi.json index 245869a..24135ce 100644 --- a/client/shared/src/desktopMain/resources/localization/vi.json +++ b/client/shared/src/desktopMain/resources/localization/vi.json @@ -1860,7 +1860,7 @@ "login_privacy": "Xem chính sách bảo mật", "login_reset_device": "Đặt lại thiết bị", "login_reset_device_cancel": "Hủy", - "login_reset_device_confirm_body": "Thao tác này sẽ xóa tất cả dữ liệu cục bộ và quay lại trình hướng dẫn cài đặt. Hành động này không thể hoàn tác.", + "login_reset_device_confirm_body": "Thao tác này sẽ xóa dữ liệu nút được lưu trữ trên CHÍNH thiết bị này và quay lại trình hướng dẫn cài đặt. Nút mà bạn đang kết nối trên một máy khác sẽ không bị ảnh hưởng. Không thể hoàn tác.", "login_reset_device_confirm_title": "Đặt lại thiết bị?", "login_setup_complete_relogin": "Thiết lập đã hoàn tất. Nút của bạn đã khởi động lại, vui lòng đăng nhập lại bằng đúng thông tin đăng nhập mà bạn đã dùng để hoàn tất thiết lập.", "login_signin_apple": "Đăng nhập bằng Apple", diff --git a/client/shared/src/desktopMain/resources/localization/yo.json b/client/shared/src/desktopMain/resources/localization/yo.json index d7f8400..c7c0a0f 100644 --- a/client/shared/src/desktopMain/resources/localization/yo.json +++ b/client/shared/src/desktopMain/resources/localization/yo.json @@ -1858,10 +1858,10 @@ "login_owner_hint": "Ìwọlé ìkẹ̀yìn: {owner}", "login_password_label": "Ọ̀rọ̀ Àṣírí", "login_privacy": "Wo Ìlànà Àṣírí", - "login_reset_device": "Tún ẹ̀rọ ṣe", + "login_reset_device": "Tún ẹ̀rọ yìí ṣe", "login_reset_device_cancel": "Fagilé", - "login_reset_device_confirm_body": "Èyí yóò pa gbogbo dátà àdúgbò run kí ó sì padà sí olùṣèlú ìṣètò. Èyí kò lè yípadà.", - "login_reset_device_confirm_title": "Tún ẹ̀rọ ṣe?", + "login_reset_device_confirm_body": "Èyí yóò pa dátà nódù tí a fipamọ́ sórí Ẹ̀RỌ YÌÍ run kí ó sì padà sí olùtọ́nisọ́nà ìṣètò. Èyí kò ní kan nódù tí ẹ ti so pọ̀ mọ́ ẹ̀rọ mìíràn. Èyí kò ṣeépadà.", + "login_reset_device_confirm_title": "Tún ẹ̀rọ yìí ṣe?", "login_setup_complete_relogin": "Ìṣètò ti parí. Node rẹ tún bẹ̀rẹ̀, nítorí náà jọ̀wọ́ wọlé padà pẹ̀lú àwọn ẹ̀rí ìdánimọ̀ kan náà tí o lò láti parí ìṣètò.", "login_signin_apple": "Wọlé pẹ̀lú Apple", "login_signin_google": "Wọlé pẹ̀lú Google", diff --git a/client/shared/src/desktopMain/resources/localization/zh.json b/client/shared/src/desktopMain/resources/localization/zh.json index 2bcede3..570ceda 100644 --- a/client/shared/src/desktopMain/resources/localization/zh.json +++ b/client/shared/src/desktopMain/resources/localization/zh.json @@ -1860,7 +1860,7 @@ "login_privacy": "查看隐私政策", "login_reset_device": "重置设备", "login_reset_device_cancel": "取消", - "login_reset_device_confirm_body": "此操作将清除所有本地数据并返回设置向导。此操作无法撤销。", + "login_reset_device_confirm_body": "此操作将清除存储在本设备上的节点数据并返回设置向导。您在其他设备上已连接的节点不会受到影响。此操作无法撤销。", "login_reset_device_confirm_title": "重置设备?", "login_setup_complete_relogin": "设置已完成。您的节点已重新启动,请使用完成设置时使用的相同凭据重新登录。", "login_signin_apple": "使用 Apple 登录", From 50e21c1c3ecdee21b8650c918c6041e1b8e3dce9 Mon Sep 17 00:00:00 2001 From: Eric Moore Date: Fri, 28 Aug 2026 07:55:50 -0500 Subject: [PATCH 10/11] fix(bundle): the claim PIN was in the bundle, and the GitHub issue path was never redacted MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I told this repo's owner that nothing in this client logs a credential — that I had checked rather than assumed. I had checked the wrong thing. My grep looked for a credential-named variable interpolated into a log call; it cannot see a RAW LINE forwarded verbatim that happens to contain one. StartupViewModel:189 PlatformLogger.d(TAG, "... Line #$n: ${line.take(100)}") Logger.desktop:16 DebugLogBuffer.add("DEBUG", tag, message) node banner ║ CLAIM PIN: 7F3K-Q9MZ (one-time; console-only) Every node stdout line goes through PlatformLogger — first 50, then every tenth — PlatformLogger feeds DebugLogBuffer, and the ownership banner is emitted in the node's first lines on a fresh unclaimed node. So the live one-time secret that grants ownership sits in a bundle designed to be mailed to strangers, and no pattern matched it, because it is a LABELLED BANNER and every arm was written for `name=value`. The redaction is not guarding a hypothetical. It never was. Matched on label AND shape — 8 Crockford base32 characters as XXXX-XXXX, the alphabet `PythonRuntime.desktop.CLAIM_PIN_REGEX` parses — so an ordinary dashed token in prose survives. The NodeCode on the neighbouring line is deliberately left alone: it is a public bootstrap handle and the reader needs it. AND THE OTHER EXPORT WAS NEVER SCRUBBED AT ALL. `FailurePanel.reportUrl()` builds its body from `DebugBundle.environment()` plus the RAW server detail and opens a GitHub issue — a public destination, worse than the bundle, which is at least a file the user chooses to send. `render()` scrubbed its own output while this path passed the same text through untouched, because the scrubbing was attached to a FUNCTION rather than to leaving the device. Now both go through `redactSecrets`. Third: an opening quote with no closing one — a truncated log line — fell to the unquoted arm and stopped at the first space. An unmatched quote means the value runs to end of line, so it is redacted that far. 19 redaction cases. The claim PIN is tested in both directions, and so is the NodeCode beside it. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01EGE52kPzjGFiPzGcs63ZC1 --- client/VENDORING.md | 2 +- .../shared/ui/components/DebugBundle.kt | 28 ++++++++++++++++ .../shared/ui/components/FailurePanel.kt | 10 +++++- .../ui/components/DebugBundleRedactionTest.kt | 33 +++++++++++++++++++ 4 files changed, 71 insertions(+), 2 deletions(-) diff --git a/client/VENDORING.md b/client/VENDORING.md index 76511c5..dc6e992 100644 --- a/client/VENDORING.md +++ b/client/VENDORING.md @@ -40,7 +40,7 @@ source is the pair a bisect wants: The tree's current recorded state — sha256-of-sha256s over every git-tracked file under `client/` except this one: -**state digest:** `7d8c129cbec14834c466b29557126d6134223631fa247e2a3aa9841403e1893f` +**state digest:** `b51c80e696f12fdd7ed85c406b48dcd9f16fe070064003f3ed47414978c04267` `packaging/check_vendoring.py` asserts it on every push, and refuses any tracked file matching a §2 never-vendor class. **Any commit that touches diff --git a/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundle.kt b/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundle.kt index 05fe8fd..86fd2d3 100644 --- a/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundle.kt +++ b/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundle.kt @@ -120,6 +120,25 @@ object DebugBundle { """true|false|yes|no|enabled|disabled|empty|blank|valid""" private val SECRET_PATTERNS: List> = listOf( + // THE CLAIM PIN. The one credential this client demonstrably handles. + // + // `StartupViewModel` logs the node's stdout lines through PlatformLogger + // (first 50, then every tenth), PlatformLogger feeds DebugLogBuffer, and + // the node's ownership banner — `CLAIM PIN: 7F3K-Q9MZ` — is emitted in + // its first few lines on a fresh unclaimed node. So the live one-time + // secret that grants ownership of the node sits in a bundle built to be + // mailed to strangers, and no assignment pattern matches it because it + // is a LABELLED BANNER, not a `name=value` (Codex, PR #18). + // + // Label AND shape, so an ordinary `XXXX-XXXX` in prose is untouched. + // The token is 8 Crockford base32 characters (no I/L/O/U) as XXXX-XXXX, + // the same alphabet `PythonRuntime.desktop.CLAIM_PIN_REGEX` parses. The + // NodeCode on the neighbouring line is deliberately NOT redacted: it is + // a public bootstrap handle and the reader needs it. + Regex( + """(claim\s*pin\s*[:=]\s*)[0-9A-HJKMNP-TV-Z]{4}-[0-9A-HJKMNP-TV-Z]{4}""", + RegexOption.IGNORE_CASE, + ) to "$1", // JWTs — the shape is unmistakable and never appears in prose. // `eyJ` is the shape check — that is a base64url `{"` and nothing else // starts that way by accident. Segment LENGTH is not: a JWT with a small @@ -161,6 +180,15 @@ object DebugBundle { """\b($NAME_PREFIX(?:$SECRET_NAMES))(["']?\s*[:=]\s*)['](?!(?:$NOT_A_STATE)['])((?:[^'\\]|\\.)+)[']""", RegexOption.IGNORE_CASE, ) to "\$1\$2''", + // An OPENING quote with no closing one — a truncated or line-clipped + // log entry. Both quoted arms fail, and the unquoted arm below consumes + // the quote and stops at the first space, leaving most of the credential + // in the bundle (Codex, PR #18). An unmatched quote means the value runs + // to end of line, so redact that far. + Regex( + """\b($NAME_PREFIX(?:$SECRET_NAMES))(\s*[:=]\s*)(["'])(?!(?:$NOT_A_STATE)["']?${'$'})([^"'\n]+)${'$'}""", + setOf(RegexOption.IGNORE_CASE, RegexOption.MULTILINE), + ) to "$1$2$3", // UNQUOTED name = value. Whitespace really is the delimiter here. Regex( """\b($NAME_PREFIX(?:$SECRET_NAMES))(\s*["']?\s*[:=]\s*["']?)""" + diff --git a/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/ui/components/FailurePanel.kt b/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/ui/components/FailurePanel.kt index 3b17808..7927d93 100644 --- a/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/ui/components/FailurePanel.kt +++ b/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/ui/components/FailurePanel.kt @@ -327,7 +327,15 @@ private fun reportUrl( appendLine("
Error detail") appendLine() appendLine("```") - appendLine(detail.take(4000)) + // REDACTED, like the bundle. This body goes to a PUBLIC GitHub issue, + // which is a worse destination than the bundle the redactor was written + // for — that one is a file the user chooses to send, this one is a URL + // that opens a form to the world. `DebugBundle.render()` scrubbed its + // own output while this path passed the raw server detail straight + // through (Codex, PR #18): the same text, the same risk, one export + // scrubbed and one not, because the scrubbing was attached to a + // FUNCTION rather than to leaving the device. + appendLine(DebugBundle.redactSecrets(detail).take(4000)) appendLine("```") appendLine() appendLine("
") diff --git a/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundleRedactionTest.kt b/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundleRedactionTest.kt index 95b99af..f506a30 100644 --- a/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundleRedactionTest.kt +++ b/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundleRedactionTest.kt @@ -215,4 +215,37 @@ class DebugBundleRedactionTest { assertTrue(out.contains(""), out) + } + + @Test + fun the_public_nodecode_is_left_alone() { + // It sits on the neighbouring banner line and is a PUBLIC bootstrap + // handle. Redacting it costs the reader the thing they need. + val line = "\u2551 NodeCode : CIRIS-V1-AAAA-BBBB-CCCC" + assertTrue(DebugBundle.redactSecrets(line).contains("CIRIS-V1-AAAA-BBBB-CCCC")) + } + + @Test + fun an_ordinary_dashed_token_is_not_mistaken_for_a_pin() { + // Label AND shape: without the label this would eat arbitrary text. + val line = "order ABCD-1234 shipped" + assertTrue(DebugBundle.redactSecrets(line).contains("ABCD-1234")) + } + + @Test + fun an_unterminated_quoted_credential_is_redacted_to_end_of_line() { + val out = DebugBundle.redactSecrets("password=\"correct horse battery staple") + assertFalse(out.contains("battery"), "the credential survived: $out") + assertFalse(out.contains("staple"), "the credential survived: $out") + } } \ No newline at end of file From 4c5262057f5d6016571151d3fe0d55658ff90824 Mon Sep 17 00:00:00 2001 From: Eric Moore Date: Fri, 28 Aug 2026 08:06:55 -0500 Subject: [PATCH 11/11] fix(reset): a device-local reset must ask the device-local node MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Renaming the button made the promise explicit and left the implementation pointing somewhere else. `getNodeHomePath()` queried `$baseUrl` — the ACTIVE node. Connected to a remote profile, "Reset this device" asked THAT MACHINE where its home is and handed the answer to a LOCAL filesystem wipe: deleting allow-listed directories from an unrelated local path if it happened to exist here, and otherwise reporting success having cleared nothing on the device the button names (Codex, PR #18). It now takes the node URL and defaults to LOCAL_NODE_URL, and the reset passes it explicitly. That is also the reason no ownership guard is needed: the scope is fixed by WHAT WE QUERY rather than policed after the fact. Removing the guard was right; leaving the home resolution pointed at the active node was not, and the guard had been hiding it. Also: the unterminated-quote arm I added in the previous commit used the `[^"']` value class the terminated arms had already been fixed away from, so a truncated secret containing an apostrophe or an escaped quote fell through to the first-space truncation. One arm per delimiter, consuming escapes, like the terminated pair. Same bug, one arm later. 23 redaction cases. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01EGE52kPzjGFiPzGcs63ZC1 --- client/VENDORING.md | 2 +- .../kotlin/ai/ciris/mobile/shared/CIRISApp.kt | 19 ++++++++++++++++- .../ciris/mobile/shared/api/CIRISApiClient.kt | 4 ++-- .../shared/ui/components/DebugBundle.kt | 21 +++++++++++++------ .../ui/components/DebugBundleRedactionTest.kt | 12 +++++++++++ 5 files changed, 48 insertions(+), 10 deletions(-) diff --git a/client/VENDORING.md b/client/VENDORING.md index dc6e992..f186f0b 100644 --- a/client/VENDORING.md +++ b/client/VENDORING.md @@ -40,7 +40,7 @@ source is the pair a bisect wants: The tree's current recorded state — sha256-of-sha256s over every git-tracked file under `client/` except this one: -**state digest:** `b51c80e696f12fdd7ed85c406b48dcd9f16fe070064003f3ed47414978c04267` +**state digest:** `0ff13365d8b1b367cd9bc382f0b89e2bc51200dc0629ed93523b049020e147a8` `packaging/check_vendoring.py` asserts it on every push, and refuses any tracked file matching a §2 never-vendor class. **Any commit that touches diff --git a/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/CIRISApp.kt b/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/CIRISApp.kt index deecad1..84db773 100644 --- a/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/CIRISApp.kt +++ b/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/CIRISApp.kt @@ -2091,7 +2091,24 @@ fun CIRISApp( // Ask it where it lives while it is still up, too: the answer is // authoritative and it is about to stop being available. val declaredHome = if (ai.ciris.mobile.shared.platform.isDesktop()) { - val h = runCatching { apiClient.getNodeHomePath() }.getOrNull() + // THE LOCAL NODE'S HOME, EXPLICITLY. The button + // says "Reset this device"; asking the ACTIVE + // node where it lives asks a remote machine when + // the client is pointed at one, and hands that + // path to a LOCAL filesystem wipe — deleting from + // an unrelated local directory if it happens to + // exist here, and otherwise reporting success + // having cleared nothing (Codex, PR #18). + // + // A device-local operation asks the device-local + // node. That is also why no ownership guard is + // needed: the scope is fixed by what we query, + // not policed after the fact. + val h = runCatching { + apiClient.getNodeHomePath( + ai.ciris.mobile.shared.api.CIRISApiClient.LOCAL_NODE_URL + ) + }.getOrNull() platformLog(TAG, "[INFO][onResetSetup] node declared home: ${h ?: ""}") runCatching { pythonRuntime.shutdown() } .onFailure { platformLog(TAG, "[WARN][onResetSetup] backend shutdown: ${it.message}") } diff --git a/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/api/CIRISApiClient.kt b/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/api/CIRISApiClient.kt index bc65469..a5cdd55 100644 --- a/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/api/CIRISApiClient.kt +++ b/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/api/CIRISApiClient.kt @@ -9148,7 +9148,7 @@ class CIRISApiClient( * not cost the answer. Null when the node does not declare one — the caller * then falls back to the guess, which is where it was before. */ - suspend fun getNodeHomePath(): String? = runCatching { + suspend fun getNodeHomePath(nodeUrl: String = LOCAL_NODE_URL): String? = runCatching { // A short-lived client, as the other raw scrapes in this file do: the // generated setup API binds a model that does not carry claim_pin_file. val client = io.ktor.client.HttpClient { @@ -9163,7 +9163,7 @@ class CIRISApiClient( // to compile for that target -- while desktop compiled clean. Every other // raw scrape in this file closes its client this way. val body = try { - client.get("$baseUrl/v1/setup/status").bodyAsText() + client.get("$nodeUrl/v1/setup/status").bodyAsText() } finally { client.close() } diff --git a/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundle.kt b/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundle.kt index 86fd2d3..44ac908 100644 --- a/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundle.kt +++ b/client/shared/src/commonMain/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundle.kt @@ -181,14 +181,23 @@ object DebugBundle { RegexOption.IGNORE_CASE, ) to "\$1\$2''", // An OPENING quote with no closing one — a truncated or line-clipped - // log entry. Both quoted arms fail, and the unquoted arm below consumes - // the quote and stops at the first space, leaving most of the credential - // in the bundle (Codex, PR #18). An unmatched quote means the value runs - // to end of line, so redact that far. + // log entry. Both terminated arms fail, and the unquoted arm below + // consumes the quote and stops at the first space, leaving most of the + // credential in the bundle (Codex, PR #18). + // + // ONE ARM PER DELIMITER, CONSUMING ESCAPES, exactly like the terminated + // pair — I wrote the first version of this with the `[^"']` value class + // the terminated arms had already been fixed away from, so a truncated + // secret containing an apostrophe or an escaped quote fell through to + // the same first-space truncation. Same bug, one arm later. + Regex( + """\b($NAME_PREFIX(?:$SECRET_NAMES))(\s*[:=]\s*)["](?!(?:$NOT_A_STATE)["]?${'$'})((?:[^"\\\n]|\\.)+)${'$'}""", + setOf(RegexOption.IGNORE_CASE, RegexOption.MULTILINE), + ) to "\$1\$2\"", Regex( - """\b($NAME_PREFIX(?:$SECRET_NAMES))(\s*[:=]\s*)(["'])(?!(?:$NOT_A_STATE)["']?${'$'})([^"'\n]+)${'$'}""", + """\b($NAME_PREFIX(?:$SECRET_NAMES))(\s*[:=]\s*)['](?!(?:$NOT_A_STATE)[']?${'$'})((?:[^'\\\n]|\\.)+)${'$'}""", setOf(RegexOption.IGNORE_CASE, RegexOption.MULTILINE), - ) to "$1$2$3", + ) to "\$1\$2'", // UNQUOTED name = value. Whitespace really is the delimiter here. Regex( """\b($NAME_PREFIX(?:$SECRET_NAMES))(\s*["']?\s*[:=]\s*["']?)""" + diff --git a/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundleRedactionTest.kt b/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundleRedactionTest.kt index f506a30..5baed20 100644 --- a/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundleRedactionTest.kt +++ b/client/shared/src/desktopTest/kotlin/ai/ciris/mobile/shared/ui/components/DebugBundleRedactionTest.kt @@ -248,4 +248,16 @@ class DebugBundleRedactionTest { assertFalse(out.contains("battery"), "the credential survived: $out") assertFalse(out.contains("staple"), "the credential survived: $out") } + + @Test + fun an_unterminated_secret_may_contain_the_other_quote_or_an_escape() { + for (line in listOf( + "password=\"correct horse's battery staple", + "password=\"correct \\\"horse\\\" battery staple", + )) { + val out = DebugBundle.redactSecrets(line) + assertFalse(out.contains("battery"), "the credential survived: $out") + assertFalse(out.contains("staple"), "the credential survived: $out") + } + } } \ No newline at end of file