Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
2969e58
Initial plan
Copilot Jan 31, 2026
22afcd3
Add MASTG content for Android 16 accessibilityDataSensitive feature
Copilot Jan 31, 2026
5a9c373
Update MASTG-BEST-0029 to reflect correct API level for accessibility…
Oct 24, 2026
0b296ab
Update MASTG-KNOW-0108 to reflect correct API level for accessibility…
Oct 24, 2026
fa57b9c
Update MASTG-TEST-0321 and MASTG-TEST-0322 to reflect correct API lev…
Oct 24, 2026
aea3d2f
Fix IDs to fake IDs
Oct 24, 2026
e467ffb
Merge branch 'master' of https://github.com/OWASP/mastg into copilot/…
Oct 24, 2026
8cab896
Update MASTG-BEST-0x01 to clarify accessibility data sensitivity for …
Oct 25, 2026
0b0982f
Update MASTG-KNOW-0x01 to correct available_since value, align with w…
Oct 25, 2026
baec79f
Add MASTG-TECH-0x01: Document methods for inspecting UI hierarchy and…
Oct 25, 2026
7276bf6
Add MASTG-TECH-0x02: Document methods for taking screenshots in Android
Oct 25, 2026
2118414
Add MASTG-TOOL-0x01: Document Maestro UI testing framework
Oct 25, 2026
fde73be
Add MASTG-TOOL-0x02: Document Android CLI tool
Oct 25, 2026
3164d4d
Update MASTG-TEST-0x01 and MASTG-TEST-0x02: Align with writing guidel…
Oct 25, 2026
de70df6
Refactor accessibility data sensitivity rules: streamline XML pattern…
Oct 25, 2026
597b9c7
Update MASTG-DEMO-0x01 to use programmatic view configurations and re…
Oct 25, 2026
3ca0c19
Add MASTG-DEMO-0x02: Implement runtime accessibility data sensitivity…
Oct 25, 2026
5334691
Add script.js
Oct 25, 2026
d96147d
Clarify limitations of protection and interactions with accessibility…
Oct 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions best-practices/MASTG-BEST-0x01.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
title: Restricting Accessibility Service Access to Sensitive Views
alias: restricting-accessibility-service-access-to-sensitive-views
id: MASTG-BEST-0x01
platform: android
knowledge: [MASTG-KNOW-0x01]
available_since: 34
---

Mark views that display or accept sensitive data as accessibility data sensitive (@MASTG-KNOW-0x01). On Android 14 (API level 34) and higher, this restricts access to these views to accessibility services that declare themselves as accessibility tools by setting [`android:isAccessibilityTool`](https://developer.android.com/reference/android/accessibilityservice/AccessibilityServiceInfo#attr_android:isAccessibilityTool) to `true` in their service configuration, such as screen readers. Accessibility services that don't declare `android:isAccessibilityTool="true"`, including malware that tricks the user into enabling it, can't read the content of these views or perform interactions on them, for example, capturing typed credentials or tapping a payment confirmation button on the user's behalf.

Apply this protection to views that handle or display sensitive information, such as:

- Login screens (username and password fields)
- System prompts, permissions, settings, and app-specific confirmations, such as payment and transaction confirmations
- Personal information forms (for example, national ID numbers or credit card numbers)
- Two-factor authentication inputs
- Biometric authentication prompts
- Any view displaying confidential user data

## Marking Views as Sensitive

Set the [`android:accessibilityDataSensitive`](https://developer.android.com/reference/android/view/View#attr_android:accessibilityDataSensitive) attribute to `true` in the layout file:

```xml
<EditText
android:id="@+id/passwordField"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:accessibilityDataSensitive="true" />
```

Or call [`setAccessibilityDataSensitive`](https://developer.android.com/reference/android/view/View#setAccessibilityDataSensitive(int)) with [`ACCESSIBILITY_DATA_SENSITIVE_YES`](https://developer.android.com/reference/android/view/View.html#ACCESSIBILITY_DATA_SENSITIVE_YES) for views created or configured in code:

```kotlin
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
confirmButton.setAccessibilityDataSensitive(View.ACCESSIBILITY_DATA_SENSITIVE_YES)
}
```

Views using the default value, [`ACCESSIBILITY_DATA_SENSITIVE_AUTO`](https://developer.android.com/reference/android/view/View.html#ACCESSIBILITY_DATA_SENSITIVE_AUTO), inherit the protection from any parent that is marked as sensitive. Marking a container protects all its children, so you can protect a group of related views at once. Don't set [`ACCESSIBILITY_DATA_SENSITIVE_NO`](https://developer.android.com/reference/android/view/View.html#ACCESSIBILITY_DATA_SENSITIVE_NO) on views that handle sensitive data, because it overrides the inherited protection.

## Using filterTouchesWhenObscured

Views that set [`filterTouchesWhenObscured`](https://developer.android.com/reference/android/view/View#setFilterTouchesWhenObscured(boolean)) to `true` to protect against overlay attacks (see @MASTG-BEST-0040) are also treated as accessibility data sensitive, as long as they keep the default [`ACCESSIBILITY_DATA_SENSITIVE_AUTO`](https://developer.android.com/reference/android/view/View.html#ACCESSIBILITY_DATA_SENSITIVE_AUTO) value. If your app already uses `filterTouchesWhenObscured` on sensitive views, those views are already protected. Setting `android:accessibilityDataSensitive="true"` explicitly still makes the intent clear and keeps the protection in place if the touch filtering configuration changes later.

## Caveats and Considerations

- **Impact on accessibility:** accessibility services that don't declare `android:isAccessibilityTool="true"` lose access to the marked views, which may affect users who rely on them. Apply the protection to the views that handle sensitive data rather than to entire screens, and test the app with accessibility tools such as TalkBack and Voice Access.
- **Platform version:** on devices running versions earlier than Android 14 (API level 34), the attribute is ignored and there's no equivalent platform protection against accessibility services. Use additional protections for sensitive flows, such as @MASTG-BEST-0040 against overlay attacks.
- **Not a complete defense:** accessibility services that declare `android:isAccessibilityTool="true"` keep full access to protected views and can still read and interact with them. Google Play and Google Play Protect enforce this declaration, but apps from other sources may not be subject to these checks, so a malicious app that falsely declares itself as an accessibility tool isn't blocked by this protection.

For more details, see the Android Developers Blog post ["Enhancing Android security: Stop malware from snooping on your app data"](https://android-developers.googleblog.com/2025/12/enhancing-android-security-stop-malware.html) and the Android guidance on [tapjacking mitigations](https://developer.android.com/privacy-and-security/risks/tapjacking#mitigations).
61 changes: 61 additions & 0 deletions demos/android/MASVS-PLATFORM/MASTG-DEMO-0x01/MASTG-DEMO-0x01.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
platform: android
title: Sensitive Views Without Accessibility Data Sensitivity
id: MASTG-DEMO-0x01
code: [kotlin]
test: MASTG-TEST-0x01
---

## Sample

The sample creates a login and payment form with views that handle sensitive data, each using a different accessibility data sensitivity configuration. It does so by using Kotlin and the Android `View` APIs (instead of Android layout XML).

1. **Username field:** isn't marked as accessibility data sensitive.
2. **Password field:** is explicitly marked as accessibility data sensitive using `setAccessibilityDataSensitive(View.ACCESSIBILITY_DATA_SENSITIVE_YES)`.
3. **One-time code field:** sets `filterTouchesWhenObscured`, which implies accessibility data sensitivity.
4. **Payment container:** a `LinearLayout` that is explicitly marked as accessibility data sensitive using `setAccessibilityDataSensitive(View.ACCESSIBILITY_DATA_SENSITIVE_YES)`. It contains two views:
- **Card number field:** keeps the default value, `ACCESSIBILITY_DATA_SENSITIVE_AUTO`, so it inherits the protection from the payment container.
- **Confirm Payment button:** explicitly disables the protection using `setAccessibilityDataSensitive(View.ACCESSIBILITY_DATA_SENSITIVE_NO)`, which overrides the value inherited from the payment container.
5. **Cancel button:** is outside the payment container and keeps the default value, `ACCESSIBILITY_DATA_SENSITIVE_AUTO`. It doesn't handle sensitive data, so it isn't protected.

Marking a view as accessibility data sensitive only restricts accessibility services that aren't declared as accessibility tools. Accessibility tools, such as screen readers, can still read and interact with all of these views (see "Accessibility Tools" in @MASTG-KNOW-0x01).

When you tap **Start**, the app shows the form at the bottom of the screen and lists the value returned by `isAccessibilityDataSensitive` for each view. Tapping **Confirm Payment** or **Cancel** closes the form and shows a confirmation message.

{{ MastgTest.kt # MastgTest_reversed.java }}

## Steps

Let's run our @MASTG-TOOL-0110 rule against the reverse-engineered code.

{{ ../../../../rules/mastg-android-accessibility-data-sensitive.yaml }}

{{ run.sh }}

## Observation

The rule reports the calls to `setAccessibilityDataSensitive` and `setFilterTouchesWhenObscured` in the reverse-engineered code. In the decompiled code, the constants appear as their integer values: `1` for `ACCESSIBILITY_DATA_SENSITIVE_YES` and `2` for `ACCESSIBILITY_DATA_SENSITIVE_NO`.

{{ output.txt }}

- Line 64: the password field calls `setAccessibilityDataSensitive(1)`.
- Line 67: the one-time code field calls `setFilterTouchesWhenObscured(true)`.
- Line 71: the payment container calls `setAccessibilityDataSensitive(1)`.
- Line 82: the **Confirm Payment** button calls `setAccessibilityDataSensitive(2)`.

## Evaluation

The test case fails because two views that handle sensitive data aren't accessibility data sensitive:

- The username field (line 61) doesn't appear in the output because the app doesn't configure any protection for it.
- The **Confirm Payment** button (line 82) is reported by the `mastg-android-accessibility-data-sensitive-disabled` rule because the app calls `setAccessibilityDataSensitive(2)` on it, which overrides the protection inherited from the payment container.

The remaining views are protected:

- The password field (line 64) is explicitly marked as accessibility data sensitive.
- The one-time code field (line 67) sets `filterTouchesWhenObscured` and keeps the default `ACCESSIBILITY_DATA_SENSITIVE_AUTO` value, so it's treated as accessibility data sensitive.
- The card number field (line 72) doesn't appear in the output, but the payment container (line 71) is marked as accessibility data sensitive and the field keeps the default value, so it inherits the protection.

Note that the output only lists the views for which the app configures the protection. Views without any configuration, such as the username field (line 61) and the **Cancel** button (line 85), don't appear, so you have to find them by reviewing the reverse-engineered code. This is manageable in a small sample but hard in a real app. @MASTG-DEMO-0x02 shows how enumerating the views at runtime lists every view on the screen, including these, together with its effective value.

When you tap **Start**, the app confirms these results at runtime by listing the value returned by `isAccessibilityDataSensitive` for each view: `false` for the username field and the **Confirm Payment** button, and `true` for the other fields.
131 changes: 131 additions & 0 deletions demos/android/MASVS-PLATFORM/MASTG-DEMO-0x01/MastgTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package org.owasp.mastestapp

import android.app.Activity
import android.content.Context
import android.graphics.Color
import android.os.Build
import android.text.InputType
import android.util.Log
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.view.WindowInsets
import android.widget.Button
import android.widget.EditText
import android.widget.FrameLayout
import android.widget.LinearLayout
import android.widget.Toast

// SUMMARY: This sample demonstrates views that handle sensitive data with and without accessibility data sensitivity,
// set explicitly, inherited from a parent, implied by filterTouchesWhenObscured, or explicitly disabled.

class MastgTest(private val context: Context) {

val shouldRunInMainThread = true

private val formTag = "mastg-demo-form"

fun mastgTest(): String {
val r = DemoResults("0x01")

if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
r.add(Status.ERROR, "Accessibility data sensitivity requires Android 14 (API level 34) or higher.")
return r.toJson()
}

val form = LinearLayout(context).apply {
tag = formTag
orientation = LinearLayout.VERTICAL
setBackgroundColor(Color.rgb(48, 48, 48))
setOnApplyWindowInsetsListener { view, insets ->
val bottom = insets.getInsets(WindowInsets.Type.systemBars() or WindowInsets.Type.ime()).bottom
view.setPadding(48, 32, 48, 32 + bottom)
insets
}
}

// FAIL: [MASTG-TEST-0x01] The username field handles credentials but isn't marked as accessibility data sensitive.
val usernameField = createField("Username", InputType.TYPE_CLASS_TEXT)
form.addView(usernameField)

// PASS: [MASTG-TEST-0x01] The password field is explicitly marked as accessibility data sensitive.
val passwordField = createField("Password", InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD)
passwordField.setAccessibilityDataSensitive(View.ACCESSIBILITY_DATA_SENSITIVE_YES)
form.addView(passwordField)

// PASS: [MASTG-TEST-0x01] The one-time code field uses filterTouchesWhenObscured, which implies accessibility data sensitivity.
val otpField = createField("One-time code", InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_PASSWORD)
otpField.filterTouchesWhenObscured = true
form.addView(otpField)

// PASS: [MASTG-TEST-0x01] The payment container is marked as accessibility data sensitive, so its children inherit the protection.
val paymentContainer = LinearLayout(context).apply {
orientation = LinearLayout.VERTICAL
}
paymentContainer.setAccessibilityDataSensitive(View.ACCESSIBILITY_DATA_SENSITIVE_YES)

val cardField = createField("Card number", InputType.TYPE_CLASS_NUMBER)
paymentContainer.addView(cardField)

// FAIL: [MASTG-TEST-0x01] The payment confirmation button explicitly disables the protection inherited from its parent.
val confirmButton = Button(context).apply {
text = "Confirm Payment"
setOnClickListener { closeForm(form, "Payment confirmed.") }
}
confirmButton.setAccessibilityDataSensitive(View.ACCESSIBILITY_DATA_SENSITIVE_NO)
paymentContainer.addView(confirmButton)

form.addView(paymentContainer)

val cancelButton = Button(context).apply {
text = "Cancel"
setOnClickListener { closeForm(form, "Payment cancelled.") }
}
form.addView(cancelButton)

(context as? Activity)?.let { activity ->
val root = activity.findViewById<ViewGroup>(android.R.id.content)
root.findViewWithTag<View>(formTag)?.let { root.removeView(it) }
root.addView(
form,
FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.WRAP_CONTENT,
Gravity.BOTTOM
)
)
form.requestApplyInsets()
}

listOf(
"Username field" to usernameField,
"Password field" to passwordField,
"One-time code field" to otpField,
"Card number field" to cardField,
"Confirm Payment button" to confirmButton
).forEach { (name, view) ->
val sensitive = view.isAccessibilityDataSensitive()
r.add(
if (sensitive) Status.PASS else Status.FAIL,
"$name: isAccessibilityDataSensitive=$sensitive"
)
}

return r.toJson()
}

private fun closeForm(form: View, message: String) {
(form.parent as? ViewGroup)?.removeView(form)
Log.i("MASTG-DEMO", message)
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}

private fun createField(hintText: String, type: Int): EditText {
return EditText(context).apply {
hint = hintText
inputType = type
setTextColor(Color.WHITE)
setHintTextColor(Color.LTGRAY)
}
}
}
Loading
Loading