Files
Dorod-Sky/skyvern/cli/core/guards.py

82 lines
2.6 KiB
Python

"""Shared input validation guards for both MCP and CLI surfaces."""
from __future__ import annotations
import re
PASSWORD_PATTERN = re.compile(
r"\bpass(?:word|phrase|code)s?\b|\bsecret\b|\bcredential\b|\bpin\s*(?:code)?\b|\bpwd\b|\bpasswd\b",
re.IGNORECASE,
)
JS_PASSWORD_PATTERN = re.compile(
r"""(?:type\s*=\s*['"]?password|\.type\s*===?\s*['"]password|input\[type=password\]).*?\.value\s*=""",
re.IGNORECASE,
)
CREDENTIAL_HINT = (
"Use skyvern_login with a stored credential to authenticate. "
"Create credentials via CLI: skyvern credentials add. "
"Never pass passwords through tool calls."
)
VALID_WAIT_UNTIL = ("load", "domcontentloaded", "networkidle", "commit")
VALID_BUTTONS = ("left", "right", "middle")
VALID_ELEMENT_STATES = ("visible", "hidden", "attached", "detached")
class GuardError(Exception):
"""Raised when an input guard blocks an operation."""
def __init__(self, message: str, hint: str = "") -> None:
super().__init__(message)
self.hint = hint
def check_password_prompt(text: str) -> None:
"""Block prompts containing password/credential terms."""
if PASSWORD_PATTERN.search(text):
raise GuardError(
"Cannot perform password/credential actions — credentials must not be passed through tool calls",
CREDENTIAL_HINT,
)
def check_js_password(expression: str) -> None:
"""Block JS expressions that set password field values."""
if JS_PASSWORD_PATTERN.search(expression):
raise GuardError(
"Cannot set password field values via JavaScript — credentials must not be passed through tool calls",
CREDENTIAL_HINT,
)
def validate_wait_until(value: str | None) -> None:
if value is not None and value not in VALID_WAIT_UNTIL:
raise GuardError(
f"Invalid wait_until: {value}",
"Use load, domcontentloaded, networkidle, or commit",
)
def validate_button(value: str | None) -> None:
if value is not None and value not in VALID_BUTTONS:
raise GuardError(f"Invalid button: {value}", "Use left, right, or middle")
def resolve_ai_mode(
selector: str | None,
intent: str | None,
) -> tuple[str | None, str | None]:
"""Determine AI mode from selector/intent combination.
Returns (ai_mode, error_code) -- if error_code is set, the call should fail.
"""
if intent and not selector:
return "proactive", None
if intent and selector:
return "fallback", None
if selector and not intent:
return None, None
return None, "INVALID_INPUT"