Repair an Android app's links, components, network policy and storage
Runs in Docker with a pinned Android SDK image; no Android Studio, emulator or device.
Verified on 11 October 2026 with Docker 29.4 on an Apple silicon Mac:
  image   ghcr.io/cirruslabs/android-sdk:36 pinned by digest (Ubuntu 24.04,
          OpenJDK 21, Android 16 platform API 36, build-tools 36.0.0)
  build   Gradle 9.8.1, Android Gradle Plugin 9.4.1 with its built-in Kotlin
  tests   JUnit 4.13.2 and Robolectric 4.17 (Android 16 framework), Android Lint
Run the image as linux/amd64 (the command below does): Google publishes the Android Gradle
Plugin's aapt2 and the SDK build tools for x86-64 Linux only, so an arm64 container can't
build the app. Apple silicon runs it through Rosetta or emulation.

Reference solution: not included here, so the exercise does not hand you the answer.
Attempt the repair first. Then download android-platform-boundaries-solution.zip from
the project card on https://softwaresecurity.com/labs/#project-android-platform-boundaries
and extract it into this folder; it adds a reference/ folder without changing your
files, and the reference commands below need it.

Objective: a small banking app pays invoices straight from a link, forwards an Intent
that another app hands it, exports an internal payment screen, allows cleartext and
user-installed certificate authorities, and keeps its refresh token in plain text in
SharedPreferences. Repair exercise/ until the Robolectric tests pass and Android Lint
reports no errors.
Prerequisites: Kotlin, AndroidManifest.xml, intents and Android resources.

Files
  build.gradle.kts   one app module; EXERCISE_MODULE picks exercise/ or reference/
  app/               app shell: activities, Payments, KeystoreTokenCipher (do not edit)
  exercise/          what you repair: AndroidManifest.xml, Links.kt, SessionStore.kt,
                     res/xml/network_security_config.xml (add more res/xml files here)
  test/              the Robolectric tests (20 tests)
  lint.xml           Lint checks raised to errors for this project
  run-tests.sh       fetches the pinned Gradle if needed, runs testDebugUnitTest and
                     lintDebug, and lists the Lint issues

1. Run the tests and Lint in the pinned image (one command; the volumes cache Gradle
   and Robolectric's downloads between runs):
     docker run --rm --init --platform linux/amd64 \
       -v "$PWD":/project -w /project \
       -v android-boundaries-gradle:/root/.gradle -v android-boundaries-m2:/root/.m2 \
       -e EXERCISE_MODULE \
       ghcr.io/cirruslabs/android-sdk:36@sha256:f9b3ea9ed2b5fc9522adae82c7b4622ab7aa54207ef532c8e615a347dca08f31 \
       sh run-tests.sh
   The first run needs internet access: Docker reports the image as 3.9 GB, and
   Gradle 9.8.1 (checked against its published SHA-256), the build dependencies and
   Robolectric's Android 16 framework jar add about 1.2 GB to the two volumes. Later
   runs take one to two minutes. Expect "20 tests completed, 13 failed" and three
   Lint errors: AppLinkWarning (no autoVerify), InsecureBaseConfiguration (cleartext)
   and AcceptsUserCertificates. On Linux, files under build/ and .gradle/ are created
   by the container's root user.
   Without Docker: install JDK 21 and the Android SDK (platform 36 and build-tools
   36.0.0, ANDROID_HOME set) and run sh run-tests.sh; it downloads Gradle the same way
   unless gradle is on PATH.

2. Storage (exercise/SessionStore.kt, StorageTests). SharedPreferences is a plain XML
   file that Auto Backup copies to the cloud and to a new phone. Encrypt the token
   with the TokenCipher passed to SessionStore (in the app, KeystoreTokenCipher: an
   AES-256-GCM key generated inside the Android Keystore that can't be exported) and
   write the ciphertext to context.noBackupFilesDir, which backups and device transfer
   never copy. When decryption fails, as it will after a restore onto another phone,
   delete the blob and return null so the user signs in again. Jetpack Security's
   EncryptedSharedPreferences is deprecated (all APIs, from 1.1.0-alpha07 in April
   2025); Google points to platform APIs and the Android Keystore directly. Robolectric
   has no AndroidKeyStore, so the tests pass a software AES-GCM key instead and check
   the Keystore key specification separately.

3. Network policy (exercise/res/xml/network_security_config.xml, NetworkConfigTests).
   Set cleartextTrafficPermitted="false" and trust only system certificate authorities
   in base-config. If testers need a proxy CA, put <certificates src="user"/> inside
   <debug-overrides>, which applies only when android:debuggable is true.

4. Links and intents (exercise/Links.kt, LinkTests). Any app can declare bankapp://
   and start the exported InvoiceLinkActivity with any data and extras. Accept exactly
   https://bank.example.com/invoices/<id> and bankapp://invoices/<id> (id: 8 to 20
   characters A-Z or 0-9), compare parsed fields rather than substrings, and reject
   user info, ports, queries and fragments. Uri.getPath() is percent-decoded, so match
   the whole path. A link only opens InvoiceReviewActivity with an explicit Intent;
   it never pays, and it never starts an Intent taken from the caller's extras
   (intent redirection). Lint's UnsafeIntentLaunch check does not see this
   redirection, because the Intent reaches handleLink as a parameter: the test does.

5. Components and backups (exercise/AndroidManifest.xml, ComponentTests and
   StorageTests). Export only MainActivity and InvoiceLinkActivity; set
   android:exported="false" on InvoiceReviewActivity and TransferActivity. Add
   android:autoVerify="true" to the https intent filter so Android checks
   https://bank.example.com/.well-known/assetlinks.json. Older versions of the app
   kept the token in shared_prefs/session.xml: add android:dataExtractionRules (Android
   12 and later: both <cloud-backup> and <device-transfer>) and
   android:fullBackupContent (Android 11 and lower), each excluding domain "sharedpref"
   path "session.xml", as res/xml resources in exercise/res/xml/.

6. Run step 1 until it reports BUILD SUCCESSFUL: 20 tests, no Lint errors. Two Lint
   warnings remain, OldTargetApi and GradleDependency, because API 37 (Android 17) is
   newer than the platform in the pinned image; Google Play requires API 36 since
   31 August 2026. Then extract the solution and run the same command with
   EXERCISE_MODULE=reference in front of docker run.

MASVS v2.1.0 controls and MASTG v2.0.0 tests (IDs checked on mas.owasp.org)
  StorageTests        MASVS-STORAGE-1, MASVS-STORAGE-2, MASVS-CRYPTO-2;
                      MASTG-TEST-0287 and 0207 (MASWE-0001), MASTG-TEST-0262 (MASWE-0006,
                      backups), MASWE-0003 (keys outside the platform keystore)
  NetworkConfigTests  MASVS-NETWORK-1; MASTG-TEST-0235 (cleartext, MASWE-0026) and
                      MASTG-TEST-0286 (user-provided CAs, MASWE-0027)
  LinkTests           MASVS-PLATFORM-1, MASVS-CODE-4; MASTG-TEST-0394 (custom URL scheme
                      handlers, MASWE-0029); intent redirection is a cause listed under
                      MASWE-0032, which has no dedicated MASTG test
  ComponentTests      MASVS-PLATFORM-1; MASTG-TEST-0364 (exported activities, MASWE-0018)
                      and MASTG-TEST-0393 (unverified App Links, MASWE-0029)
  Android Lint        static checks for the same configuration (lint.xml); MASTG does
                      not list Android Lint as a tool

Keep: the failing output from step 1, your diff of exercise/, and the passing output.

Scope: JVM tests with Robolectric's Android 16 framework, the merged manifest and the
compiled resources that the build produces, plus Lint. They are not an emulator or a
device. Robolectric doesn't implement the Android Keystore or apply the network
security configuration, so those are checked as the key specification and the
compiled configuration. Not covered: App Links verification against a live
assetlinks.json, Play Integrity and certificate pinning. Payments stands in for a
server that must authorize every payment itself.

Sources (checked 11 October 2026):
https://developer.android.com/jetpack/androidx/releases/security
https://developer.android.com/privacy-and-security/cryptography
https://developer.android.com/privacy-and-security/keystore
https://developer.android.com/privacy-and-security/security-config
https://developer.android.com/training/app-links/verify-applinks
https://developer.android.com/identity/data/autobackup
https://developer.android.com/privacy-and-security/risks/intent-redirection
https://robolectric.org/getting-started/
https://googlesamples.github.io/android-custom-lint-rules/checks/index.md.html
https://mas.owasp.org/MASVS/
https://mas.owasp.org/MASTG/tests/android/MASVS-NETWORK/MASTG-TEST-0235/
https://mas.owasp.org/MASTG/tests/android/MASVS-NETWORK/MASTG-TEST-0286/
https://mas.owasp.org/MASTG/tests/android/MASVS-PLATFORM/MASTG-TEST-0364/
https://mas.owasp.org/MASTG/tests/android/MASVS-PLATFORM/MASTG-TEST-0393/
