From 049e93ae3d535659bf5674e4c6842e5c9cf8d89e Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Sat, 25 Jul 2026 00:17:08 +0000 Subject: [PATCH] fix: prevent bearer setup migration bypass --- CHANGELOG.md | 2 ++ PROJECT_PLAN.md | 4 +++- .../onboarding/SetupWizardModels.kt | 5 +---- .../onboarding/SetupWizardReducerTest.kt | 21 +++++++++++++++++-- docs/BLOCKERS.md | 2 ++ docs/ROADMAP.md | 2 +- 6 files changed, 28 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 071c705..b60db57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,7 @@ All notable Hermes Mobile source changes are recorded here. Entries are added du - Ignored `.gradle-user/` and `.dev/` alongside existing generated build artifacts. ### Fixed +- Fixed the first-run gate so an uncompleted Bearer profile cannot self-migrate after a failed/offline test and bypass setup on restart; only profiles with recorded legacy provenance migrate. - Fixed the setup gate so valid pre-wizard bearer profiles already stored under the current connection keys receive the one-time completion migration; untested no-auth first-run profiles remain gated. - Fixed first-run setup so a profile saved by a failed/offline compatibility test cannot bypass mandatory setup after process restart; only completed or migrated valid profiles skip the wizard. - Fixed durable stream completion so a stale request ID cannot publish a terminal state or clear a newer reservation. @@ -67,6 +68,7 @@ All notable Hermes Mobile source changes are recorded here. Entries are added du - Fixed companion/mobile lint issues from missing Node globals and an unused React settings value. ### Verification +- Focused wizard regression: `./gradlew --offline --no-daemon -Dorg.gradle.jvmargs= -Dorg.gradle.daemon=false -Pkotlin.compiler.execution.strategy=in-process :app:testDebugUnitTest --tests 'cloud.molberg.hermesmobile.onboarding.SetupWizardReducerTest' :app:assembleDebug` — passed (43 tasks, 9 executed), including the Bearer restart regression. - `./gradlew --offline --no-daemon -Dorg.gradle.jvmargs= -Dorg.gradle.daemon=false -Pkotlin.compiler.execution.strategy=in-process :app:testDebugUnitTest --tests 'cloud.molberg.hermesmobile.onboarding.SetupWizardReducerTest' :app:assembleDebug` — passed (43 tasks, 9 executed), including the new current-key migration regression. - Task 5 source-current re-verification on 2026-07-24: `/root/.openclaw/workspace/scripts/hermes-mobile-preflight.sh` passed; `./gradlew --offline --no-daemon -Dorg.gradle.jvmargs= -Dorg.gradle.daemon=false -Pkotlin.compiler.execution.strategy=in-process :app:testDebugUnitTest :app:assembleDebug` passed (43 tasks, 1 executed); the generated Gradle XML report records 106 tests with 0 failures/errors, including 29 `SetupWizardReducerTest` cases; a localhost gateway health probe returned HTTP 200, establishing reachability only; the debug APK reports package `cloud.molberg.hermesmobile`, version `0.1.0-beta.1` (`10001`), and SHA-256 `45e4ab4783a74eb766070b0f670a2631a9ebdc044468bbe03087788f8106f150`; and `git diff --check` passed. No ADB device/emulator or `/dev/kvm` was available; no device installation, real authentication, release, or end-to-end gateway verification is claimed. - Later Task 5 current-sandbox rerun on 2026-07-24: the exact guide Gradle command was blocked before project execution because no usable wildcard IP was available; the offline/no-daemon variant was blocked because Gradle could not open its daemon TCP socket (`java.net.SocketException: Operation not permitted`). Direct JUnit execution of the existing source-current compiled `SetupWizardReducerTest` classes passed all 29 tests. The existing XML reports remain 106 tests with 0 failures/errors, and the existing ignored APK remained unchanged; `aapt` and SHA-256 inspection reconfirmed package `cloud.molberg.hermesmobile`, version `0.1.0-beta.1` (`10001`), and SHA-256 `45e4ab4783a74eb766070b0f670a2631a9ebdc044468bbe03087788f8106f150`. ADB 37.0.0 could not start its daemon, and `/dev/kvm` plus `/dev/bus/usb` were absent. This adds fresh focused reducer execution only; no fresh Gradle compilation/test task, APK assembly, device, authentication, release, or end-to-end evidence is claimed. diff --git a/PROJECT_PLAN.md b/PROJECT_PLAN.md index 77f90e1..68effc0 100644 --- a/PROJECT_PLAN.md +++ b/PROJECT_PLAN.md @@ -58,7 +58,9 @@ _Move finished items here with date, commit, and verification. Keep this section - Added direct password login through `/auth/password-login`, allowlisted session-cookie extraction, encrypted cookie/password storage, credential redaction, replacement/retention rules, and cookie-authenticated gateway probes without persisting raw credentials in UI state or connection forms. - Added Settings entry for editing the same profile, skip behavior for existing valid configurations, one-time legacy/current-profile setup-version migration, and accessible adaptive Compose semantics plus phone/tablet/light/dark previews. - Fixed a restart bypass: a profile saved by a failed first-run connection test remains in mandatory setup until setup completion is durably recorded; valid migrated and completed profiles still skip setup. - - Repaired current-key existing-user migration: a valid pre-wizard bearer profile stored under the then-current connection keys now receives the one-time completion migration, while untested no-auth profiles remain mandatory. + - Repaired the setup gate so only valid profiles with recorded legacy provenance receive the one-time completion migration; untested first-run None and Bearer profiles remain mandatory after restart. + - Follow-up regression repair on 2026-07-25: removed bearer-token-only self-migration, which could classify a failed/offline first-run Bearer profile as a historical profile. Added a focused restart regression covering that state. + - Verification for the follow-up repair: offline focused `SetupWizardReducerTest` plus `:app:assembleDebug` passed (43 tasks, 9 executed); `git diff --check` passed. - Verification for the migration repair: source-current offline focused `SetupWizardReducerTest` plus `:app:assembleDebug` passed (43 tasks, 9 executed); `git diff --check` passed. - Verification on 2026-07-24: source-current offline `:app:testDebugUnitTest :app:assembleDebug` passed (43 tasks, 4 executed; all unit tests passed), including the focused restart-regression test; `git diff --check` passed. No device, release, or real gateway credential test is claimed. - Task 5 source-current re-verification on 2026-07-24: `/root/.openclaw/workspace/scripts/hermes-mobile-preflight.sh` passed; the documented offline `:app:testDebugUnitTest :app:assembleDebug` command passed (43 tasks, 1 executed); the generated Gradle XML report records 106 tests with 0 failures/errors, including 29 `SetupWizardReducerTest` cases; and `git diff --check` passed. A localhost gateway health probe returned HTTP 200, establishing reachability only. The debug APK reports package `cloud.molberg.hermesmobile`, version `0.1.0-beta.1` (`10001`), SHA-256 `45e4ab4783a74eb766070b0f670a2631a9ebdc044468bbe03087788f8106f150`. No ADB device/emulator or `/dev/kvm` was available; no device installation, real authentication, release, or end-to-end gateway verification is claimed. diff --git a/apps/mobile/android/app/src/main/java/cloud/molberg/hermesmobile/onboarding/SetupWizardModels.kt b/apps/mobile/android/app/src/main/java/cloud/molberg/hermesmobile/onboarding/SetupWizardModels.kt index b170536..1904f55 100644 --- a/apps/mobile/android/app/src/main/java/cloud/molberg/hermesmobile/onboarding/SetupWizardModels.kt +++ b/apps/mobile/android/app/src/main/java/cloud/molberg/hermesmobile/onboarding/SetupWizardModels.kt @@ -260,7 +260,7 @@ object SetupWizardReducer { val existing = entry as? SetupEntry.ExistingConfiguration val migrateExistingUser = setupVersion < CURRENT_SETUP_VERSION && existing != null && - (hasLegacyConfiguration || existing.configuration.isPreWizardCurrentConfiguration()) + hasLegacyConfiguration return SetupGateResolution( entry = if (setupVersion >= CURRENT_SETUP_VERSION || migrateExistingUser) { entry @@ -513,9 +513,6 @@ private fun StoredGatewayConfiguration.isValid(): Boolean { } } -private fun StoredGatewayConfiguration.isPreWizardCurrentConfiguration(): Boolean = - authChoice == SetupAuthChoice.Bearer && hasBearerCredential - private fun ConnectionConfig.toStoredGatewayConfiguration(): StoredGatewayConfiguration = StoredGatewayConfiguration( localUrl = localUrl, diff --git a/apps/mobile/android/app/src/test/java/cloud/molberg/hermesmobile/onboarding/SetupWizardReducerTest.kt b/apps/mobile/android/app/src/test/java/cloud/molberg/hermesmobile/onboarding/SetupWizardReducerTest.kt index 006bf2b..ebd03e5 100644 --- a/apps/mobile/android/app/src/test/java/cloud/molberg/hermesmobile/onboarding/SetupWizardReducerTest.kt +++ b/apps/mobile/android/app/src/test/java/cloud/molberg/hermesmobile/onboarding/SetupWizardReducerTest.kt @@ -199,7 +199,7 @@ class SetupWizardReducerTest { } @Test - fun validPreWizardCurrentKeyConfigSkipsSetupAndRequestsVersionMigration() { + fun validPreWizardCurrentKeyConfigWithRecordedProvenanceRequestsMigration() { val resolution = SetupWizardReducer.resolveEntry( config = ConnectionConfig( localUrl = LOCAL, @@ -208,7 +208,7 @@ class SetupWizardReducerTest { hasBearerToken = true ), setupVersion = 0, - hasLegacyConfiguration = false + hasLegacyConfiguration = true ) assertTrue(resolution.entry is SetupEntry.ExistingConfiguration) @@ -245,6 +245,23 @@ class SetupWizardReducerTest { assertFalse(resolution.migrateExistingUser) } + @Test + fun failedFirstRunSavedBearerProfileCannotSkipMandatorySetupAfterRestart() { + val resolution = SetupWizardReducer.resolveEntry( + config = ConnectionConfig( + localUrl = LOCAL, + remoteUrl = REMOTE, + authMode = ConnectionAuthMode.BearerToken, + hasBearerToken = true + ), + setupVersion = 0, + hasLegacyConfiguration = false + ) + + assertTrue(resolution.entry is SetupEntry.Mandatory) + assertFalse(resolution.migrateExistingUser) + } + @Test fun completedExistingConfigSkipsWithoutRequestingMigration() { val config = ConnectionConfig( diff --git a/docs/BLOCKERS.md b/docs/BLOCKERS.md index ef88410..b7d752b 100644 --- a/docs/BLOCKERS.md +++ b/docs/BLOCKERS.md @@ -12,6 +12,8 @@ The `v0.1.0-beta.1` Android package metadata and direct-gateway build/install gu A later Task 5 rerun on 2026-07-24 was blocked before project execution: the guide's exact Gradle command could not determine a usable wildcard IP, and the offline/no-daemon variant could not open Gradle's daemon TCP socket (`java.net.SocketException: Operation not permitted`). Both attempts left the existing ignored APK unchanged. The source-current generated XML reports still record 106 tests with 0 failures/errors, including 29 `SetupWizardReducerTest` cases, while fresh `aapt` and SHA-256 inspection reconfirmed the package/version/hash above. ADB 37.0.0 was present at `/opt/android-sdk/platform-tools/adb` but could not create its smart-socket/netlink sockets; `/dev/kvm` and `/dev/bus/usb` were absent. This rerun provides no fresh Gradle test/assembly, device, authentication, release, or end-to-end evidence. +A follow-up P0 audit identified that a failed/offline first-run Bearer profile could be mistaken for a historical current-key profile and bypass setup after restart. On 2026-07-25, the gate was corrected to require recorded legacy provenance for migration and a focused Bearer restart regression was added. The offline focused `SetupWizardReducerTest` and `:app:assembleDebug` command then passed (43 tasks, 9 executed). This is source-level verification only; no fresh clean-install, device, authentication, release, or end-to-end evidence is claimed. + The first-run Setup Wizard is also source-complete and unit/build verified, including None/Bearer/password choices and direct password-session cookie handling. No real None/Bearer/password credential flow has been exercised against a live gateway, so R2 must include the chosen authentication mode and observed connection result without recording credentials. ADB is installed, but the R1 evidence above confirms that its daemon sockets are prohibited and that no emulator executable, AVD, `/dev/kvm`, USB device bus, or running target is available. Package metadata and the SHA-256 of the existing ignored source-current APK were inspected, but no fresh assembly, clean uninstall/install, launch, or direct-gateway device connection was verified here. No APK was published or uploaded, and no final release criterion is claimed. diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index eec7ce7..845ebf3 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -7,7 +7,7 @@ - Welcome/privacy guidance precedes configuration; Remote HTTPS is required and Local HTTPS is optional for the same logical gateway profile. - Authentication choices are None, Bearer token, or username/password. Password login exchanges credentials directly with the configured remote gateway and stores encrypted credentials/session state outside Compose UI models. - Test-and-finish uses the existing local-first route selection, requires a successful compatibility check before completion, and exposes the same profile for later Settings edits. -- Existing completed valid profiles skip mandatory setup; legacy/current valid profiles receive a one-time setup-version migration without forcing re-entry. A failed/offline first-run test cannot create a restart bypass. +- Existing completed valid profiles skip mandatory setup; valid profiles with recorded legacy provenance receive a one-time setup-version migration without forcing re-entry. A failed/offline first-run None or Bearer test cannot create a restart bypass. - Compose semantics, minimum touch targets, adaptive layout behavior, and phone/tablet/light/dark previews cover the wizard source contract. - Existing source-current Gradle output records 106 unit tests with 0 failures/errors and a debug APK assembled after the latest Android source change. No real device, release, or real gateway credential walkthrough has been performed; those remain part of R1/R2 evidence. - Task 5 source-current re-verification on 2026-07-24 passed the external recovery preflight and documented offline Gradle test/assembly command (43 tasks, 1 executed); the generated Gradle XML report records 106 passing tests with 0 failures/errors, including 29 `SetupWizardReducerTest` cases. A localhost gateway health probe returned HTTP 200, establishing reachability only. The debug APK is package `cloud.molberg.hermesmobile`, version `0.1.0-beta.1` (`10001`), SHA-256 `45e4ab4783a74eb766070b0f670a2631a9ebdc044468bbe03087788f8106f150`; `git diff --check` passed. No ADB device/emulator or `/dev/kvm` was available; no device installation, real authentication, release, or end-to-end gateway verification was performed.