fix(security): validate intent action before consuming notification session id (issue #832) (#845)

MainActivity is exported (launcher requirement) and consumed the
session-id extra from any incoming intent without checking the action —
a foreign app could launch it explicitly and force-navigate to a chat
screen. Guard consumeNotificationIntent() on the app's own notification
action and hoist the action string into a shared const so
ChatNotificationService and MainActivity can never drift.

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
This commit is contained in:
M57andM57 authored and GitHub committed 2026-08-07 22:42:39 +03:00
1 parent e854559de4
commit 3e9672f0ad
2 files changed
+16 -1

No files matched your search

@@ -18,6 +18,18 @@ import com.m57.hermescontrol.theme.HermesControlTheme
import com.m57.hermescontrol.util.LocaleContextWrapper
class MainActivity : ComponentActivity() {
companion object {
/**
* Action that identifies notification-driven "open this chat" intents.
* [ChatNotificationService] stamps it on the content intent; the
* consumer refuses anything else (issue #832 — MainActivity is
* exported as the launcher, so a foreign app could otherwise inject
* a session id via an explicit intent).
*/
const val ACTION_OPEN_CHAT_FROM_NOTIFICATION =
"com.m57.hermescontrol.ACTION_OPEN_CHAT_FROM_NOTIFICATION"
}
/**
* Apply the user-selected display language before any view is inflated.
* Reads the persisted code from [AuthManager]; an uninitialized store
@@ -62,6 +74,9 @@ class MainActivity : ComponentActivity() {
}
private fun consumeNotificationIntent(intent: Intent?) {
// Issue #832: MainActivity is exported (launcher requirement) — only
// honor intents stamped with our own notification action.
if (intent?.action != ACTION_OPEN_CHAT_FROM_NOTIFICATION) return
val sessionId = intent?.getStringExtra(NotificationReplyReceiver.EXTRA_SESSION_ID)
intent?.removeExtra(NotificationReplyReceiver.EXTRA_SESSION_ID)
sessionId?.takeIf { it.isNotBlank() }?.let(NavigationController::openChatSession)
@@ -185,7 +185,7 @@ class ChatNotificationService : Service() {
private fun buildContentIntent(sessionId: String?): PendingIntent {
val intent =
Intent(this, MainActivity::class.java).apply {
action = "$packageName.ACTION_OPEN_CHAT_FROM_NOTIFICATION"
action = MainActivity.ACTION_OPEN_CHAT_FROM_NOTIFICATION
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
}
if (!sessionId.isNullOrBlank()) {