Skip to content

Security: warn user when accessibility service is active before seed phrase reveal #20965

@xAlisher

Description

@xAlisher

Summary

PR #20942 correctly suppresses the seed phrase from the accessibility tree (Accessible.ignored). This issue proposes a second gate: detect when an active accessibility service is running and warn the user before they tap Reveal.

An app holding BIND_ACCESSIBILITY_SERVICE can read on-screen content even after #20942 if the user taps Reveal — once the words are user-initiated-revealed, they are intentionally visible. The risk is that the user does not know a third-party app is listening.

Proposed UX

When the user navigates to the seed phrase reveal screen, and before they tap Reveal:

  1. Query the list of enabled, non-system accessibility services
  2. If any are found → show an inline warning banner:

An app with screen reader access is active
[App name] can read content on this screen. Make sure you trust this app before revealing your recovery phrase.
[Continue anyway] [Cancel]

  1. If no third-party accessibility services → proceed normally, no interruption

This is distinct from blocking the reveal — it is an informed-consent step. The user keeps full control.

Implementation sketch (Java + Qt/QML — matching Status codebase pattern)

Android side — Java (following the existing helper pattern in mobile/android/qt6/src/app/status/mobile/):

// AccessibilityServiceHelper.java
package app.status.mobile;

import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.Context;
import android.view.accessibility.AccessibilityManager;
import java.util.ArrayList;
import java.util.List;

public class AccessibilityServiceHelper {

    public static String[] getActiveThirdPartyServices(Context context) {
        AccessibilityManager am =
            (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
        List<AccessibilityServiceInfo> services =
            am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK);

        List<String> result = new ArrayList<>();
        for (AccessibilityServiceInfo info : services) {
            String pkg = info.getResolveInfo().serviceInfo.packageName;
            // Exclude known system/OEM packages
            if (pkg.startsWith("com.android") ||
                pkg.startsWith("com.samsung") ||
                pkg.startsWith("com.sec") ||
                pkg.equals(context.getPackageName())) continue;

            // On API 31+, prefer isAccessibilityTool() to skip declared assistive tools
            if (android.os.Build.VERSION.SDK_INT >= 31 && info.isAccessibilityTool()) continue;

            result.add(info.getResolveInfo().loadLabel(context.getPackageManager()).toString());
        }
        return result.toArray(new String[0]);
    }
}

Expose via JNI from StatusQtActivity.java (same pattern as mainWindowReady(), openDeepLink() etc.):

// In StatusQtActivity.java
public static String[] getThirdPartyA11yServices() {
    if (sInstance == null) return new String[0];
    return AccessibilityServiceHelper.getActiveThirdPartyServices(sInstance);
}

QML side — call before reveal:

// SeedPhrase.qml
property var activeA11yServices: []

Component.onCompleted: {
    // Qt.callMethod or existing JNI bridge pattern used in the codebase
    activeA11yServices = StatusAndroid.getThirdPartyA11yServices()
}

StatusBanner {
    visible: activeA11yServices.length > 0
    type: StatusBanner.Type.Warning
    statusText: qsTr("An app with screen reader access is active: %1. It may be able to read this screen after you reveal your recovery phrase.")
        .arg(activeA11yServices.join(", "))
}

Why this matters

  • Accessible.ignored (PR fix: exclude seed phrase words from accessibility tree before Reveal tap #20942) protects the pre-reveal tree — correct and necessary
  • Post-reveal, the words are intentionally shown — no suppression is appropriate
  • Without this warning, a malicious accessibility service (e.g. Crocodilus, SeedSnatcher) could log the words the moment the user taps Reveal, without any indication
  • This check costs one API call and adds at most one confirmation step for affected users

Reference

Metadata

Metadata

Assignees

Type

No fields configured for Task.

Projects

Status

Code Review

Relationships

None yet

Development

No branches or pull requests

Issue actions