Add app/device attestation for gated info-server requests - #6137
Add app/device attestation for gated info-server requests#6137paullinator wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.
Done
Or push these changes by commenting:
@cursor push b8caa6fd86
Preview (b8caa6fd86)
diff --git a/src/locales/en_US.ts b/src/locales/en_US.ts
--- a/src/locales/en_US.ts
+++ b/src/locales/en_US.ts
@@ -694,7 +694,7 @@
string_ok_cap: 'OK',
clock_skew_title: 'Device clock is wrong',
clock_skew_message:
- "Your device time differs from Edge's servers by more than two minutes. Turn on automatic date & time in your system settings so one-time codes and other time-based features keep working.",
+ 'Your device time differs from our servers by more than two minutes. Turn on automatic date & time in your system settings so one-time codes and other time-based features keep working.',
string_forget: 'Forget',
string_delete: 'Delete',
string_keep: 'Keep',
diff --git a/src/locales/strings/enUS.json b/src/locales/strings/enUS.json
--- a/src/locales/strings/enUS.json
+++ b/src/locales/strings/enUS.json
@@ -523,7 +523,7 @@
"string_cancel": "CANCEL",
"string_ok_cap": "OK",
"clock_skew_title": "Device clock is wrong",
- "clock_skew_message": "Your device time differs from Edge's servers by more than two minutes. Turn on automatic date & time in your system settings so one-time codes and other time-based features keep working.",
+ "clock_skew_message": "Your device time differs from our servers by more than two minutes. Turn on automatic date & time in your system settings so one-time codes and other time-based features keep working.",
"string_forget": "Forget",
"string_delete": "Delete",
"string_keep": "Keep",
diff --git a/src/util/attestation.ts b/src/util/attestation.ts
--- a/src/util/attestation.ts
+++ b/src/util/attestation.ts
@@ -1,6 +1,7 @@
import { NativeModules, Platform } from 'react-native'
import { showButtonsModal } from '../components/modals/ButtonsModal'
+import { ENV } from '../env'
import { lstrings } from '../locales/strings'
import { monotonicNow } from './monotonicTime'
import { fetchInfo } from './network'
@@ -318,7 +319,9 @@
throw error
}
// noKey / invalidKey / native signing failure: fall back to full attestation.
- console.log('[attestation] assertion unavailable:', String(error))
+ if (ENV.DEBUG_VERBOSE_LOGGING) {
+ console.log('[attestation] assertion unavailable:', String(error))
+ }
return undefined
}
@@ -589,7 +592,9 @@
lastFailureAt = undefined
consecutiveFailures = 0
cachedToken = freshToken
- console.log('[attestation] handshake ok')
+ if (ENV.DEBUG_VERBOSE_LOGGING) {
+ console.log('[attestation] handshake ok')
+ }
scheduleRefresh(freshToken.expiresMono)
})
.catch((error: unknown) => {You can send follow-ups to the cloud agent here.
Reviewed by Cursor Bugbot for commit 52196d0. Configure here.
ae37d31 to
95985d3
Compare
Run a background attestation engine that handshakes with Apple App Attest or Android Keystore, caches a short-lived JWT on a monotonic clock, and refreshes ahead of expiry. Gated Simplex and Banxa signing calls attach x-attestation-token when a token is available; otherwise the info server decides. Includes native modules, Keychain/Keystore key lifecycle, ramp error surfacing, and the Simplex supported-countries response check.
95985d3 to
faaf688
Compare
|
Review note from an automated pass (sonnet-5 subagents + adversarial verification) run per the Asana task. Two info-server findings are fixed on #158; this one is in your native concurrency design, so I am raising it rather than changing it.
Sequence:
The premise in step 2 is the part worth a second look: after a timeout (as opposed to an explicit retryable error) the key may in fact have been consumed. The outcome is bounded and self-healing the way the adjacent races in that file are (B fails, the pending slot clears, the next handshake generates a fresh key and enrolls), so this costs one wasted attestation and one extra round trip, not a stuck device. Both obvious remedies have real costs you already reasoned about in that file, which is why I did not pick one:
No action needed if you consider it in the same bounded-and-self-healing class as the |
fixup! Add app/device attestation for gated info-server requests Document that an operationTimeout may leave a pending key that Apple has already consumed, so the next handshake can double-attest and self-heal. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Agreed — the timeout-path premise was slightly wrong. After Pushed a comment-only fixup clarifying that. Not changing the policy — clearing pending on timeout burns keys; enrolling from the late callback doesn’t help because the server never saw that attestation. |


Summary
src/util/attestation.ts) plus iOS App Attest and Android Keystore native modules that enroll once, refresh via assertion/signature, and cache JWTs on a monotonic clock with lead-ahead refresh and failure backoff.x-attestation-tokenwhen available; surface gated signing failures in ramp UI instead of silently no-oping.docs/APP_ATTESTATION.md. Depends on info-server lifetime/expiresIn/serverTimesupport (edge-info-server#158).Test plan
npm test -- src/__tests__/util/attestation.test.ts src/__tests__/util/attestationNativeBridge.test.tstsc/ lint on changed files;npm run fix-kotlin/ ktlint on attestation Kotlin modulesgenerateAssertionsignChallengeENV.INFO_SERVERat a local info server from edge-info-server#158; confirm Banxa/Simplex signing sendsx-attestation-tokenand fails loudly on 403Note
Cursor Bugbot is generating a summary for commit e88bd5c. Configure here.