v0.14 is out: a mobile auth pack for Swift/iOS and Android, catching insecure token storage, cleartext traffic, and OAuth in embedded WebViews. Read more →
MEDIUM AI PREVALENCE: MEDIUM auth.xml.android.exported-oauth-redirect

An exported activity registers a BROWSABLE intent-filter for a custom-scheme OAuth redirect (android:scheme="com.example.app" with an oauth/callback/ redirect host).

Why AI tools produce this: AI coding tools produce this regularly, typically when prompted for a shortcut or a quick fix.

Why this matters

Custom URI schemes are not owned by any one app: a malicious app can register the same scheme and intercept the authorization-code redirect, capturing the code (CWE-926). AI-generated manifests wire the OAuth callback to a hand-rolled exported activity on a custom scheme.

Prefer a verified HTTPS App Link (android:scheme="https" with android:autoVerify="true"), which only your app, proven via Digital Asset Links, can claim. If you use AppAuth, let its net.openid.appauth.RedirectUriReceiverActivity handle the redirect (not flagged), and always pair the flow with PKCE so an intercepted code is useless.

VULNERABLE
vulnerable.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.app">
    <application android:label="Example">
        <!-- ruleid: auth.xml.android.exported-oauth-redirect -->
        <activity
            android:name=".OAuthCallbackActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="com.example.app" android:host="oauth2callback" />
            </intent-filter>
        </activity>
    </application>
</manifest>
SAFE
safe.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.example.app">
    <application android:label="Example">

        <!-- AppAuth's own redirect receiver: safe (library-managed) -->
        <activity
            android:name="net.openid.appauth.RedirectUriReceiverActivity"
            android:exported="true"
            tools:node="replace">
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="com.example.app" android:host="oauth2redirect" />
            </intent-filter>
        </activity>

        <!-- verified https App Link redirect: safe -->
        <activity
            android:name=".HttpsCallbackActivity"
            android:exported="true">
            <intent-filter android:autoVerify="true">
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="https" android:host="app.example.com" android:path="/oauth2redirect" />
            </intent-filter>
        </activity>

    </application>
</manifest>

Suppressing this rule

If a finding is a genuine false positive, scope the suppression to the exact line and leave a reason, never disable the rule project-wide. Disable directives are line-scoped by design.

// oauthlint-disable-next-line auth.xml.android.exported-oauth-redirect -- <reason>

References

https://developer.android.com/training/app-links ↗https://datatracker.ietf.org/doc/html/rfc8252#section-8.6 ↗https://cwe.mitre.org/data/definitions/926.html ↗