[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[tor-commits] [Git][tpo/applications/tor-browser][tor-browser-153.2.0esr-16.0-1] 2 commits: TB 45621: Implement linter for Tor Browser-specific checks



Title: GitLab

brizental pushed to branch tor-browser-153.2.0esr-16.0-1 at The Tor Project / Applications / Tor Browser

Commits:

  • 068fb46d
    by Beatriz Rizental at 2026-09-10T19:33:43+02:00
    TB 45621: Implement linter for Tor Browser-specific checks
    
  • 52a46146
    by Beatriz Rizental at 2026-09-10T19:33:43+02:00
    fixup! [android] Disable features and functionality
    
    Bug 45621: Prevent other apps from opening Tor Browser
    

8 changed files:

Changes:

  • mobile/android/fenix/app/src/main/AndroidManifest.xml
    ... ... @@ -505,7 +505,7 @@
    505 505
             <activity
    
    506 506
                 android:name=".HomeActivity"
    
    507 507
                 android:theme="@style/SplashScreenThemeBase"
    
    508
    -            android:exported="true"
    
    508
    +            android:exported="false"
    
    509 509
                 android:configChanges="keyboard|keyboardHidden|mcc|mnc|orientation|screenSize|layoutDirection|smallestScreenSize|screenLayout"
    
    510 510
                 android:launchMode="singleTask"
    
    511 511
                 android:taskAffinity=""
    
    ... ... @@ -660,7 +660,7 @@
    660 660
     
    
    661 661
             <service
    
    662 662
                 android:name=".customtabs.CustomTabsService"
    
    663
    -            android:exported="true"
    
    663
    +            android:exported="false"
    
    664 664
                 tools:ignore="ExportedService">
    
    665 665
                 <intent-filter>
    
    666 666
                     <action android:name="android.support.customtabs.action.CustomTabsService" />
    
    ... ... @@ -674,7 +674,7 @@
    674 674
     
    
    675 675
             <receiver
    
    676 676
                 android:name="org.mozilla.gecko.search.SearchWidgetProvider"
    
    677
    -            android:exported="true">
    
    677
    +            android:exported="false">
    
    678 678
                 <intent-filter>
    
    679 679
                     <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
    
    680 680
                 </intent-filter>
    
    ... ... @@ -684,7 +684,7 @@
    684 684
             </receiver>
    
    685 685
     
    
    686 686
             <receiver android:name=".onboarding.WidgetPinnedReceiver"
    
    687
    -            android:exported="true">
    
    687
    +            android:exported="false">
    
    688 688
                 <intent-filter>
    
    689 689
                     <action android:name="org.mozilla.fenix.onboarding.WidgetPinnedReceiver.widgetPinned"/>
    
    690 690
                 </intent-filter>
    
    ... ... @@ -790,7 +790,7 @@
    790 790
             <!-- https://dev.adjust.com/en/sdk/android/setup/preinstalled#system-installer-receiver-->
    
    791 791
             <receiver
    
    792 792
                 android:name="com.adjust.sdk.AdjustPreinstallReferrerReceiver"
    
    793
    -            android:exported="true"
    
    793
    +            android:exported="false"
    
    794 794
                 tools:ignore="ExportedReceiver">
    
    795 795
                 <intent-filter>
    
    796 796
                     <action android:name="com.attribution.SYSTEM_INSTALLER_REFERRER" />
    
    ... ... @@ -813,7 +813,7 @@
    813 813
     
    
    814 814
             <receiver
    
    815 815
               android:name="org.mozilla.fenix.messaging.QAMessageNotificationWorkerReceiver"
    
    816
    -          android:exported="true"
    
    816
    +          android:exported="false"
    
    817 817
               android:enabled="true"
    
    818 818
               android:permission="android.permission.DUMP">
    
    819 819
               <intent-filter>
    
    ... ... @@ -823,7 +823,7 @@
    823 823
     
    
    824 824
             <receiver
    
    825 825
               android:name="org.mozilla.fenix.experiments.QANimbusToolingReceiver"
    
    826
    -          android:exported="true"
    
    826
    +          android:exported="false"
    
    827 827
               android:enabled="true"
    
    828 828
               android:permission="android.permission.DUMP">
    
    829 829
               <intent-filter>
    

  • mobile/android/fenix/app/src/nightly/AndroidManifest.xml
    ... ... @@ -9,7 +9,7 @@
    9 9
     
    
    10 10
             <service
    
    11 11
                 android:name=".customtabs.CustomTabsService"
    
    12
    -            android:exported="true">
    
    12
    +            android:exported="false">
    
    13 13
                 <intent-filter>
    
    14 14
                     <action android:name="android.support.customtabs.action.CustomTabsService" />
    
    15 15
                 </intent-filter>
    
    ... ... @@ -20,7 +20,7 @@
    20 20
             <provider
    
    21 21
                 android:name=".perf.ProfilerProvider"
    
    22 22
                 android:authorities="${applicationId}.profiler"
    
    23
    -            android:exported="true"
    
    23
    +            android:exported="false"
    
    24 24
                 android:enabled="true"
    
    25 25
                 tools:replace="android:exported,android:enabled" />
    
    26 26
     
    

  • tools/lint/test/files/tor-browser-checks/bad/AndroidManifest.xml
    1
    +<?xml version="1.0" encoding="utf-8"?>
    
    2
    +<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    
    3
    +    <application>
    
    4
    +        <activity
    
    5
    +            android:name=".IntentReceiverActivity"
    
    6
    +            android:exported="false">
    
    7
    +        </activity>
    
    8
    +
    
    9
    +        <!-- No intent-filter at all. Mirrors the original tor-browser#45145
    
    10
    +             regression (org.mozilla.gecko.BrowserApp). -->
    
    11
    +        <activity-alias
    
    12
    +            android:name="org.mozilla.gecko.BrowserApp"
    
    13
    +            android:targetActivity=".IntentReceiverActivity"
    
    14
    +            android:exported="true">
    
    15
    +        </activity-alias>
    
    16
    +
    
    17
    +        <!-- Has its own "open this link" intent-filter, but that's VIEW,
    
    18
    +             not MAIN+LAUNCHER, so it still doesn't need to be exported. -->
    
    19
    +        <activity-alias
    
    20
    +            android:name="org.mozilla.gecko.PermissiveApp"
    
    21
    +            android:targetActivity=".IntentReceiverActivity"
    
    22
    +            android:exported="true">
    
    23
    +            <intent-filter>
    
    24
    +                <action android:name="android.intent.action.VIEW" />
    
    25
    +                <category android:name="android.intent.category.BROWSABLE" />
    
    26
    +                <category android:name="android.intent.category.DEFAULT" />
    
    27
    +                <data android:scheme="http" />
    
    28
    +                <data android:scheme="https" />
    
    29
    +            </intent-filter>
    
    30
    +        </activity-alias>
    
    31
    +
    
    32
    +        <!-- Has LAUNCHER without MAIN: still not a real launcher entry
    
    33
    +             point, so this should be flagged too since both are required. -->
    
    34
    +        <activity-alias
    
    35
    +            android:name="org.mozilla.gecko.LauncherCategoryOnlyApp"
    
    36
    +            android:targetActivity=".IntentReceiverActivity"
    
    37
    +            android:exported="true">
    
    38
    +            <intent-filter>
    
    39
    +                <category android:name="android.intent.category.LAUNCHER" />
    
    40
    +            </intent-filter>
    
    41
    +        </activity-alias>
    
    42
    +
    
    43
    +        <!-- Not an activity at all: the check covers every exportable
    
    44
    +             component type, not just activities/aliases. -->
    
    45
    +        <receiver
    
    46
    +            android:name="com.example.evil.UnlistedReceiver"
    
    47
    +            android:exported="true">
    
    48
    +            <intent-filter>
    
    49
    +                <action android:name="com.example.evil.SOME_ACTION" />
    
    50
    +            </intent-filter>
    
    51
    +        </receiver>
    
    52
    +    </application>
    
    53
    +</manifest>

  • tools/lint/test/files/tor-browser-checks/good/AndroidManifest.xml
    1
    +<?xml version="1.0" encoding="utf-8"?>
    
    2
    +<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    
    3
    +    <application>
    
    4
    +        <activity
    
    5
    +            android:name=".IntentReceiverActivity"
    
    6
    +            android:exported="false">
    
    7
    +        </activity>
    
    8
    +
    
    9
    +        <activity-alias
    
    10
    +            android:name="org.mozilla.gecko.BrowserApp"
    
    11
    +            android:targetActivity=".IntentReceiverActivity"
    
    12
    +            android:exported="false">
    
    13
    +        </activity-alias>
    
    14
    +
    
    15
    +        <!-- Exported, but has a MAIN+LAUNCHER intent-filter: the one case
    
    16
    +             Android itself requires exported="true" for. -->
    
    17
    +        <activity-alias
    
    18
    +            android:name="${applicationId}.App"
    
    19
    +            android:exported="true"
    
    20
    +            android:targetActivity="HomeActivity">
    
    21
    +            <intent-filter>
    
    22
    +                <action android:name="android.intent.action.MAIN" />
    
    23
    +                <category android:name="android.intent.category.LAUNCHER" />
    
    24
    +            </intent-filter>
    
    25
    +        </activity-alias>
    
    26
    +
    
    27
    +        <!-- Exported and not a MAIN+LAUNCHER entry point, but it doesn't
    
    28
    +             matter since exported="false" is always fine to flag as good. -->
    
    29
    +        <receiver
    
    30
    +            android:name="onboarding.WidgetPinnedReceiver"
    
    31
    +            android:exported="false">
    
    32
    +            <intent-filter>
    
    33
    +                <action android:name="org.mozilla.fenix.onboarding.WidgetPinnedReceiver.widgetPinned" />
    
    34
    +            </intent-filter>
    
    35
    +        </receiver>
    
    36
    +    </application>
    
    37
    +</manifest>

  • tools/lint/test/python.toml
    ... ... @@ -69,6 +69,10 @@ skip-if = ["os == 'win'"]
    69 69
     ["test_shellcheck.py"]
    
    70 70
     
    
    71 71
     ["test_stylelint.py"]
    
    72
    +
    
    73
    +["test_tor_browser_checks.py"]
    
    74
    +tags = "base-browser"
    
    75
    +
    
    72 76
     skip-if = ["os == 'win'"]  # busts the tree for subsequent tasks on the same worker (bug 1708591)
    
    73 77
     # Setup conflicts with eslint setup so this should run sequentially.
    
    74 78
     sequential = true
    

  • tools/lint/test/test_tor_browser_checks.py
    1
    +import mozunit
    
    2
    +
    
    3
    +LINTER = "tor-browser-checks"
    
    4
    +
    
    5
    +
    
    6
    +def test_flags_exported_non_launcher_components(lint, paths):
    
    7
    +    results = lint(paths("bad/AndroidManifest.xml"))
    
    8
    +    assert len(results) == 4
    
    9
    +
    
    10
    +    flagged_names = {
    
    11
    +        "org.mozilla.gecko.BrowserApp",
    
    12
    +        "org.mozilla.gecko.PermissiveApp",
    
    13
    +        "org.mozilla.gecko.LauncherCategoryOnlyApp",
    
    14
    +        "com.example.evil.UnlistedReceiver",
    
    15
    +    }
    
    16
    +    for expected_name in flagged_names:
    
    17
    +        assert any(expected_name in r.message for r in results)
    
    18
    +
    
    19
    +    for r in results:
    
    20
    +        assert r.rule == "unnecessary-exported-component"
    
    21
    +
    
    22
    +
    
    23
    +def test_allows_launcher_entries_and_unexported_components(lint, paths):
    
    24
    +    results = lint(paths("good/AndroidManifest.xml"))
    
    25
    +    assert len(results) == 0
    
    26
    +
    
    27
    +
    
    28
    +if __name__ == "__main__":
    
    29
    +    mozunit.main()

  • tools/lint/tor-browser-checks.yml
    1
    +---
    
    2
    +tor-browser-checks:
    
    3
    +    description: >-
    
    4
    +        Tor Browser specific static checks (one linter, several
    
    5
    +        independent checks -- see tools/lint/tor-browser-checks/__init__.py)
    
    6
    +    include:
    
    7
    +        - 'mobile/android/fenix/app/src/main/AndroidManifest.xml'
    
    8
    +        - 'mobile/android/fenix/app/src/debug/AndroidManifest.xml'
    
    9
    +        - 'mobile/android/fenix/app/src/beta/AndroidManifest.xml'
    
    10
    +        - 'mobile/android/fenix/app/src/release/AndroidManifest.xml'
    
    11
    +        - 'mobile/android/fenix/app/src/nightly/AndroidManifest.xml'
    
    12
    +        - 'mobile/android/fenix/app/src/benchmark/AndroidManifest.xml'
    
    13
    +    support-files:
    
    14
    +        - 'tools/lint/tor-browser-checks/**'
    
    15
    +    type: external
    
    16
    +    payload: tor-browser-checks:lint

  • tools/lint/tor-browser-checks/__init__.py
    1
    +# This Source Code Form is subject to the terms of the Mozilla Public
    
    2
    +# License, v. 2.0. If a copy of the MPL was not distributed with this
    
    3
    +# file, You can obtain one at http://mozilla.org/MPL/2.0/.
    
    4
    +
    
    5
    +import xml.etree.ElementTree as ET
    
    6
    +
    
    7
    +from mozlint import result
    
    8
    +
    
    9
    +ANDROID_NS = "{http://schemas.android.com/apk/res/android}"
    
    10
    +
    
    11
    +# Component types that can carry android:exported.
    
    12
    +EXPORTABLE_TAGS = ("activity", "activity-alias", "service", "receiver", "provider")
    
    13
    +
    
    14
    +MAIN_ACTION = "android.intent.action.MAIN"
    
    15
    +LAUNCHER_CATEGORY = "android.intent.category.LAUNCHER"
    
    16
    +
    
    17
    +
    
    18
    +def _find_lineno(lines, name):
    
    19
    +    # ElementTree doesn't track source positions. This is a best-effort fallback
    
    20
    +    # instead: find the line where this element's own android:name is written
    
    21
    +    # out. Good enough to jump to the right spot, but not a guarantee if `name`
    
    22
    +    # shows up as some other attribute's value too.
    
    23
    +    needle = f'name="{name}"'
    
    24
    +    for lineno, line in enumerate(lines, start=1):
    
    25
    +        if needle in line:
    
    26
    +            return lineno
    
    27
    +    return 0
    
    28
    +
    
    29
    +
    
    30
    +def _has_launcher_intent_filter(el):
    
    31
    +    # A MAIN+LAUNCHER intent-filter is the one case Android itself makes
    
    32
    +    # exported="true" load-bearing: that's what lets the home screen (a
    
    33
    +    # separate app, from the OS's point of view) resolve and start this
    
    34
    +    # component when its icon is tapped. Nothing else needs to be exported
    
    35
    +    # for the app to function -- everything else is a deliberate choice to
    
    36
    +    # let other apps reach in, and should default to false.
    
    37
    +    for intent_filter in el.findall("intent-filter"):
    
    38
    +        actions = {a.get(ANDROID_NS + "name") for a in intent_filter.findall("action")}
    
    39
    +        categories = {
    
    40
    +            c.get(ANDROID_NS + "name") for c in intent_filter.findall("category")
    
    41
    +        }
    
    42
    +        if MAIN_ACTION in actions and LAUNCHER_CATEGORY in categories:
    
    43
    +            return True
    
    44
    +    return False
    
    45
    +
    
    46
    +
    
    47
    +def _check_only_launcher_entries_are_exported(path, config):
    
    48
    +    """Flags any exported component in a fenix AndroidManifest.xml that
    
    49
    +    isn't a MAIN+LAUNCHER entry point.
    
    50
    +
    
    51
    +    This enforces the rule directly: android:exported="true" is only ever
    
    52
    +    structurally required for a component that's meant to be launched from
    
    53
    +    the home screen. Anything else that's exported can be set to exported="false"
    
    54
    +    instead, even if that means turning off the feature that needed it.
    
    55
    +
    
    56
    +    Not autofixable: turning exported="false" off may mean losing the
    
    57
    +    feature that needed it (e.g. PWA shortcut relaunching), which needs a
    
    58
    +    human to decide whether that tradeoff is acceptable case by case.
    
    59
    +
    
    60
    +    Only looks at AndroidManifest.xml fragments; anything else is skipped.
    
    61
    +    """
    
    62
    +    if not path.endswith("AndroidManifest.xml"):
    
    63
    +        return []
    
    64
    +
    
    65
    +    try:
    
    66
    +        # Manifest fragments (e.g. src/nightly/AndroidManifest.xml) can be a
    
    67
    +        # bare <manifest> with no <application> at all -- nothing to check.
    
    68
    +        app = ET.parse(path).getroot().find("application")
    
    69
    +    except ET.ParseError:
    
    70
    +        return []
    
    71
    +    if app is None:
    
    72
    +        return []
    
    73
    +
    
    74
    +    with open(path, encoding="utf-8") as f:
    
    75
    +        lines = f.readlines()
    
    76
    +
    
    77
    +    issues = []
    
    78
    +    for tag in EXPORTABLE_TAGS:
    
    79
    +        for el in app.findall(tag):
    
    80
    +            if el.get(ANDROID_NS + "exported") != "true":
    
    81
    +                continue
    
    82
    +            if _has_launcher_intent_filter(el):
    
    83
    +                continue
    
    84
    +
    
    85
    +            name = el.get(ANDROID_NS + "name")
    
    86
    +
    
    87
    +            issues.append(
    
    88
    +                result.from_config(
    
    89
    +                    config,
    
    90
    +                    path=path,
    
    91
    +                    lineno=_find_lineno(lines, name),
    
    92
    +                    message=(
    
    93
    +                        f'<{tag}> "{name}" is exported="true" but has no '
    
    94
    +                        "MAIN+LAUNCHER intent-filter, so Android doesn't "
    
    95
    +                        'require it to be exported -- set exported="false" '
    
    96
    +                        "(see tor-browser#45145)."
    
    97
    +                    ),
    
    98
    +                    level="error",
    
    99
    +                    rule="unnecessary-exported-component",
    
    100
    +                ),
    
    101
    +            )
    
    102
    +
    
    103
    +    return issues
    
    104
    +
    
    105
    +
    
    106
    +# Add new Tor Browser security checks here. Each one receives a file path
    
    107
    +# and is responsible for deciding whether that path is relevant to it, so
    
    108
    +# checks are free to target entirely different kinds of files.
    
    109
    +#
    
    110
    +# Note: Remember to extend the linter's `include`/`extensions` in
    
    111
    +# tor-browser-checks.yml if a new check needs paths that aren't already covered.
    
    112
    +CHECKS = [
    
    113
    +    _check_only_launcher_entries_are_exported,
    
    114
    +]
    
    115
    +
    
    116
    +
    
    117
    +def lint(paths, config, **lintargs):
    
    118
    +    results = []
    
    119
    +
    
    120
    +    for path in paths:
    
    121
    +        for check in CHECKS:
    
    122
    +            results.extend(check(path, config))
    
    123
    +
    
    124
    +    return results

  • _______________________________________________
    tor-commits mailing list -- tor-commits@xxxxxxxxxxxxxxxxxxxx
    To unsubscribe send an email to tor-commits-leave@xxxxxxxxxxxxxxxxxxxx