diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-0x36/MASTG-DEMO-0x36.md b/demos/android/MASVS-CODE/MASTG-DEMO-0x36/MASTG-DEMO-0x36.md new file mode 100644 index 00000000000..04c65211c70 --- /dev/null +++ b/demos/android/MASVS-CODE/MASTG-DEMO-0x36/MASTG-DEMO-0x36.md @@ -0,0 +1,31 @@ +--- +platform: android +title: Enforced Immediate Updates with Play Core API detected using semgrep +id: MASTG-DEMO-0x36 +code: [kotlin] +test: MASTG-TEST-0x336 +--- + +### Sample + +The following code implements immediate in-app updates using the Google Play Core API by calling `startUpdateFlowForResult` with `AppUpdateOptions.newBuilder(1)`. + +{{ MastgTest.kt # MastgTest_reversed.java }} + +### Steps + +Let's run @MASTG-TOOL-0110 rules against the sample code. + +{{ ../../../../rules/mastg-android-enforced-updating.yml }} + +{{ run.sh }} + +### Observation + +The output file shows usages of the Google Play Core API enforcing immediate update. + +{{ output.txt }} + +### Evaluation + +The test passes because the app forces users to update the application immediately and does not provide a fallback. diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-0x36/MainActivity.kt b/demos/android/MASVS-CODE/MASTG-DEMO-0x36/MainActivity.kt new file mode 100644 index 00000000000..ae87b064799 --- /dev/null +++ b/demos/android/MASVS-CODE/MASTG-DEMO-0x36/MainActivity.kt @@ -0,0 +1,83 @@ +package org.owasp.mastestapp + +import android.os.Bundle +import android.util.Log +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.IntentSenderRequest +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp + +const val MASTG_TEXT_TAG = "mastgTestText" + +class MainActivity : ComponentActivity() { + + private val mastgTest by lazy { MastgTest(applicationContext) } + private lateinit var appUpdateResultLauncher: ActivityResultLauncher + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enableEdgeToEdge() + + appUpdateResultLauncher = registerForActivityResult( + ActivityResultContracts.StartIntentSenderForResult() + ) { result -> + if (result.resultCode != RESULT_OK) { + Log.e( + "MainActivity", + "Update flow was cancelled or failed! Result code: ${result.resultCode}. Re-initiating." + ) + + mastgTest.checkForUpdate(appUpdateResultLauncher) + } else { + Log.d("MainActivity", "Update accepted. The update is now in progress.") + } + } + + setContent { + MainScreen( + displayString = "App is running. Checking for mandatory updates...", + onStartClick = { + mastgTest.checkForUpdate(appUpdateResultLauncher) + } + ) + } + + mastgTest.checkForUpdate(appUpdateResultLauncher) + } + + override fun onResume() { + super.onResume() + mastgTest.resumeUpdateIfInProgress(appUpdateResultLauncher) + } +} + +@Preview +@Composable +fun MainScreen( + displayString: String = "App is running.", + onStartClick: () -> Unit = {} +) { + BaseScreen(onStartClick = onStartClick) { + Text( + modifier = Modifier + .padding(16.dp) + .testTag(MASTG_TEXT_TAG), + text = displayString, + color = Color.White, + fontSize = 16.sp, + fontFamily = FontFamily.Monospace + ) + } +} \ No newline at end of file diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-0x36/MainActivityKt_reversed.java b/demos/android/MASVS-CODE/MASTG-DEMO-0x36/MainActivityKt_reversed.java new file mode 100644 index 00000000000..2942c2ca312 --- /dev/null +++ b/demos/android/MASVS-CODE/MASTG-DEMO-0x36/MainActivityKt_reversed.java @@ -0,0 +1,97 @@ +package org.owasp.mastestapp; + +import androidx.compose.foundation.layout.PaddingKt; +import androidx.compose.material3.TextKt; +import androidx.compose.runtime.Composer; +import androidx.compose.runtime.ComposerKt; +import androidx.compose.runtime.RecomposeScopeImplKt; +import androidx.compose.runtime.ScopeUpdateScope; +import androidx.compose.runtime.internal.ComposableLambdaKt; +import androidx.compose.ui.Modifier; +import androidx.compose.ui.graphics.Color; +import androidx.compose.ui.platform.TestTagKt; +import androidx.compose.ui.text.TextLayoutResult; +import androidx.compose.ui.text.TextStyle; +import androidx.compose.ui.text.font.FontFamily; +import androidx.compose.ui.text.font.FontStyle; +import androidx.compose.ui.text.font.FontWeight; +import androidx.compose.ui.text.style.TextAlign; +import androidx.compose.ui.text.style.TextDecoration; +import androidx.compose.ui.unit.Dp; +import androidx.compose.ui.unit.TextUnitKt; +import kotlin.Metadata; +import kotlin.Unit; +import kotlin.jvm.functions.Function0; +import kotlin.jvm.functions.Function1; +import kotlin.jvm.functions.Function2; + +/* compiled from: MainActivity.kt */ +@Metadata(d1 = {"\u0000\u0018\n\u0000\n\u0002\u0010\u000e\n\u0000\n\u0002\u0010\u0002\n\u0002\b\u0002\n\u0002\u0018\u0002\n\u0002\b\u0002\u001a'\u0010\u0002\u001a\u00020\u00032\b\b\u0002\u0010\u0004\u001a\u00020\u00012\u000e\b\u0002\u0010\u0005\u001a\b\u0012\u0004\u0012\u00020\u00030\u0006H\u0007¢\u0006\u0002\u0010\u0007\"\u000e\u0010\u0000\u001a\u00020\u0001X\u0086T¢\u0006\u0002\n\u0000¨\u0006\b"}, d2 = {"MASTG_TEXT_TAG", "", "MainScreen", "", "displayString", "onStartClick", "Lkotlin/Function0;", "(Ljava/lang/String;Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;II)V", "app_debug"}, k = 2, mv = {2, 0, 0}, xi = 48) +/* loaded from: classes3.dex */ +public final class MainActivityKt { + public static final String MASTG_TEXT_TAG = "mastgTestText"; + + /* JADX INFO: Access modifiers changed from: private */ + public static final Unit MainScreen$lambda$1(String str, Function0 function0, int i, int i2, Composer composer, int i3) { + MainScreen(str, function0, composer, RecomposeScopeImplKt.updateChangedFlags(i | 1), i2); + return Unit.INSTANCE; + } + + public static final void MainScreen(final String displayString, final Function0 function0, Composer $composer, final int $changed, final int i) { + Composer $composer2 = $composer.startRestartGroup(-958245146); + ComposerKt.sourceInformation($composer2, "C(MainScreen)101@4458L280,101@4418L320:MainActivity.kt#vyvp3i"); + int $dirty = $changed; + int i2 = i & 1; + if (i2 != 0) { + $dirty |= 6; + } else if (($changed & 14) == 0) { + $dirty |= $composer2.changed(displayString) ? 4 : 2; + } + int i3 = i & 2; + if (i3 != 0) { + $dirty |= 48; + } else if (($changed & 112) == 0) { + $dirty |= $composer2.changedInstance(function0) ? 32 : 16; + } + if (($dirty & 91) != 18 || !$composer2.getSkipping()) { + if (i2 != 0) { + displayString = "App is running."; + } + if (i3 != 0) { + function0 = new Function0() { // from class: org.owasp.mastestapp.MainActivityKt$$ExternalSyntheticLambda0 + @Override // kotlin.jvm.functions.Function0 + public final Object invoke() { + return Unit.INSTANCE; + } + }; + } + BaseScreenKt.BaseScreen(function0, ComposableLambdaKt.rememberComposableLambda(891526107, true, new Function2() { // from class: org.owasp.mastestapp.MainActivityKt.MainScreen.2 + @Override // kotlin.jvm.functions.Function2 + public /* bridge */ /* synthetic */ Unit invoke(Composer composer, Integer num) { + invoke(composer, num.intValue()); + return Unit.INSTANCE; + } + + public final void invoke(Composer $composer3, int $changed2) { + ComposerKt.sourceInformation($composer3, "C102@4468L264:MainActivity.kt#vyvp3i"); + if (($changed2 & 11) != 2 || !$composer3.getSkipping()) { + TextKt.m2715Text4IGK_g(displayString, TestTagKt.testTag(PaddingKt.m681padding3ABfNKs(Modifier.INSTANCE, Dp.m6664constructorimpl(16)), MainActivityKt.MASTG_TEXT_TAG), Color.INSTANCE.m4219getWhite0d7_KjU(), TextUnitKt.getSp(16), (FontStyle) null, (FontWeight) null, (FontFamily) FontFamily.INSTANCE.getMonospace(), 0L, (TextDecoration) null, (TextAlign) null, 0L, 0, false, 0, 0, (Function1) null, (TextStyle) null, $composer3, 3504, 0, 130992); + return; + } + $composer3.skipToGroupEnd(); + } + }, $composer2, 54), $composer2, (($dirty >> 3) & 14) | 48, 0); + } else { + $composer2.skipToGroupEnd(); + } + ScopeUpdateScope scopeUpdateScopeEndRestartGroup = $composer2.endRestartGroup(); + if (scopeUpdateScopeEndRestartGroup != null) { + scopeUpdateScopeEndRestartGroup.updateScope(new Function2() { // from class: org.owasp.mastestapp.MainActivityKt$$ExternalSyntheticLambda1 + @Override // kotlin.jvm.functions.Function2 + public final Object invoke(Object obj, Object obj2) { + return MainActivityKt.MainScreen$lambda$1(displayString, function0, $changed, i, (Composer) obj, ((Integer) obj2).intValue()); + } + }); + } + } +} diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-0x36/MastgTest.kt b/demos/android/MASVS-CODE/MASTG-DEMO-0x36/MastgTest.kt new file mode 100644 index 00000000000..b96f7020212 --- /dev/null +++ b/demos/android/MASVS-CODE/MASTG-DEMO-0x36/MastgTest.kt @@ -0,0 +1,62 @@ +package org.owasp.mastestapp + +import android.content.Context +import android.util.Log +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.IntentSenderRequest +import com.google.android.play.core.appupdate.AppUpdateManager +import com.google.android.play.core.appupdate.AppUpdateManagerFactory +import com.google.android.play.core.appupdate.AppUpdateOptions +import com.google.android.play.core.install.model.AppUpdateType +import com.google.android.play.core.install.model.UpdateAvailability + +class MastgTest(context: Context) { + + private val appUpdateManager: AppUpdateManager = AppUpdateManagerFactory.create(context) + + /** + * Checks if an IMMEDIATE update is available on the Play Store. + */ + fun checkForUpdate( + appUpdateResultLauncher: ActivityResultLauncher + ) { + Log.d("MastgTest", "Checking for an update...") + val appUpdateInfoTask = appUpdateManager.appUpdateInfo + + appUpdateInfoTask.addOnSuccessListener { appUpdateInfo -> + val isUpdateAvailable = appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE + val isImmediateUpdateAllowed = appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE) + + if (isUpdateAvailable && isImmediateUpdateAllowed) { + Log.d("MastgTest", "Immediate update available. Starting flow.") + appUpdateManager.startUpdateFlowForResult( + appUpdateInfo, + appUpdateResultLauncher, + AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE).build() + ) + } else { + Log.d("MastgTest", "No immediate update available.") + } + }.addOnFailureListener { e -> + Log.e("MastgTest", "Failed to check for updates.", e) + } + } + + /** + * Resumes an update that is already in progress. This is critical for onResume(). + */ + fun resumeUpdateIfInProgress( + appUpdateResultLauncher: ActivityResultLauncher + ) { + appUpdateManager.appUpdateInfo.addOnSuccessListener { appUpdateInfo -> + if (appUpdateInfo.updateAvailability() == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) { + Log.d("MastgTest", "Resuming in-progress update.") + appUpdateManager.startUpdateFlowForResult( + appUpdateInfo, + appUpdateResultLauncher, + AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE).build() + ) + } + } + } +} diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-0x36/MastgTest_reversed.java b/demos/android/MASVS-CODE/MASTG-DEMO-0x36/MastgTest_reversed.java new file mode 100644 index 00000000000..47cf08633ca --- /dev/null +++ b/demos/android/MASVS-CODE/MASTG-DEMO-0x36/MastgTest_reversed.java @@ -0,0 +1,117 @@ +package org.owasp.mastestapp; + +import android.content.Context; +import android.util.Log; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.IntentSenderRequest; +import com.google.android.gms.tasks.OnFailureListener; +import com.google.android.gms.tasks.OnSuccessListener; +import com.google.android.gms.tasks.Task; +import com.google.android.play.core.appupdate.AppUpdateInfo; +import com.google.android.play.core.appupdate.AppUpdateManager; +import com.google.android.play.core.appupdate.AppUpdateManagerFactory; +import com.google.android.play.core.appupdate.AppUpdateOptions; +import kotlin.Metadata; +import kotlin.Unit; +import kotlin.jvm.functions.Function1; +import kotlin.jvm.internal.Intrinsics; + +/* compiled from: MastgTest.kt */ +@Metadata(d1 = {"\u0000*\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0003\n\u0002\u0018\u0002\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\b\u0002\b\u0007\u0018\u00002\u00020\u0001B\u000f\u0012\u0006\u0010\u0002\u001a\u00020\u0003¢\u0006\u0004\b\u0004\u0010\u0005J\u0014\u0010\b\u001a\u00020\t2\f\u0010\n\u001a\b\u0012\u0004\u0012\u00020\f0\u000bJ\u0014\u0010\r\u001a\u00020\t2\f\u0010\n\u001a\b\u0012\u0004\u0012\u00020\f0\u000bR\u000e\u0010\u0006\u001a\u00020\u0007X\u0082\u0004¢\u0006\u0002\n\u0000¨\u0006\u000e"}, d2 = {"Lorg/owasp/mastestapp/MastgTest;", "", "context", "Landroid/content/Context;", "", "(Landroid/content/Context;)V", "appUpdateManager", "Lcom/google/android/play/core/appupdate/AppUpdateManager;", "checkForUpdate", "", "appUpdateResultLauncher", "Landroidx/activity/result/ActivityResultLauncher;", "Landroidx/activity/result/IntentSenderRequest;", "resumeUpdateIfInProgress", "app_debug"}, k = 1, mv = {2, 0, 0}, xi = 48) +/* loaded from: classes3.dex */ +public final class MastgTest { + public static final int $stable = 8; + private final AppUpdateManager appUpdateManager; + + public MastgTest(Context context) { + Intrinsics.checkNotNullParameter(context, "context"); + AppUpdateManager appUpdateManagerCreate = AppUpdateManagerFactory.create(context); + Intrinsics.checkNotNullExpressionValue(appUpdateManagerCreate, "create(...)"); + this.appUpdateManager = appUpdateManagerCreate; + } + + public final void checkForUpdate(final ActivityResultLauncher appUpdateResultLauncher) { + Intrinsics.checkNotNullParameter(appUpdateResultLauncher, "appUpdateResultLauncher"); + Log.d("MastgTest", "Checking for an update..."); + Task appUpdateInfoTask = this.appUpdateManager.getAppUpdateInfo(); + Intrinsics.checkNotNullExpressionValue(appUpdateInfoTask, "getAppUpdateInfo(...)"); + final Function1 function1 = new Function1() { // from class: org.owasp.mastestapp.MastgTest$$ExternalSyntheticLambda0 + @Override // kotlin.jvm.functions.Function1 + public final Object invoke(Object obj) { + return MastgTest.checkForUpdate$lambda$0(this.f$0, appUpdateResultLauncher, (AppUpdateInfo) obj); + } + }; + appUpdateInfoTask.addOnSuccessListener(new OnSuccessListener() { // from class: org.owasp.mastestapp.MastgTest$$ExternalSyntheticLambda1 + @Override // com.google.android.gms.tasks.OnSuccessListener + public final void onSuccess(Object obj) { + MastgTest.checkForUpdate$lambda$1(function1, obj); + } + }).addOnFailureListener(new OnFailureListener() { // from class: org.owasp.mastestapp.MastgTest$$ExternalSyntheticLambda2 + @Override // com.google.android.gms.tasks.OnFailureListener + public final void onFailure(Exception exc) { + MastgTest.checkForUpdate$lambda$2(exc); + } + }); + } + + /* JADX INFO: Access modifiers changed from: private */ + public static final void checkForUpdate$lambda$1(Function1 tmp0, Object p0) { + Intrinsics.checkNotNullParameter(tmp0, "$tmp0"); + tmp0.invoke(p0); + } + + /* JADX INFO: Access modifiers changed from: private */ + public static final Unit checkForUpdate$lambda$0(MastgTest this$0, ActivityResultLauncher appUpdateResultLauncher, AppUpdateInfo appUpdateInfo) { + Intrinsics.checkNotNullParameter(this$0, "this$0"); + Intrinsics.checkNotNullParameter(appUpdateResultLauncher, "$appUpdateResultLauncher"); + boolean isUpdateAvailable = appUpdateInfo.updateAvailability() == 2; + boolean isImmediateUpdateAllowed = appUpdateInfo.isUpdateTypeAllowed(1); + if (!isUpdateAvailable || !isImmediateUpdateAllowed) { + Log.d("MastgTest", "No immediate update available."); + } else { + Log.d("MastgTest", "Immediate update available. Starting flow."); + this$0.appUpdateManager.startUpdateFlowForResult(appUpdateInfo, appUpdateResultLauncher, AppUpdateOptions.newBuilder(1).build()); + } + return Unit.INSTANCE; + } + + /* JADX INFO: Access modifiers changed from: private */ + public static final void checkForUpdate$lambda$2(Exception e) { + Intrinsics.checkNotNullParameter(e, "e"); + Log.e("MastgTest", "Failed to check for updates.", e); + } + + /* JADX INFO: Access modifiers changed from: private */ + public static final void resumeUpdateIfInProgress$lambda$4(Function1 tmp0, Object p0) { + Intrinsics.checkNotNullParameter(tmp0, "$tmp0"); + tmp0.invoke(p0); + } + + public final void resumeUpdateIfInProgress(final ActivityResultLauncher appUpdateResultLauncher) { + Intrinsics.checkNotNullParameter(appUpdateResultLauncher, "appUpdateResultLauncher"); + Task appUpdateInfo = this.appUpdateManager.getAppUpdateInfo(); + final Function1 function1 = new Function1() { // from class: org.owasp.mastestapp.MastgTest$$ExternalSyntheticLambda3 + @Override // kotlin.jvm.functions.Function1 + public final Object invoke(Object obj) { + return MastgTest.resumeUpdateIfInProgress$lambda$3(this.f$0, appUpdateResultLauncher, (AppUpdateInfo) obj); + } + }; + appUpdateInfo.addOnSuccessListener(new OnSuccessListener() { // from class: org.owasp.mastestapp.MastgTest$$ExternalSyntheticLambda4 + @Override // com.google.android.gms.tasks.OnSuccessListener + public final void onSuccess(Object obj) { + MastgTest.resumeUpdateIfInProgress$lambda$4(function1, obj); + } + }); + } + + /* JADX INFO: Access modifiers changed from: private */ + public static final Unit resumeUpdateIfInProgress$lambda$3(MastgTest this$0, ActivityResultLauncher appUpdateResultLauncher, AppUpdateInfo appUpdateInfo) { + Intrinsics.checkNotNullParameter(this$0, "this$0"); + Intrinsics.checkNotNullParameter(appUpdateResultLauncher, "$appUpdateResultLauncher"); + if (appUpdateInfo.updateAvailability() == 3) { + Log.d("MastgTest", "Resuming in-progress update."); + this$0.appUpdateManager.startUpdateFlowForResult(appUpdateInfo, appUpdateResultLauncher, AppUpdateOptions.newBuilder(1).build()); + } + return Unit.INSTANCE; + } +} diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-0x36/build.gradle.kts.libs b/demos/android/MASVS-CODE/MASTG-DEMO-0x36/build.gradle.kts.libs new file mode 100644 index 00000000000..383c18b99ae --- /dev/null +++ b/demos/android/MASVS-CODE/MASTG-DEMO-0x36/build.gradle.kts.libs @@ -0,0 +1 @@ +implementation("com.google.android.play:app-update-ktx:2.1.0") \ No newline at end of file diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-0x36/output.txt b/demos/android/MASVS-CODE/MASTG-DEMO-0x36/output.txt new file mode 100644 index 00000000000..ab33fb75e54 --- /dev/null +++ b/demos/android/MASVS-CODE/MASTG-DEMO-0x36/output.txt @@ -0,0 +1,15 @@ + + +┌─────────────────┐ +│ 2 Code Findings │ +└─────────────────┘ + + MastgTest_reversed.java + ❯❱rules.mastg-android-enforced-updating + [MASVS-CODE-1] immediate in-app update enforcement detected. + + 73┆ this$0.appUpdateManager.startUpdateFlowForResult(appUpdateInfo, appUpdateResultLauncher, + AppUpdateOptions.newBuilder(1).build()); + ⋮┆---------------------------------------- + 113┆ this$0.appUpdateManager.startUpdateFlowForResult(appUpdateInfo, appUpdateResultLauncher, + AppUpdateOptions.newBuilder(1).build()); diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-0x36/run.sh b/demos/android/MASVS-CODE/MASTG-DEMO-0x36/run.sh new file mode 100755 index 00000000000..5c2cbfe9d35 --- /dev/null +++ b/demos/android/MASVS-CODE/MASTG-DEMO-0x36/run.sh @@ -0,0 +1 @@ +NO_COLOR=true semgrep -c ../../../../rules/mastg-android-enforced-updating.yml ./MastgTest_reversed.java > output.txt \ No newline at end of file diff --git a/rules/mastg-android-enforced-updating.yml b/rules/mastg-android-enforced-updating.yml new file mode 100644 index 00000000000..1291d937197 --- /dev/null +++ b/rules/mastg-android-enforced-updating.yml @@ -0,0 +1,12 @@ +rules: + - id: mastg-android-enforced-updating + severity: WARNING + languages: + - java + metadata: + summary: This rule detects usage of Play Core's immediate in-app update enforcement + message: "[MASVS-CODE-1] immediate in-app update enforcement detected." + patterns: + - pattern-either: + - pattern: $OBJ.startUpdateFlowForResult($INFO, $LAUNCHER, + AppUpdateOptions.newBuilder(1).build()) \ No newline at end of file diff --git a/tests-beta/android/MASVS-CODE/MASTG-TEST-0x36.md b/tests-beta/android/MASVS-CODE/MASTG-TEST-0x36.md new file mode 100644 index 00000000000..9daa64b3e72 --- /dev/null +++ b/tests-beta/android/MASVS-CODE/MASTG-TEST-0x36.md @@ -0,0 +1,24 @@ +--- +title: Enforcing Mandatory In-App Updates +platform: android +id: MASTG-TEST-0x36 +type: [static] +weakness: MASWE-0075 +profiles: [L2] +--- + +## Overview + +The goal of this test is to verify whether the application enforces mandatory updates, preventing users from accessing the app until the latest version has been successfully downloaded and installed. A mandatory update can typically be achieved by using the [Google Play Core In-App Update API](https://developer.android.com/guide/playcore/in-app-updates/kotlin-java) and invoking `startUpdateFlowForResult` with an Immediate update type option `AppUpdateType.IMMEDIATE` or value `1`. + +## Steps + +1. Run a static analysis tool such as @MASTG-TOOL-0110 on codebase for usages of the calls to the Play Core in-app update API, specifically `startUpdateFlowForResult`, that are configured with the integer value `1` (`AppUpdateType.IMMEDIATE`). + +## Observation + +The output should contain the locations where `startUpdateFlowForResult` with `AppUpdateOptions.newBuilder(1).build()` is called. + +## Evaluation + +The test fails if the app does not enforce updates and still allows users to skip or ignore them. diff --git a/tests/android/MASVS-CODE/MASTG-TEST-0036.md b/tests/android/MASVS-CODE/MASTG-TEST-0036.md index 41625857ee7..ef4e4d6f6ea 100644 --- a/tests/android/MASVS-CODE/MASTG-TEST-0036.md +++ b/tests/android/MASVS-CODE/MASTG-TEST-0036.md @@ -8,6 +8,9 @@ title: Testing Enforced Updating masvs_v1_levels: - L2 profiles: [L2] +status: deprecated +covered_by: [MASTG-TEST-0x36] +deprecation_note: New version available in MASTG V2 --- ## Overview @@ -84,7 +87,7 @@ protected void onResume() { } ``` ->Source: [https://developer.android.com/guide/app-bundle/in-app-updates](https://developer.android.com/guide/app-bundle/in-app-updates "Support in-app updates") +> Source: [https://developer.android.com/guide/app-bundle/in-app-updates](https://developer.android.com/guide/app-bundle/in-app-updates "Support in-app updates") ## Dynamic analysis