Skip to content

Add app/device attestation for gated info-server requests - #6137

Open
paullinator wants to merge 1 commit into
developfrom
paul/appAttestationV2
Open

Add app/device attestation for gated info-server requests#6137
paullinator wants to merge 1 commit into
developfrom
paul/appAttestationV2

Conversation

@paullinator

@paullinator paullinator commented Aug 4, 2026

Copy link
Copy Markdown
Member

Summary

  • Add a background attestation engine (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.
  • Wire Simplex/Banxa jwtSign and createHmac call sites to attach x-attestation-token when available; surface gated signing failures in ramp UI instead of silently no-oping.
  • Document the client architecture in docs/APP_ATTESTATION.md. Depends on info-server lifetime/expiresIn/serverTime support (edge-info-server#158).

Test plan

  • npm test -- src/__tests__/util/attestation.test.ts src/__tests__/util/attestationNativeBridge.test.ts
  • tsc / lint on changed files; npm run fix-kotlin / ktlint on attestation Kotlin modules
  • Physical iOS device: boot → challenge → App Attest enroll → token cached; later refresh via generateAssertion
  • Physical Android (TEE/StrongBox): enroll via Keystore attestation; refresh via signChallenge
  • Point ENV.INFO_SERVER at a local info server from edge-info-server#158; confirm Banxa/Simplex signing sends x-attestation-token and fails loudly on 403
  • Simulator / unsupported device: engine stops cleanly; gated plugins omit the header

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

Done

Create PR

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.

Comment thread src/locales/en_US.ts Outdated
Comment thread src/util/attestation.ts
@paullinator
paullinator force-pushed the paul/appAttestationV2 branch from ae37d31 to 95985d3 Compare August 4, 2026 14:37
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.
@paullinator
paullinator force-pushed the paul/appAttestationV2 branch from 95985d3 to faaf688 Compare August 4, 2026 14:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant