From bb3bb4d5200d9a48f6444af4bd110618e4ab2deb Mon Sep 17 00:00:00 2001 From: Carlos Holguera Date: Sun, 31 Aug 2025 17:55:01 +0200 Subject: [PATCH 01/28] Enhance documentation on screenshot handling in Android, detailing system-generated and user-initiated snapshots, and their security implications. --- knowledge/android/MASVS-STORAGE/MASTG-KNOW-0053.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/knowledge/android/MASVS-STORAGE/MASTG-KNOW-0053.md b/knowledge/android/MASVS-STORAGE/MASTG-KNOW-0053.md index 5a82a7d9ea3..54d0373ee97 100644 --- a/knowledge/android/MASVS-STORAGE/MASTG-KNOW-0053.md +++ b/knowledge/android/MASVS-STORAGE/MASTG-KNOW-0053.md @@ -2,8 +2,17 @@ masvs_category: MASVS-STORAGE platform: android title: Screenshots +best-practices: [MASTG-BEST-0014] --- -Manufacturers want to provide device users with an aesthetically pleasing experience at application startup and exit, so they introduced the screenshot-saving feature for use when the application is backgrounded. This feature may pose a security risk. Sensitive data may be exposed if the user deliberately screenshots the application while sensitive data is displayed. A malicious application that is running on the device and able to continuously capture the screen may also expose data. Screenshots are written to local storage, from which they may be recovered by a rogue application (if the device is rooted) or someone who has stolen the device. +## System-Generated Snapshots -For example, capturing a screenshot of a banking application may reveal information about the user's account, credit, transactions, and so on. +Android captures visual representations of apps for different purposes, which can lead to unintended exposure of sensitive data. When an app goes into the background, the system creates a snapshot of its UI for the [Recents screen](https://developer.android.com/guide/components/activities/recents). This behavior is automatic and helps the user visually recognize tasks when switching apps. If the app does not obscure its UI, sensitive information may appear in these snapshots. + +System-generated snapshots for the Recents screen are stored under `/data/system_ce//snapshots` on modern Android versions, or `/data/system` on older versions. These locations are protected by the system sandbox, meaning ordinary apps cannot read them, but they can be accessed on rooted or compromised devices or through forensic acquisition. The exact path and structure may vary depending on the Android version and device vendor. + +## User-Initiated Screenshots + +Users can take screenshots at any time using system shortcuts. A malicious app with screen-capture capabilities, or malware on a rooted or compromised device, can also capture the display contents without the user's knowledge. + +User-initiated screenshots are stored in the **shared external storage**, typically under `/sdcard/Pictures/Screenshots/` or a similar public media directory depending on the device and Android version. These files are accessible to the user and to any app with storage or media access permissions. They may also be synced automatically to cloud services such as Google Photos. Because they are outside the app sandbox, once a screenshot is taken the app has no control over its lifecycle, distribution, or deletion. From fff4ec91229d4aa845d5c023efb57575229e5c15 Mon Sep 17 00:00:00 2001 From: Carlos Holguera Date: Sun, 31 Aug 2025 17:55:11 +0200 Subject: [PATCH 02/28] Revise guidelines on preventing sensitive data capture, detailing risks of screenshots, screen recordings, and nonsecure displays, while emphasizing the use of FLAG_SECURE for enhanced security. --- best-practices/MASTG-BEST-0014.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/best-practices/MASTG-BEST-0014.md b/best-practices/MASTG-BEST-0014.md index c7db8951230..609024e885b 100644 --- a/best-practices/MASTG-BEST-0014.md +++ b/best-practices/MASTG-BEST-0014.md @@ -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.
@@ -20,4 +27,4 @@ Setting [`FLAG_SECURE`](https://developer.android.com/security/fraud-prevention/
-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. From a9e5cbc1b078d873b1f8e1b3de4899c91dc755e4 Mon Sep 17 00:00:00 2001 From: Carlos Holguera Date: Sun, 31 Aug 2025 17:55:46 +0200 Subject: [PATCH 03/28] Add additional rules for FLAG_SECURE handling and recents screenshot management in Android --- ...g-android-sensitive-data-in-screenshot.yml | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/rules/mastg-android-sensitive-data-in-screenshot.yml b/rules/mastg-android-sensitive-data-in-screenshot.yml index 7f833943403..56a48ddd810 100644 --- a/rules/mastg-android-sensitive-data-in-screenshot.yml +++ b/rules/mastg-android-sensitive-data-in-screenshot.yml @@ -16,3 +16,92 @@ rules: - metavariable-regex: metavariable: $FLAGS regex: ^(FLAG_SECURE|8192|0x2000)$ + + - id: mastg-android-flag-secure-clear-flags + languages: [java] + severity: WARNING + metadata: + category: screen-capture + summary: Window clears or overwrites FLAG_SECURE + message: "[MASVS-PLATFORM] Window clears or overwrites FLAG_SECURE." + pattern-either: + - patterns: + - pattern: $W.clearFlags($FLAGS) + - metavariable-regex: + metavariable: $FLAGS + regex: ^(FLAG_SECURE|8192|0x2000)$ + - patterns: + - pattern: $W.setFlags(0, $FLAGS) + - metavariable-regex: + metavariable: $FLAGS + regex: ^(FLAG_SECURE|8192|0x2000)$ + + - id: android-set-recents-screenshot + languages: [java] + severity: WARNING + metadata: + category: screen-capture + summary: Application sets recents screenshot enabled + message: "[MASVS-PLATFORM] Application controls whether recents screenshots are enabled." + pattern-either: + - patterns: + - pattern: $A.setRecentsScreenshotEnabled($B) + - metavariable-regex: + metavariable: $B + regex: ^(true|false)$ + - patterns: + - pattern: setRecentsScreenshotEnabled($B) + - metavariable-regex: + metavariable: $B + regex: ^(true|false)$ + + - id: android-secure-flag-policy-info + languages: [java] + severity: INFO + metadata: + category: screen-capture + summary: DialogProperties created with SecureFlagPolicy.SecureOn + message: "[MASVS-PLATFORM] DialogProperties uses SecureFlagPolicy.SecureOn." + pattern-either: + - pattern: new DialogProperties($A, $B, SecureFlagPolicy.SecureOn, ...) + + - id: android-secure-flag-policy-warn + languages: [java] + severity: WARNING + metadata: + category: screen-capture + summary: DialogProperties created without SecureFlagPolicy.SecureOn + message: "[MASVS-PLATFORM] DialogProperties does not set SecureFlagPolicy.SecureOn. Screens may be capturable." + pattern-either: + - patterns: + - pattern: new DialogProperties($A, $B, $P, ...) + - metavariable-regex: + metavariable: $P + regex: "^(?!SecureFlagPolicy\\.SecureOn$).+" + - patterns: + - pattern: new DialogProperties($A, $B, $P, ...) + - metavariable-regex: + metavariable: $P + regex: "SecureFlagPolicy^(?!\\.SecureOn$).+" + + - id: android-set-recents-screenshot-enabled + languages: [java] + severity: INFO + metadata: + category: screen-capture + summary: Application sets recents screenshot enabled to true + message: "[MASVS-PLATFORM] Application sets recents screenshot enabled to true." + pattern-either: + - pattern: $A.setRecentsScreenshotEnabled(true) + - pattern: setRecentsScreenshotEnabled(true) + + - id: android-set-recents-screenshot-disabled + languages: [java] + severity: WARNING + metadata: + category: screen-capture + summary: Application sets recents screenshot enabled to false + message: "[MASVS-PLATFORM] Application sets recents screenshot enabled to false." + pattern-either: + - pattern: $A.setRecentsScreenshotEnabled(false) + - pattern: setRecentsScreenshotEnabled(false) From 870fca762d7dd159dcab8dd37478b931d234087a Mon Sep 17 00:00:00 2001 From: Carlos Holguera Date: Sun, 31 Aug 2025 18:02:20 +0200 Subject: [PATCH 04/28] Add guidelines for identifying sensitive screens during testing --- prerequisites/identify-sensitive-screens.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 prerequisites/identify-sensitive-screens.md diff --git a/prerequisites/identify-sensitive-screens.md b/prerequisites/identify-sensitive-screens.md new file mode 100644 index 00000000000..e7a7ef5995f --- /dev/null +++ b/prerequisites/identify-sensitive-screens.md @@ -0,0 +1,9 @@ +## Identify Sensitive Screens + +When testing for sensitive data exposure in screenshots, the tester must first know which screens or UI states contain confidential information based on the app's business logic and threat model. Typical examples include login forms, banking details, payment cards, personal identifiers, chat messages, medical records, or corporate documents. + +During test scoping or walkthrough, developers or security teams should provide a list of app flows where sensitive data appears. The tester can then navigate to those screens and attempt to take screenshots or background the app to verify whether the data is exposed. + +This context is equally important for automated testing. Automated test suites cannot reliably determine which screens contain sensitive data unless that information is explicitly documented and shared. Without it, tests may be inconsistent across different runs or environments and results may be difficult to reproduce, which weakens their value for both manual and automated analysis. + +Clear communication of sensitive screens ensures that testing is targeted, consistent, and reproducible, allowing security analysts, developers, and QA teams to verify protections with confidence. From 875244f46c4f4004a6a02faf3a36b77ae55600ae Mon Sep 17 00:00:00 2001 From: Carlos Holguera Date: Wed, 3 Sep 2025 11:07:36 +0200 Subject: [PATCH 05/28] Update FLAG_SECURE usage examples and documentation in Android MASTG-DEMO-0061 - Revise title and sample description to clarify the use of FLAG_SECURE in Activities and Dialogs. - Enhance the implementation in `MastgTest.kt` to demonstrate setting and clearing FLAG_SECURE for both Activity and Dialog. - Update `MastgTest_reversed.java` to reflect changes in the FLAG_SECURE handling. - Modify output.txt to include multiple code findings related to FLAG_SECURE usage. --- .../MASTG-DEMO-0061/MASTG-DEMO-0061.md | 22 ++++++++++---- .../MASTG-DEMO-0061/MastgTest.kt | 19 +++++++++++- .../MASTG-DEMO-0061/MastgTest_reversed.java | 18 +++++++++++- .../MASVS-PLATFORM/MASTG-DEMO-0061/output.txt | 29 +++++++++++++++---- 4 files changed, 74 insertions(+), 14 deletions(-) diff --git a/demos/android/MASVS-PLATFORM/MASTG-DEMO-0061/MASTG-DEMO-0061.md b/demos/android/MASVS-PLATFORM/MASTG-DEMO-0061/MASTG-DEMO-0061.md index f34e98773b7..5edc7564aef 100644 --- a/demos/android/MASVS-PLATFORM/MASTG-DEMO-0061/MASTG-DEMO-0061.md +++ b/demos/android/MASVS-PLATFORM/MASTG-DEMO-0061/MASTG-DEMO-0061.md @@ -1,6 +1,6 @@ --- 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 @@ -8,13 +8,13 @@ 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 }} +{{ MastgTest.kt # MastgTest_reversed.java # MastgTestKt_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 }} @@ -22,10 +22,20 @@ Let's run our @MASTG-TOOL-0110 rule against the reversed java code. ### 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) diff --git a/demos/android/MASVS-PLATFORM/MASTG-DEMO-0061/MastgTest.kt b/demos/android/MASVS-PLATFORM/MASTG-DEMO-0061/MastgTest.kt index 808fa9c4948..ca9e32a9cb9 100644 --- a/demos/android/MASVS-PLATFORM/MASTG-DEMO-0061/MastgTest.kt +++ b/demos/android/MASVS-PLATFORM/MASTG-DEMO-0061/MastgTest.kt @@ -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 @@ -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" } + } } diff --git a/demos/android/MASVS-PLATFORM/MASTG-DEMO-0061/MastgTest_reversed.java b/demos/android/MASVS-PLATFORM/MASTG-DEMO-0061/MastgTest_reversed.java index 373ab3d0475..e86ebc2b06f 100644 --- a/demos/android/MASVS-PLATFORM/MASTG-DEMO-0061/MastgTest_reversed.java +++ b/demos/android/MASVS-PLATFORM/MASTG-DEMO-0061/MastgTest_reversed.java @@ -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; @@ -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"; } diff --git a/demos/android/MASVS-PLATFORM/MASTG-DEMO-0061/output.txt b/demos/android/MASVS-PLATFORM/MASTG-DEMO-0061/output.txt index 681173a0341..be65c681871 100644 --- a/demos/android/MASVS-PLATFORM/MASTG-DEMO-0061/output.txt +++ b/demos/android/MASVS-PLATFORM/MASTG-DEMO-0061/output.txt @@ -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); From 1a6695738d8a06a8e57568de9c9118b6b6fb8baf Mon Sep 17 00:00:00 2001 From: Carlos Holguera Date: Wed, 3 Sep 2025 11:07:59 +0200 Subject: [PATCH 06/28] Add new MASTG-DEMO-0062 for setRecentsScreenshotEnabled in Android 14+ --- .../MASTG-DEMO-0062/MASTG-DEMO-0062.md | 31 ++++++++++++++ .../MASTG-DEMO-0062/MastgTest.kt | 27 ++++++++++++ .../MASTG-DEMO-0062/MastgTest_reversed.java | 41 +++++++++++++++++++ .../MASVS-PLATFORM/MASTG-DEMO-0062/output.txt | 12 ++++++ .../MASVS-PLATFORM/MASTG-DEMO-0062/run.sh | 1 + 5 files changed, 112 insertions(+) create mode 100644 demos/android/MASVS-PLATFORM/MASTG-DEMO-0062/MASTG-DEMO-0062.md create mode 100644 demos/android/MASVS-PLATFORM/MASTG-DEMO-0062/MastgTest.kt create mode 100644 demos/android/MASVS-PLATFORM/MASTG-DEMO-0062/MastgTest_reversed.java create mode 100644 demos/android/MASVS-PLATFORM/MASTG-DEMO-0062/output.txt create mode 100755 demos/android/MASVS-PLATFORM/MASTG-DEMO-0062/run.sh diff --git a/demos/android/MASVS-PLATFORM/MASTG-DEMO-0062/MASTG-DEMO-0062.md b/demos/android/MASVS-PLATFORM/MASTG-DEMO-0062/MASTG-DEMO-0062.md new file mode 100644 index 00000000000..7465d3908ba --- /dev/null +++ b/demos/android/MASVS-PLATFORM/MASTG-DEMO-0062/MASTG-DEMO-0062.md @@ -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. diff --git a/demos/android/MASVS-PLATFORM/MASTG-DEMO-0062/MastgTest.kt b/demos/android/MASVS-PLATFORM/MASTG-DEMO-0062/MastgTest.kt new file mode 100644 index 00000000000..9bdc8dedf6d --- /dev/null +++ b/demos/android/MASVS-PLATFORM/MASTG-DEMO-0062/MastgTest.kt @@ -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" + } + + } +} diff --git a/demos/android/MASVS-PLATFORM/MASTG-DEMO-0062/MastgTest_reversed.java b/demos/android/MASVS-PLATFORM/MASTG-DEMO-0062/MastgTest_reversed.java new file mode 100644 index 00000000000..66210339cce --- /dev/null +++ b/demos/android/MASVS-PLATFORM/MASTG-DEMO-0062/MastgTest_reversed.java @@ -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;", "", "(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"; + } +} diff --git a/demos/android/MASVS-PLATFORM/MASTG-DEMO-0062/output.txt b/demos/android/MASVS-PLATFORM/MASTG-DEMO-0062/output.txt new file mode 100644 index 00000000000..76f806e7a65 --- /dev/null +++ b/demos/android/MASVS-PLATFORM/MASTG-DEMO-0062/output.txt @@ -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); + diff --git a/demos/android/MASVS-PLATFORM/MASTG-DEMO-0062/run.sh b/demos/android/MASVS-PLATFORM/MASTG-DEMO-0062/run.sh new file mode 100755 index 00000000000..6a99723e55e --- /dev/null +++ b/demos/android/MASVS-PLATFORM/MASTG-DEMO-0062/run.sh @@ -0,0 +1 @@ +NO_COLOR=true semgrep -c ../../../../rules/mastg-android-sensitive-data-in-screenshot.yml ./MastgTest_reversed.java > output.txt From 6604db30033ffad419c42e5b9089d94dff5a5168 Mon Sep 17 00:00:00 2001 From: Carlos Holguera Date: Wed, 3 Sep 2025 11:08:51 +0200 Subject: [PATCH 07/28] Add new MASTG-DEMO-0063 for Jetpack Compose Dialogs --- .../MASTG-DEMO-0063/MASTG-DEMO-0063.md | 31 ++++ .../MASTG-DEMO-0063/MastgTest.kt | 67 +++++++ .../MASTG-DEMO-0063/MastgTestKt_reversed.java | 170 ++++++++++++++++++ .../MASTG-DEMO-0063/MastgTest_reversed.java | 93 ++++++++++ .../MASVS-PLATFORM/MASTG-DEMO-0063/output.txt | 15 ++ .../MASVS-PLATFORM/MASTG-DEMO-0063/run.sh | 1 + 6 files changed, 377 insertions(+) create mode 100644 demos/android/MASVS-PLATFORM/MASTG-DEMO-0063/MASTG-DEMO-0063.md create mode 100644 demos/android/MASVS-PLATFORM/MASTG-DEMO-0063/MastgTest.kt create mode 100644 demos/android/MASVS-PLATFORM/MASTG-DEMO-0063/MastgTestKt_reversed.java create mode 100644 demos/android/MASVS-PLATFORM/MASTG-DEMO-0063/MastgTest_reversed.java create mode 100644 demos/android/MASVS-PLATFORM/MASTG-DEMO-0063/output.txt create mode 100755 demos/android/MASVS-PLATFORM/MASTG-DEMO-0063/run.sh diff --git a/demos/android/MASVS-PLATFORM/MASTG-DEMO-0063/MASTG-DEMO-0063.md b/demos/android/MASVS-PLATFORM/MASTG-DEMO-0063/MASTG-DEMO-0063.md new file mode 100644 index 00000000000..5fb2ec4cc72 --- /dev/null +++ b/demos/android/MASVS-PLATFORM/MASTG-DEMO-0063/MASTG-DEMO-0063.md @@ -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. diff --git a/demos/android/MASVS-PLATFORM/MASTG-DEMO-0063/MastgTest.kt b/demos/android/MASVS-PLATFORM/MASTG-DEMO-0063/MastgTest.kt new file mode 100644 index 00000000000..7e42238c7de --- /dev/null +++ b/demos/android/MASVS-PLATFORM/MASTG-DEMO-0063/MastgTest.kt @@ -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") + } + } + } + } + } +} \ No newline at end of file diff --git a/demos/android/MASVS-PLATFORM/MASTG-DEMO-0063/MastgTestKt_reversed.java b/demos/android/MASVS-PLATFORM/MASTG-DEMO-0063/MastgTestKt_reversed.java new file mode 100644 index 00000000000..71ee3039daa --- /dev/null +++ b/demos/android/MASVS-PLATFORM/MASTG-DEMO-0063/MastgTestKt_reversed.java @@ -0,0 +1,170 @@ +package org.owasp.mastestapp; + +import androidx.compose.foundation.layout.Arrangement; +import androidx.compose.foundation.layout.ColumnKt; +import androidx.compose.foundation.layout.ColumnScopeInstance; +import androidx.compose.foundation.layout.PaddingKt; +import androidx.compose.material3.ButtonKt; +import androidx.compose.material3.MaterialThemeKt; +import androidx.compose.material3.SurfaceKt; +import androidx.compose.material3.TextKt; +import androidx.compose.runtime.Applier; +import androidx.compose.runtime.ComposablesKt; +import androidx.compose.runtime.Composer; +import androidx.compose.runtime.ComposerKt; +import androidx.compose.runtime.CompositionLocalMap; +import androidx.compose.runtime.RecomposeScopeImplKt; +import androidx.compose.runtime.ScopeUpdateScope; +import androidx.compose.runtime.Updater; +import androidx.compose.runtime.internal.ComposableLambdaKt; +import androidx.compose.ui.Alignment; +import androidx.compose.ui.ComposedModifierKt; +import androidx.compose.ui.Modifier; +import androidx.compose.ui.layout.MeasurePolicy; +import androidx.compose.ui.node.ComposeUiNode; +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.window.AndroidDialog_androidKt; +import androidx.compose.ui.window.DialogProperties; +import androidx.compose.ui.window.SecureFlagPolicy; +import kotlin.Metadata; +import kotlin.Unit; +import kotlin.jvm.functions.Function0; +import kotlin.jvm.functions.Function1; +import kotlin.jvm.functions.Function2; +import kotlin.jvm.internal.Intrinsics; +import kotlinx.coroutines.scheduling.WorkQueueKt; + +/* compiled from: MastgTest.kt */ +@Metadata(d1 = {"\u0000\u0010\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0002\u001a\u001b\u0010\u0000\u001a\u00020\u00012\f\u0010\u0002\u001a\b\u0012\u0004\u0012\u00020\u00010\u0003H\u0003¢\u0006\u0002\u0010\u0004¨\u0006\u0005"}, d2 = {"SecureComposeDialog", "", "onDismiss", "Lkotlin/Function0;", "(Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;I)V", "app_debug"}, k = 2, mv = {2, 0, 0}, xi = 48) +/* loaded from: classes3.dex */ +public final class MastgTestKt { + /* JADX INFO: Access modifiers changed from: private */ + public static final Unit SecureComposeDialog$lambda$0(Function0 onDismiss, int i, Composer composer, int i2) { + Intrinsics.checkNotNullParameter(onDismiss, "$onDismiss"); + SecureComposeDialog(onDismiss, composer, RecomposeScopeImplKt.updateChangedFlags(i | 1)); + return Unit.INSTANCE; + } + + /* JADX INFO: Access modifiers changed from: private */ + public static final void SecureComposeDialog(final Function0 function0, Composer $composer, final int $changed) { + Composer $composer2 = $composer.startRestartGroup(69645263); + ComposerKt.sourceInformation($composer2, "C(SecureComposeDialog)56@1997L462,53@1864L595:MastgTest.kt#vyvp3i"); + int $dirty = $changed; + if (($changed & 14) == 0) { + $dirty |= $composer2.changedInstance(function0) ? 4 : 2; + } + if (($dirty & 11) != 2 || !$composer2.getSkipping()) { + AndroidDialog_androidKt.Dialog(function0, new DialogProperties(false, false, SecureFlagPolicy.SecureOff, false, false, 27, null), ComposableLambdaKt.rememberComposableLambda(-882880282, true, new Function2() { // from class: org.owasp.mastestapp.MastgTestKt.SecureComposeDialog.1 + @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, "C57@2021L432,57@2007L446:MastgTest.kt#vyvp3i"); + if (($changed2 & 11) != 2 || !$composer3.getSkipping()) { + final Function0 function02 = function0; + MaterialThemeKt.MaterialTheme(null, null, null, ComposableLambdaKt.rememberComposableLambda(1688954298, true, new Function2() { // from class: org.owasp.mastestapp.MastgTestKt.SecureComposeDialog.1.1 + @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 $composer4, int $changed3) { + ComposerKt.sourceInformation($composer4, "C58@2043L400,58@2035L408:MastgTest.kt#vyvp3i"); + if (($changed3 & 11) != 2 || !$composer4.getSkipping()) { + final Function0 function03 = function02; + SurfaceKt.m2565SurfaceT9BRK9s(null, null, 0L, 0L, 0.0f, 0.0f, null, ComposableLambdaKt.rememberComposableLambda(-195282113, true, new Function2() { // from class: org.owasp.mastestapp.MastgTestKt.SecureComposeDialog.1.1.1 + @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 $composer5, int $changed4) { + ComposerKt.sourceInformation($composer5, "C59@2061L368:MastgTest.kt#vyvp3i"); + if (($changed4 & 11) != 2 || !$composer5.getSkipping()) { + Modifier modifier$iv = PaddingKt.m681padding3ABfNKs(Modifier.INSTANCE, Dp.m6664constructorimpl(16)); + Function0 function04 = function03; + ComposerKt.sourceInformationMarkerStart($composer5, -483455358, "CC(Column)P(2,3,1)86@4330L61,87@4396L133:Column.kt#2w3rfo"); + Arrangement.Vertical verticalArrangement$iv = Arrangement.INSTANCE.getTop(); + Alignment.Horizontal horizontalAlignment$iv = Alignment.INSTANCE.getStart(); + MeasurePolicy measurePolicy$iv = ColumnKt.columnMeasurePolicy(verticalArrangement$iv, horizontalAlignment$iv, $composer5, ((6 >> 3) & 14) | ((6 >> 3) & 112)); + int $changed$iv$iv = (6 << 3) & 112; + ComposerKt.sourceInformationMarkerStart($composer5, -1323940314, "CC(Layout)P(!1,2)79@3208L23,82@3359L411:Layout.kt#80mrfh"); + int compositeKeyHash$iv$iv = ComposablesKt.getCurrentCompositeKeyHash($composer5, 0); + CompositionLocalMap localMap$iv$iv = $composer5.getCurrentCompositionLocalMap(); + Modifier materialized$iv$iv = ComposedModifierKt.materializeModifier($composer5, modifier$iv); + Function0 factory$iv$iv$iv = ComposeUiNode.INSTANCE.getConstructor(); + int $changed$iv$iv$iv = (($changed$iv$iv << 6) & 896) | 6; + ComposerKt.sourceInformationMarkerStart($composer5, -692256719, "CC(ReusableComposeNode)P(1,2)376@14062L9:Composables.kt#9igjgp"); + if (!($composer5.getApplier() instanceof Applier)) { + ComposablesKt.invalidApplier(); + } + $composer5.startReusableNode(); + if ($composer5.getInserting()) { + $composer5.createNode(factory$iv$iv$iv); + } else { + $composer5.useNode(); + } + Composer $this$Layout_u24lambda_u240$iv$iv = Updater.m3675constructorimpl($composer5); + Updater.m3682setimpl($this$Layout_u24lambda_u240$iv$iv, measurePolicy$iv, ComposeUiNode.INSTANCE.getSetMeasurePolicy()); + Updater.m3682setimpl($this$Layout_u24lambda_u240$iv$iv, localMap$iv$iv, ComposeUiNode.INSTANCE.getSetResolvedCompositionLocals()); + Function2 block$iv$iv$iv = ComposeUiNode.INSTANCE.getSetCompositeKeyHash(); + if ($this$Layout_u24lambda_u240$iv$iv.getInserting() || !Intrinsics.areEqual($this$Layout_u24lambda_u240$iv$iv.rememberedValue(), Integer.valueOf(compositeKeyHash$iv$iv))) { + $this$Layout_u24lambda_u240$iv$iv.updateRememberedValue(Integer.valueOf(compositeKeyHash$iv$iv)); + $this$Layout_u24lambda_u240$iv$iv.apply(Integer.valueOf(compositeKeyHash$iv$iv), block$iv$iv$iv); + } + Updater.m3682setimpl($this$Layout_u24lambda_u240$iv$iv, materialized$iv$iv, ComposeUiNode.INSTANCE.getSetModifier()); + int i = ($changed$iv$iv$iv >> 6) & 14; + ComposerKt.sourceInformationMarkerStart($composer5, -384784025, "C88@4444L9:Column.kt#2w3rfo"); + ColumnScopeInstance columnScopeInstance = ColumnScopeInstance.INSTANCE; + int i2 = ((6 >> 6) & 112) | 6; + ComposerKt.sourceInformationMarkerStart($composer5, 1156231831, "C60@2146L33,61@2200L42,62@2263L148:MastgTest.kt#vyvp3i"); + TextKt.m2715Text4IGK_g("This is a Compose dialog.", (Modifier) null, 0L, 0L, (FontStyle) null, (FontWeight) null, (FontFamily) null, 0L, (TextDecoration) null, (TextAlign) null, 0L, 0, false, 0, 0, (Function1) null, (TextStyle) null, $composer5, 6, 0, 131070); + TextKt.m2715Text4IGK_g("Secure policy is set to SecureOff.", (Modifier) null, 0L, 0L, (FontStyle) null, (FontWeight) null, (FontFamily) null, 0L, (TextDecoration) null, (TextAlign) null, 0L, 0, false, 0, 0, (Function1) null, (TextStyle) null, $composer5, 6, 0, 131070); + ButtonKt.Button(function04, PaddingKt.m685paddingqDBjuR0$default(Modifier.INSTANCE, 0.0f, Dp.m6664constructorimpl(12), 0.0f, 0.0f, 13, null), false, null, null, null, null, null, null, ComposableSingletons$MastgTestKt.INSTANCE.m8560getLambda1$app_debug(), $composer5, 805306416, 508); + ComposerKt.sourceInformationMarkerEnd($composer5); + ComposerKt.sourceInformationMarkerEnd($composer5); + $composer5.endNode(); + ComposerKt.sourceInformationMarkerEnd($composer5); + ComposerKt.sourceInformationMarkerEnd($composer5); + ComposerKt.sourceInformationMarkerEnd($composer5); + return; + } + $composer5.skipToGroupEnd(); + } + }, $composer4, 54), $composer4, 12582912, WorkQueueKt.MASK); + } else { + $composer4.skipToGroupEnd(); + } + } + }, $composer3, 54), $composer3, 3072, 7); + } else { + $composer3.skipToGroupEnd(); + } + } + }, $composer2, 54), $composer2, ($dirty & 14) | 432, 0); + } else { + $composer2.skipToGroupEnd(); + } + ScopeUpdateScope scopeUpdateScopeEndRestartGroup = $composer2.endRestartGroup(); + if (scopeUpdateScopeEndRestartGroup != null) { + scopeUpdateScopeEndRestartGroup.updateScope(new Function2() { // from class: org.owasp.mastestapp.MastgTestKt$$ExternalSyntheticLambda0 + @Override // kotlin.jvm.functions.Function2 + public final Object invoke(Object obj, Object obj2) { + return MastgTestKt.SecureComposeDialog$lambda$0(function0, $changed, (Composer) obj, ((Integer) obj2).intValue()); + } + }); + } + } +} diff --git a/demos/android/MASVS-PLATFORM/MASTG-DEMO-0063/MastgTest_reversed.java b/demos/android/MASVS-PLATFORM/MASTG-DEMO-0063/MastgTest_reversed.java new file mode 100644 index 00000000000..37206cc5ab2 --- /dev/null +++ b/demos/android/MASVS-PLATFORM/MASTG-DEMO-0063/MastgTest_reversed.java @@ -0,0 +1,93 @@ +package org.owasp.mastestapp; + +import android.app.Activity; +import android.content.Context; +import android.view.ViewGroup; +import android.view.ViewParent; +import androidx.compose.runtime.Composer; +import androidx.compose.runtime.ComposerKt; +import androidx.compose.runtime.internal.ComposableLambdaKt; +import androidx.compose.ui.platform.ComposeView; +import kotlin.Metadata; +import kotlin.Unit; +import kotlin.jvm.functions.Function0; +import kotlin.jvm.functions.Function2; +import kotlin.jvm.internal.Intrinsics; +import org.owasp.mastestapp.MastgTest; + +/* 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;", "", "(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) { + ComposeView composeHost = new ComposeView(this.context, null, 0, 6, null); + ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(-1, -1); + composeHost.setContent(ComposableLambdaKt.composableLambdaInstance(-1584904752, true, new AnonymousClass1(composeHost))); + ((Activity) this.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."; + } + return "ERROR: Context is not an Activity"; + } + + /* compiled from: MastgTest.kt */ + @Metadata(k = 3, mv = {2, 0, 0}, xi = 48) + /* renamed from: org.owasp.mastestapp.MastgTest$mastgTest$1, reason: invalid class name */ + static final class AnonymousClass1 implements Function2 { + final /* synthetic */ ComposeView $composeHost; + + AnonymousClass1(ComposeView composeView) { + this.$composeHost = composeView; + } + + @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 $composer, int $changed) { + ComposerKt.sourceInformation($composer, "C34@1210L220:MastgTest.kt#vyvp3i"); + if (($changed & 11) != 2 || !$composer.getSkipping()) { + final ComposeView composeView = this.$composeHost; + MastgTestKt.SecureComposeDialog(new Function0() { // from class: org.owasp.mastestapp.MastgTest$mastgTest$1$$ExternalSyntheticLambda0 + @Override // kotlin.jvm.functions.Function0 + public final Object invoke() { + return MastgTest.AnonymousClass1.invoke$lambda$0(composeView); + } + }, $composer, 0); + } else { + $composer.skipToGroupEnd(); + } + } + + /* JADX INFO: Access modifiers changed from: private */ + public static final Unit invoke$lambda$0(ComposeView composeHost) { + Intrinsics.checkNotNullParameter(composeHost, "$composeHost"); + ViewParent parent = composeHost.getParent(); + ViewGroup parent2 = parent instanceof ViewGroup ? (ViewGroup) parent : null; + if (parent2 != null) { + parent2.removeView(composeHost); + } + return Unit.INSTANCE; + } + } +} diff --git a/demos/android/MASVS-PLATFORM/MASTG-DEMO-0063/output.txt b/demos/android/MASVS-PLATFORM/MASTG-DEMO-0063/output.txt new file mode 100644 index 00000000000..6d8c2e3bf98 --- /dev/null +++ b/demos/android/MASVS-PLATFORM/MASTG-DEMO-0063/output.txt @@ -0,0 +1,15 @@ + + +┌────────────────┐ +│ 1 Code Finding │ +└────────────────┘ + + MastgTestKt_reversed.java + ❯❱ rules.android-secure-flag-policy-warn + [MASVS-PLATFORM] DialogProperties does not set SecureFlagPolicy.SecureOn. Screens may be capturable. + + 64┆ AndroidDialog_androidKt.Dialog(function0, new DialogProperties(false, false, + SecureFlagPolicy.SecureOff, false, false, 27, null), + ComposableLambdaKt.rememberComposableLambda(-882880282, true, new Function2() { // from class: org.owasp.mastestapp.MastgTestKt.SecureComposeDialog.1 + diff --git a/demos/android/MASVS-PLATFORM/MASTG-DEMO-0063/run.sh b/demos/android/MASVS-PLATFORM/MASTG-DEMO-0063/run.sh new file mode 100755 index 00000000000..21ebdf50560 --- /dev/null +++ b/demos/android/MASVS-PLATFORM/MASTG-DEMO-0063/run.sh @@ -0,0 +1 @@ +NO_COLOR=true semgrep -c ../../../../rules/mastg-android-sensitive-data-in-screenshot.yml ./MastgTest_reversed.java ./MastgTestKt_reversed.java > output.txt From fa08647c3e50351cb3be3ffc7a39ad54ee6ddb6e Mon Sep 17 00:00:00 2001 From: Carlos Holguera Date: Wed, 3 Sep 2025 11:09:26 +0200 Subject: [PATCH 08/28] Remove redundant rules for recents screenshot enabled/disabled in sensitive data handling --- ...g-android-sensitive-data-in-screenshot.yml | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/rules/mastg-android-sensitive-data-in-screenshot.yml b/rules/mastg-android-sensitive-data-in-screenshot.yml index 56a48ddd810..0462c104a38 100644 --- a/rules/mastg-android-sensitive-data-in-screenshot.yml +++ b/rules/mastg-android-sensitive-data-in-screenshot.yml @@ -83,25 +83,3 @@ rules: - metavariable-regex: metavariable: $P regex: "SecureFlagPolicy^(?!\\.SecureOn$).+" - - - id: android-set-recents-screenshot-enabled - languages: [java] - severity: INFO - metadata: - category: screen-capture - summary: Application sets recents screenshot enabled to true - message: "[MASVS-PLATFORM] Application sets recents screenshot enabled to true." - pattern-either: - - pattern: $A.setRecentsScreenshotEnabled(true) - - pattern: setRecentsScreenshotEnabled(true) - - - id: android-set-recents-screenshot-disabled - languages: [java] - severity: WARNING - metadata: - category: screen-capture - summary: Application sets recents screenshot enabled to false - message: "[MASVS-PLATFORM] Application sets recents screenshot enabled to false." - pattern-either: - - pattern: $A.setRecentsScreenshotEnabled(false) - - pattern: setRecentsScreenshotEnabled(false) From 771a7fcf8a5e6d6c285008588515399ab516f4d7 Mon Sep 17 00:00:00 2001 From: Carlos Holguera Date: Wed, 3 Sep 2025 11:50:33 +0200 Subject: [PATCH 09/28] Enhance documentation for screen capture prevention APIs, including detailed guidelines for FLAG_SECURE and Recents screenshot handling in Android 13+ --- .../android/MASVS-PLATFORM/MASTG-TEST-0291.md | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0291.md b/tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0291.md index 099718dbb64..3678c80acfa 100644 --- a/tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0291.md +++ b/tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0291.md @@ -10,9 +10,32 @@ weakness: MASWE-0055 ## Overview -This test verifies whether an app references Android screen capture prevention APIs. On Android, developers can prevent screenshots and nonsecure display mirroring using [`FLAG_SECURE`](https://developer.android.com/security/fraud-prevention/activities#flag_secure). When set, Android blocks screenshots and prevents content from appearing on a nonsecure display, including remote screen sharing. Users see a blank screen if they attempt a screenshot or when the app moves to the background. +This test verifies whether an app references Android screen capture prevention APIs. On Android, developers can prevent screenshots and non-secure display mirroring using [`FLAG_SECURE`](https://developer.android.com/security/fraud-prevention/activities#flag_secure). When set, Android blocks screenshots and prevents content from appearing on a non-secure display, including remote screen sharing. Screenshots are blocked (resulting images are blank/obscured and/or the system shows a policy message), and the app's content is not shown in Recents when backgrounded. -Developers typically apply the flag with [`addFlags()`](https://developer.android.com/reference/android/view/Window#addFlags(int)) or [`setFlags()`](https://developer.android.com/reference/android/view/Window#setFlags(int,int)). Common failure modes include not setting `FLAG_SECURE` on all sensitive screens or clearing the flag during transitions e.g., using [`clearFlags()`](https://developer.android.com/reference/android/view/Window#clearFlags(int)) or `setFlags()`. +**WindowManager-based UI Components:** + +Developers must set `FLAG_SECURE` on each window that displays sensitive content. Common components include Activities, Dialogs, DialogFragments, AlertDialogs, and PopupWindows or any custom Window via WindowManager. + +The flag is typically applied with [`addFlags()`](https://developer.android.com/reference/android/view/Window#addFlags(int)) or [`setFlags()`](https://developer.android.com/reference/android/view/Window#setFlags(int,int)). Common failure modes include not setting `FLAG_SECURE` on all sensitive screens or clearing the flag during transitions e.g., using [`clearFlags()`](https://developer.android.com/reference/android/view/Window#clearFlags(int)) or `setFlags()`. + +**Compose Dialogs:** + +In Compose dialogs, developers can set the secure policy which enables or disables the `FLAG_SECURE` flag to the dialog window using: + +- [`SecureOn`](https://developer.android.com/reference/kotlin/androidx/compose/ui/window/SecureFlagPolicy#SecureOn) +- [`Inherit`](https://developer.android.com/reference/kotlin/androidx/compose/ui/window/SecureFlagPolicy#Inherit) +- [`SecureOff`](https://developer.android.com/reference/kotlin/androidx/compose/ui/window/SecureFlagPolicy#SecureOff) + +**Recents Screenshots:** + +Starting with Android 13 (API level 33), developers can disable Recents screenshots using [`setRecentsScreenshotEnabled()`](https://developer.android.com/reference/android/app/Activity#setRecentsScreenshotEnabled(boolean)): + +- This prevents sensitive content from appearing in the system-generated snapshots used for the Recents screen: + - the snapshots saved to `/data/system_ce//snapshots` will be blank/obscured. + - the previews blank after the user switches away from the app completely, similar to the behavior of `FLAG_SECURE` +- It does not prevent user-initiated screenshots or screen recordings. + - Users can take screenshots using the hardware shortcut and the content will appear in those screenshots. + - Tapping "Screenshot" in Recents will not produce a snapshot (the system may show a policy message depending on device/ROM). ## Steps @@ -25,3 +48,11 @@ The output should include a list of locations where the relevant APIs are used. ## Evaluation The test case fails if the relevant APIs are missing or inconsistently applied on any UI component that displays sensitive data, or if code paths clear the protection without an adequate justification. + +NOTE: Detecting all possible instances of `FLAG_SECURE` can be challenging: + +- This flag cannot be simply applied globally, every component that creates a window must explicitly set this flag, e.g. Activities, Dialogs, DialogFragments, AlertDialog, PopupWindow or any custom Window via WindowManager. Sometimes apps use a base UI class and make all sensitive screens inherit it. +- The lifecycle of the app must be considered, as the flag needs to be set whenever a new window is created. For example, trying to add the flag in the `onPause()` isn't effective, as the preview is created before this method is called. +- Special care must be taken with fragments and their lifecycle, as they can be added or removed dynamically. + +Also note that even if the flag is correctly set, the content isn't obscured right away when the user switches away from the app. The user must manually switch to another app or home screen for the content to be obscured. From 81c631092d833d9a2db24386eadf9a0c25e9d46b Mon Sep 17 00:00:00 2001 From: Carlos Holguera Date: Sat, 6 Sep 2025 09:43:49 +0200 Subject: [PATCH 10/28] rm extra ref to file --- demos/android/MASVS-PLATFORM/MASTG-DEMO-0061/MASTG-DEMO-0061.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demos/android/MASVS-PLATFORM/MASTG-DEMO-0061/MASTG-DEMO-0061.md b/demos/android/MASVS-PLATFORM/MASTG-DEMO-0061/MASTG-DEMO-0061.md index 5edc7564aef..5f7e4a7ad6f 100644 --- a/demos/android/MASVS-PLATFORM/MASTG-DEMO-0061/MASTG-DEMO-0061.md +++ b/demos/android/MASVS-PLATFORM/MASTG-DEMO-0061/MASTG-DEMO-0061.md @@ -10,7 +10,7 @@ test: MASTG-TEST-0291 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 # MastgTestKt_reversed.java }} +{{ MastgTest.kt # MastgTest_reversed.java }} ### Steps From 7af35bd07889303053ceff3078b49052d6cecc56 Mon Sep 17 00:00:00 2001 From: Carlos Holguera Date: Sat, 6 Sep 2025 10:50:27 +0200 Subject: [PATCH 11/28] refine documentation on FLAG_SECURE usage for Android UI components --- .../android/MASVS-PLATFORM/MASTG-TEST-0291.md | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0291.md b/tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0291.md index 3678c80acfa..11d07559863 100644 --- a/tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0291.md +++ b/tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0291.md @@ -12,11 +12,24 @@ weakness: MASWE-0055 This test verifies whether an app references Android screen capture prevention APIs. On Android, developers can prevent screenshots and non-secure display mirroring using [`FLAG_SECURE`](https://developer.android.com/security/fraud-prevention/activities#flag_secure). When set, Android blocks screenshots and prevents content from appearing on a non-secure display, including remote screen sharing. Screenshots are blocked (resulting images are blank/obscured and/or the system shows a policy message), and the app's content is not shown in Recents when backgrounded. -**WindowManager-based UI Components:** +**UI Components Exposing Window Getters or LayoutParams:** -Developers must set `FLAG_SECURE` on each window that displays sensitive content. Common components include Activities, Dialogs, DialogFragments, AlertDialogs, and PopupWindows or any custom Window via WindowManager. +`FLAG_SECURE` is applied per-window. Any UI that creates its own `Window` must set it if sensitive content can appear there. This includes all components that: -The flag is typically applied with [`addFlags()`](https://developer.android.com/reference/android/view/Window#addFlags(int)) or [`setFlags()`](https://developer.android.com/reference/android/view/Window#setFlags(int,int)). Common failure modes include not setting `FLAG_SECURE` on all sensitive screens or clearing the flag during transitions e.g., using [`clearFlags()`](https://developer.android.com/reference/android/view/Window#clearFlags(int)) or `setFlags()`. +- can call `getWindow()` (e.g., `Activity`, `Dialog`, `Presentation`) +- expose `.window` (e.g., `DialogFragment`) +- allow setting `LayoutParams` (e.g., overlays or custom views added with `WindowManager.addView()`) + +Other components that create separate windows but do not expose `getWindow()` directly (e.g., `PopupWindow`) cannot have `FLAG_SECURE` applied directly. + +The flag is applied with [`addFlags()`](https://developer.android.com/reference/android/view/Window#addFlags(int)) or [`setFlags()`](https://developer.android.com/reference/android/view/Window#setFlags(int,int)). Common failure modes include: + +- Missing a window: not setting `FLAG_SECURE` on all windows that can show sensitive data. +- Lifecycle gaps: setting too late or clearing during transitions (e.g., using [`clearFlags()`](https://developer.android.com/reference/android/view/Window#clearFlags(int)) or `setFlags()` without re-applying). Remember previews for Recents can be captured before callbacks like `onPause()`. + +**SurfaceViews:** + +`SurfaceView` and its subclasses (e.g., `VideoView`, `GLSurfaceView`) create a separate surface that is not protected by `FLAG_SECURE` even if the containing window has it set. To protect content in a `SurfaceView`, developers must use [`setSecure(true)`](https://developer.android.com/reference/android/view/SurfaceView#setSecure(boolean)) before the surface view's containing window is attached to the window manager. **Compose Dialogs:** From a6bbca36009d7662413da0c6950138323b3aba4c Mon Sep 17 00:00:00 2001 From: Carlos Holguera Date: Sat, 6 Sep 2025 11:00:39 +0200 Subject: [PATCH 12/28] refine wording and clarity in MASTG-TEST-0291.md regarding FLAG_SECURE usage --- .../android/MASVS-PLATFORM/MASTG-TEST-0291.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0291.md b/tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0291.md index 11d07559863..54b22178952 100644 --- a/tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0291.md +++ b/tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0291.md @@ -10,11 +10,11 @@ weakness: MASWE-0055 ## Overview -This test verifies whether an app references Android screen capture prevention APIs. On Android, developers can prevent screenshots and non-secure display mirroring using [`FLAG_SECURE`](https://developer.android.com/security/fraud-prevention/activities#flag_secure). When set, Android blocks screenshots and prevents content from appearing on a non-secure display, including remote screen sharing. Screenshots are blocked (resulting images are blank/obscured and/or the system shows a policy message), and the app's content is not shown in Recents when backgrounded. +This test verifies whether an app references Android screen capture prevention APIs. On Android, developers can prevent screenshots and non-secure display mirroring using [`FLAG_SECURE`](https://developer.android.com/security/fraud-prevention/activities#flag_secure). When set, Android blocks screenshots, prevents content from appearing on non-secure displays (including remote screen sharing), and blocks capture through MediaProjection or virtual displays. Screenshots are blocked (resulting images are blank or obscured and the system may show a policy message), and the app's content is not shown in Recents when backgrounded. **UI Components Exposing Window Getters or LayoutParams:** -`FLAG_SECURE` is applied per-window. Any UI that creates its own `Window` must set it if sensitive content can appear there. This includes all components that: +`FLAG_SECURE` is applied per window. Any UI that creates its own `Window` must set it if sensitive content can appear there. This includes all components that: - can call `getWindow()` (e.g., `Activity`, `Dialog`, `Presentation`) - expose `.window` (e.g., `DialogFragment`) @@ -25,11 +25,11 @@ Other components that create separate windows but do not expose `getWindow()` di The flag is applied with [`addFlags()`](https://developer.android.com/reference/android/view/Window#addFlags(int)) or [`setFlags()`](https://developer.android.com/reference/android/view/Window#setFlags(int,int)). Common failure modes include: - Missing a window: not setting `FLAG_SECURE` on all windows that can show sensitive data. -- Lifecycle gaps: setting too late or clearing during transitions (e.g., using [`clearFlags()`](https://developer.android.com/reference/android/view/Window#clearFlags(int)) or `setFlags()` without re-applying). Remember previews for Recents can be captured before callbacks like `onPause()`. +- Lifecycle gaps: setting too late or clearing during transitions (e.g., using [`clearFlags()`](https://developer.android.com/reference/android/view/Window#clearFlags(int)) or `setFlags()` without reapplying). Sensitive previews for Recents can be captured before callbacks like `onPause()`. **SurfaceViews:** -`SurfaceView` and its subclasses (e.g., `VideoView`, `GLSurfaceView`) create a separate surface that is not protected by `FLAG_SECURE` even if the containing window has it set. To protect content in a `SurfaceView`, developers must use [`setSecure(true)`](https://developer.android.com/reference/android/view/SurfaceView#setSecure(boolean)) before the surface view's containing window is attached to the window manager. +`SurfaceView` create a separate surface that is not protected by `FLAG_SECURE` even if the containing window has it set. To protect content in a `SurfaceView`, developers must use [`setSecure(true)`](https://developer.android.com/reference/android/view/SurfaceView#setSecure(boolean)) before the surface view's containing window is attached to the window manager. **Compose Dialogs:** @@ -64,8 +64,8 @@ The test case fails if the relevant APIs are missing or inconsistently applied o NOTE: Detecting all possible instances of `FLAG_SECURE` can be challenging: -- This flag cannot be simply applied globally, every component that creates a window must explicitly set this flag, e.g. Activities, Dialogs, DialogFragments, AlertDialog, PopupWindow or any custom Window via WindowManager. Sometimes apps use a base UI class and make all sensitive screens inherit it. -- The lifecycle of the app must be considered, as the flag needs to be set whenever a new window is created. For example, trying to add the flag in the `onPause()` isn't effective, as the preview is created before this method is called. +- This flag cannot be applied globally. Every component that creates a window must explicitly set it, e.g. `Activity`, `Dialog`, `DialogFragment`, `AlertDialog`, or any custom window added via `WindowManager`. Sometimes apps use a base UI class and make all sensitive screens inherit it. +- The lifecycle of the app must be considered, as the flag needs to be set before sensitive content is rendered in a new window. For example, trying to add the flag in `onPause()` is not effective, as the preview is created before this method is called. - Special care must be taken with fragments and their lifecycle, as they can be added or removed dynamically. -Also note that even if the flag is correctly set, the content isn't obscured right away when the user switches away from the app. The user must manually switch to another app or home screen for the content to be obscured. +Also note that even if the flag is correctly set, the content is not obscured until the user actually switches to another app or the home screen. From e4d875446dd04a7d66ada80e4b8488106b36c03d Mon Sep 17 00:00:00 2001 From: Carlos Holguera Date: Sat, 6 Sep 2025 11:09:57 +0200 Subject: [PATCH 13/28] fix regex --- rules/mastg-android-sensitive-data-in-screenshot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules/mastg-android-sensitive-data-in-screenshot.yml b/rules/mastg-android-sensitive-data-in-screenshot.yml index 0462c104a38..8ade1d33dec 100644 --- a/rules/mastg-android-sensitive-data-in-screenshot.yml +++ b/rules/mastg-android-sensitive-data-in-screenshot.yml @@ -82,4 +82,4 @@ rules: - pattern: new DialogProperties($A, $B, $P, ...) - metavariable-regex: metavariable: $P - regex: "SecureFlagPolicy^(?!\\.SecureOn$).+" + regex: "^SecureFlagPolicy(?!\\.SecureOn$).+" From 1c3369a5e7ba09fff9ed4e184c302402949f2353 Mon Sep 17 00:00:00 2001 From: Carlos Date: Sat, 6 Sep 2025 18:09:38 +0200 Subject: [PATCH 14/28] Split best practices for preventing screenshots in Android apps. --- best-practices/MASTG-BEST-0014.md | 20 ++++---------------- best-practices/MASTG-BEST-0015.md | 27 +++++++++++++++++++++++++++ best-practices/MASTG-BEST-0016.md | 31 +++++++++++++++++++++++++++++++ best-practices/MASTG-BEST-0017.md | 11 +++++++++++ best-practices/MASTG-BEST-0018.md | 11 +++++++++++ 5 files changed, 84 insertions(+), 16 deletions(-) create mode 100644 best-practices/MASTG-BEST-0015.md create mode 100644 best-practices/MASTG-BEST-0016.md create mode 100644 best-practices/MASTG-BEST-0017.md create mode 100644 best-practices/MASTG-BEST-0018.md diff --git a/best-practices/MASTG-BEST-0014.md b/best-practices/MASTG-BEST-0014.md index 609024e885b..04f7c601886 100644 --- a/best-practices/MASTG-BEST-0014.md +++ b/best-practices/MASTG-BEST-0014.md @@ -3,28 +3,16 @@ title: Preventing Screenshots and Screen Recording alias: preventing-screenshots-and-screen-recording id: MASTG-BEST-0014 platform: android +parent: None --- -Apps should prevent sensitive data from being captured from the UI, leaving the control of the user, and potentially leaking. This includes: +Apps should prevent sensitive data from being captured from the UI, leaving the control of the user, and potentially leaking. This includes the following considerations: - **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. +- **Non-secure 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. - -
-
- Task switcher without FLAG_SECURE -
Without FLAG_SECURE
-
-
- Task switcher with FLAG_SECURE -
With FLAG_SECURE
-
-
- -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. +**Consider adding an in-app setting** to enable or disable this behavior, giving users control without compromising security by default. diff --git a/best-practices/MASTG-BEST-0015.md b/best-practices/MASTG-BEST-0015.md new file mode 100644 index 00000000000..7307615a7f3 --- /dev/null +++ b/best-practices/MASTG-BEST-0015.md @@ -0,0 +1,27 @@ +--- +title: Use `setRecentsScreenshotEnabled` to Prevent Screenshots When Backgrounded +alias: preventing-screenshots-when-backgrounded +id: MASTG-BEST-0015 +parent: MASTG-BEST-0014 +platform: android +component: android.app.Activity +available_since: 33 +--- + +Preventing screenshots when an app is backgrounded helps protect sensitive data from being exposed in system-generated snapshots used by the [Recents screen](https://developer.android.com/guide/components/activities/recents). This is especially important for apps that handle sensitive information, such as banking, healthcare, or enterprise apps. For example, a banking app should ensure that account balances, transaction details, or card numbers never appear in the Recents screen, unless the user explicitly chooses to allow it using an in-app setting. + +Even though **system-generated snapshots** are sandboxed, they may still be accessible on rooted or compromised devices or through forensic acquisition. + +Using `FLAG_SECURE` consistently on all UI components as indicated in @MASTG-BEST-0014 helps prevent system-generated snapshots from showing sensitive data in the [Recents screen](https://developer.android.com/guide/components/activities/recents) when the app is backgrounded. However, getting it right can be challenging, as there's no global setting to enforce it across the entire app. + +Starting with Android 13 (API level 33), you can use [`setRecentsScreenshotEnabled(false)`](https://developer.android.com/reference/android/app/Activity#setRecentsScreenshotEnabled(boolean)) to ensure that the app's content is not shown in the Recents screen when backgrounded. + +- The snapshots saved to `/data/system_ce//snapshots` will be blank/obscured. +- The previews in the Recents screen will be blank after the user switches away from the app completely, similar to the behavior of `FLAG_SECURE`. + +!!! warning "What `setRecentsScreenshotEnabled` Does Not" + + Setting `setRecentsScreenshotEnabled(false)` does not prevent user-initiated screenshots or screen recordings. + + - Users can take screenshots using the hardware shortcut and the content will appear in those screenshots. + - Tapping "Screenshot" in the Recents screen will not work (the system may show a policy message depending on device/ROM). diff --git a/best-practices/MASTG-BEST-0016.md b/best-practices/MASTG-BEST-0016.md new file mode 100644 index 00000000000..45f63e1faca --- /dev/null +++ b/best-practices/MASTG-BEST-0016.md @@ -0,0 +1,31 @@ +--- +title: Use `SECURE_FLAG` to Prevent Screenshots and Screen Recording +alias: use-secure-flag-to-prevent-screenshots-and-screen-recording +id: MASTG-BEST-0016 +parent: MASTG-BEST-0014 +platform: android +component: android.view.Window +available_since: 1 +--- + +Setting [`FLAG_SECURE`](https://developer.android.com/security/fraud-prevention/activities#flag_secure) on a window prevents screenshots (or makes them appear black), blocks screen recording, and hides content on non-secure displays and ensures that the app's content is not shown in the [Recents screen](https://developer.android.com/guide/components/activities/recents) when backgrounded. + +
+
+ Task switcher without FLAG_SECURE +
Without FLAG_SECURE
+
+
+ Task switcher with FLAG_SECURE +
With FLAG_SECURE
+
+
+ +The flag is applied with [`addFlags()`](https://developer.android.com/reference/android/view/Window#addFlags(int)) or [`setFlags()`](https://developer.android.com/reference/android/view/Window#setFlags(int,int)). Common best practices include: + +- This flag cannot be applied globally. Apply the flag to all windows that can show sensitive data, including `Activity`, `Dialog`, `DialogFragment`, `AlertDialog`, or any custom window added via `WindowManager`. Sometimes apps use a base UI class and make all sensitive screens inherit it. +- Set the flag as early as possible in the lifecycle, before sensitive content is rendered. For example, setting it in `onCreate()` is better than `onResume()`, and trying to add the flag in `onPause()` is not effective, as the preview is created before this method is called. +- Be careful with fragments and their lifecycle, as they can be added or removed dynamically. +- Avoid clearing the flag during transitions (e.g., using [`clearFlags()`](https://developer.android.com/reference/android/view/Window#clearFlags(int)) or `setFlags()` without reapplying), as sensitive previews for the Recents screen can be captured before callbacks like `onPause()`. + +For more information, refer to the official documentation: ["Secure sensitive activities"](https://developer.android.com/security/fraud-prevention/activities). diff --git a/best-practices/MASTG-BEST-0017.md b/best-practices/MASTG-BEST-0017.md new file mode 100644 index 00000000000..66412d5d409 --- /dev/null +++ b/best-practices/MASTG-BEST-0017.md @@ -0,0 +1,11 @@ +--- +title: Use `setSecure` to Prevent Screenshots in SurfaceViews +alias: preventing-screenshots-and-screen-recording +id: MASTG-BEST-0017 +parent: MASTG-BEST-0014 +platform: android +component: android.view.SurfaceView +available_since: 17 +--- + +`SurfaceView` creates a separate surface that is not protected by `FLAG_SECURE` even if the containing window has it set. To protect content in a `SurfaceView`, use [`setSecure(true)`](https://developer.android.com/reference/android/view/SurfaceView#setSecure(boolean)) before the surface view's containing window is attached to the window manager. diff --git a/best-practices/MASTG-BEST-0018.md b/best-practices/MASTG-BEST-0018.md new file mode 100644 index 00000000000..63e5d113d2e --- /dev/null +++ b/best-practices/MASTG-BEST-0018.md @@ -0,0 +1,11 @@ +--- +title: Use `SecureOn` to Prevent Screenshots in Compose Dialogs +alias: use-secureon-in-compose-dialogs +id: MASTG-BEST-0018 +parent: MASTG-BEST-0014 +platform: android +component: androidx.compose.ui:ui +available_since: 1.0.0 +--- + +In Compose dialogs, set the secure policy to enable `FLAG_SECURE` flag for the dialog window using [`SecureOn`](https://developer.android.com/reference/kotlin/androidx/compose/ui/window/SecureFlagPolicy#SecureOn). From 4a483b483544518f68b692c90af400cd12212401 Mon Sep 17 00:00:00 2001 From: Carlos Date: Sat, 6 Sep 2025 18:10:09 +0200 Subject: [PATCH 15/28] Split tests for preventing screenshots in Android apps. --- .../android/MASVS-PLATFORM/MASTG-TEST-0291.md | 34 +++---------------- .../android/MASVS-PLATFORM/MASTG-TEST-0292.md | 28 +++++++++++++++ .../android/MASVS-PLATFORM/MASTG-TEST-0293.md | 32 +++++++++++++++++ .../android/MASVS-PLATFORM/MASTG-TEST-0294.md | 30 ++++++++++++++++ 4 files changed, 95 insertions(+), 29 deletions(-) create mode 100644 tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0292.md create mode 100644 tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0293.md create mode 100644 tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0294.md diff --git a/tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0291.md b/tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0291.md index 54b22178952..ec781a409d5 100644 --- a/tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0291.md +++ b/tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0291.md @@ -1,18 +1,17 @@ --- -title: References to Screen Capturing Prevention APIs +title: "`FLAG_SECURE` Not Used to Prevent Screenshots and Screen Recording" platform: android id: MASTG-TEST-0291 type: [static] profiles: [L2] -best-practices: [MASTG-BEST-0014] +best-practices: [MASTG-BEST-0014, MASTG-BEST-0016] weakness: MASWE-0055 +threat: [app, root] --- ## Overview -This test verifies whether an app references Android screen capture prevention APIs. On Android, developers can prevent screenshots and non-secure display mirroring using [`FLAG_SECURE`](https://developer.android.com/security/fraud-prevention/activities#flag_secure). When set, Android blocks screenshots, prevents content from appearing on non-secure displays (including remote screen sharing), and blocks capture through MediaProjection or virtual displays. Screenshots are blocked (resulting images are blank or obscured and the system may show a policy message), and the app's content is not shown in Recents when backgrounded. - -**UI Components Exposing Window Getters or LayoutParams:** +This test verifies whether an app references Android screen capture prevention APIs. On Android, developers can prevent screenshots and non-secure display mirroring using [`FLAG_SECURE`](https://developer.android.com/security/fraud-prevention/activities#flag_secure). When set, Android blocks screenshots, prevents content from appearing on non-secure displays (including remote screen sharing), and blocks capture through MediaProjection or virtual displays. Screenshots are blocked (resulting images are blank or obscured and the system may show a policy message), and the app's content is not shown in the Recents screen when backgrounded. `FLAG_SECURE` is applied per window. Any UI that creates its own `Window` must set it if sensitive content can appear there. This includes all components that: @@ -27,29 +26,6 @@ The flag is applied with [`addFlags()`](https://developer.android.com/reference/ - Missing a window: not setting `FLAG_SECURE` on all windows that can show sensitive data. - Lifecycle gaps: setting too late or clearing during transitions (e.g., using [`clearFlags()`](https://developer.android.com/reference/android/view/Window#clearFlags(int)) or `setFlags()` without reapplying). Sensitive previews for Recents can be captured before callbacks like `onPause()`. -**SurfaceViews:** - -`SurfaceView` create a separate surface that is not protected by `FLAG_SECURE` even if the containing window has it set. To protect content in a `SurfaceView`, developers must use [`setSecure(true)`](https://developer.android.com/reference/android/view/SurfaceView#setSecure(boolean)) before the surface view's containing window is attached to the window manager. - -**Compose Dialogs:** - -In Compose dialogs, developers can set the secure policy which enables or disables the `FLAG_SECURE` flag to the dialog window using: - -- [`SecureOn`](https://developer.android.com/reference/kotlin/androidx/compose/ui/window/SecureFlagPolicy#SecureOn) -- [`Inherit`](https://developer.android.com/reference/kotlin/androidx/compose/ui/window/SecureFlagPolicy#Inherit) -- [`SecureOff`](https://developer.android.com/reference/kotlin/androidx/compose/ui/window/SecureFlagPolicy#SecureOff) - -**Recents Screenshots:** - -Starting with Android 13 (API level 33), developers can disable Recents screenshots using [`setRecentsScreenshotEnabled()`](https://developer.android.com/reference/android/app/Activity#setRecentsScreenshotEnabled(boolean)): - -- This prevents sensitive content from appearing in the system-generated snapshots used for the Recents screen: - - the snapshots saved to `/data/system_ce//snapshots` will be blank/obscured. - - the previews blank after the user switches away from the app completely, similar to the behavior of `FLAG_SECURE` -- It does not prevent user-initiated screenshots or screen recordings. - - Users can take screenshots using the hardware shortcut and the content will appear in those screenshots. - - Tapping "Screenshot" in Recents will not produce a snapshot (the system may show a policy message depending on device/ROM). - ## Steps 1. Run a static analysis (@MASTG-TECH-0014) tool to identify instances of the relevant APIs. @@ -60,7 +36,7 @@ The output should include a list of locations where the relevant APIs are used. ## Evaluation -The test case fails if the relevant APIs are missing or inconsistently applied on any UI component that displays sensitive data, or if code paths clear the protection without an adequate justification. +The test case fails if, for **any UI component that displays sensitive data**, the relevant API calls are missing, inconsistently applied, or the protection is explicitly cleared. NOTE: Detecting all possible instances of `FLAG_SECURE` can be challenging: diff --git a/tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0292.md b/tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0292.md new file mode 100644 index 00000000000..1066ce93b96 --- /dev/null +++ b/tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0292.md @@ -0,0 +1,28 @@ +--- +title: "`setRecentsScreenshotEnabled` Not Used to Prevent Screenshots When Backgrounded" +platform: android +id: MASTG-TEST-0292 +type: [static] +profiles: [L2] +best-practices: [MASTG-BEST-0014, MASTG-BEST-0015] +weakness: MASWE-0055 +threat: [root] +--- + +## Overview + +This test verifies whether an app prevents sensitive data from being captured in the Recents screen when backgrounded. Starting with Android 13 (API level 33), developers could be explicitly allowing _Recents screenshots_ using [`setRecentsScreenshotEnabled(true)`](https://developer.android.com/reference/android/app/Activity#setRecentsScreenshotEnabled(boolean)) or not calling it at all, which results in the default behavior of allowing Recents screenshots. This does not affect user-initiated screenshots or screen recordings. + +**Note:** [`FLAG_SECURE`](https://developer.android.com/security/fraud-prevention/activities#flag_secure) also prevents the app's content from appearing in the Recents screen when backgrounded and it's addressed in @MASTG-TEST-0291. + +## Steps + +1. Run a static analysis (@MASTG-TECH-0014) tool to identify instances of the relevant APIs. + +## Observation + +The output should include a list of locations where the relevant APIs are used. + +## Evaluation + +The test case fails if `setRecentsScreenshotEnabled(true)` is called or the app does not call it at all on Android 13 (API level 33) or higher. diff --git a/tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0293.md b/tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0293.md new file mode 100644 index 00000000000..fc7db14d0ea --- /dev/null +++ b/tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0293.md @@ -0,0 +1,32 @@ +--- +title: "`setSecure` Not Used to Prevent Screenshots in SurfaceViews" +platform: android +id: MASTG-TEST-0293 +type: [static] +profiles: [L2] +best-practices: [MASTG-BEST-0014, MASTG-BEST-0017] +weakness: MASWE-0055 +threat: [app, root] +--- + +## Overview + +This test verifies whether an app references Android screen capture prevention APIs for components that don't support [`FLAG_SECURE`](https://developer.android.com/security/fraud-prevention/activities#flag_secure). + +When this protection is enabled, Android blocks screenshots, prevents content from appearing on non-secure displays (including remote screen sharing), and blocks capture through MediaProjection or virtual displays. Screenshots are blocked (resulting images are blank or obscured and the system may show a policy message), and the app's content is not shown in the Recents screen when backgrounded. + +**SurfaceViews:** + +`SurfaceView` creates a separate surface that is not protected by `FLAG_SECURE` even if the containing window has it set. Developers could be explicitly using [`setSecure(false)`](https://developer.android.com/reference/android/view/SurfaceView#setSecure(boolean)) or not calling it at all, which results in the default behavior of allowing screenshots. + +## Steps + +1. Run a static analysis (@MASTG-TECH-0014) tool to identify instances of the relevant APIs. + +## Observation + +The output should include a list of locations where the relevant APIs are used. + +## Evaluation + +The test case fails if, for **any UI component that displays sensitive data**, the relevant API calls are missing, inconsistently applied, or the protection is explicitly cleared. diff --git a/tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0294.md b/tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0294.md new file mode 100644 index 00000000000..ff64c8c41e7 --- /dev/null +++ b/tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0294.md @@ -0,0 +1,30 @@ +--- +title: "`SecureOn` Not Used to Prevent Screenshots in Compose Dialogs" +platform: android +id: MASTG-TEST-0294 +type: [static] +profiles: [L2] +best-practices: [MASTG-BEST-0014, MASTG-BEST-0018] +weakness: MASWE-0055 +threat: [app, root] +--- + +## Overview + +This test verifies whether an app references Android screen capture prevention APIs for components that don't support [`FLAG_SECURE`](https://developer.android.com/security/fraud-prevention/activities#flag_secure). + +When this protection is enabled, Android blocks screenshots, prevents content from appearing on non-secure displays (including remote screen sharing), and blocks capture through MediaProjection or virtual displays. Screenshots are blocked (resulting images are blank or obscured and the system may show a policy message), and the app's content is not shown in the Recents screen when backgrounded. + +In Compose dialogs, developers can be explicitly disabling `FLAG_SECURE` by setting the secure policy for the dialog window using [`SecureOff`](https://developer.android.com/reference/kotlin/androidx/compose/ui/window/SecureFlagPolicy#SecureOff) instead of enabling it with [`SecureOn`](https://developer.android.com/reference/kotlin/androidx/compose/ui/window/SecureFlagPolicy#SecureOn). A third option is [`Inherit`](https://developer.android.com/reference/kotlin/androidx/compose/ui/window/SecureFlagPolicy#Inherit), which applies the policy of the parent window. + +## Steps + +1. Run a static analysis (@MASTG-TECH-0014) tool to identify instances of the relevant APIs. + +## Observation + +The output should include a list of locations where the relevant APIs are used. + +## Evaluation + +The test case fails if, for **any UI component that displays sensitive data**, the relevant API calls are missing, inconsistently applied, or the protection is explicitly cleared. From 3250c83c7ec2255740284b869bebc90b58678cbd Mon Sep 17 00:00:00 2001 From: Carlos Date: Sat, 6 Sep 2025 18:10:30 +0200 Subject: [PATCH 16/28] Fix test refs --- .../MASVS-PLATFORM/MASTG-DEMO-0062/MASTG-DEMO-0062.md | 6 +++--- .../MASVS-PLATFORM/MASTG-DEMO-0063/MASTG-DEMO-0063.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/demos/android/MASVS-PLATFORM/MASTG-DEMO-0062/MASTG-DEMO-0062.md b/demos/android/MASVS-PLATFORM/MASTG-DEMO-0062/MASTG-DEMO-0062.md index 7465d3908ba..77120afc63a 100644 --- a/demos/android/MASVS-PLATFORM/MASTG-DEMO-0062/MASTG-DEMO-0062.md +++ b/demos/android/MASVS-PLATFORM/MASTG-DEMO-0062/MASTG-DEMO-0062.md @@ -3,12 +3,12 @@ platform: android title: Enabling Screenshots in Recents via setRecentsScreenshotEnabled with semgrep id: MASTG-DEMO-0062 code: [kotlin] -test: MASTG-TEST-0291 +test: MASTG-TEST-0292 --- ### Sample -The sample demonstrates how an app does not prevent screenshots/leaks of sensitive content in Android 14+ recents via calls to `setRecentsScreenshotEnabled(true)`. +The sample demonstrates how an app does not prevent screenshots/leaks of sensitive content in the Recents screen via calls to `setRecentsScreenshotEnabled(true)`. {{ MastgTest.kt # MastgTest_reversed.java }} @@ -28,4 +28,4 @@ The rule has identified one location in the code file where the app calls `setRe ### 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. +The test fails because the app calls `setRecentsScreenshotEnabled(true)`, 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. diff --git a/demos/android/MASVS-PLATFORM/MASTG-DEMO-0063/MASTG-DEMO-0063.md b/demos/android/MASVS-PLATFORM/MASTG-DEMO-0063/MASTG-DEMO-0063.md index 5fb2ec4cc72..a43fcb9cd35 100644 --- a/demos/android/MASVS-PLATFORM/MASTG-DEMO-0063/MASTG-DEMO-0063.md +++ b/demos/android/MASVS-PLATFORM/MASTG-DEMO-0063/MASTG-DEMO-0063.md @@ -3,7 +3,7 @@ platform: android title: Incorrectly Preventing Screenshots with SecureFlagPolicy in Compose Dialogs with semgrep id: MASTG-DEMO-0063 code: [kotlin] -test: MASTG-TEST-0291 +test: MASTG-TEST-0293 --- ### Sample From de7c3a0151d5584ba94ccb0478cfd70555699b70 Mon Sep 17 00:00:00 2001 From: Carlos Date: Sat, 6 Sep 2025 18:23:09 +0200 Subject: [PATCH 17/28] add refs --- best-practices/MASTG-BEST-0016.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/best-practices/MASTG-BEST-0016.md b/best-practices/MASTG-BEST-0016.md index 45f63e1faca..d0fea923007 100644 --- a/best-practices/MASTG-BEST-0016.md +++ b/best-practices/MASTG-BEST-0016.md @@ -6,9 +6,13 @@ parent: MASTG-BEST-0014 platform: android component: android.view.Window available_since: 1 +refs: +- https://developer.android.com/security/fraud-prevention/activities#flag_secure +- https://support.google.com/googleplay/android-developer/answer/16273414 +- https://support.google.com/googleplay/android-developer/answer/14638385 --- -Setting [`FLAG_SECURE`](https://developer.android.com/security/fraud-prevention/activities#flag_secure) on a window prevents screenshots (or makes them appear black), blocks screen recording, and hides content on non-secure displays and ensures that the app's content is not shown in the [Recents screen](https://developer.android.com/guide/components/activities/recents) when backgrounded. +Setting [`FLAG_SECURE`](https://developer.android.com/reference/android/view/WindowManager.LayoutParams#FLAG_SECURE) on a window prevents screenshots (or makes them appear black), blocks screen recording, and hides content on non-secure displays and ensures that the app's content is not shown in the [Recents screen](https://developer.android.com/guide/components/activities/recents) when backgrounded.
@@ -28,4 +32,4 @@ The flag is applied with [`addFlags()`](https://developer.android.com/reference/ - Be careful with fragments and their lifecycle, as they can be added or removed dynamically. - Avoid clearing the flag during transitions (e.g., using [`clearFlags()`](https://developer.android.com/reference/android/view/Window#clearFlags(int)) or `setFlags()` without reapplying), as sensitive previews for the Recents screen can be captured before callbacks like `onPause()`. -For more information, refer to the official documentation: ["Secure sensitive activities"](https://developer.android.com/security/fraud-prevention/activities). +For more information, refer to the official documentation. From 3111edad06e9d8357a175e4bf0007f16d701db7e Mon Sep 17 00:00:00 2001 From: Carlos Date: Sat, 6 Sep 2025 19:09:42 +0200 Subject: [PATCH 18/28] link best to knowledge --- best-practices/MASTG-BEST-0014.md | 1 + best-practices/MASTG-BEST-0015.md | 1 + best-practices/MASTG-BEST-0016.md | 4 ++++ best-practices/MASTG-BEST-0017.md | 1 + knowledge/android/MASVS-STORAGE/MASTG-KNOW-0053.md | 1 - 5 files changed, 7 insertions(+), 1 deletion(-) diff --git a/best-practices/MASTG-BEST-0014.md b/best-practices/MASTG-BEST-0014.md index 04f7c601886..5f591781db1 100644 --- a/best-practices/MASTG-BEST-0014.md +++ b/best-practices/MASTG-BEST-0014.md @@ -2,6 +2,7 @@ title: Preventing Screenshots and Screen Recording alias: preventing-screenshots-and-screen-recording id: MASTG-BEST-0014 +knowledge: MASTG-KNOW-0053 platform: android parent: None --- diff --git a/best-practices/MASTG-BEST-0015.md b/best-practices/MASTG-BEST-0015.md index 7307615a7f3..bb9c7d9dcad 100644 --- a/best-practices/MASTG-BEST-0015.md +++ b/best-practices/MASTG-BEST-0015.md @@ -3,6 +3,7 @@ title: Use `setRecentsScreenshotEnabled` to Prevent Screenshots When Backgrounde alias: preventing-screenshots-when-backgrounded id: MASTG-BEST-0015 parent: MASTG-BEST-0014 +knowledge: MASTG-KNOW-0053 platform: android component: android.app.Activity available_since: 33 diff --git a/best-practices/MASTG-BEST-0016.md b/best-practices/MASTG-BEST-0016.md index d0fea923007..369fdf74ed8 100644 --- a/best-practices/MASTG-BEST-0016.md +++ b/best-practices/MASTG-BEST-0016.md @@ -3,6 +3,7 @@ title: Use `SECURE_FLAG` to Prevent Screenshots and Screen Recording alias: use-secure-flag-to-prevent-screenshots-and-screen-recording id: MASTG-BEST-0016 parent: MASTG-BEST-0014 +knowledge: MASTG-KNOW-0053 platform: android component: android.view.Window available_since: 1 @@ -32,4 +33,7 @@ The flag is applied with [`addFlags()`](https://developer.android.com/reference/ - Be careful with fragments and their lifecycle, as they can be added or removed dynamically. - Avoid clearing the flag during transitions (e.g., using [`clearFlags()`](https://developer.android.com/reference/android/view/Window#clearFlags(int)) or `setFlags()` without reapplying), as sensitive previews for the Recents screen can be captured before callbacks like `onPause()`. +Note that you cannot set `FLAG_SECURE` directly on components that do not expose `getWindow()`, such as `android.widget.PopupWindow` or `android.widget.Toast`. +- `android.view.View` and subclasses like `android.widget.TextView`, `android.widget.EditText`, `android.webkit.WebView`, `android.view.TextureView`, etc. don't support `FLAG_SECURE`. You must secure their containing Window instead. Alternatively, you can mask passwords and other sensitive by following the recommendations in @MASTG-BEST-0019. + For more information, refer to the official documentation. diff --git a/best-practices/MASTG-BEST-0017.md b/best-practices/MASTG-BEST-0017.md index 66412d5d409..9e8c7e7c5eb 100644 --- a/best-practices/MASTG-BEST-0017.md +++ b/best-practices/MASTG-BEST-0017.md @@ -3,6 +3,7 @@ title: Use `setSecure` to Prevent Screenshots in SurfaceViews alias: preventing-screenshots-and-screen-recording id: MASTG-BEST-0017 parent: MASTG-BEST-0014 +knowledge: MASTG-KNOW-0053 platform: android component: android.view.SurfaceView available_since: 17 diff --git a/knowledge/android/MASVS-STORAGE/MASTG-KNOW-0053.md b/knowledge/android/MASVS-STORAGE/MASTG-KNOW-0053.md index 54d0373ee97..7e17c24fb67 100644 --- a/knowledge/android/MASVS-STORAGE/MASTG-KNOW-0053.md +++ b/knowledge/android/MASVS-STORAGE/MASTG-KNOW-0053.md @@ -2,7 +2,6 @@ masvs_category: MASVS-STORAGE platform: android title: Screenshots -best-practices: [MASTG-BEST-0014] --- ## System-Generated Snapshots From 3c6eb43ce452ede1f169456491cc382466ff3bc6 Mon Sep 17 00:00:00 2001 From: Carlos Date: Sat, 6 Sep 2025 19:09:56 +0200 Subject: [PATCH 19/28] Clarify wording in MASTG-TEST-0291.md for better readability --- tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0291.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0291.md b/tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0291.md index ec781a409d5..31dbbd279be 100644 --- a/tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0291.md +++ b/tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0291.md @@ -15,11 +15,11 @@ This test verifies whether an app references Android screen capture prevention A `FLAG_SECURE` is applied per window. Any UI that creates its own `Window` must set it if sensitive content can appear there. This includes all components that: -- can call `getWindow()` (e.g., `Activity`, `Dialog`, `Presentation`) +- can call `getWindow()` (e.g., `Activity`, `Dialog` and subclasses, `Presentation`) - expose `.window` (e.g., `DialogFragment`) - allow setting `LayoutParams` (e.g., overlays or custom views added with `WindowManager.addView()`) -Other components that create separate windows but do not expose `getWindow()` directly (e.g., `PopupWindow`) cannot have `FLAG_SECURE` applied directly. +Other components that create separate windows but do not expose `getWindow()` directly (e.g., `PopupWindow` or `Toast`) cannot have `FLAG_SECURE` applied directly. The flag is applied with [`addFlags()`](https://developer.android.com/reference/android/view/Window#addFlags(int)) or [`setFlags()`](https://developer.android.com/reference/android/view/Window#setFlags(int,int)). Common failure modes include: From 13975cb4292d5946245e409a1db581444fe22677 Mon Sep 17 00:00:00 2001 From: Carlos Date: Sat, 6 Sep 2025 19:10:14 +0200 Subject: [PATCH 20/28] Add best practice for non-caching input types in sensitive fields --- best-practices/MASTG-BEST-0019.md | 36 +++++++++++++++++++ .../android/MASVS-PLATFORM/MASTG-TEST-0258.md | 1 + 2 files changed, 37 insertions(+) create mode 100644 best-practices/MASTG-BEST-0019.md diff --git a/best-practices/MASTG-BEST-0019.md b/best-practices/MASTG-BEST-0019.md new file mode 100644 index 00000000000..7810d4cbe93 --- /dev/null +++ b/best-practices/MASTG-BEST-0019.md @@ -0,0 +1,36 @@ +--- +title: Use Non-Caching Input Types for Sensitive Fields +alias: use-non-caching-input-types +id: MASTG-BEST-0019 +knowledge: MASTG-KNOW-0055 +platform: android +refs: +- https://developer.android.com/reference/android/text/InputType +- https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/KeyboardOptions +--- + +Use non-caching input types for any UI element that handles sensitive information (passwords, passphrases, PINs, recovery phrases, payment data). Password-style input types instruct the on-screen keyboard not to cache or learn from the entered text and disable suggestions and auto-correction. Password variations disable dictionary learning and suggestions in most IMEs and prevent caching of entered secrets. + +Combine this practice with broader data leakage controls e.g., @MASTG-BEST-0014 when appropriate. + +Recommendations: + +- XML layouts (EditText) + - Passwords: `android:inputType="textPassword"` + - Numeric PINs: `android:inputType="numberPassword"` + - Avoid: `textVisiblePassword` and plain `text`/`number` without password variation. + +- Programmatic (View-based UI) + - Passwords: `setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD)` + - Numeric PINs: `setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD)` + - Optionally disable suggestions for non-password sensitive fields: add `InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS`. + +- Jetpack Compose + - Passwords: `KeyboardOptions(keyboardType = KeyboardType.Password, autoCorrect = false)` + - Numeric PINs: `KeyboardOptions(keyboardType = KeyboardType.NumberPassword, autoCorrect = false)` + - Also set a visual transformation where appropriate (e.g., `PasswordVisualTransformation()`). + +- For sensitive but non-password inputs (e.g., security answers), prefer password variations or at minimum disable suggestions (`TYPE_TEXT_FLAG_NO_SUGGESTIONS`, `autoCorrect = false`). + +- Overriding a non-caching type later in code with a generic class type (e.g., setting `TYPE_NUMBER_VARIATION_PASSWORD` and then setting `TYPE_CLASS_NUMBER`). + diff --git a/tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0258.md b/tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0258.md index acf17a81158..4d0b96a17aa 100644 --- a/tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0258.md +++ b/tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0258.md @@ -5,6 +5,7 @@ id: MASTG-TEST-0258 type: [static] weakness: MASWE-0053 profiles: [L2] +best-practices: [MASTG-BEST-0019] --- ## Overview From 1d946d8347031501071bd02deac9045c2b340ec9 Mon Sep 17 00:00:00 2001 From: Carlos Date: Sat, 6 Sep 2025 20:27:15 +0200 Subject: [PATCH 21/28] Update MASTG-BEST-0018.md to clarify usage of SecureFlagPolicy.SecureOn for Compose components --- best-practices/MASTG-BEST-0018.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/best-practices/MASTG-BEST-0018.md b/best-practices/MASTG-BEST-0018.md index 63e5d113d2e..ef34440c3ad 100644 --- a/best-practices/MASTG-BEST-0018.md +++ b/best-practices/MASTG-BEST-0018.md @@ -1,11 +1,16 @@ --- -title: Use `SecureOn` to Prevent Screenshots in Compose Dialogs -alias: use-secureon-in-compose-dialogs +title: Use `SecureFlagPolicy.SecureOn` to Prevent Screenshots in Compose Components +alias: use-secureon-in-compose-components id: MASTG-BEST-0018 parent: MASTG-BEST-0014 +knowledge: MASTG-KNOW-0053 platform: android component: androidx.compose.ui:ui available_since: 1.0.0 --- -In Compose dialogs, set the secure policy to enable `FLAG_SECURE` flag for the dialog window using [`SecureOn`](https://developer.android.com/reference/kotlin/androidx/compose/ui/window/SecureFlagPolicy#SecureOn). +Use [`SecureFlagPolicy.SecureOn`](https://developer.android.com/reference/kotlin/androidx/compose/ui/window/SecureFlagPolicy#SecureOn) for Jetpack Compose dialogs and popups that display sensitive information (via [`DialogProperties.securePolicy`](https://developer.android.com/reference/kotlin/androidx/compose/ui/window/DialogProperties#securePolicy()) or [`PopupProperties.securePolicy`](https://developer.android.com/reference/kotlin/androidx/compose/ui/window/PopupProperties#securePolicy()) respectively). This applies the underlying Window's [`FLAG_SECURE`](https://developer.android.com/reference/android/view/WindowManager.LayoutParams#FLAG_SECURE), which blocks screenshots, screen recordings, display on insecure devices and prevents previews in the Recents screen. + +Define the policy at creation time and avoid toggling it while visible. If the dialog or popup should follow the host window's state, use [`SecureFlagPolicy.Inherit`](https://developer.android.com/reference/kotlin/androidx/compose/ui/window/SecureFlagPolicy#Inherit). Avoid [`SecureFlagPolicy.SecureOff`](https://developer.android.com/reference/kotlin/androidx/compose/ui/window/SecureFlagPolicy#SecureOff) for sensitive content. + +Be aware that the use of this API may interfere with workflows like screenshots or screen sharing. If your threat model allows, consider offering an explicit user option to disable secure mode for non-sensitive scenarios, but default to secure behavior. From 01e9d6935588231294ca36795e52d64f5a137c42 Mon Sep 17 00:00:00 2001 From: Carlos Date: Sun, 7 Sep 2025 10:34:58 +0200 Subject: [PATCH 22/28] Add reference to overlay attack warnings and improve wording in MASTG-BEST-0016.md --- best-practices/MASTG-BEST-0016.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/best-practices/MASTG-BEST-0016.md b/best-practices/MASTG-BEST-0016.md index 369fdf74ed8..dcadd9e636e 100644 --- a/best-practices/MASTG-BEST-0016.md +++ b/best-practices/MASTG-BEST-0016.md @@ -11,6 +11,7 @@ refs: - https://developer.android.com/security/fraud-prevention/activities#flag_secure - https://support.google.com/googleplay/android-developer/answer/16273414 - https://support.google.com/googleplay/android-developer/answer/14638385 +- https://proandroiddev.com/multitasking-intrusion-and-preventing-screenshots-in-android-app-15bd8757c24d --- Setting [`FLAG_SECURE`](https://developer.android.com/reference/android/view/WindowManager.LayoutParams#FLAG_SECURE) on a window prevents screenshots (or makes them appear black), blocks screen recording, and hides content on non-secure displays and ensures that the app's content is not shown in the [Recents screen](https://developer.android.com/guide/components/activities/recents) when backgrounded. @@ -28,7 +29,7 @@ Setting [`FLAG_SECURE`](https://developer.android.com/reference/android/view/Win The flag is applied with [`addFlags()`](https://developer.android.com/reference/android/view/Window#addFlags(int)) or [`setFlags()`](https://developer.android.com/reference/android/view/Window#setFlags(int,int)). Common best practices include: -- This flag cannot be applied globally. Apply the flag to all windows that can show sensitive data, including `Activity`, `Dialog`, `DialogFragment`, `AlertDialog`, or any custom window added via `WindowManager`. Sometimes apps use a base UI class and make all sensitive screens inherit it. +- This flag cannot be applied globally. Apply the flag to all windows that can show sensitive data, including `Activity`, `Dialog`, `DialogFragment`, `AlertDialog`, or any custom window added via `WindowManager`. Sometimes apps **use a base UI class and use inheritance** to ensure all sensitive screens have it set. - Set the flag as early as possible in the lifecycle, before sensitive content is rendered. For example, setting it in `onCreate()` is better than `onResume()`, and trying to add the flag in `onPause()` is not effective, as the preview is created before this method is called. - Be careful with fragments and their lifecycle, as they can be added or removed dynamically. - Avoid clearing the flag during transitions (e.g., using [`clearFlags()`](https://developer.android.com/reference/android/view/Window#clearFlags(int)) or `setFlags()` without reapplying), as sensitive previews for the Recents screen can be captured before callbacks like `onPause()`. @@ -37,3 +38,6 @@ Note that you cannot set `FLAG_SECURE` directly on components that do not expose - `android.view.View` and subclasses like `android.widget.TextView`, `android.widget.EditText`, `android.webkit.WebView`, `android.view.TextureView`, etc. don't support `FLAG_SECURE`. You must secure their containing Window instead. Alternatively, you can mask passwords and other sensitive by following the recommendations in @MASTG-BEST-0019. For more information, refer to the official documentation. + +**Important:** Google warns that this approach isn't reliable for protecting against overlay attacks (@MASTG-KNOW-0022). See ["Secure sensitive activities - Best practices"](https://developer.android.com/security/fraud-prevention/activities#best-practices). + From 83c1346e32eb722bec7da919838b94620a2299ef Mon Sep 17 00:00:00 2001 From: Carlos Date: Sun, 7 Sep 2025 10:35:37 +0200 Subject: [PATCH 23/28] Correct MASTG-KNOW-0053 location --- .../{MASVS-STORAGE => MASVS-PLATFORM}/MASTG-KNOW-0053.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename knowledge/android/{MASVS-STORAGE => MASVS-PLATFORM}/MASTG-KNOW-0053.md (98%) diff --git a/knowledge/android/MASVS-STORAGE/MASTG-KNOW-0053.md b/knowledge/android/MASVS-PLATFORM/MASTG-KNOW-0053.md similarity index 98% rename from knowledge/android/MASVS-STORAGE/MASTG-KNOW-0053.md rename to knowledge/android/MASVS-PLATFORM/MASTG-KNOW-0053.md index 7e17c24fb67..75031263424 100644 --- a/knowledge/android/MASVS-STORAGE/MASTG-KNOW-0053.md +++ b/knowledge/android/MASVS-PLATFORM/MASTG-KNOW-0053.md @@ -1,5 +1,5 @@ --- -masvs_category: MASVS-STORAGE +masvs_category: MASVS-PLATFORM platform: android title: Screenshots --- From 8d83288457791a72f3b7ef0951f4dadfe6b86759 Mon Sep 17 00:00:00 2001 From: Carlos Date: Sun, 7 Sep 2025 10:36:04 +0200 Subject: [PATCH 24/28] Update MASTG-KNOW-0022.md to correct Tapjacking reference and add best practices for overlay attacks --- knowledge/android/MASVS-PLATFORM/MASTG-KNOW-0022.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/knowledge/android/MASVS-PLATFORM/MASTG-KNOW-0022.md b/knowledge/android/MASVS-PLATFORM/MASTG-KNOW-0022.md index ecba8bf596e..1b8bb4269a6 100644 --- a/knowledge/android/MASVS-PLATFORM/MASTG-KNOW-0022.md +++ b/knowledge/android/MASVS-PLATFORM/MASTG-KNOW-0022.md @@ -8,10 +8,12 @@ Screen overlay attacks occur when a malicious application manages to put itself There are several attacks affecting different Android versions including: -- [**Tapjacking**](https://medium.com/devknoxio/what-is-tapjacking-in-android-and-how-to-prevent-it-50140e57bf44 "What is Tapjacking in Android and How to Prevent It") (Android 6.0 (API level 23) and lower) abuses the screen overlay feature of Android listening for taps and intercepting any information being passed to the underlying activity. +- [**Tapjacking**](https://developer.android.com/privacy-and-security/risks/tapjacking) (Android 6.0 (API level 23) and lower) abuses the screen overlay feature of Android listening for taps and intercepting any information being passed to the underlying activity. - [**Cloak & Dagger**](https://cloak-and-dagger.org/ "Cloak & Dagger") attacks affect apps targeting Android 5.0 (API level 21) to Android 7.1 (API level 25). They abuse one or both of the `SYSTEM_ALERT_WINDOW` ("draw on top") and `BIND_ACCESSIBILITY_SERVICE` ("a11y") permissions that, in case the app is installed from the Play Store, the users do not need to explicitly grant and for which they are not even notified. - [**Toast Overlay**](https://unit42.paloaltonetworks.com/unit42-android-toast-overlay-attack-cloak-and-dagger-with-no-permissions/ "Android Toast Overlay Attack: Cloak and Dagger with No Permissions") is quite similar to Cloak & Dagger but do not require specific Android permissions to be granted by users. It was closed with CVE-2017-0752 on Android 8.0 (API level 26). Usually, this kind of attacks are inherent to an Android system version having certain vulnerabilities or design issues. This makes them challenging and often virtually impossible to prevent unless the app is upgraded targeting a safe Android version (API level). Over the years many known malware like MazorBot, BankBot or MysteryBot have been abusing the screen overlay feature of Android to target business critical applications, namely in the banking sector. This [blog](https://www.infosecurity-magazine.com/opinions/overlay-attacks-safeguard-mobile/ "Dealing with Overlay Attacks: Adopting Built-in Security to Safeguard Mobile Experience") discusses more about this type of malware. + +See ["Secure sensitive activities - Best practices"](https://developer.android.com/security/fraud-prevention/activities#best-practices) for more information and recommendations. From 532d4a1e5b11ff97894d6de2a71f450d2b3aae68 Mon Sep 17 00:00:00 2001 From: Carlos Date: Sun, 14 Sep 2025 11:52:31 +0200 Subject: [PATCH 25/28] Update best practices and knowledge documents for screenshot prevention techniques --- best-practices/MASTG-BEST-0014.md | 1 - best-practices/MASTG-BEST-0015.md | 2 +- best-practices/MASTG-BEST-0016.md | 2 +- best-practices/MASTG-BEST-0017.md | 2 +- best-practices/MASTG-BEST-0018.md | 2 +- best-practices/MASTG-BEST-0019.md | 2 +- knowledge/android/MASVS-PLATFORM/MASTG-KNOW-0053.md | 10 +--------- knowledge/android/MASVS-PLATFORM/MASTG-KNOW-0105.md | 11 +++++++++++ knowledge/android/MASVS-PLATFORM/MASTG-KNOW-0106.md | 11 +++++++++++ 9 files changed, 28 insertions(+), 15 deletions(-) create mode 100644 knowledge/android/MASVS-PLATFORM/MASTG-KNOW-0105.md create mode 100644 knowledge/android/MASVS-PLATFORM/MASTG-KNOW-0106.md diff --git a/best-practices/MASTG-BEST-0014.md b/best-practices/MASTG-BEST-0014.md index 5f591781db1..04f7c601886 100644 --- a/best-practices/MASTG-BEST-0014.md +++ b/best-practices/MASTG-BEST-0014.md @@ -2,7 +2,6 @@ title: Preventing Screenshots and Screen Recording alias: preventing-screenshots-and-screen-recording id: MASTG-BEST-0014 -knowledge: MASTG-KNOW-0053 platform: android parent: None --- diff --git a/best-practices/MASTG-BEST-0015.md b/best-practices/MASTG-BEST-0015.md index bb9c7d9dcad..d693cc603bb 100644 --- a/best-practices/MASTG-BEST-0015.md +++ b/best-practices/MASTG-BEST-0015.md @@ -3,7 +3,7 @@ title: Use `setRecentsScreenshotEnabled` to Prevent Screenshots When Backgrounde alias: preventing-screenshots-when-backgrounded id: MASTG-BEST-0015 parent: MASTG-BEST-0014 -knowledge: MASTG-KNOW-0053 +knowledge: [MASTG-KNOW-0053] platform: android component: android.app.Activity available_since: 33 diff --git a/best-practices/MASTG-BEST-0016.md b/best-practices/MASTG-BEST-0016.md index dcadd9e636e..92b4d894c0c 100644 --- a/best-practices/MASTG-BEST-0016.md +++ b/best-practices/MASTG-BEST-0016.md @@ -3,7 +3,7 @@ title: Use `SECURE_FLAG` to Prevent Screenshots and Screen Recording alias: use-secure-flag-to-prevent-screenshots-and-screen-recording id: MASTG-BEST-0016 parent: MASTG-BEST-0014 -knowledge: MASTG-KNOW-0053 +knowledge: [MASTG-KNOW-0053, MASTG-KNOW-0105, MASTG-KNOW-0106] platform: android component: android.view.Window available_since: 1 diff --git a/best-practices/MASTG-BEST-0017.md b/best-practices/MASTG-BEST-0017.md index 9e8c7e7c5eb..58ac2b2f689 100644 --- a/best-practices/MASTG-BEST-0017.md +++ b/best-practices/MASTG-BEST-0017.md @@ -3,7 +3,7 @@ title: Use `setSecure` to Prevent Screenshots in SurfaceViews alias: preventing-screenshots-and-screen-recording id: MASTG-BEST-0017 parent: MASTG-BEST-0014 -knowledge: MASTG-KNOW-0053 +knowledge: [MASTG-KNOW-0053, MASTG-KNOW-0105, MASTG-KNOW-0106] platform: android component: android.view.SurfaceView available_since: 17 diff --git a/best-practices/MASTG-BEST-0018.md b/best-practices/MASTG-BEST-0018.md index ef34440c3ad..3f1b5bbf60e 100644 --- a/best-practices/MASTG-BEST-0018.md +++ b/best-practices/MASTG-BEST-0018.md @@ -3,7 +3,7 @@ title: Use `SecureFlagPolicy.SecureOn` to Prevent Screenshots in Compose Compone alias: use-secureon-in-compose-components id: MASTG-BEST-0018 parent: MASTG-BEST-0014 -knowledge: MASTG-KNOW-0053 +knowledge: [MASTG-KNOW-0053, MASTG-KNOW-0105, MASTG-KNOW-0106] platform: android component: androidx.compose.ui:ui available_since: 1.0.0 diff --git a/best-practices/MASTG-BEST-0019.md b/best-practices/MASTG-BEST-0019.md index 7810d4cbe93..566c9218a7c 100644 --- a/best-practices/MASTG-BEST-0019.md +++ b/best-practices/MASTG-BEST-0019.md @@ -2,7 +2,7 @@ title: Use Non-Caching Input Types for Sensitive Fields alias: use-non-caching-input-types id: MASTG-BEST-0019 -knowledge: MASTG-KNOW-0055 +knowledge: [MASTG-KNOW-0055] platform: android refs: - https://developer.android.com/reference/android/text/InputType diff --git a/knowledge/android/MASVS-PLATFORM/MASTG-KNOW-0053.md b/knowledge/android/MASVS-PLATFORM/MASTG-KNOW-0053.md index 75031263424..801c48ea143 100644 --- a/knowledge/android/MASVS-PLATFORM/MASTG-KNOW-0053.md +++ b/knowledge/android/MASVS-PLATFORM/MASTG-KNOW-0053.md @@ -1,17 +1,9 @@ --- masvs_category: MASVS-PLATFORM platform: android -title: Screenshots +title: System-Generated Screenshots --- -## System-Generated Snapshots - Android captures visual representations of apps for different purposes, which can lead to unintended exposure of sensitive data. When an app goes into the background, the system creates a snapshot of its UI for the [Recents screen](https://developer.android.com/guide/components/activities/recents). This behavior is automatic and helps the user visually recognize tasks when switching apps. If the app does not obscure its UI, sensitive information may appear in these snapshots. System-generated snapshots for the Recents screen are stored under `/data/system_ce//snapshots` on modern Android versions, or `/data/system` on older versions. These locations are protected by the system sandbox, meaning ordinary apps cannot read them, but they can be accessed on rooted or compromised devices or through forensic acquisition. The exact path and structure may vary depending on the Android version and device vendor. - -## User-Initiated Screenshots - -Users can take screenshots at any time using system shortcuts. A malicious app with screen-capture capabilities, or malware on a rooted or compromised device, can also capture the display contents without the user's knowledge. - -User-initiated screenshots are stored in the **shared external storage**, typically under `/sdcard/Pictures/Screenshots/` or a similar public media directory depending on the device and Android version. These files are accessible to the user and to any app with storage or media access permissions. They may also be synced automatically to cloud services such as Google Photos. Because they are outside the app sandbox, once a screenshot is taken the app has no control over its lifecycle, distribution, or deletion. diff --git a/knowledge/android/MASVS-PLATFORM/MASTG-KNOW-0105.md b/knowledge/android/MASVS-PLATFORM/MASTG-KNOW-0105.md new file mode 100644 index 00000000000..1428f94fa36 --- /dev/null +++ b/knowledge/android/MASVS-PLATFORM/MASTG-KNOW-0105.md @@ -0,0 +1,11 @@ +--- +title: User-Initiated Screenshots and Screen Recording +platform: android +masvs_category: MASVS-PLATFORM +--- + +Users can take screenshots or screen recordings at any time using [system shortcuts](https://support.google.com/android/answer/9075928). + +User-initiated screenshots are stored in the **shared external storage**, typically under `/sdcard/Pictures/Screenshots/` or a similar public media directory depending on the device and Android version. These files are accessible to the user and to any app with storage or media access permissions. They may also be synced automatically to cloud services such as Google Photos. Because they are outside the app sandbox, once a screenshot is taken the app has no control over its lifecycle, distribution, or deletion. + +A malicious app with screen-capture capabilities, or malware on a rooted or compromised device, can also capture the display contents without the user's knowledge. See @MASTG-KNOW-0106 for details on app-initiated screen capture. diff --git a/knowledge/android/MASVS-PLATFORM/MASTG-KNOW-0106.md b/knowledge/android/MASVS-PLATFORM/MASTG-KNOW-0106.md new file mode 100644 index 00000000000..cb04c40b3e5 --- /dev/null +++ b/knowledge/android/MASVS-PLATFORM/MASTG-KNOW-0106.md @@ -0,0 +1,11 @@ +--- +title: App-Initiated Screenshots and Screen Recording +platform: android +masvs_category: MASVS-PLATFORM +--- + +Another app can capture your screen by starting a [MediaProjection](https://developer.android.com/media/grow/media-projection) session. It requests user consent with [`MediaProjectionManager.createScreenCaptureIntent`](https://developer.android.com/reference/android/media/projection/MediaProjectionManager), the user approves in a system dialog, then the app creates a `VirtualDisplay` and records frames from the device screen or a selected app window. On Android 14 and newer, the system supports app screen sharing so the user can choose a single window, and for apps targeting API 34 or higher, [consent is required for each capture session](https://developer.android.com/about/versions/14/behavior-changes-14#media-projection-consent). + +The capturing app must run a foreground service of type media projection, declared in the manifest and started with `startForeground`, or the system throws an error. This requirement is enforced on modern Android versions and is documented in the media projection guide and foreground service changes. + +Your app's windows are capturable unless you mark them as "secure". Setting `FLAG_SECURE`, including via `SecureFlagPolicy.SecureOn` in Compose, causes your content to be blanked in screenshots, screen recordings, and media projection or casting outputs. This protection is per window. From 07a89e6672b8b3237da4065a35293192f29676db Mon Sep 17 00:00:00 2001 From: Carlos Date: Sun, 14 Sep 2025 11:55:45 +0200 Subject: [PATCH 26/28] Add MASTG-KNOW-0107.md for screenshots and screen recording detection guidance --- .../android/MASVS-PLATFORM/MASTG-KNOW-0107.md | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 knowledge/android/MASVS-PLATFORM/MASTG-KNOW-0107.md diff --git a/knowledge/android/MASVS-PLATFORM/MASTG-KNOW-0107.md b/knowledge/android/MASVS-PLATFORM/MASTG-KNOW-0107.md new file mode 100644 index 00000000000..ad5597021d3 --- /dev/null +++ b/knowledge/android/MASVS-PLATFORM/MASTG-KNOW-0107.md @@ -0,0 +1,38 @@ +--- +title: Screenshots and Screen Recording Detection +platform: android +masvs_category: MASVS-PLATFORM +--- + +Screen recording detection lets an app become aware when its screen is being captured or recorded. + +## Screenshot Detection + +Starting on Android 14 (API Level 34), apps can [detect when a screenshot is taken](https://developer.android.com/about/versions/14/features/screenshot-detection). To implement screenshot detection the app must: + +- Declare the install-time permission [`android.permission.DETECT_SCREEN_CAPTURE`](https://developer.android.com/reference/android/Manifest.permission#DETECT_SCREEN_CAPTURE). +- In every activity where screen capture detection is needed, apps can implement the `Activity.ScreenCaptureCallback` interface, override `onScreenCaptured()`, then register and unregister the callback with `registerScreenCaptureCallback(...)` and `unregisterScreenCaptureCallback(...)` in `onStart()` and `onStop()` respectively. + +**NOTE:** The API only detects screenshots via hardware key combinations; screenshots initiated via ADB or automation are not detected (see ["Use Cases"](https://developer.android.com/about/versions/14/features/screenshot-detection#use-cases)). + +## Screen Recording Detection + +Starting on Android 15 (API Level 35), apps can [detect when screen recording is active](https://developer.android.com/about/versions/15/features#screen-recording-detection). To implement screen recording detection the app must: + +- Declare the install-time permission [`android.permission.DETECT_SCREEN_RECORDING`](https://developer.android.com/reference/android/Manifest.permission#DETECT_SCREEN_RECORDING) in the manifest. +- Use the [`addScreenRecordingCallback(...)`](https://developer.android.com/reference/android/view/WindowManager#addScreenRecordingCallback(java.util.concurrent.Executor,%20java.util.function.Consumer%3Cjava.lang.Integer%3E)) and [`removeScreenRecordingCallback(...)`](https://developer.android.com/reference/android/view/WindowManager#removeScreenRecordingCallback(java.util.function.Consumer%3Cjava.lang.Integer%3E)) methods on the `Activity` to monitor recording state changes. Callback provides `SCREEN_RECORDING_STATE_VISIBLE` when the app is visible in the recording and `SCREEN_RECORDING_STATE_NOT_VISIBLE` when not. + +## Pre-Permissions Detection + +Before these official APIs were available, [older security guidance](https://wwws.nightwatchcybersecurity.com/2016/04/13/research-securing-android-applications-from-screen-capture/) recommended `FLAG_SECURE` and related mitigations rather than detection, since no stable detection API existed for third party apps. + +Some of those workarounds were: + +- **MediaProjection inspection**: call `MediaProjectionManager.getActiveProjectionInfo()` to check if a screen-capture session is active. A non-null return indicates active recording/capture. This API is [hidden](https://android.googlesource.com/platform/frameworks/base/+/9e8d9ac/media/java/android/media/projection/MediaProjectionManager.java#110) (`@hide`), so not officially supported. +- **Monitoring MediaProjection sessions**: internal methods like `MediaProjectionManager.addCallback(...)` and `removeCallback(...)` exist but require `MANAGE_MEDIA_PROJECTION` permission and are intended for system-level use. + +These workarounds lacked reliable callbacks, depended on hidden APIs or OEM behavior, and were not guaranteed to work across devices or Android versions, which is why the new permissions based detection APIs were introduced. + +## Security Considerations + +In contrast to screen recording prevention, which actively blocks screen recording attempts, screen recording detection is not a protection mechanism but can be used to let the user know that their actions are being recorded, or to disable certain features while recording is active, e.g. in-app notifications for privacy reasons. See @MASTG-BEST-0014 for best practices on preventing screenshots and screen recording. From 5c8a99e7436b793215eaf58da99ba4989787ee8a Mon Sep 17 00:00:00 2001 From: Carlos Holguera Date: Fri, 19 Sep 2025 08:46:06 +0200 Subject: [PATCH 27/28] fix indentation --- best-practices/MASTG-BEST-0019.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/best-practices/MASTG-BEST-0019.md b/best-practices/MASTG-BEST-0019.md index 566c9218a7c..e7a2dd8515a 100644 --- a/best-practices/MASTG-BEST-0019.md +++ b/best-practices/MASTG-BEST-0019.md @@ -16,19 +16,19 @@ Combine this practice with broader data leakage controls e.g., @MASTG-BEST-0014 Recommendations: - XML layouts (EditText) - - Passwords: `android:inputType="textPassword"` - - Numeric PINs: `android:inputType="numberPassword"` - - Avoid: `textVisiblePassword` and plain `text`/`number` without password variation. + - Passwords: `android:inputType="textPassword"` + - Numeric PINs: `android:inputType="numberPassword"` + - Avoid: `textVisiblePassword` and plain `text`/`number` without password variation. - Programmatic (View-based UI) - - Passwords: `setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD)` - - Numeric PINs: `setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD)` - - Optionally disable suggestions for non-password sensitive fields: add `InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS`. + - Passwords: `setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD)` + - Numeric PINs: `setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD)` + - Optionally disable suggestions for non-password sensitive fields: add `InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS`. - Jetpack Compose - - Passwords: `KeyboardOptions(keyboardType = KeyboardType.Password, autoCorrect = false)` - - Numeric PINs: `KeyboardOptions(keyboardType = KeyboardType.NumberPassword, autoCorrect = false)` - - Also set a visual transformation where appropriate (e.g., `PasswordVisualTransformation()`). + - Passwords: `KeyboardOptions(keyboardType = KeyboardType.Password, autoCorrect = false)` + - Numeric PINs: `KeyboardOptions(keyboardType = KeyboardType.NumberPassword, autoCorrect = false)` + - Also set a visual transformation where appropriate (e.g., `PasswordVisualTransformation()`). - For sensitive but non-password inputs (e.g., security answers), prefer password variations or at minimum disable suggestions (`TYPE_TEXT_FLAG_NO_SUGGESTIONS`, `autoCorrect = false`). From a95e414b2d4412c0e6253a58c701ac1d5abc6623 Mon Sep 17 00:00:00 2001 From: Carlos Holguera Date: Fri, 19 Sep 2025 09:04:58 +0200 Subject: [PATCH 28/28] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- best-practices/MASTG-BEST-0016.md | 1 + best-practices/MASTG-BEST-0019.md | 2 +- tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0293.md | 2 +- tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0294.md | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/best-practices/MASTG-BEST-0016.md b/best-practices/MASTG-BEST-0016.md index 92b4d894c0c..a54adaf89f2 100644 --- a/best-practices/MASTG-BEST-0016.md +++ b/best-practices/MASTG-BEST-0016.md @@ -35,6 +35,7 @@ The flag is applied with [`addFlags()`](https://developer.android.com/reference/ - Avoid clearing the flag during transitions (e.g., using [`clearFlags()`](https://developer.android.com/reference/android/view/Window#clearFlags(int)) or `setFlags()` without reapplying), as sensitive previews for the Recents screen can be captured before callbacks like `onPause()`. Note that you cannot set `FLAG_SECURE` directly on components that do not expose `getWindow()`, such as `android.widget.PopupWindow` or `android.widget.Toast`. + - `android.view.View` and subclasses like `android.widget.TextView`, `android.widget.EditText`, `android.webkit.WebView`, `android.view.TextureView`, etc. don't support `FLAG_SECURE`. You must secure their containing Window instead. Alternatively, you can mask passwords and other sensitive by following the recommendations in @MASTG-BEST-0019. For more information, refer to the official documentation. diff --git a/best-practices/MASTG-BEST-0019.md b/best-practices/MASTG-BEST-0019.md index e7a2dd8515a..5015f4dce31 100644 --- a/best-practices/MASTG-BEST-0019.md +++ b/best-practices/MASTG-BEST-0019.md @@ -32,5 +32,5 @@ Recommendations: - For sensitive but non-password inputs (e.g., security answers), prefer password variations or at minimum disable suggestions (`TYPE_TEXT_FLAG_NO_SUGGESTIONS`, `autoCorrect = false`). -- Overriding a non-caching type later in code with a generic class type (e.g., setting `TYPE_NUMBER_VARIATION_PASSWORD` and then setting `TYPE_CLASS_NUMBER`). +- Avoid overriding a non-caching type later in code with a generic class type (e.g., setting `TYPE_NUMBER_VARIATION_PASSWORD` and then setting `TYPE_CLASS_NUMBER`). diff --git a/tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0293.md b/tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0293.md index fc7db14d0ea..4afe562b51f 100644 --- a/tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0293.md +++ b/tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0293.md @@ -11,7 +11,7 @@ threat: [app, root] ## Overview -This test verifies whether an app references Android screen capture prevention APIs for components that don't support [`FLAG_SECURE`](https://developer.android.com/security/fraud-prevention/activities#flag_secure). +This test verifies whether an app references Android screen capture prevention APIs for components that don't support [`FLAG_SECURE`](https://developer.android.com/security/fraud-prevention/activities#flag_secure). When this protection is enabled, Android blocks screenshots, prevents content from appearing on non-secure displays (including remote screen sharing), and blocks capture through MediaProjection or virtual displays. Screenshots are blocked (resulting images are blank or obscured and the system may show a policy message), and the app's content is not shown in the Recents screen when backgrounded. diff --git a/tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0294.md b/tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0294.md index ff64c8c41e7..b6f6db750ae 100644 --- a/tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0294.md +++ b/tests-beta/android/MASVS-PLATFORM/MASTG-TEST-0294.md @@ -11,7 +11,7 @@ threat: [app, root] ## Overview -This test verifies whether an app references Android screen capture prevention APIs for components that don't support [`FLAG_SECURE`](https://developer.android.com/security/fraud-prevention/activities#flag_secure). +This test verifies whether an app references Android screen capture prevention APIs for components that don't support [`FLAG_SECURE`](https://developer.android.com/security/fraud-prevention/activities#flag_secure). When this protection is enabled, Android blocks screenshots, prevents content from appearing on non-secure displays (including remote screen sharing), and blocks capture through MediaProjection or virtual displays. Screenshots are blocked (resulting images are blank or obscured and the system may show a policy message), and the app's content is not shown in the Recents screen when backgrounded.