Maestro YAML File Security: Critical Vulnerabilities and Mitigation Strategies
Maestro YAML files can execute arbitrary JavaScript, access environment variables without isolation, and traverse the file system without sandboxing, making untrusted flow files a significant security risk that requires strict input validation and least-privilege execution.
Maestro, developed by mobile-dev-inc, uses YAML-based flow files to drive mobile UI automation through the Maestro orchestration engine. Because these files can reference external scripts, recursively include sub-flows, and execute arbitrary code through embedded JavaScript engines, understanding Maestro YAML file security is essential when running flows from external sources, public repositories, or CI/CD pipelines.
Arbitrary Code Execution via JavaScript
The runScript Command and Rhino/Graal Engines
According to the Maestro source code in maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlFluentCommand.kt, the runScript command resolves script paths and constructs RunScriptCommand objects. When runScript != null, the parser calls resolvePath() and builds a command executed by Orchestra.runScriptCommand() in Orchestra.kt. This method passes the script content to JsEngine.evaluateScript(), which evaluates the code using either Rhino or Graal JavaScript engines without built-in sandboxing.
The script runs with full access to Maestro’s API, including device control, network operations, and host file system access. A malicious script could exfiltrate sensitive data, tamper with the connected device, or establish persistent backdoors on the host system.
// From YamlFluentCommand.kt - builds script execution command
runScript?.let {
resolvePath(flowPath, it).let { resolvedPath ->
RunScriptCommand(
script = resolvedPath,
env = env, // Environment variables passed directly to script
condition = condition
)
}
}
Environment Variable Injection
The RunScriptCommand defined in maestro-orchestra-models/src/main/java/maestro/orchestra/Commands.kt stores environment variables in a map and passes them to JsEngine.evaluateScript(). As implemented in maestro-client/src/main/java/maestro/js/JsEngine.kt, each entry is injected as a JavaScript variable using Context.javaToJS.
This creates two critical vulnerabilities: secrets passed via the env field can be leaked to script logic, and malicious flows can overwrite existing variables to manipulate execution context or bypass security checks.
# ❌ Dangerous - exposes API keys to script context where they can be exfiltrated
env:
API_KEY: "sk-live-secret-key"
DATABASE_PASSWORD: "production-pass"
runScript: untrusted-analytics.js
File System Traversal and Unrestricted Access
Path Resolution Without Sandboxing
The resolvePath() function in YamlFluentCommand.kt and resolveDependencyFile() in maestro-cli/src/main/java/maestro/cli/util/DependencyResolver.kt resolve relative paths against the current flow's directory without validation or chroot restrictions. Both utilities accept absolute paths as-is, allowing reads of any file the runner process can access, including system configuration files and private keys.
# ❌ Dangerous - absolute path can read any file the runner can access
addMedia: /etc/passwd
To mitigate this, implement a whitelist check before path resolution:
// Example of adding a whitelist check before resolving a path (illustrative)
private fun resolvePath(flowPath: Path, requestedPath: String): Path {
val path = flowPath.fileSystem.getPath(requestedPath)
// Reject absolute paths and any path that escapes the repo root
require(!path.isAbsolute) { "Absolute paths are not allowed: $requestedPath" }
val resolved = flowPath.resolveSibling(path).normalize()
require(resolved.startsWith(flowPath.root)) { "Path escapes allowed directory: $requestedPath" }
return resolved
}
Media File and Screenshot Assertions
Commands like addMedia and assertScreenshot referenced in YamlFluentCommand.kt read image files without MIME type validation, size limits, or integrity checks. An attacker could force the flow to load extremely large or malformed files, causing denial-of-service through memory exhaustion or triggering vulnerabilities in image parsing libraries.
# ❌ Dangerous - loading untrusted images can cause DoS
assertScreenshot: /tmp/bad.png
Supply Chain Risks in Flow Dependencies
Dynamic Sub-flow Inclusion
The runFlow command allows unlimited chaining of sub-flows through runFlowCommand() in YamlFluentCommand.kt and the MaestroFlowParser. This enables hiding malicious logic in deeply nested dependency files that may not be immediately visible during code review or static analysis.
# Malicious flow hiding payload in nested dependency traversal
runFlow: ../../../etc/cron.daily/backdoor.yaml
Missing Script Content Validation
In maestro-orchestra/src/main/java/maestro/orchestra/Orchestra.kt, the runScriptCommand() method evaluates scripts directly without static analysis, signature verification, or sandboxing of the script content. There is no built-in mechanism to restrict the JavaScript API surface, enforce execution timeouts, or prevent access to sensitive host APIs.
To sandbox JavaScript execution, you would need to patch the engine implementation:
// Restrict JavaScript API surface (illustrative patch to RhinoJsEngine)
override fun evaluateScript(
script: String,
env: Map<String, String>,
sourceName: String,
runInSubScope: Boolean,
): Any? {
// Disable network calls unless explicitly permitted
if (script.contains("http.") || script.contains("java.io.")) {
throw SecurityException("Network and filesystem access is disabled for untrusted scripts")
}
return context.evaluateString(globalScope, script, sourceName, 1, null)
}
Securing Your Maestro Implementation
To mitigate Maestro YAML file security risks, implement the following defense-in-depth controls:
-
Treat flow files as untrusted input - Only execute flows from cryptographically signed repositories or trusted version control sources with strict change review processes.
-
Restrict file access - Modify
resolvePath()inYamlFluentCommand.ktorDependencyResolver.ktto enforce a whitelist of directories, rejecting absolute paths and..segments that escape the repository root. -
Sandbox JavaScript execution - Limit APIs exposed to scripts by patching
JsEngine.evaluateScript()to disablehttp.request, file-system access, and reflection APIs; enforce strict execution timeouts. -
Sanitize environment variables - Never pass secrets through flow file
envblocks; instead, use secure CI/CD secret management that injects values at runtime without exposing them to the YAML parser or script context. -
Validate media files - Implement MIME type checking, size limits, and image integrity validation before using files in
assertScreenshotoraddMediacommands. -
Limit recursion depth - Cap nested
runFlow,retry, andrepeatcommand depth inMaestroFlowParserto prevent resource exhaustion attacks through deeply nested flow chains. -
Run with least privilege - Execute the Maestro CLI or daemon as a non-privileged user without administrator or root access to minimize the impact of path traversal or arbitrary code execution.
# ✅ Safe practice - relative path within allowed directory only
runFlow: subflows/login.yaml
# ✅ Safe practice - pass only non-sensitive configuration data
env:
USER_ID: "12345"
ENVIRONMENT: "staging"
runScript: scripts/collectMetrics.js
Summary
- Maestro YAML files execute arbitrary JavaScript via
runScriptwith full API access throughOrchestra.runScriptCommand()andJsEngine.evaluateScript(), using Rhino or Graal engines without sandboxing. - Path resolution in
YamlFluentCommand.ktandDependencyResolver.ktlacks sandboxing, enabling directory traversal attacks using absolute paths or..sequences that escape the flow directory. - Environment variables passed via the
envmap are injected directly into the JavaScript context throughContext.javaToJS, risking secret leakage and variable manipulation by malicious scripts. - Dynamic flow inclusion via
runFlowallows unlimited sub-flow chaining throughMaestroFlowParser, potentially hiding malicious logic in dependency trees that evade manual review. - Effective security requires treating flows as untrusted input, implementing path whitelists in dependency resolution, sandboxing the JavaScript engine to restrict API access, and running Maestro with minimal OS privileges.
Frequently Asked Questions
Can Maestro YAML files execute arbitrary code?
Yes. The runScript command evaluates JavaScript using embedded Rhino or Graal engines via Orchestra.runScriptCommand() in maestro-orchestra/src/main/java/maestro/orchestra/Orchestra.kt. Scripts run with full access to Maestro's API, including device control and host file system operations, without built-in sandboxing, content validation, or permission checks.
How do I prevent path traversal attacks in Maestro flows?
Implement a whitelist check in the resolvePath() function within YamlFluentCommand.kt or DependencyResolver.kt in maestro-cli/src/main/java/maestro/cli/util/DependencyResolver.kt. Reject absolute paths and verify that resolved paths remain within the allowed repository root using startsWith() checks on normalized paths. Additionally, avoid running Maestro as root to limit file system exposure.
Are environment variables safe to use in Maestro scripts?
No. Environment variables passed via the env field are injected into the JavaScript context through Context.javaToJS in the JS engine implementation (maestro-client/src/main/java/maestro/js/JsEngine.kt). This exposes values to script logic, creating leakage risks. Avoid passing secrets through flow files; instead, use secure CI/CD secret management that doesn't expose values to the YAML parser or JavaScript runtime.
Should I run Maestro as root or administrator?
Absolutely not. Maestro executes all commands as the runner user without internal permission checks. Running with elevated privileges allows malicious flows to perform privileged actions on the host system, including reading sensitive system files via path traversal, modifying system configuration, or installing persistent backdoors through script execution. Always run Maestro with the least privilege necessary.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →