# Maestro YAML File Security: Critical Vulnerabilities and Mitigation Strategies

> Discover Maestro YAML file security risks like arbitrary JavaScript execution & file system access. Learn essential mitigation strategies to secure your mobile development.

- Repository: [Maestro/Maestro](https://github.com/mobile-dev-inc/Maestro)
- Tags: best-practices
- Published: 2026-03-20

---

**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`](https://github.com/mobile-dev-inc/Maestro/blob/main/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`](https://github.com/mobile-dev-inc/Maestro/blob/main/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.

```kotlin
// 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`](https://github.com/mobile-dev-inc/Maestro/blob/main/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`](https://github.com/mobile-dev-inc/Maestro/blob/main/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.

```yaml

# ❌ 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`](https://github.com/mobile-dev-inc/Maestro/blob/main/YamlFluentCommand.kt) and `resolveDependencyFile()` in [`maestro-cli/src/main/java/maestro/cli/util/DependencyResolver.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/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.

```yaml

# ❌ Dangerous - absolute path can read any file the runner can access

addMedia: /etc/passwd

```

To mitigate this, implement a whitelist check before path resolution:

```kotlin
// 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`](https://github.com/mobile-dev-inc/Maestro/blob/main/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.

```yaml

# ❌ 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`](https://github.com/mobile-dev-inc/Maestro/blob/main/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.

```yaml

# 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`](https://github.com/mobile-dev-inc/Maestro/blob/main/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:

```kotlin
// 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:

1. **Treat flow files as untrusted input** - Only execute flows from cryptographically signed repositories or trusted version control sources with strict change review processes.

2. **Restrict file access** - Modify `resolvePath()` in [`YamlFluentCommand.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/YamlFluentCommand.kt) or [`DependencyResolver.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/DependencyResolver.kt) to enforce a whitelist of directories, rejecting absolute paths and `..` segments that escape the repository root.

3. **Sandbox JavaScript execution** - Limit APIs exposed to scripts by patching `JsEngine.evaluateScript()` to disable `http.request`, file-system access, and reflection APIs; enforce strict execution timeouts.

4. **Sanitize environment variables** - Never pass secrets through flow file `env` blocks; instead, use secure CI/CD secret management that injects values at runtime without exposing them to the YAML parser or script context.

5. **Validate media files** - Implement MIME type checking, size limits, and image integrity validation before using files in `assertScreenshot` or `addMedia` commands.

6. **Limit recursion depth** - Cap nested `runFlow`, `retry`, and `repeat` command depth in `MaestroFlowParser` to prevent resource exhaustion attacks through deeply nested flow chains.

7. **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.

```yaml

# ✅ 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 `runScript` with full API access through `Orchestra.runScriptCommand()` and `JsEngine.evaluateScript()`, using Rhino or Graal engines without sandboxing.
- Path resolution in [`YamlFluentCommand.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/YamlFluentCommand.kt) and [`DependencyResolver.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/DependencyResolver.kt) lacks sandboxing, enabling directory traversal attacks using absolute paths or `..` sequences that escape the flow directory.
- Environment variables passed via the `env` map are injected directly into the JavaScript context through `Context.javaToJS`, risking secret leakage and variable manipulation by malicious scripts.
- Dynamic flow inclusion via `runFlow` allows unlimited sub-flow chaining through `MaestroFlowParser`, 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`](https://github.com/mobile-dev-inc/Maestro/blob/main/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`](https://github.com/mobile-dev-inc/Maestro/blob/main/YamlFluentCommand.kt) or [`DependencyResolver.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/DependencyResolver.kt) in [`maestro-cli/src/main/java/maestro/cli/util/DependencyResolver.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/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`](https://github.com/mobile-dev-inc/Maestro/blob/main/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.