fix(android): smooth bootstrap approval wait#59593
Conversation
Greptile SummaryThis PR smooths the Android bootstrap onboarding flow: the client retries on Confidence Score: 5/5
Prompt To Fix All With AIThis is a comment left during a code review.
Path: apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt
Line: 1253
Comment:
**Overly broad `"approve"` substring match**
`lower.contains("approve")` matches "approved", "unapproved", "self-approve", etc. A status message like "Device already approved, reconnecting…" from the gateway would cause this function to return `true` and briefly show the wrong "awaiting operator approval" state. The two other patterns are already specific enough to cover real pending-approval messages; `"approve"` alone adds risk of false positives.
```suggestion
return lower.contains("pairing required") || lower.contains("waiting for approval") || lower.contains("awaiting operator approval")
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: src/infra/pairing-files.ts
Line: 67-73
Comment:
**Surprising opt-out default for `shouldReuseReplacementRequestId`**
When `shouldReuseReplacementRequestId` is omitted, `params.shouldReuseReplacementRequestId?.({…})` returns `undefined`, and `undefined !== false` is `true` — so the default behaviour is to **reuse** the old request ID. Prior to this PR the default was to always generate a new ID. Future callers that don't pass the parameter will silently get ID reuse, which is the less safe/predictable default.
Consider flipping to an explicit opt-in:
```typescript
params.shouldReuseReplacementRequestId?.({
existing: params.existing,
incoming: params.incoming,
request,
}) === true
```
This way, omitting the callback preserves the previous "always generate a new ID" behaviour.
How can I resolve this? If you propose a fix, please make it concise.Reviews (2): Last reviewed commit: "fix(gateway): tighten bootstrap approval..." | Re-trigger Greptile |
🔒 Aisle Security AnalysisWe found 3 potential security issue(s) in this PR:
1. 🟠 Privilege escalation: device can request operator token via roles[] without operator approval check
Description
As a result:
Vulnerable code (authorization checks role != token issuance roles): const approvalRole = resolvePendingApprovalRole(pending);
if (approvalRole) {
const requestedOperatorScopes = normalizeDeviceAuthScopes(pending.scopes)
.filter((scope) => scope.startsWith(OPERATOR_SCOPE_PREFIX));
// ... checks use approvalRole ...
}
const requestedRoles = new Set(resolveRequestedRoles(pending));
for (const roleForToken of roles ?? []) {
if (!requestedRoles.has(roleForToken)) continue;
// token minted for roleForToken (including "operator")
}RecommendationEnsure approval authorization is enforced for every privileged role being approved (not just a single Example fix: const requestedRoles = new Set(resolveRequestedRoles(pending));
// Enforce operator approval whenever operator role is requested
if (requestedRoles.has("operator")) {
const requestedOperatorScopes = normalizeDeviceAuthScopes(pending.scopes)
.filter((s) => s.startsWith(OPERATOR_SCOPE_PREFIX));
if (!options?.callerScopes) {
return { status: "forbidden", missingScope: requestedOperatorScopes[0] ?? "callerScopes-required" };
}
const missingScope = resolveMissingRequestedScope({
role: "operator",
requestedScopes: requestedOperatorScopes,
allowedScopes: options.callerScopes,
});
if (missingScope) return { status: "forbidden", missingScope };
}
// (Optionally) also validate/whitelist roles in pairing requestsAlso consider rejecting pairing requests with unknown roles (whitelist to expected set, e.g. 2. 🟡 Pairing approval TOCTOU: pending request content can change while keeping same requestId
Description
Impact:
Vulnerable code: const replacementRequestId = params.existing[0]?.requestId;
...
const reconciledRequest =
replacementRequestId && request.requestId !== replacementRequestId
? { ...request, requestId: replacementRequestId }
: request;
params.pendingById[reconciledRequest.requestId] = reconciledRequest;RecommendationTreat Recommended fixes (pick one):
// when creating pending request
pending.approvalVersion = crypto.randomUUID();
// approval RPC requires both
approveDevicePairing({ requestId, approvalVersion })
// approve handler
const pending = state.pendingById[requestId];
if (!pending || pending.approvalVersion !== approvalVersion) return null;Also consider adding audit logging for changes to pending requests (before/after roles/scopes) to support detection/forensics. 3. 🟡 Unbounded 3s reconnect loop on PAIRING_REQUIRED can cause sustained gateway load (Android client)
DescriptionAndroid This creates an availability risk:
Vulnerable code: if (err is GatewayConnectFailure &&
err.gatewayError.details?.code == "PAIRING_REQUIRED" &&
options.retryOnPairingRequired) {
return 3_000L
}RecommendationAdd bounded/backoff retry behavior and/or a cap while approval is pending. Suggested safer approach:
Example (conceptual): private fun resolveReconnectDelayMs(err: Throwable, options: GatewayConnectOptions, attempt: Int): Long {
if (err is GatewayConnectFailure && err.gatewayError.details?.code == "PAIRING_REQUIRED" && options.retryOnPairingRequired) {
val base = minOf(60_000L, (3_000L * (1 shl minOf(attempt, 5)))) // cap at 60s
val jitter = (base * 0.2).toLong()
return base + Random.nextLong(-jitter, jitter)
}
return minOf(8_000L, (350.0 * Math.pow(1.7, attempt.toDouble())).toLong())
}Also consider making Analyzed PR: #59593 at commit Last updated on: 2026-04-02T13:29:19Z |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9c9d1009e8
ℹ️ 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".
|
Addressed review follow-up in
Targeted Android unit tests re-run and passed. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 22e620758f
ℹ️ 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".
|
Fixed the stale approval race. Root cause: repeated Android operator reconnects could reconcile the pending pairing request into a replacement request id before |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 426edc61f0
ℹ️ 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".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ddd4689ebb
ℹ️ 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".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6915a670c0
ℹ️ 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".
|
@greptile-apps review. |
|
Addressed the stale-approval-id issue in What changed:
Why this still keeps onboarding at one approval:
Verification:
|
|
Superseded by #60221. |
Summary
Verification