Submission Target
The repository does not contain a .github/ISSUE_TEMPLATE bug-report template. It does contain SECURITY.md, which says not to open public GitHub issues for security vulnerabilities and to report them through GitHub Security Advisories:
https://github.com/AidanPark/openclaw-android/security/advisories/new
Affected Versions
- Confirmed affected: OpenClaw Android
0.4.0, commit cfb0740fc0961f1dd1c2a22ecf133eae443fa96f
- Other versions: Not verified
- Fixed: Not available at the time of reporting
Description
The Android app exposes a native JavaScript bridge named window.OpenClaw to WebView content. The bridge includes runCommand and runCommandAsync, both of which accept a caller-controlled command string from JavaScript and pass it directly to the command runner.
The command runner executes the supplied string with sh -c, so shell metacharacters such as ;, &&, pipes, redirects, and command substitution are interpreted by the shell. There is no command allowlist, argument separation, origin-bound authorization check, or validation step before execution.
As a result, any attacker who obtains JavaScript execution in the app WebView can escalate that capability into arbitrary shell command execution under the app's Termux/OpenClaw runtime context.
Evidence
The WebView enables JavaScript, permits broad file URL access, registers the native bridge, and loads local WebView content:
# https://github.com/AidanPark/openclaw-android/blob/cfb0740fc0961f1dd1c2a22ecf133eae443fa96f/android/app/src/main/java/com/openclaw/android/MainActivity.kt#L130-L172
binding.webView.apply {
clearCache(true)
settings.javaScriptEnabled = true
settings.domStorageEnabled = true
settings.allowFileAccess = true
@Suppress("DEPRECATION")
settings.allowFileAccessFromFileURLs = true
@Suppress("DEPRECATION")
settings.allowUniversalAccessFromFileURLs = true
settings.cacheMode = android.webkit.WebSettings.LOAD_NO_CACHE
addJavascriptInterface(jsBridge, "OpenClaw")
...
}
...
binding.webView.loadUrl(url)
The bridge exposes runCommandAsync and passes the JavaScript-controlled cmd directly to CommandRunner.runStreaming:
# https://github.com/AidanPark/openclaw-android/blob/cfb0740fc0961f1dd1c2a22ecf133eae443fa96f/android/app/src/main/java/com/openclaw/android/JsBridge.kt#L447-L468
@JavascriptInterface
fun runCommandAsync(
callbackId: String,
cmd: String,
) {
launchWithErrorHandling(
errorEventType = "command_output",
errorContext = mapOf("callbackId" to callbackId, "done" to true),
) {
val env = EnvironmentBuilder.build(activity)
CommandRunner.runStreaming(cmd, env, bootstrapManager.homeDir) { output ->
eventBridge.emit(
"command_output",
mapOf("callbackId" to callbackId, "data" to output, "done" to false),
)
}
eventBridge.emit(
"command_output",
mapOf("callbackId" to callbackId, "data" to "", "done" to true),
)
}
}
CommandRunner.runStreaming executes the string through sh -c:
# https://github.com/AidanPark/openclaw-android/blob/cfb0740fc0961f1dd1c2a22ecf133eae443fa96f/android/app/src/main/java/com/openclaw/android/CommandRunner.kt#L55-L77
suspend fun runStreaming(
command: String,
env: Map<String, String>,
workDir: File,
onOutput: (String) -> Unit,
) = withContext(Dispatchers.IO) {
try {
val shell = env["PREFIX"]?.let { "$it/bin/sh" } ?: "/system/bin/sh"
val pb = ProcessBuilder(shell, "-c", command)
pb.environment().clear()
pb.environment().putAll(env)
pb.directory(workDir)
pb.redirectErrorStream(true)
val process = pb.start()
process.inputStream.bufferedReader().forEachLine { line ->
onOutput(line)
}
process.waitFor()
} catch (e: Exception) {
onOutput("Error: ${e.message}")
}
}
Steps to Reproduce
Use a controlled debug or instrumentation environment where JavaScript can be evaluated inside the app WebView.
- Check out the affected commit:
git checkout cfb0740fc0961f1dd1c2a22ecf133eae443fa96f
-
Build and launch the Android app in a test device or emulator.
-
Execute the following JavaScript in the app WebView context:
window.OpenClaw.runCommandAsync(
"poc",
"printf openclaw-poc > $HOME/openclaw-bridge-poc.txt"
)
-
Verify from the same runtime or terminal that $HOME/openclaw-bridge-poc.txt was created with the content openclaw-poc.
-
Repeat with a compound command to confirm shell metacharacter interpretation:
window.OpenClaw.runCommandAsync(
"poc2",
"printf first; printf second > $HOME/openclaw-bridge-poc-2.txt"
)
The second command after the semicolon is executed, demonstrating that the JavaScript-controlled command string is interpreted by the shell.
Impact Assessment
This vulnerability allows an attacker with WebView JavaScript execution to:
- Execute arbitrary shell commands under the app's OpenClaw/Termux runtime context
- Read, modify, or delete files available to that runtime, including local configuration and CLI credential files
- Download or run additional scripts, alter update/setup scripts, or persist malicious changes inside the app runtime
This is not a zero-click remote RCE by itself. The primary exploitation precondition is JavaScript execution inside the OpenClaw WebView, such as compromised WebView assets, an XSS-like bug in the UI, or a compromised WebView update supply chain. However, WebView JavaScript execution alone normally does not imply shell command execution; this bridge turns that condition into native command execution.
Suggested Fix
Avoid exposing unrestricted shell execution to WebView JavaScript.
Recommended changes:
- Remove
runCommand and runCommandAsync from the public JavaScript bridge if they are not strictly required.
- Replace arbitrary command strings with a small allowlist of named operations, such as
getNodeVersion or getGitVersion.
- Avoid
sh -c for WebView-controlled input. Execute fixed binaries with explicit argument arrays through ProcessBuilder.
- Validate command requests on the native side, not only in TypeScript or UI code.
- Reduce WebView bridge exposure and origin risk by limiting file URL access where possible and adding strict navigation/origin controls.
- Treat downloaded or OTA WebView assets as privileged code because they can reach the native bridge.
Reporter Notes
No public GitHub issue should be opened for this report because SECURITY.md explicitly asks reporters not to disclose security vulnerabilities through public issues.
Submission Target
The repository does not contain a
.github/ISSUE_TEMPLATEbug-report template. It does containSECURITY.md, which says not to open public GitHub issues for security vulnerabilities and to report them through GitHub Security Advisories:https://github.com/AidanPark/openclaw-android/security/advisories/new
Affected Versions
0.4.0, commitcfb0740fc0961f1dd1c2a22ecf133eae443fa96fDescription
The Android app exposes a native JavaScript bridge named
window.OpenClawto WebView content. The bridge includesrunCommandandrunCommandAsync, both of which accept a caller-controlled command string from JavaScript and pass it directly to the command runner.The command runner executes the supplied string with
sh -c, so shell metacharacters such as;,&&, pipes, redirects, and command substitution are interpreted by the shell. There is no command allowlist, argument separation, origin-bound authorization check, or validation step before execution.As a result, any attacker who obtains JavaScript execution in the app WebView can escalate that capability into arbitrary shell command execution under the app's Termux/OpenClaw runtime context.
Evidence
The WebView enables JavaScript, permits broad file URL access, registers the native bridge, and loads local WebView content:
The bridge exposes
runCommandAsyncand passes the JavaScript-controlledcmddirectly toCommandRunner.runStreaming:CommandRunner.runStreamingexecutes the string throughsh -c:Steps to Reproduce
Use a controlled debug or instrumentation environment where JavaScript can be evaluated inside the app WebView.
Build and launch the Android app in a test device or emulator.
Execute the following JavaScript in the app WebView context:
Verify from the same runtime or terminal that
$HOME/openclaw-bridge-poc.txtwas created with the contentopenclaw-poc.Repeat with a compound command to confirm shell metacharacter interpretation:
The second command after the semicolon is executed, demonstrating that the JavaScript-controlled command string is interpreted by the shell.
Impact Assessment
This vulnerability allows an attacker with WebView JavaScript execution to:
This is not a zero-click remote RCE by itself. The primary exploitation precondition is JavaScript execution inside the OpenClaw WebView, such as compromised WebView assets, an XSS-like bug in the UI, or a compromised WebView update supply chain. However, WebView JavaScript execution alone normally does not imply shell command execution; this bridge turns that condition into native command execution.
Suggested Fix
Avoid exposing unrestricted shell execution to WebView JavaScript.
Recommended changes:
runCommandandrunCommandAsyncfrom the public JavaScript bridge if they are not strictly required.getNodeVersionorgetGitVersion.sh -cfor WebView-controlled input. Execute fixed binaries with explicit argument arrays throughProcessBuilder.Reporter Notes
No public GitHub issue should be opened for this report because
SECURITY.mdexplicitly asks reporters not to disclose security vulnerabilities through public issues.