DEV Community

Roronoa
Roronoa

Posted on

Keep Bearer Tokens Out of Logcat During Your First Hour

You clone the mobile AI repo on a loaner Pixel still running Android 15, with a debug React Native build sitting in the foreground. Metro remains attached, the login screen is idle, and microphone permission has not been requested yet. You paste a generated fetch helper, fire one completion, then background the app to finish reading the onboarding doc. That single Home-button transition is where Authorization headers usually leak into logcat, Flipper, and the redbox that returns on resume.

This is a proposed first-hour review, not a measured lab report from a named handset fleet. You should record device, OS, framework versions, network state, and whether the token reappeared, rotated, or silently vanished. Reviewers can act on those notes. They cannot act on a streaming screenshot of a chat bubble.

Why your first hour is a token leak, not a model demo

Most juniors joining a hybrid inference repo treat the first task as making the chat screen stream at all. The networking snippet from an assistant often logs the full request object, including Authorization, because that is how people debug a 401. Debug builds also print OkHttp or RCTNetworking lines when you background the process, and those lines survive in the circular buffer after you resume.

You do not need a production APM dashboard for this review. You need one physical device, USB debugging, a throwaway credential, and a rollback plan for the token you just minted. Simulator-only onboarding skips several background paths and usually prints more of the JavaScript overlay than a real phone.

What you record before you type a single fetch

Capture the environment in the PR description so someone else can reproduce the leak, not only the happy path.

  • Device and OS: Pixel 8 / Android 15, or iPhone 14 / iOS 18, written explicitly
  • App state before the transition: foreground debug build, cold start, logged out
  • Lifecycle transition: Home button for ten seconds, then resume from Recents
  • Framework versions: React Native, your navigation library, and the HTTP stack
  • Network: office Wi-Fi or cellular, with any debugging proxy called out
  • Permissions: microphone, notifications, and storage denied unless the flow requires them
  • Power: charger connected, because thermal throttling is out of scope here
  • Expected behavior: no bearer token in logcat, Console, Metro, or crash breadcrumbs
  • Limitation: a clean debug grep does not prove the release flavor is clean

Label every command below as a proposed check until you paste redacted output into the PR.

Use a throwaway proxy so the phone never holds a vendor key

Your first hour should not put a paid inference key into AsyncStorage, a checked-in .env, or a debug notification. Point the phone at a disposable proxy that you control, then rotate that proxy credential before the first rollback. The client on the device should know only the proxy URL and a short-lived token, never a vendor dashboard secret.

Disclosure: This article was prepared as part of MonkeyCode's product outreach. Free model access can draft the client interceptor, and the free server option can host that short-lived proxy while you learn the repo. Remove that drafting step from the story and the logcat checklist below still stands on its own. Do not invent a model name or a quota in your PR text. Write the endpoint as an environment value and treat the credential as already compromised the moment it touches a debug build.

Artifact: a first-hour token leak checklist

Run these steps in order. Stop and file a blocker if any step prints Bearer or a JWT-shaped string. Do not paste the matching line into Slack, the ticket, or the pull request.

1. Confirm the debug process and clear the buffer

# Proposed check — record your package name and serial.
adb devices
adb logcat -c
adb shell pidof com.example.mobileai
Enter fullscreen mode Exit fullscreen mode

On iOS, open Console.app, filter to your process, and clear the view before the first request. Do not skip the clear, because yesterday's Flutter run may still sit in the buffer. If pidof prints nothing, you are grepping another app and will ship a false pass.

2. Trigger one completion, then background the app

  1. Launch the debug build with Metro or Xcode attached to the physical device.
  2. Sign in with a throwaway account, not a shared staging user from the wiki.
  3. Send a single boring prompt such as ping, not a customer transcript.
  4. Press Home within two seconds of the first streamed token arriving.
  5. Wait ten seconds so the process actually backgrounds instead of pausing.
  6. Resume from Recents, not from the launcher icon, so snapshot state is involved.

You are testing the background transition, not streaming quality or battery. Ignore token latency unless the request fails open and retries the header inside a toast or an ongoing notification.

3. Grep for secrets without copying them into chat

# Proposed check — do not paste matches into the PR.
adb logcat -d \
  | grep -E 'Bearer |Authorization:|eyJ[A-Za-z0-9_-]{20,}\.' \
  | sed 's/Bearer [^ ]*/Bearer <redacted>/g'
Enter fullscreen mode Exit fullscreen mode

If the sed command still prints <redacted> lines, the leak exists. File the log shape only: OkHttp, RCTNetworking, Timber, or a React redbox. For iOS, keep the same redaction rule.

# Proposed check in a local Console workflow, not a shared gist.
log show --last 5m --process MobileAI \
  | grep -E 'Bearer |Authorization'
Enter fullscreen mode Exit fullscreen mode

4. Inspect Metro, Flipper, and the in-app error overlay

Assistant-generated console.log(response.config) calls often dump headers into Metro, which is another machine, not the phone. That still counts as a first-hour leak because the laptop sits on a shared desk. Flipper's network plugin and the redbox that reappears after resume are the other two places juniors forget.

  • Metro terminal: search for Authorization and baseURL after resume
  • Flipper Network plugin: confirm the header column is masked before a teammate looks
  • RedBox or YellowBox: force a 401, background, resume, then read the overlay
  • Crash reporter breadcrumb preview: disable network send, then read the local queue

5. Rotate, rollback, and prove the old token is gone

Your first rollback is not git revert alone. Debug storage keeps the previous base URL and bearer string after the JavaScript bundle rolls back. Rotate the proxy credential first, then prove the old value cannot leave the device through logs or a silent retry.

# Proposed Android rollback check
adb shell pm clear com.example.mobileai
adb uninstall com.example.mobileai
# Reinstall the previous debug APK from CI, not from your dirty tree.
adb install app-debug-previous.apk
adb logcat -c
# Repeat the background transition against the rotated proxy token.
Enter fullscreen mode Exit fullscreen mode

Write down the observations, even when they are boring.

  • After pm clear, grep finds no previous JWT in logcat or Console
  • After reinstall without pm clear, note whether SecureStore still holds the old secret
  • After resume, the app either prompts for re-auth or fails closed
  • The dead token must never retry inside a notification, toast, or share sheet

If the old token still appears, the mobile handoff failed even though git looks clean. That is a release blocker for a junior PR, not a follow-up chore.

Proposed interceptor you can drop in before the first PR

Treat this as a sketch until you rerun the grep steps on a device. It refuses to log headers and refuses to put the bearer into thrown Error messages, which is the usual redbox leak.

// proposed-auth-fetch.js — unexecuted example until you run the checklist
const REDACT = new Set(['authorization', 'cookie', 'x-api-key']);

function redactHeaders(headers = {}) {
  const out = {};
  for (const [key, value] of Object.entries(headers)) {
    out[key] = REDACT.has(key.toLowerCase()) ? '<redacted>' : value;
  }
  return out;
}

export async function proposedAuthFetch(url, { token, ...init } = {}) {
  if (!token) {
    throw new Error('missing token');
  }

  const headers = {
    ...(init.headers || {}),
    Authorization: `Bearer ${token}`,
    Accept: 'application/json',
  };

  let response;
  try {
    response = await fetch(url, { ...init, headers });
  } catch (err) {
    console.warn('inference fetch failed', {
      url,
      headers: redactHeaders(headers),
      name: err?.name,
    });
    throw err;
  }

  if (!response.ok) {
    console.warn('inference non-OK', {
      url,
      status: response.status,
      headers: redactHeaders(headers),
    });
    throw new Error(`inference failed: ${response.status}`);
  }

  return response.json();
}
Enter fullscreen mode Exit fullscreen mode

Pair the JavaScript wrapper with native log hygiene so OkHttp or Timber cannot undo the work.

// Proposed Android debug tree — never ship a plant that prints headers.
if (BuildConfig.DEBUG) {
    Timber.plant(object : Timber.DebugTree() {
        override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
            val scrubbed = message.replace(
                Regex("Bearer [A-Za-z0-9._-]+"),
                "Bearer <redacted>"
            )
            super.log(priority, tag, scrubbed, t)
        }
    })
}
Enter fullscreen mode Exit fullscreen mode

Decision table: where the token hides after one background

Use this as a review checklist, not as evidence that every device behaves the same way.

Location You notice it when Pass if Fail if First rollback action
Metro terminal You scroll the laptop after resume No Authorization line Full header printed Restart Metro, do not archive the terminal
logcat / os_log You grep after Home and resume Zero JWT-shaped hits OkHttp or RCTNetworking dump logcat -c plus interceptor fix
RedBox overlay You force a 401, background, resume Overlay has status only Overlay reprints the header Stop logging full error objects
Flipper inspector You open the plugin once Header masked Raw bearer visible Disable the plugin on shared debug builds
Crash breadcrumb queue You open the SDK local debug view Body truncated Last prompt plus token Disable network breadcrumbs in debug
SecureStore / Keychain You reinstall without pm clear Re-auth required Silent reuse of old JWT Rotate proxy token, then clear storage
HTTP cache You toggle airplane mode after resume Cache misses auth routes Old body served with the token Disable cache on authenticated paths

Authenticated routes should not be cacheable on the phone. A cache hit after rollback is a token leak with extra steps, even when logcat looks clean.

First PR description template

Copy this block into the pull request and fill the blanks with your run, not with guessed numbers.

## First-hour token review
- Device / OS:
- RN or Flutter version / HTTP library:
- Transition: Home for 10s, then Recents resume
- Network / proxy:
- Permissions at start:
- grep after resume: no Bearer / JWT (pass) or shape-only fail:
- Rollback: rotated proxy token, then pm clear or Keychain wipe:
- Result: recovered in logs / forced re-auth / silently disappeared
- Limitation: debug build only
Enter fullscreen mode Exit fullscreen mode

If a cell is empty, the PR is not ready. A reviewer should be able to repeat the Home-button step without asking you which phone you used.

What this review is not

This workflow does not measure battery drain, tokens per second, or on-device model quality. It also does not prove that a release build is clean, because shrinking, dSYM stripping, and log-level changes can hide the same bug until a crash reporter re-enables it. Flutter isolate restarts and native modules that log at VERBOSE can reintroduce the header after a JavaScript-only fix.

You should not follow this approach if you cannot rotate the proxy credential. You should also skip it when the team forbids physical-device USB debugging, or when regulation requires full request capture. In those cases, put a dedicated redaction layer in the HTTP stack first, then argue about assistants and free servers. Do not treat a simulator pass as equivalent to a device pass.

Ask for comparable evidence, not a chat UI recording

If you run this during onboarding, comment with environment facts. Include device, OS, React Native or Flutter version, and the HTTP library. Name the exact transition: Home, Recents, or a phone-call interruption. Say whether the token recovered in logs, forced a restart, or disappeared after resume, and whether pm clear or a Keychain wipe was required after your first rollback.

If you used that free model access to draft the interceptor, still run the logcat steps on a physical device before you open the PR.

Top comments (0)