Skip to content
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
bb3bb4d
Enhance documentation on screenshot handling in Android, detailing sy…
cpholguera Aug 31, 2025
fff4ec9
Revise guidelines on preventing sensitive data capture, detailing ris…
cpholguera Aug 31, 2025
a9e5cbc
Add additional rules for FLAG_SECURE handling and recents screenshot …
cpholguera Aug 31, 2025
870fca7
Add guidelines for identifying sensitive screens during testing
cpholguera Aug 31, 2025
875244f
Update FLAG_SECURE usage examples and documentation in Android MASTG-…
cpholguera Sep 3, 2025
1a66957
Add new MASTG-DEMO-0062 for setRecentsScreenshotEnabled in Android 14+
cpholguera Sep 3, 2025
6604db3
Add new MASTG-DEMO-0063 for Jetpack Compose Dialogs
cpholguera Sep 3, 2025
fa08647
Remove redundant rules for recents screenshot enabled/disabled in sen…
cpholguera Sep 3, 2025
771a7fc
Enhance documentation for screen capture prevention APIs, including d…
cpholguera Sep 3, 2025
81c6310
rm extra ref to file
cpholguera Sep 6, 2025
c58f7b3
Merge branch 'master' of https://github.com/OWASP/owasp-mastg into mo…
cpholguera Sep 6, 2025
7af35bd
refine documentation on FLAG_SECURE usage for Android UI components
cpholguera Sep 6, 2025
a6bbca3
refine wording and clarity in MASTG-TEST-0291.md regarding FLAG_SECUR…
cpholguera Sep 6, 2025
e4d8754
fix regex
cpholguera Sep 6, 2025
1c3369a
Split best practices for preventing screenshots in Android apps.
cpholguera Sep 6, 2025
4a483b4
Split tests for preventing screenshots in Android apps.
cpholguera Sep 6, 2025
3250c83
Fix test refs
cpholguera Sep 6, 2025
de7c3a0
add refs
cpholguera Sep 6, 2025
3111eda
link best to knowledge
cpholguera Sep 6, 2025
3c6eb43
Clarify wording in MASTG-TEST-0291.md for better readability
cpholguera Sep 6, 2025
13975cb
Add best practice for non-caching input types in sensitive fields
cpholguera Sep 6, 2025
1d946d8
Update MASTG-BEST-0018.md to clarify usage of SecureFlagPolicy.Secure…
cpholguera Sep 6, 2025
01e9d69
Add reference to overlay attack warnings and improve wording in MASTG…
cpholguera Sep 7, 2025
83c1346
Correct MASTG-KNOW-0053 location
cpholguera Sep 7, 2025
8d83288
Update MASTG-KNOW-0022.md to correct Tapjacking reference and add bes…
cpholguera Sep 7, 2025
532d4a1
Update best practices and knowledge documents for screenshot preventi…
cpholguera Sep 14, 2025
07a89e6
Add MASTG-KNOW-0107.md for screenshots and screen recording detection…
cpholguera Sep 14, 2025
5c8a99e
fix indentation
cpholguera Sep 19, 2025
d9ff8d4
Merge branch 'master' into more-android-screenshots-testing
cpholguera Sep 19, 2025
a95e414
Apply suggestions from code review
cpholguera Sep 19, 2025
b18f69c
Merge branch 'master' of https://github.com/OWASP/owasp-mastg into mo…
cpholguera Sep 20, 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
13 changes: 10 additions & 3 deletions best-practices/MASTG-BEST-0014.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,16 @@ id: MASTG-BEST-0014
platform: android
---

Ensure the app hides sensitive content, such as card numbers and passcodes, from screenshots, screen recording, nonsecure displays, task switcher thumbnails, and remote screen sharing. Malware may capture screen output and extract confidential information. Protect on screen keyboards or custom keypad views as they may leak keystrokes from passcode fields. Screenshots can be saved in locations accessible to other apps or a local attacker.
Apps should prevent sensitive data from being captured from the UI, leaving the control of the user, and potentially leaking. This includes:

Setting [`FLAG_SECURE`](https://developer.android.com/security/fraud-prevention/activities#flag_secure) on the window prevents screenshots (or appear black), blocks screen recording, and hides content on nonsecure displays and in the system task switcher.
- **System-generated snapshots** are sandboxed, but may still be accessible on rooted or compromised devices or through forensic acquisition.
- **User-initiated screenshots** are accessible to the user and to any app with storage or media access. They may also be automatically synced to cloud services such as Google Photos.
- **Screen recordings** can capture all visual content, including transient data such as passcodes or one-time codes, and may be stored or shared without the user's awareness.
- **Nonsecure displays and remote screen sharing** can expose sensitive information to external monitors, casting devices, or remote control software.

Depending on the threat model of the app, one or more protections may be required. For example, on-screen keyboards or custom keypad views should be secured to prevent keystroke leakage from passcode fields. A banking app should ensure that account balances or transaction details are never visible in captured images, unless the user explicitly chooses to allow screenshots.

Setting [`FLAG_SECURE`](https://developer.android.com/security/fraud-prevention/activities#flag_secure) on the window prevents screenshots (or makes them appear black), blocks screen recording, and hides content on nonsecure displays and in the system task switcher.

<div style="display:flex; flex-wrap:wrap; gap:16px; align-items:flex-start; margin:16px 0;">
<figure style="flex:1 1 220px; margin:0; text-align:center;">
Expand All @@ -20,4 +27,4 @@ Setting [`FLAG_SECURE`](https://developer.android.com/security/fraud-prevention/
</figure>
</div>

You can follow the official documentation to implement `FLAG_SECURE` in your app, see ["Secure sensitive activities"](https://developer.android.com/security/fraud-prevention/activities).
Refer to the official documentation for implementation details, see ["Secure sensitive activities"](https://developer.android.com/security/fraud-prevention/activities). Consider adding an in-app setting to enable or disable this behavior, giving users control without compromising security by default.
20 changes: 15 additions & 5 deletions demos/android/MASVS-PLATFORM/MASTG-DEMO-0061/MASTG-DEMO-0061.md
Original file line number Diff line number Diff line change
@@ -1,31 +1,41 @@
---
platform: android
title: Uses of FLAG_SECURE with semgrep
title: Setting and Clearing FLAG_SECURE in an Activity and Dialog with semgrep
id: MASTG-DEMO-0061
code: [kotlin]
test: MASTG-TEST-0291
---

### Sample

The sample uses the `addFlags` method to set the `FLAG_SECURE` window flag on an activity that displays sensitive data.
The sample demonstrates multiple ways of preventing screenshots of sensitive content in two Android components: an Activity and a standard Dialog. It also demonstrates incorrect ways of removing the screenshot prevention.

{{ MastgTest.kt # MastgTest_reversed.java }}

### Steps

Let's run our @MASTG-TOOL-0110 rule against the reversed java code.
Let's run our @MASTG-TOOL-0110 rule against the reversed Java code.

{{ ../../../../rules/mastg-android-sensitive-data-in-screenshot.yml }}

{{ run.sh }}

### Observation

The rule has identified one location in the code file where the app has set the `FLAG_SECURE` window flag using the `addFlags` method.
The rule has identified several locations in the code file where the app sets or clears the `FLAG_SECURE` window flag.

{{ output.txt }}

### Evaluation

This test passes because the app used the `addFlags` method to set the `FLAG_SECURE` window flag on an activity that displays sensitive data.
The test fails because even though the app demonstrates correct protection patterns by setting `FLAG_SECURE` for an Activity and a dialog, it subsequently removes protection by clearing/overwriting `FLAG_SECURE` on both the Activity and the dialog.

We can see this in `MastgTest_reversed.java`:

- For the activity:
- it adds the `FLAG_SECURE` flag via `getWindow().addFlags(8192)` (line 35)
- sets it again with `getWindow().setFlags(8192, 8192)` (line 36)
- and then immediately clears it with `getWindow().setFlags(0, 8192)` (line 37)
- For the dialog:
- it sets `window.setFlags(8192, 8192)` (line 42)
- and then calls `window2.clearFlags(8192)` (line 46)
19 changes: 18 additions & 1 deletion demos/android/MASVS-PLATFORM/MASTG-DEMO-0061/MastgTest.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.owasp.mastestapp

import android.app.Activity
import android.app.AlertDialog
import android.content.Context
import android.view.WindowManager.LayoutParams

Expand All @@ -12,9 +13,25 @@ class MastgTest (private val context: Context){
if (context is Activity) {
context.window.addFlags(LayoutParams.FLAG_SECURE)

return "SUCCESS!!\n\nThe FLAG_SECURE has been set"
// Activity window
context.window.setFlags(LayoutParams.FLAG_SECURE, LayoutParams.FLAG_SECURE)
context.window.setFlags(0, LayoutParams.FLAG_SECURE)

// Standard dialog
val dialog = AlertDialog.Builder(context)
.setTitle("Secure dialog")
.setMessage("FLAG_SECURE is applied to this dialog.")
.setPositiveButton("OK", null)
.create()
dialog.show()
dialog.window?.setFlags(LayoutParams.FLAG_SECURE, LayoutParams.FLAG_SECURE)
dialog.window?.clearFlags(LayoutParams.FLAG_SECURE)


return "SUCCESS!!\n\nFLAG_SECURE has been set for the Activity window"
} else {
return "ERROR: Context is not an Activity"
}

}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package org.owasp.mastestapp;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.view.Window;
import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;

Expand Down Expand Up @@ -30,7 +33,20 @@ public final void setShouldRunInMainThread(boolean z) {
public final String mastgTest() {
if (this.context instanceof Activity) {
((Activity) this.context).getWindow().addFlags(8192);
return "SUCCESS!!\n\nThe FLAG_SECURE has been set";
((Activity) this.context).getWindow().setFlags(8192, 8192);
((Activity) this.context).getWindow().setFlags(0, 8192);
AlertDialog dialog = new AlertDialog.Builder(this.context).setTitle("Secure dialog").setMessage("FLAG_SECURE is applied to this dialog.").setPositiveButton("OK", (DialogInterface.OnClickListener) null).create();
dialog.show();
Window window = dialog.getWindow();
if (window != null) {
window.setFlags(8192, 8192);
}
Window window2 = dialog.getWindow();
if (window2 != null) {
window2.clearFlags(8192);
return "SUCCESS!!\n\nFLAG_SECURE has been set for the Activity window";
}
return "SUCCESS!!\n\nFLAG_SECURE has been set for the Activity window";
}
return "ERROR: Context is not an Activity";
}
Expand Down
29 changes: 23 additions & 6 deletions demos/android/MASVS-PLATFORM/MASTG-DEMO-0061/output.txt
Original file line number Diff line number Diff line change
@@ -1,12 +1,29 @@


┌────────────────┐
1 Code Finding
└────────────────┘
┌────────────────
5 Code Findings
└────────────────

MastgTest_reversed.java
❱ rules.mastg-android-flag-secure-enable-flags
[MASVS-PLATFORM] Make sure you use this flag for all screens with sensitive data

32┆ ((Activity) this.context).getWindow().addFlags(8192);
35┆ ((Activity) this.context).getWindow().addFlags(8192);
⋮┆----------------------------------------
36┆ ((Activity) this.context).getWindow().setFlags(8192, 8192);

❯❱ rules.mastg-android-flag-secure-clear-flags
[MASVS-PLATFORM] Window clears or overwrites FLAG_SECURE.

37┆ ((Activity) this.context).getWindow().setFlags(0, 8192);

❱ rules.mastg-android-flag-secure-enable-flags
[MASVS-PLATFORM] Make sure you use this flag for all screens with sensitive data

42┆ window.setFlags(8192, 8192);

❯❱ rules.mastg-android-flag-secure-clear-flags
[MASVS-PLATFORM] Window clears or overwrites FLAG_SECURE.

46┆ window2.clearFlags(8192);

31 changes: 31 additions & 0 deletions demos/android/MASVS-PLATFORM/MASTG-DEMO-0062/MASTG-DEMO-0062.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
platform: android
title: Enabling Screenshots in Recents via setRecentsScreenshotEnabled with semgrep
id: MASTG-DEMO-0062
code: [kotlin]
test: MASTG-TEST-0291
---

### Sample

The sample demonstrates how an app does not prevent screenshots/leaks of sensitive content in Android 14+ recents via calls to `setRecentsScreenshotEnabled(true)`.

{{ MastgTest.kt # MastgTest_reversed.java }}

### Steps

Let's run our @MASTG-TOOL-0110 rule against the reversed Java code.

{{ ../../../../rules/mastg-android-sensitive-data-in-screenshot.yml }}

{{ run.sh }}

### Observation

The rule has identified one location in the code file where the app calls `setRecentsScreenshotEnabled()`.

{{ output.txt }}

### Evaluation

The test fails because the app calls `setRecentsScreenshotEnabled(true)` on Android 14+, which allows screenshots in recents and may lead to sensitive data exposure. The app does not use `FLAG_SECURE` or any other screenshot prevention mechanism.
27 changes: 27 additions & 0 deletions demos/android/MASVS-PLATFORM/MASTG-DEMO-0062/MastgTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.owasp.mastestapp

import android.app.Activity
import android.content.Context
import android.os.Build

class MastgTest (private val context: Context){

var shouldRunInMainThread: Boolean = true

fun mastgTest(): String {
if (context is Activity) {

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
context.setRecentsScreenshotEnabled(true)
}
else {
return "ERROR: The setRecentsScreenshotEnabled() method is not available on Android versions below 34."
}

return "SUCCESS!!\n\nThe setRecentsScreenshotEnabled() method has been set to true."
} else {
return "ERROR: Context is not an Activity"
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package org.owasp.mastestapp;

import android.app.Activity;
import android.content.Context;
import android.os.Build;
import kotlin.Metadata;
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\u0010\u000b\n\u0002\b\u0005\n\u0002\u0010\u000e\n\u0000\b\u0007\u0018\u00002\u00020\u0001B\u000f\u0012\u0006\u0010\u0002\u001a\u00020\u0003¢\u0006\u0004\b\u0004\u0010\u0005J\u0006\u0010\f\u001a\u00020\rR\u000e\u0010\u0002\u001a\u00020\u0003X\u0082\u0004¢\u0006\u0002\n\u0000R\u001a\u0010\u0006\u001a\u00020\u0007X\u0086\u000e¢\u0006\u000e\n\u0000\u001a\u0004\b\b\u0010\t\"\u0004\b\n\u0010\u000b¨\u0006\u000e"}, d2 = {"Lorg/owasp/mastestapp/MastgTest;", "", "context", "Landroid/content/Context;", "<init>", "(Landroid/content/Context;)V", "shouldRunInMainThread", "", "getShouldRunInMainThread", "()Z", "setShouldRunInMainThread", "(Z)V", "mastgTest", "", "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 Context context;
private boolean shouldRunInMainThread;

public MastgTest(Context context) {
Intrinsics.checkNotNullParameter(context, "context");
this.context = context;
this.shouldRunInMainThread = true;
}

public final boolean getShouldRunInMainThread() {
return this.shouldRunInMainThread;
}

public final void setShouldRunInMainThread(boolean z) {
this.shouldRunInMainThread = z;
}

public final String mastgTest() {
if (this.context instanceof Activity) {
if (Build.VERSION.SDK_INT >= 34) {
((Activity) this.context).setRecentsScreenshotEnabled(true);
return "SUCCESS!!\n\nThe setRecentsScreenshotEnabled() method has been set to true.";
}
return "ERROR: The setRecentsScreenshotEnabled() method is not available on Android versions below 34.";
}
return "ERROR: Context is not an Activity";
}
}
12 changes: 12 additions & 0 deletions demos/android/MASVS-PLATFORM/MASTG-DEMO-0062/output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@


┌────────────────┐
│ 1 Code Finding │
└────────────────┘

MastgTest_reversed.java
❯❱ rules.android-set-recents-screenshot
[MASVS-PLATFORM] Application controls whether recents screenshots are enabled.

34┆ ((Activity) this.context).setRecentsScreenshotEnabled(true);

1 change: 1 addition & 0 deletions demos/android/MASVS-PLATFORM/MASTG-DEMO-0062/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
NO_COLOR=true semgrep -c ../../../../rules/mastg-android-sensitive-data-in-screenshot.yml ./MastgTest_reversed.java > output.txt
31 changes: 31 additions & 0 deletions demos/android/MASVS-PLATFORM/MASTG-DEMO-0063/MASTG-DEMO-0063.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
platform: android
title: Incorrectly Preventing Screenshots with SecureFlagPolicy in Compose Dialogs with semgrep
id: MASTG-DEMO-0063
code: [kotlin]
test: MASTG-TEST-0291
---

### Sample

The sample demonstrates how an app allows screenshots/leaks of sensitive content by misusing screenshot prevention APIs in a Jetpack Compose dialog that uses `DialogProperties(securePolicy = SecureFlagPolicy.SecureOff)`.

{{ MastgTest.kt # MastgTest_reversed.java # MastgTestKt_reversed.java }}

### Steps

Let's run our @MASTG-TOOL-0110 rule against the reversed Java code.

{{ ../../../../rules/mastg-android-sensitive-data-in-screenshot.yml }}

{{ run.sh }}

### Observation

The rule has identified one location in `MastgTestKt_reversed.java` where the app sets the `SecureFlagPolicy` in a Jetpack Compose `DialogProperties`.

{{ output.txt }}

### Evaluation

The test fails because `SecureFlagPolicy` is set to `SecureOff`, which doesn't prevent screenshots or screen recordings of the dialog content. The app does not use `FLAG_SECURE` or any other screenshot prevention mechanism.
67 changes: 67 additions & 0 deletions demos/android/MASVS-PLATFORM/MASTG-DEMO-0063/MastgTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package org.owasp.mastestapp

import android.app.Activity
import android.content.Context
import android.view.ViewGroup
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.DialogProperties
import androidx.compose.ui.window.SecureFlagPolicy
import androidx.compose.runtime.Composable

class MastgTest (private val context: Context){

var shouldRunInMainThread: Boolean = true

fun mastgTest(): String {
if (context is Activity) {

// Compose dialog with SecureFlagPolicy.SecureOn
val composeHost = ComposeView(context)
val lp = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
composeHost.setContent {
SecureComposeDialog(
onDismiss = {
val parent = composeHost.parent as? ViewGroup
parent?.removeView(composeHost)
}
)
}
context.addContentView(composeHost, lp)

return "SUCCESS!!\n\nThe Compose dialog should be visible.\n\nIt has SecureFlagPolicy.SecureOff set, so it should appear in screenshots or the recent apps view."
} else {
return "ERROR: Context is not an Activity"
}

}
}

@Composable
private fun SecureComposeDialog(onDismiss: () -> Unit) {
androidx.compose.ui.window.Dialog(
onDismissRequest = onDismiss,
properties = DialogProperties(securePolicy = SecureFlagPolicy.SecureOff)
) {
MaterialTheme {
Surface {
Column(modifier = androidx.compose.ui.Modifier.padding(16.dp)) {
Text("This is a Compose dialog.")
Text("Secure policy is set to SecureOff.")
Button(onClick = onDismiss, modifier = androidx.compose.ui.Modifier.padding(top = 12.dp)) {
Text("OK")
}
}
}
}
}
}
Loading