feat(android): add profile management for multi-squad support#158
feat(android): add profile management for multi-squad support#158yuke-x68 wants to merge 2 commits into
Conversation
- Add Profile data class storing SSH, session and dashboard config per profile - Centralize ProfileViewModel state mutations through a single setter so activeProfile updates always sync SharedPreferences in one place - Move profile switcher from TopAppBar dropdown to BottomNavigation - Add profile list UI in Settings (create / duplicate / delete) - Add per-profile dashboard filename (defaults to dashboard.md) - Block deletion of the currently active profile with a Toast warning - Add SwipeRefresh on Dashboard, gated by WebView scroll position - Use tmux window index 0 instead of hardcoded "main" for the shogun session target to support a wider range of tmux layouts - Bump versionCode to 3 and versionName to 4.2 Allows monitoring multiple multi-agent setups (e.g. dev/prod or separate squads) from a single companion app instance.
yohey-w
left a comment
There was a problem hiding this comment.
Good addition, @yuke-x68. The change is well scoped — all modifications stay inside android/, the core shogun system is untouched.
What's covered:
Profiledata class +ProfileRepositoryfor persistence (Room/DataStore pattern)ProfileViewModeldrives the profile picker UIShogunScreen/AgentsScreen/DashboardScreen/SettingsScreenall wired to the active profile's SSH credentialsConstants.ktupdated for multi-profile supportMainActivity.ktbootstraps the profile context
The use case is real — users running dev/prod environments or separate squads genuinely need profile switching without reinstalling.
Can't build-test Android from here, but the architecture (ViewModel + Repository + profile-scoped SSH credentials) looks sound. Merging is safe from the core-system perspective.
yohey-w
left a comment
There was a problem hiding this comment.
Thanks for the contribution, @yuke-x68 — the profile management feature is well-structured and the motivation is clear. One security issue needs to be addressed before merge.
Required change: sshPassword plain-text storage
Profile.kt includes:
val sshPassword: String = ""This gets serialized to JSON and saved in SharedPreferences, which is unencrypted app-local storage. On rooted devices or via ADB backup, this is readable without special privileges.
Please replace SharedPreferences with EncryptedSharedPreferences (Jetpack Security) for the profile store:
// build.gradle.kts — add dependency
implementation("androidx.security:security-crypto:1.1.0-alpha06")
// ProfileRepository.kt — use EncryptedSharedPreferences
val prefs = EncryptedSharedPreferences.create(
context,
"shogun_profiles",
MasterKey.Builder(context).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build(),
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)This encrypts the entire profile store (not just the password field), which is the right scope since SSH host/user/key paths are also sensitive.
Alternatively, if SSH key auth is the primary use case and password auth is rarely needed, removing sshPassword from the data class entirely and asking users to rely on key-based auth would also be acceptable.
Everything else looks good — the BottomNav profile switcher, create/duplicate/delete flow, active-profile deletion guard, and the tmux window index fix are all solid. Happy to re-approve once the storage concern is addressed.
Address the plaintext credential storage flagged in PR review by migrating the entire PREFS_NAME store to EncryptedSharedPreferences. This covers both storage paths that held the SSH password in plaintext: - PROFILES_JSON via ProfileRepository (the original review finding) - the flat SSH_PASSWORD key written by ProfileViewModel.syncToPrefs and read back by AgentsScreen/ShogunScreen (an additional path found during investigation) A single central EncryptedPrefsProvider (AES256_GCM / AES256_SIV) backs all consumers, so encrypting once seals both paths. The sshPassword field is retained: SshManager uses it as the passphrase for passphrase-protected SSH keys (SshManager.kt L80-81), so removing it would break key-based auth for those users. PreferencesMigration transparently moves legacy plaintext data into the encrypted store on first launch and securely erases the old XML (commit() ordering hardened against OOM-kill data loss). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Thank you for the security reviewThank you for the thorough security review and for identifying the plaintext storage vulnerability in our SharedPreferences implementation. We take security concerns seriously and appreciate your diligent oversight. Analysis and Additional FindingsUpon conducting a deeper investigation into the identified issue, we discovered an additional vulnerability beyond the original concern: Original Issue (Route A)
Additional Finding (Route B - Parallel Storage)
This dual-path design was not immediately obvious from a static review of ProfileRepository alone, which explains why it appeared as a single-vector issue initially. Implemented Solution: Unified Encrypted SharedPreferences MigrationTo comprehensively address both Route A and Route B, we have implemented the following solution: 1. Unified Encryption StrategyThe entire PREFS_NAME store has been migrated to EncryptedSharedPreferences
2. Preservation of sshPassword FieldAs part of the encryption migration, we have retained the sshPassword field within the Profile object. Here's why: Technical Reason: The User Impact: Users who have configured passphrase-protected SSH keys depend on this field for successful SSH connections. Removing this field would break the following user workflow:
Forward Compatibility: By retaining the field but encrypting its storage, we maintain backward compatibility while securing the data. 3. User Migration StrategyFor existing users with plaintext data in SharedPreferences:
What Was ImplementedThe implementation includes:
Verification PerformedWe have validated the implementation through:
Follow-up: Automated TestingWe are prepared to add comprehensive automated testing in a follow-up iteration:
ConclusionThis comprehensive solution addresses both the original concern and the additional vulnerability we identified. By unifying all sensitive data under EncryptedSharedPreferences while respecting legitimate use cases for the sshPassword field, we achieve a robust and maintainable security posture. We welcome your feedback on this implementation. Status: The changes are now ready for your re-review. Please let us know if you have any questions or if additional validation is needed. |
Summary
Add profile management so a single Shogun companion app can monitor and
control multiple multi-agent setups (e.g. dev / prod, separate squads)
without reinstalling or re-entering connection details.
Motivation
The app currently stores a single SSH + session configuration. When the
same user runs multiple multi-agent setups in parallel (different hosts,
different tmux session names, different dashboard files), they have to
edit settings each time they switch context. This PR introduces named
profiles that bundle every per-setup setting and lets the user switch
between them from the bottom navigation bar.
Changes
data/Profile.kt— Profile data class holding SSH, session anddashboard config per profile
data/ProfileRepository.kt— SharedPreferences-backed persistenceviewmodel/ProfileViewModel.kt— single-setter pattern soactiveProfile updates always sync SharedPreferences in one place
viewmodel/DashboardViewModelFactory.kt— wires the repository intothe dashboard view model
MainActivity.kt— move profile switcher from TopAppBar toBottomNavigation;
key(activeProfile?.id)forces clean re-init onprofile change
ui/SettingsScreen.kt— profile list with create / duplicate /delete; active-profile deletion is blocked with a Toast
ui/DashboardScreen.kt— SwipeRefresh gated by WebView scrollposition (avoids the WebView-vs-Compose nested-scroll conflict)
ui/ShogunScreen.kt,ui/AgentsScreen.kt— re-connect when theactive profile changes
util/Constants.kt— adddashboard.mddefault file nameviewmodel/ShogunViewModel.kt— use tmux window index0insteadof the hard-coded
main, so the app works with a wider range oftmux layouts
app/build.gradle.kts— bump versionCode to 3, versionName to 4.2Testing
bats unit / integration tests do not cover Android code)
make checkpasses (generated instructions in sync)profile create / duplicate / delete / switch flows, verified
Shogun / Agents / Dashboard tabs reflect the active profile,
confirmed pull-to-refresh on Dashboard
Note:
make lint(shellcheck) andmake test(bats) were not runlocally because this PR touches only Kotlin sources; CI will run them.
Screenshots
(Lord, please attach screenshots of the new BottomNav profile chip and
the Settings profile list if desired.)
Related Issues
None.
備考
私の手元にある、某白い悪魔のいるSF世界観に魔改造されたmulti-agent-shogunは複数部隊運用を可能にしているので、アプリから全部隊へアクセスできるようにするためにアプリも魔改造しました。
Xを検索した限り、複数軍運用できるように魔改造されている人は他にもいるようなので、この機能には需要があるかなと思っています。
なお、アクセス先IPとポート単位でプロファイルを作れるようにしているので、複数PCや仮想マシンにmulti-agent-shogunを導入している方であれば、複数軍運用が可能です。
私の手元には正規Androidビルドに必要な証明書がないため、承認いただいた暁には、お手数ですがreleaseビルドをお願い致します。
初めてのコントリビュートで緊張していますが、なにとぞよろしくお願いします。
なお、この備考以外の文言は艦長(=将軍ポジのエージェント)が書きました。いい時代ですね、戦国と宇宙世紀ですけど。