Back to skill

Security audit

Ngrok Preview

Security checks for vulnerabilities and agentic risk

Overview

The skill is generally aligned with making ngrok previews, but it needs review because public links may outlive their advertised expiry and unsafe session handling can delete files outside the intended cache area.

Install only if you are comfortable with selected files being copied into a local cache and exposed through a public ngrok URL. Use only narrow, non-sensitive files, avoid directory sources, choose safe session IDs, and manually run down or cleanup immediately after use; do not rely on the displayed expiry to stop access automatically.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/ngrok_preview.py:341
Finding

Path Traversal Through Unsanitized Session Identifiers Enables Out-of-Scope Recursive Deletion

Content
View full analysis
tuple[Path, dict[str, Any]]: path = STATE_DIR / f"{session_id}.json" if not path.exists(): raise FileNotFoundError(f"No session state found for: {session_id}") return path, read_json(path) ``` ```python if args.delete_session_dir: session_dir = Path(state.get("workspace_dir", "")) if session_dir.exists() and str(session_dir).startswith(str(SESSIONS_DIR)): shutil.rmtree(session_dir) ``` The cleanup command uses the same unsafe deletion check: ```python session_dir = Path(state.get("workspace_dir", "")) if session_dir.exists() and str(session_dir).startswith(str(SESSIONS_DIR)): shutil.rmtree(session_dir) ``` ### Technical Analysis The user-controlled `--session-id` value is directly incorporated into session-directory and state-file paths without validation. Components such as `..`, path separators, and absolute-path syntax are not rejected. The deletion guard compares unresolved path strings: ```python str(session_dir).startswith(str(SESSIONS_DIR)) ``` String-prefix comparison does not establish filesystem containment. A path such as: ```text /home/user/.cache/openclaw-ngrok-preview/sessions/../sessions-victim ``` starts with the textual sessions-directory prefix but resolves to: ```text /home/user/.cache/openclaw-ngrok-preview/sessions-victim ``` Consequently, the check can approve a directory outside `SESSIONS_DIR` ...[truncated 2138 chars]
Remediation
View remediation
str: if not SESSION_ID_RE.fullmatch(session_id): raise ValueError("Invalid session ID") return session_id ``` 2. Resolve paths and verify semantic containment before every creation, read, write, or deletion: ```python def contained_path(root: Path, child: str) -> Path: resolved_root = root.resolve() candidate = (resolved_root / child).resolve() if not candidate.is_relative_to(resolved_root): raise ValueError("Path escapes the permitted root") return candidate ``` 3. Do not use display identifiers as directory names. Generate an internal random identifier, such as a UUID, and store the user-facing session label only as metadata. 4. Before recursive deletion: - Resolve the candidate and root. - Require `candidate.is_relative_to(root)`. - Reject deletion of the root itself. - Consider opening and tracking session directories through trusted internal state rather than accepting reconstructed paths. 5. Treat state files as untrusted input. Validate the schema, normalize paths, and independently re-check containment instead of trusting `workspace_dir`. 6. Add regression tests covering `../`, nested traversal, absolute paths, symbolic links, prefix-collision directories such as `sessions-victim`, and platform-specific path separators. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/ngrok_preview.py:346
Finding

Advertised TTL Does Not Automatically Terminate the Public Tunnel

Content
View full analysis
int: ensure_dirs() now = utc_now() removed: list[str] = [] for path in sorted(STATE_DIR.glob("*.json")): state = read_json(path) session_id = state.get("session_id") try: expires_at = datetime.fromisoformat(state.get("expires_at", "").replace("Z", "+00:00")) except ValueError: expires_at = now should_remove = expires_at <= now if args.force: should_remove = True if not should_remove: continue stop_pid(state.get("ngrok_pid")) stop_pid(state.get("http_pid")) session_dir = Path(state.get("workspace_dir", "")) if session_dir.exists() and str(session_dir).startswith(str(SESSIONS_DIR)): shutil.rmtree(session_dir) `` ...[truncated 2118 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (8)

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

The declared description says the skill only generates temporary preview links and shares them in Telegram, but the behavior indicates broader functionality such as copying files into persistent workspace/cache areas and maintaining session/process state. This mismatch is dangerous because users and policy systems may trust the narrow description while the implementation actually retains artifacts and manages longer-lived local state, increasing the risk of unintended data exposure through the ngrok-served content or residual cached files.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding

The skill requests or implies use of powerful capabilities including shell, filesystem, environment access, and network tunneling, but does not declare any explicit tool scope or permission boundaries. In a skill that exposes local artifacts through ngrok, missing scope constraints increases the chance of overbroad access, accidental publication of sensitive files, or unsafe operator assumptions about what the skill is allowed to do.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/ngrok_preview.py (reported line 94)May include surrounding context.

python
def run(cmd: list[str], *, check: bool = True, capture: bool = True) -> subprocess.CompletedProcess[str]:
    return subprocess.run(
        cmd,
        check=check,
        capture_output=capture,

Internal Network Request

Medium
Category
Server-Side Request Forgery
Confidence
70% confidence
Finding

Code issues a request to a loopback, link-local, or private-range host. This can reach internal services not meant to be exposed and is a common SSRF pivot.

Content

Scanner excerpt · scripts/ngrok_preview.py (reported line 307)May include surrounding context.

python
while time.time() < deadline:
        try:
            with urllib.request.urlopen("http://127.0.0.1:4040/api/tunnels", timeout=1.2) as resp:
                payload = json.loads(resp.read().decode("utf-8"))
        except (urllib.error.URLError, TimeoutError, json.JSONDecodeError):
            time.sleep(0.35)

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The tool automatically copies arbitrary local files or directories and publishes them through a public ngrok URL without any explicit confirmation, sensitivity check, or access control. In an agent context, this materially increases the risk of unintentional exfiltration of local artifacts, including entire directories, to anyone possessing the link or to third-party infrastructure handling the tunnel.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/ngrok_preview.py (reported line 367)May include surrounding context.

python
http_log = (LOG_DIR / f"{session_id}-http.log").open("a", encoding="utf-8")
    ngrok_log = (LOG_DIR / f"{session_id}-ngrok.log").open("a", encoding="utf-8")

    http_proc = subprocess.Popen(
        [
            sys.executable,
            "-m",

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/ngrok_preview.py (reported line 382)May include surrounding context.

python
stderr=subprocess.STDOUT,
    )

    ngrok_proc = subprocess.Popen(
        [
            "ngrok",
            "http",

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The cleanup command deletes session directories, log files, and state files, and --force removes all sessions, but there is no explicit confirmation prompt or prominent warning beyond terse help text. This is a destructive operation affecting stored preview data and metadata, so users may not receive sufficient disclosure before irreversible removal.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.