Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
2bb57d9
deprecated_notes
sk3l10x1ng Aug 14, 2025
07109c2
added test-beta
sk3l10x1ng Aug 20, 2025
d11e628
added Demo
sk3l10x1ng Aug 20, 2025
a9708b1
updated test-beta
sk3l10x1ng Aug 20, 2025
d79bb96
Revert "added Demo"
sk3l10x1ng Aug 20, 2025
46700ae
fix demo files
sk3l10x1ng Aug 20, 2025
b42de04
fix test-beta & demo.md
sk3l10x1ng Aug 21, 2025
a9d792a
fix lint
sk3l10x1ng Aug 21, 2025
8e29eb0
fix spell & add rule
sk3l10x1ng Aug 21, 2025
0673135
fixe DEMO-0062.md
sk3l10x1ng Sep 19, 2025
486b13d
fix MainActivity.kt
sk3l10x1ng Sep 19, 2025
428c988
Merge branch 'master' into port_mastg_test_0036
cpholguera Sep 23, 2025
ebf4d1c
MASTG-DEMO-0062.md
sk3l10x1ng Sep 23, 2025
753f2ef
updated file id's
sk3l10x1ng Sep 24, 2025
1173e1b
added build.gradle.kts.libs file
sk3l10x1ng Sep 24, 2025
14961c2
updated MASTG-TEST-0x36
sk3l10x1ng Sep 30, 2025
831a08d
updated changes
sk3l10x1ng Sep 30, 2025
d42e1d9
updated demo files
sk3l10x1ng Oct 7, 2025
fb39bc4
markdown_lint fix
sk3l10x1ng Oct 7, 2025
29666ed
updated MainActivity.kt
sk3l10x1ng Oct 10, 2025
28f7340
Merge branch 'master' into port_mastg_test_0036
cpholguera Nov 2, 2025
30b04d2
removed comments
sk3l10x1ng Oct 28, 2025
3e2e821
updated run.sh
sk3l10x1ng Oct 28, 2025
c801172
updated MASTG-TEST
sk3l10x1ng Oct 28, 2025
4cce45a
MASTG-TEST for dynamic analysis
sk3l10x1ng Dec 15, 2025
ac5ceb2
rename files
sk3l10x1ng Dec 15, 2025
1123387
rename TEST
sk3l10x1ng Dec 15, 2025
4518e1e
fix spell
sk3l10x1ng Dec 15, 2025
6468467
rename files
sk3l10x1ng Dec 15, 2025
bba9d1d
fix numbering
sk3l10x1ng Dec 15, 2025
3f4d639
fix lint
sk3l10x1ng Dec 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions demos/android/MASVS-CODE/MASTG-DEMO-0x36/MASTG-DEMO-0x36.md
Original file line number Diff line number Diff line change
@@ -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-0x36
---

### 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

This code correctly implements mandatory immediate updates using the Play Core API. The app calls `startUpdateFlowForResult()` with priority level 1 (IMMEDIATE), which forces the user to install the update before continuing. There is no fallback or way to bypass the update, ensuring users cannot access the app with an outdated version.
83 changes: 83 additions & 0 deletions demos/android/MASVS-CODE/MASTG-DEMO-0x36/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -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<IntentSenderRequest>

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
)
}
}
Original file line number Diff line number Diff line change
@@ -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<Unit> 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<Composer, Integer, Unit>() { // 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<? super TextLayoutResult, Unit>) 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());
}
});
}
}
}
62 changes: 62 additions & 0 deletions demos/android/MASVS-CODE/MASTG-DEMO-0x36/MastgTest.kt
Original file line number Diff line number Diff line change
@@ -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<IntentSenderRequest>
) {
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<IntentSenderRequest>
) {
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()
)
}
}
}
}
117 changes: 117 additions & 0 deletions demos/android/MASVS-CODE/MASTG-DEMO-0x36/MastgTest_reversed.java
Original file line number Diff line number Diff line change
@@ -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;", "<init>", "(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<IntentSenderRequest> 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<IntentSenderRequest> appUpdateResultLauncher) {
Intrinsics.checkNotNullParameter(appUpdateResultLauncher, "appUpdateResultLauncher");
Task<AppUpdateInfo> 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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
implementation("com.google.android.play:app-update-ktx:2.1.0")
Loading
Loading