Skip to content

fix(android): auto-resume pairing approval#63199

Merged
obviyus merged 6 commits intomainfrom
codex/android-pairing-auto-resume
Apr 8, 2026
Merged

fix(android): auto-resume pairing approval#63199
obviyus merged 6 commits intomainfrom
codex/android-pairing-auto-resume

Conversation

@obviyus
Copy link
Copy Markdown
Contributor

@obviyus obviyus commented Apr 8, 2026

Summary

  • retry Android gateway pairing automatically while the connect and onboarding screens stay open
  • update pairing UI copy to reflect automatic resume instead of requiring another tap
  • keep the change scoped to the Android pairing UI surfaces

Testing

  • ./gradlew testPlayDebugUnitTest --tests 'ai.openclaw.app.GatewayBootstrapAuthTest' --tests 'ai.openclaw.app.gateway.GatewaySessionInvokeTest'
  • ./gradlew testThirdPartyDebugUnitTest --tests 'ai.openclaw.app.GatewayBootstrapAuthTest' --tests 'ai.openclaw.app.gateway.GatewaySessionInvokeTest'
  • installed thirdPartyDebug over ADB and verified QR pairing now resumes automatically after approval

@aisle-research-bot
Copy link
Copy Markdown

aisle-research-bot Bot commented Apr 8, 2026

🔒 Aisle Security Analysis

We found 2 potential security issue(s) in this PR:

# Severity Title
1 🟠 High Operator session can authenticate using gateway bootstrap token when no operator device token exists
2 🟡 Medium Unbounded auto-retry loop triggered by untrusted status text causes repeated reconnect/auth attempts (client-side DoS)
1. 🟠 Operator session can authenticate using gateway bootstrap token when no operator device token exists
Property Value
Severity High
CWE CWE-269
Location apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt:1341-1348

Description

resolveOperatorSessionConnectAuth now allows the operator gateway session to connect using an explicit bootstrapToken whenever no stored operator device token exists.

This is risky because bootstrap/setup tokens are typically intended only for initial provisioning / seeding a device token, and may be:

  • embedded in a setup QR / setup code
  • shared more broadly during onboarding
  • valid for longer than desired if not rotated

With this change:

  • Input: auth.bootstrapToken (coming from setup code / onboarding config)
  • Decision: if there is no stored operator token, the function returns a connect auth that includes bootstrapToken
  • Sink: the operator GatewaySession will send auth.bootstrapToken in the connect request for role="operator" (see GatewaySession.buildConnectParams which serializes bootstrapToken into the auth object).

If the gateway server accepts bootstrap tokens for role=operator with operator scopes, a leaked bootstrap token could enable operator-level access (approvals/read/write and potentially secret-related scope) without the normal device-token approval/pairing path.

Vulnerable code (new behavior):

val explicitBootstrapToken = auth.bootstrapToken?.trim()?.takeIf { it.isNotEmpty() }
if (explicitBootstrapToken != null) {
  return NodeRuntime.GatewayConnectAuth(
    token = null,
    bootstrapToken = explicitBootstrapToken,
    password = null,
  )
}

Recommendation

Do not allow bootstrap/setup tokens to be used to authenticate an operator session unless they are explicitly designed/scoped for operator access.

Client-side hardening:

  • Remove the operator bootstrap fallback and require either:
    • an explicit shared operator token/password, or
    • a stored operator device token (after approval/pairing)

For example:

// Only allow explicit token/password for operator connect.
val storedToken = storedOperatorToken?.trim()?.takeIf { it.isNotEmpty() }
if (storedToken != null) {
  return NodeRuntime.GatewayConnectAuth(token = null, bootstrapToken = null, password = null)
}

​// Do NOT fall back to bootstrap for operator.
return null

Server-side defense-in-depth (recommended even if client is fixed):

  • Reject bootstrap tokens for role="operator" or restrict them to minimal onboarding scopes.
  • Ensure bootstrap tokens are short-lived, single-use, and rotated after pairing.
2. 🟡 Unbounded auto-retry loop triggered by untrusted status text causes repeated reconnect/auth attempts (client-side DoS)
Property Value
Severity Medium
CWE CWE-400
Location apps/android/app/src/main/java/ai/openclaw/app/ui/GatewayPairingRetry.kt:30-43

Description

PairingAutoRetryEffect starts an infinite loop that calls refreshGatewayConnection() every 6 seconds while the screen is STARTED and enabled.

The enabled condition is derived from gatewayStatusLooksLikePairing(statusText), which performs broad substring matching ("pair" / "approve") over statusText.

Security impact:

  • Untrusted trigger: statusText is ultimately driven by gateway/network connection state/messages. A malicious gateway or on-path attacker that can influence displayed error/status text can keep it containing pair/approve, forcing the app to continuously reconnect.
  • Unbounded retries: The loop has no maximum attempts and no backoff, generating persistent foreground network traffic and battery drain.
  • Repeated auth/reconnect attempts: refreshGatewayConnection() calls connectWithAuth(..., reconnect = true) which reconnects both sessions using stored auth material, potentially causing account/device rate-limit lockouts on the gateway.

Vulnerable code:

internal fun gatewayStatusLooksLikePairing(statusText: String): Boolean {
  val lower = gatewayStatusForDisplay(statusText).lowercase()
  return lower.contains("pair") || lower.contains("approve")
}

LaunchedEffect(enabled, lifecycleStarted) {
  if (!enabled || !lifecycleStarted) {
    return@​LaunchedEffect
  }
  while (true) {
    delay(PAIRING_AUTO_RETRY_MS)
    onRetry()
  }
}

Recommendation

Avoid driving retry behavior from free-form/untrusted statusText, and bound retries.

Suggested fixes (combine as appropriate):

  1. Use a structured error/state from the connection/auth layer (e.g., an enum like GatewayAuthState.PairingRequired) rather than substring matching.

  2. Cap retries + exponential backoff + jitter and/or require explicit user action after some attempts.

Example (bounded + backoff):

LaunchedEffect(enabled, lifecycleStarted) {
  if (!enabled || !lifecycleStarted) return@​LaunchedEffect

  var attempt = 0
  while (enabled && lifecycleStarted && attempt < 10) {
    val backoffMs = (6_000L shl attempt.coerceAtMost(4)) // up to 96s
    delay(backoffMs)
    onRetry()
    attempt++
  }
}
  1. Consider adding gateway-side rate limiting handling and surfacing a UI message when retries are paused.

Analyzed PR: #63199 at commit 184debd

Last updated on: 2026-04-08T16:28:40Z

@openclaw-barnacle openclaw-barnacle Bot added app: android App: android size: XS maintainer Maintainer-authored PR labels Apr 8, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 8, 2026

Greptile Summary

This PR adds automatic pairing approval retry to the Android app: a LaunchedEffect in both ConnectTabScreen and OnboardingFlow's FinalStep polls viewModel.refreshGatewayConnection() every 6 seconds while the pairing state is active, clearing reconnectPausedForAuthFailure on each attempt. UI copy is updated to reflect the auto-retry behavior, and SecurePrefs.clearGatewaySetupAuth() is added to consolidate auth-clearing logic previously scattered across callsites.

Confidence Score: 5/5

Safe to merge — auto-retry logic is correctly scoped and all findings are P2 or lower.

No P0 or P1 issues found. The LaunchedEffect keying is correct, cancellation via delay() is handled idiomatically, reconnectPausedForAuthFailure is properly cleared on retry, and the clearGatewaySetupAuth() implementation is consistent with loadGatewayToken()'s lazy-load pattern. The only prior concern (duplicate constant) was already addressed in an earlier review thread.

No files require special attention.

Vulnerabilities

No security concerns identified. The auto-retry path reuses the same stored credentials as a manual reconnect; no new credential exposure or auth bypasses are introduced.

Reviews (2): Last reviewed commit: "fix(android): reset auth on new setup co..." | Re-trigger Greptile

Comment thread apps/android/app/src/main/java/ai/openclaw/app/ui/ConnectTabScreen.kt Outdated
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a28b8e660c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/android/app/src/main/java/ai/openclaw/app/ui/ConnectTabScreen.kt Outdated
@obviyus
Copy link
Copy Markdown
Contributor Author

obviyus commented Apr 8, 2026

@greptile-apps re-review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 936c6ac5e8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt
Comment thread apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt Outdated
@obviyus obviyus self-assigned this Apr 8, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 767ff879da

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@obviyus obviyus force-pushed the codex/android-pairing-auto-resume branch from 24e23b9 to 184debd Compare April 8, 2026 16:26
@obviyus obviyus merged commit 21d0f7c into main Apr 8, 2026
8 checks passed
@obviyus obviyus deleted the codex/android-pairing-auto-resume branch April 8, 2026 16:29
@obviyus
Copy link
Copy Markdown
Contributor Author

obviyus commented Apr 8, 2026

Landed on main.

ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app: android App: android maintainer Maintainer-authored PR size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant