Thuta Learning
ExercisesMobile Developmentintermediate

Exercise: A Mobile Security Audit

What you'll walk away with

  • Explain the core ideas behind Exercise: A Mobile Security Audit
  • Read the diagram/checklist and trace how the mobile architecture or decision connects
  • Explain how this applies to a real mobile app project

Build the mental model

A security audit isn't one big test — it's a habit of asking the same short list of questions about any mobile app's setup. None of these questions require deep cryptography knowledge. They require noticing patterns that keep repeating across real apps, because the same handful of mistakes show up again and again in apps that get compromised.

  • Where do secrets and API keys actually live?
  • Which permissions get requested, and when?
  • How is sensitive data stored on the device?
  • What ends up printed in logs?
  • Which decisions is the server still trusting the client to make honestly?

This exercise hands you a fictional shopping app, QuickCart, and a description of how it's built. Hidden in that description are five separate problems, each a variation on a mistake this course already named. Your task is to find all five and describe what the fix would actually change — not just label something as "bad."

Audit, don't attack

This exercise is defensive only: you're reading a description and pointing out where trust is placed unsafely. A real audit report recommends fixes — it never produces a working exploit.

text
QUICKCART: THE FLAWED SETUP (AUDIT TARGET)
------------------------------------------
QUICKCART: THE FLAWED SETUP (AUDIT TARGET)
--------------------------------------------
QuickCart Mobile App (installed on user's phone)

  [1] ANALYTICS_KEY (privileged, elevated access)
      ... hardcoded directly in the app's source

  [2] First launch permission screen asks for:
      Camera + Contacts + Precise Location (all 3)
      before any feature actually needs them

  [3] access_token stored in plain local storage
      (not Keychain / Keystore-backed secure storage)

  [4] Debug build logs: console.log(authResponse)
      -> prints the full response, tokens included

  [5] Checkout: discount price computed ONLY
      on-device, then sent as-is to the server

                        |
                        v
              Backend / Payment API
      (bills whatever price the app tells it to)

Connect it to a real scenario

Here's how a working audit of QuickCart would go, issue by issue, in the same order the setup described them.

Bundled analytics key

A privileged, elevated-access key has no business shipping inside an app binary that anyone can decompile and read. The fix moves any call that needs that key behind the app's own backend, so the privileged credential never leaves the server at all.

All permissions at launch

Camera, contacts, and precise location all get requested before the user has touched a single feature that needs them. The fix requests each permission at the moment its feature is actually used, with a short explanation of why it's needed.

Token in plain storage

An access token sitting in plain local storage can be read by anything on the device with file access. The fix moves it into the platform's secure storage — Keychain on iOS or Keystore-backed storage on Android — so the operating system enforces who can read it.

Logged auth response

A debug log that prints the full auth response ships tokens straight into crash reports and log aggregators. The fix redacts sensitive fields before logging anything, in every build, not only production ones.

Client-only price check

A modified app or an intercepted request can send whatever discount it likes when price is only checked client-side. The fix always re-validates price and eligibility on the server before charging anything.

Try the working example

javascript
function auditMobileAppSetup(setup) {
  const issues = [];

  if (setup.hasPrivilegedSecretBundled) {
    issues.push({
      area: "secrets",
      problem: "A privileged secret is bundled inside the app binary.",
      fix: "Move any call needing this secret to your backend; never ship privileged keys client-side."
    });
  }

  if (setup.requestsAllPermissionsAtLaunch) {
    issues.push({
      area: "permissions",
      problem: "Sensitive permissions are requested all at once at first launch.",
      fix: "Request each permission just-in-time, when the feature that needs it is used."
    });
  }

  if (setup.tokensInPlainStorage) {
    issues.push({
      area: "storage",
      problem: "Access tokens are stored in plain, unencrypted local storage.",
      fix: "Store tokens in platform secure storage (Keychain on iOS, Keystore-backed storage on Android)."
    });
  }

  if (setup.logsAuthTokens) {
    issues.push({
      area: "logging",
      problem: "Debug logs print the full authentication response, including tokens.",
      fix: "Redact sensitive fields before logging, in every build, not just production."
    });
  }

  if (!setup.priceValidatedServerSide) {
    issues.push({
      area: "trust boundary",
      problem: "Price/discount is only checked in the client, with no server re-validation.",
      fix: "Always re-validate price and eligibility on the server before charging."
    });
  }

  return {
    appName: setup.appName || "unnamed app",
    issueCount: issues.length,
    issues,
    verdict: issues.length === 0 ? "PASS - no flagged issues" : "FAIL - see issues"
  };
}

// The flawed QuickCart setup described in this lesson:
const quickCartFlawed = {
  appName: "QuickCart",
  hasPrivilegedSecretBundled: true,
  requestsAllPermissionsAtLaunch: true,
  tokensInPlainStorage: true,
  logsAuthTokens: true,
  priceValidatedServerSide: false
};

// A properly configured app, for contrast:
const quickCartFixed = {
  appName: "QuickCart (fixed)",
  hasPrivilegedSecretBundled: false,
  requestsAllPermissionsAtLaunch: false,
  tokensInPlainStorage: false,
  logsAuthTokens: false,
  priceValidatedServerSide: true
};

console.log(JSON.stringify(auditMobileAppSetup(quickCartFlawed), null, 2));
console.log(JSON.stringify(auditMobileAppSetup(quickCartFixed), null, 2));
You should see
Running auditMobileAppSetup on the flawed QuickCart setup returns issueCount: 5 and verdict: "FAIL - see issues", with all five problems listed in the issues array, each carrying an area, problem, and fix. Running it on the fixed setup — where every flag is false except priceValidatedServerSide, which is true — returns issueCount: 0, an empty issues array, and verdict: "PASS - no flagged issues", because none of the five checks trigger.

5-minute try-it

Before checking the worked answer key in this lesson's practical section, try the audit yourself first. Reread the QuickCart setup described in the diagram above — five components are described: the analytics key, the permission request screen, how the access token is stored, what the debug log prints, and how the discount price gets checked at checkout. For each of those five, write down two things: what's wrong with it, and specifically what you would change to fix it, not just why it feels risky. Then compare your list against the answer key. Did you find all five? Did your proposed fix for each one match the direction of the real fix, or did you only describe the symptom?

One important caution

Stopping at "this looks insecure" without stating the concrete fix — an audit that doesn't say what changes isn't finished.

Treating audits as a one-time task instead of a checklist you re-run every time the app's setup changes.

OWASP Mobile Application SecurityHow Mobile Apps Work

Easy traps

  • Stopping at "this looks insecure" without stating the concrete fix — an audit that doesn't say what changes isn't finished.
  • Treating audits as a one-time task instead of a checklist you re-run every time the app's setup changes.
  • This course does not re-teach the Android Development, Flutter, iOS Development, or React Native tutorials -- continue to those for hands-on framework depth. This course teaches the framework-neutral mobile architecture, decision-making, and build/deployment/security concepts that sit above all four.

Exercise

Before checking the worked answer key in this lesson's practical section, try the audit yourself first. Reread the QuickCart setup described in the diagram above — five components are described: the analytics key, the permission request screen, how the access token is stored, what the debug log prints, and how the discount price gets checked at checkout. For each of those five, write down two things: what's wrong with it, and specifically what you would change to fix it, not just why it feels risky. Then compare your list against the answer key. Did you find all five? Did your proposed fix for each one match the direction of the real fix, or did you only describe the symptom?

You'll know it worked when: Running auditMobileAppSetup on the flawed QuickCart setup returns issueCount: 5 and verdict: "FAIL - see issues", with all five problems listed in the issues array, each carrying an area, problem, and fix. Running it on the fixed setup — where every flag is false except priceValidatedServerSide, which is true — returns issueCount: 0, an empty issues array, and verdict: "PASS - no flagged issues", because none of the five checks trigger.

Exercise: A Mobile Security Audit | Thuta Learning