Mobile Apps
Noir extracts mobile app entry points — the deep links and exported components an Android or iOS app exposes to the outside world — as endpoints, and links them to the source code that handles them. Deep links are a classic mobile attack surface: an externally supplied URL or intent flows into the app and can reach a WebView, an SQL query, or another component.
What Noir parses
| Platform | Source file | What it yields |
|---|---|---|
| Android | AndroidManifest.xml |
custom URL-scheme deep links, exported intent components, exported ContentProviders, verified App Links |
| Android | res/values/strings.xml |
resolves @string/ references used in schemes / hosts / paths |
| Android | build.gradle / build.gradle.kts |
resolves ${applicationId} and custom manifestPlaceholders used in package / component names / schemes / hosts; supplies the package when the manifest has no package attribute |
| Android | res/navigation/*.xml |
Jetpack Navigation <deepLink app:uri="..."> deep links — the owning destination becomes the handling component |
| iOS | Info.plist (any *.plist declaring CFBundleURLTypes) |
custom URL schemes — also resolves $(VAR) / ${VAR} placeholders from .xcconfig / project.pbxproj |
| iOS | *.entitlements |
associated-domains applinks: universal links and appclips: App Clip launch URLs |
| Android | /.well-known/assetlinks.json |
server-side App Links association (Digital Asset Links) |
| iOS | apple-app-site-association |
server-side universal-link paths / components patterns |
The first six rows are the client side of the association, declared in the app bundle. The last two are the server side — the well-known files a host publishes so the OS opens the app for its URLs. Both flow through the same universal-link protocol and output model.
Endpoint model
Mobile entry points are endpoints with method = "GET"; their nature is carried in protocol:
| protocol | meaning | example URL |
|---|---|---|
mobile-scheme |
custom URL-scheme deep link | myapp://complex/:id |
android-intent |
exported intent component reachable by intent — a data-less action filter, or no intent-filter at all (explicit-intent only) | intent://com.example.app/.SyncService |
android-provider |
exported ContentProvider reachable by another app through a content://authority URI |
content://com.example.app.provider |
universal-link |
verified Android App Link / iOS universal link, or a server-side assetlinks.json / apple-app-site-association pattern |
https://app.example.com/complex/:id, /buy/* |
In plain output they render under a SCHEME / INTENT / PROVIDER / UNIVERSAL prefix. The handling component, intent action and category, host, and package are stored in a per-endpoint metadata map (serialized in JSON / YAML; omitted entirely for ordinary HTTP endpoints):
SCHEME myapp://complex/:id
○ via: .DeepLinkActivity
○ action: android.intent.action.VIEW
○ host: complex
○ package: com.example.myapp
Linking to handler code
When the handler source is present in the scan, Noir connects each deep-link endpoint to the code that processes it:
- Android — the manifest component (e.g.
.DeepLinkActivity) is resolved to its.kt/.javafile. The intent-handling methods (onCreate,onNewIntent,onReceive, …) contribute 1-hop callees, and the inputs they read become parameters:uri.getQueryParameter("q")→ aqueryparam (baked into the URL), and theget*Extrafamily → anextraparam. - iOS — deep links are dispatched centrally, so Noir discovers the handlers and attaches them by kind:
.onOpenURL/application(_:open:)/scene(_:openURLContexts:)to custom schemes, and theuserActivityhandlers to universal links.URLQueryItemreads becomequeryparams.
With --ai-context, the handler body feeds Noir's source / sink / guard inference, so a deep link that flows into a WebView load or another sink surfaces with the taint path attached.
Output behavior
Mobile endpoints stay in the structured inventory — JSON, JSONL, YAML, SARIF, Markdown, mermaid, HTML — and in the Elasticsearch / webhook exports, because they are part of the app's surface. They are excluded from HTTP-shaped output and delivery — cURL, HTTPie, PowerShell, OpenAPI 2.0 / 3.0, and active probe / proxy delivery — because an app URL is something you open, not an HTTP request you send.
Notes and limitations
- Source code must be present for handler linkage. A manifest- or plist-only scan still yields the endpoints, just without callees, params, or AI context.
- Binary (compiled)
Info.plistfiles are skipped; source-repo XML plists are parsed. - gradle placeholder resolution reads the nearest
build.gradle(.kts)above the manifest; when the same placeholder is declared more than once (e.g. abuildTypesoverride), the first declaration — by conventiondefaultConfig— wins. Variant-specific values are not modeled. A placeholder with no gradle value stays verbatim in the URL and the endpoint is taggedunresolved. - Scheme-less Jetpack Navigation URIs (which match both http and https at runtime) are emitted once under
https.{arg}segments become:argpath params,?key={arg}query placeholders becomequeryparams, and a trailing.*wildcard keeps the literal prefix. - An
android:exported="true"component with no intent-filter is reported as an explicit-intent surface (android-intent, withexplicit/exported/component_typein metadata): an explicit intent naming the component still reaches it even with no action, category, or data URI. A filter-less component that is not exported, or isandroid:enabled="false", is not reported. A guardingandroid:permissionis recorded in metadata but does not suppress the surface —normal/dangerouspermissions remain obtainable by another app. - An exported
<provider>(ContentProvider) is reported as anandroid-providersurface — onecontent://authorityendpoint perandroid:authoritiesentry. The provider class links as the handler (via), so itsquery/insert/openFilemethods contribute callees (a raw SQL / file sink surfaces in AI context).android:readPermission/android:writePermission/android:permission,android:grantUriPermissions(or a<grant-uri-permission>child), and the presence of<path-permission>overrides ride in metadata — all recorded, never suppressed. Only an explicitly exported provider is reported (a provider defaults to not-exported on modern SDKs); the effective per-path permission of a<path-permission>is flagged but not resolved. assetlinks.jsononly declares the package association (no paths), so it yields a single/*endpoint per app.apple-app-site-associationpaths (/buy/*,NOT /private/*) andcomponentsare emitted individually; AASA exclusions are taggedexcluded.- Only the plain-JSON form of
apple-app-site-associationis parsed; CMS-signed AASA files (older apps) are skipped.