Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
36 changes: 36 additions & 0 deletions demos/android/MASVS-PLATFORM/MASTG-DEMO-XXXA/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.INTERNET" />

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MASTestApp"
android:usesCleartextTraffic="true"
tools:targetApi="31">

<activity
android:name=".MainActivity"
android:exported="true"
android:theme="@style/Theme.MASTestApp">
<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:host="deeplink"
android:scheme="vulnerable-app" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

</application>
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="1"
android:versionName="1.0"
android:compileSdkVersion="35"
android:compileSdkVersionCodename="15"
package="org.owasp.mastestapp"
platformBuildVersionCode="35"
platformBuildVersionName="15">
<uses-sdk
android:minSdkVersion="29"
android:targetSdkVersion="35"/>
<uses-permission android:name="android.permission.INTERNET"/>
<permission
android:name="org.owasp.mastestapp.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"
android:protectionLevel="signature"/>
<uses-permission android:name="org.owasp.mastestapp.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"/>
<application
android:theme="@style/Theme.MASTestApp"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:debuggable="true"
android:allowBackup="true"
android:supportsRtl="true"
android:extractNativeLibs="false"
android:usesCleartextTraffic="true"
android:roundIcon="@mipmap/ic_launcher_round"
android:appComponentFactory="androidx.core.app.CoreComponentFactory">
<activity
android:theme="@style/Theme.MASTestApp"
android:name="org.owasp.mastestapp.MainActivity"
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="vulnerable-app"
android:host="deeplink"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name="androidx.compose.ui.tooling.PreviewActivity"
android:exported="true"/>
<activity
android:name="androidx.activity.ComponentActivity"
android:exported="true"/>
<provider
android:name="androidx.startup.InitializationProvider"
android:exported="false"
android:authorities="org.owasp.mastestapp.androidx-startup">
<meta-data
android:name="androidx.emoji2.text.EmojiCompatInitializer"
android:value="androidx.startup"/>
<meta-data
android:name="androidx.lifecycle.ProcessLifecycleInitializer"
android:value="androidx.startup"/>
<meta-data
android:name="androidx.profileinstaller.ProfileInstallerInitializer"
android:value="androidx.startup"/>
</provider>
<receiver
android:name="androidx.profileinstaller.ProfileInstallReceiver"
android:permission="android.permission.DUMP"
android:enabled="true"
android:exported="true"
android:directBootAware="false">
<intent-filter>
<action android:name="androidx.profileinstaller.action.INSTALL_PROFILE"/>
</intent-filter>
<intent-filter>
<action android:name="androidx.profileinstaller.action.SKIP_FILE"/>
</intent-filter>
<intent-filter>
<action android:name="androidx.profileinstaller.action.SAVE_PROFILE"/>
</intent-filter>
<intent-filter>
<action android:name="androidx.profileinstaller.action.BENCHMARK_OPERATION"/>
</intent-filter>
</receiver>
</application>
</manifest>
32 changes: 32 additions & 0 deletions demos/android/MASVS-PLATFORM/MASTG-DEMO-XXXA/MASTG-DEMO-XXXA.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
platform: android
title: Unvalidated URL from Deep Link Loaded in WebView with semgrep
id: MASTG-DEMO-XXXA
code: [kotlin]
test: MASTG-TEST-XXXA
status: new
Comment on lines +1 to +7
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The demo front matter uses placeholder IDs (id: MASTG-DEMO-XXXA, test: MASTG-TEST-XXXA). Demos must use the final numeric demo ID in both the folder name and the id: field, and test: should reference the actual numeric test ID.

Copilot uses AI. Check for mistakes.
---

## Sample

The following is a sample code file that contains a function to handle a deep link, which insecurely loads a URL into a WebView.

{{ MastgTest.kt # MastgTest_reversed.java }}

## Steps

Let's run @MASTG-TOOL-0110 rules against the sample code.

{{ ../../../../rules/mastg-android-unvalidated-deeplink-data.yml }}

{{ run.sh }}

## Observation

The output file shows usage of dangerous data flow from a source `getQueryParameter` to a sink `loadUrl`.

{{ output.txt }}

## Evaluation

The test fails because the app loads a user-controllable URL from a deep link directly into a WebView without validation.
32 changes: 32 additions & 0 deletions demos/android/MASVS-PLATFORM/MASTG-DEMO-XXXA/MastgTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.owasp.mastestapp

import android.annotation.SuppressLint
import android.content.Context
import android.net.Uri
import android.webkit.WebView
import androidx.activity.ComponentActivity

class MastgTest(private val context: Context) {

fun mastgTest(): String {
return """
This app is vulnerable to deep link attacks.

Test with:
adb shell am start -a android.intent.action.VIEW -d "vulnerable-app://deeplink?url=https://example.com"
""".trimIndent()
}

@SuppressLint("SetJavaScriptEnabled")
fun processDeepLinkAndLoad(uri: Uri?) {
if (uri == null) return

val url = uri.getQueryParameter("url")
if (url != null) {
val webView = WebView(context)
webView.settings.javaScriptEnabled = true
webView.loadUrl(url)
(context as ComponentActivity).setContentView(webView)
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(context as ComponentActivity).setContentView(webView) will throw a ClassCastException if context is not a ComponentActivity (for example if a ContextWrapper is passed). For a demo that should reliably run, pass in an Activity/ComponentActivity explicitly, or use a safe cast with a clear error path.

Suggested change
(context as ComponentActivity).setContentView(webView)
(context as? ComponentActivity)?.setContentView(webView)

Copilot uses AI. Check for mistakes.
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.owasp.mastestapp;

import android.content.Context;
import android.net.Uri;
import android.webkit.WebView;
import androidx.activity.ComponentActivity;
import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;

/* compiled from: MastgTest.kt */
@Metadata(d1 = {"\u0000$\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0003\n\u0002\u0010\u000e\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n\u0000\b\u0007\u0018\u00002\u00020\u0001B\u000f\u0012\u0006\u0010\u0002\u001a\u00020\u0003¢\u0006\u0004\b\u0004\u0010\u0005J\u0006\u0010\u0006\u001a\u00020\u0007J\u0012\u0010\b\u001a\u00020\t2\b\u0010\n\u001a\u0004\u0018\u00010\u000bH\u0007R\u000e\u0010\u0002\u001a\u00020\u0003X\u0082\u0004¢\u0006\u0002\n\u0000¨\u0006\f"}, d2 = {"Lorg/owasp/mastestapp/MastgTest;", "", "context", "Landroid/content/Context;", "<init>", "(Landroid/content/Context;)V", "mastgTest", "", "processDeepLinkAndLoad", "", "uri", "Landroid/net/Uri;", "app_debug"}, k = 1, mv = {2, 0, 0}, xi = 48)
/* loaded from: classes3.dex */
public final class MastgTest {
public static final int $stable = 8;
private final Context context;

public MastgTest(Context context) {
Intrinsics.checkNotNullParameter(context, "context");
this.context = context;
}

public final String mastgTest() {
return "This app is vulnerable to deep link attacks.\n\nTest with:\nadb shell am start -a android.intent.action.VIEW -d \"vulnerable-app://deeplink?url=https://example.com\"";
}

public final void processDeepLinkAndLoad(Uri uri) {
String url;
if (uri != null && (url = uri.getQueryParameter("url")) != null) {
WebView webView = new WebView(this.context);
webView.getSettings().setJavaScriptEnabled(true);
webView.loadUrl(url);
Context context = this.context;
Intrinsics.checkNotNull(context, "null cannot be cast to non-null type androidx.activity.ComponentActivity");
((ComponentActivity) context).setContentView(webView);
}
}
}
12 changes: 12 additions & 0 deletions demos/android/MASVS-PLATFORM/MASTG-DEMO-XXXA/output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@



┌────────────────┐
│ 1 Code Finding │
└────────────────┘

MastgTest_reversed.java
❯❱ android-unvalidated-deeplink-data
[MASVS-PLATFORM] Unvalidated deep link query parameters are directly loaded into a WebView.

31┆ webView.loadUrl(url);
1 change: 1 addition & 0 deletions demos/android/MASVS-PLATFORM/MASTG-DEMO-XXXA/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
NO_COLOR=true semgrep -c ../../../../rules/mastg-android-unvalidated-deeplink-data.yml ./MastgTest_reversed.java --text -o output.txt
36 changes: 36 additions & 0 deletions demos/android/MASVS-PLATFORM/MASTG-DEMO-XXXB/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.INTERNET" />

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MASTestApp"
android:usesCleartextTraffic="true"
tools:targetApi="31">

<activity
android:name=".MainActivity"
android:exported="true"
android:theme="@style/Theme.MASTestApp">
<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:host="deeplink"
android:scheme="vulnerable-app" />
Comment on lines +26 to +27
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as the reversed manifest: this sample uses a custom scheme (vulnerable-app://...) but the demo/test text is about App Links verification (android:autoVerify), which applies to HTTPS App Links. Use an https scheme + host to demonstrate missing App Links verification correctly.

Suggested change
android:host="deeplink"
android:scheme="vulnerable-app" />
android:host="deeplink.example.com"
android:scheme="https" />

Copilot uses AI. Check for mistakes.
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

</application>
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="1"
android:versionName="1.0"
android:compileSdkVersion="35"
android:compileSdkVersionCodename="15"
package="org.owasp.mastestapp"
platformBuildVersionCode="35"
platformBuildVersionName="15">
<uses-sdk
android:minSdkVersion="29"
android:targetSdkVersion="35"/>
<uses-permission android:name="android.permission.INTERNET"/>
<permission
android:name="org.owasp.mastestapp.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"
android:protectionLevel="signature"/>
<uses-permission android:name="org.owasp.mastestapp.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"/>
<application
android:theme="@style/Theme.MASTestApp"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:debuggable="true"
android:allowBackup="true"
android:supportsRtl="true"
android:extractNativeLibs="false"
android:usesCleartextTraffic="true"
android:roundIcon="@mipmap/ic_launcher_round"
android:appComponentFactory="androidx.core.app.CoreComponentFactory">
<activity
android:theme="@style/Theme.MASTestApp"
android:name="org.owasp.mastestapp.MainActivity"
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="vulnerable-app"
android:host="deeplink"/>
Comment on lines +38 to +39
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This demo is intended to illustrate missing App Links verification (android:autoVerify), but the intent-filter shown uses a custom scheme (android:scheme="vulnerable-app"). App Links are HTTPS URLs; android:autoVerify is not applicable to custom schemes. Update the sample to use an https scheme + host (and keep android:autoVerify absent) so the demo and rule match the real failure mode.

Suggested change
android:scheme="vulnerable-app"
android:host="deeplink"/>
android:scheme="https"
android:host="vulnerable-app.example"/>

Copilot uses AI. Check for mistakes.
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name="androidx.compose.ui.tooling.PreviewActivity"
android:exported="true"/>
<activity
android:name="androidx.activity.ComponentActivity"
android:exported="true"/>
<provider
android:name="androidx.startup.InitializationProvider"
android:exported="false"
android:authorities="org.owasp.mastestapp.androidx-startup">
<meta-data
android:name="androidx.emoji2.text.EmojiCompatInitializer"
android:value="androidx.startup"/>
<meta-data
android:name="androidx.lifecycle.ProcessLifecycleInitializer"
android:value="androidx.startup"/>
<meta-data
android:name="androidx.profileinstaller.ProfileInstallerInitializer"
android:value="androidx.startup"/>
</provider>
<receiver
android:name="androidx.profileinstaller.ProfileInstallReceiver"
android:permission="android.permission.DUMP"
android:enabled="true"
android:exported="true"
android:directBootAware="false">
<intent-filter>
<action android:name="androidx.profileinstaller.action.INSTALL_PROFILE"/>
</intent-filter>
<intent-filter>
<action android:name="androidx.profileinstaller.action.SKIP_FILE"/>
</intent-filter>
<intent-filter>
<action android:name="androidx.profileinstaller.action.SAVE_PROFILE"/>
</intent-filter>
<intent-filter>
<action android:name="androidx.profileinstaller.action.BENCHMARK_OPERATION"/>
</intent-filter>
</receiver>
</application>
</manifest>
Loading
Loading