From b6e6799755a95f9e2be33bbcb101da38d325f9aa Mon Sep 17 00:00:00 2001
From: "Ben.Li"
Date: Sat, 4 Jul 2026 03:38:58 +0800
Subject: [PATCH] fix(android): block self-package notification forwarding in
allowlist mode (#99568)
Always reject the OpenClaw app package in allowsPackage, matching the
blocklist fail-closed behavior and preventing gateway/node forwarding loops.
Co-authored-by: Cursor
---
.../app/NotificationForwardingPolicy.kt | 5 +++++
.../main/java/ai/openclaw/app/SecurePrefs.kt | 1 +
.../app/NotificationForwardingPolicyTest.kt | 19 +++++++++++++++++++
.../SecurePrefsNotificationForwardingTest.kt | 16 ++++++++++++++++
4 files changed, 41 insertions(+)
diff --git a/apps/android/app/src/main/java/ai/openclaw/app/NotificationForwardingPolicy.kt b/apps/android/app/src/main/java/ai/openclaw/app/NotificationForwardingPolicy.kt
index 21ff2e2a6f32..4081c0d7fc77 100644
--- a/apps/android/app/src/main/java/ai/openclaw/app/NotificationForwardingPolicy.kt
+++ b/apps/android/app/src/main/java/ai/openclaw/app/NotificationForwardingPolicy.kt
@@ -27,6 +27,7 @@ internal data class NotificationForwardingPolicy(
val quietEnd: String,
val maxEventsPerMinute: Int,
val sessionKey: String?,
+ val selfPackageName: String = "",
)
/** Applies the operator-configured package allow/block list after trimming input. */
@@ -35,6 +36,10 @@ internal fun NotificationForwardingPolicy.allowsPackage(packageName: String): Bo
if (normalized.isEmpty()) {
return false
}
+ val self = selfPackageName.trim()
+ if (self.isNotEmpty() && normalized == self) {
+ return false
+ }
return when (mode) {
NotificationPackageFilterMode.Allowlist -> packages.contains(normalized)
NotificationPackageFilterMode.Blocklist -> !packages.contains(normalized)
diff --git a/apps/android/app/src/main/java/ai/openclaw/app/SecurePrefs.kt b/apps/android/app/src/main/java/ai/openclaw/app/SecurePrefs.kt
index fa3b35c11a14..2157fca76b4d 100644
--- a/apps/android/app/src/main/java/ai/openclaw/app/SecurePrefs.kt
+++ b/apps/android/app/src/main/java/ai/openclaw/app/SecurePrefs.kt
@@ -313,6 +313,7 @@ class SecurePrefs(
quietEnd = quietEnd,
maxEventsPerMinute = maxEvents.coerceAtLeast(1),
sessionKey = sessionKey,
+ selfPackageName = normalizedAppPackage,
)
}
diff --git a/apps/android/app/src/test/java/ai/openclaw/app/NotificationForwardingPolicyTest.kt b/apps/android/app/src/test/java/ai/openclaw/app/NotificationForwardingPolicyTest.kt
index 0388064d7ea1..e36995b11550 100644
--- a/apps/android/app/src/test/java/ai/openclaw/app/NotificationForwardingPolicyTest.kt
+++ b/apps/android/app/src/test/java/ai/openclaw/app/NotificationForwardingPolicyTest.kt
@@ -77,6 +77,25 @@ class NotificationForwardingPolicyTest {
assertFalse(policy.allowsPackage("com.other.app"))
}
+ @Test
+ fun allowsPackage_neverForwardsSelfPackageEvenInAllowlist() {
+ val policy =
+ NotificationForwardingPolicy(
+ enabled = true,
+ mode = NotificationPackageFilterMode.Allowlist,
+ packages = setOf("ai.openclaw.app", "com.other.app"),
+ quietHoursEnabled = false,
+ quietStart = "22:00",
+ quietEnd = "07:00",
+ maxEventsPerMinute = 20,
+ sessionKey = null,
+ selfPackageName = "ai.openclaw.app",
+ )
+
+ assertFalse(policy.allowsPackage("ai.openclaw.app"))
+ assertTrue(policy.allowsPackage("com.other.app"))
+ }
+
@Test
fun isWithinQuietHours_handlesWindowCrossingMidnight() {
val policy =
diff --git a/apps/android/app/src/test/java/ai/openclaw/app/SecurePrefsNotificationForwardingTest.kt b/apps/android/app/src/test/java/ai/openclaw/app/SecurePrefsNotificationForwardingTest.kt
index 891d0171bc7f..7a47c33c6b83 100644
--- a/apps/android/app/src/test/java/ai/openclaw/app/SecurePrefsNotificationForwardingTest.kt
+++ b/apps/android/app/src/test/java/ai/openclaw/app/SecurePrefsNotificationForwardingTest.kt
@@ -128,4 +128,20 @@ class SecurePrefsNotificationForwardingTest {
assertFalse(policy.enabled)
assertEquals(NotificationPackageFilterMode.Blocklist, policy.mode)
}
+
+ @Test
+ fun getNotificationForwardingPolicy_blocksSelfPackageInAllowlistMode() {
+ val context = RuntimeEnvironment.getApplication()
+ val plainPrefs = context.getSharedPreferences("openclaw.node", Context.MODE_PRIVATE)
+ plainPrefs.edit().clear().commit()
+
+ val prefs = SecurePrefs(context)
+ prefs.setNotificationForwardingMode(NotificationPackageFilterMode.Allowlist)
+ prefs.setNotificationForwardingPackages(listOf("ai.openclaw.app", "com.other.app"))
+
+ val policy = prefs.getNotificationForwardingPolicy(appPackageName = "ai.openclaw.app")
+
+ assertFalse(policy.allowsPackage("ai.openclaw.app"))
+ assertTrue(policy.allowsPackage("com.other.app"))
+ }
}