UI-TARS 源码解析 #23:安全边界与失败场景:误点击、幻觉、验证码与高风险操作如何处理?
前面几篇文章,我们已经把 UI-TARS 的执行链路基本串起来了:
截图
↓
模型推理
↓
Thought + Action
↓
parse_action_to_structure_output
↓
结构化 action dict
↓
parsing_response_to_pyautogui_code
↓
pyautogui 执行
↓
重新截图
从技术上看,这条链路已经能让模型“看屏幕、想动作、点按钮、输入文字、滚动页面”。
但真正做本地桌面自动化产品时,最重要的问题不是:
能不能执行?
而是:
能不能安全执行?
因为 GUI Agent 和普通聊天机器人不同。
普通模型答错了,最多是文字错误。
GUI Agent 一旦答错,可能真的会:
点错按钮
删除文件
发送消息
提交表单
关闭窗口
付款
操作敏感页面
绕过访问限制
所以这一篇专门讲安全边界和失败场景。
一、为什么 GUI Agent 的安全风险更高?
UI-TARS 官方 README 在 Limitations 中明确提到,UI-TARS-1.5 可能出现 GUI 元素误识别、生成不准确描述、基于错误推断采取次优动作等问题;同时也提到,GUI 自动化能力可能被滥用于访问保护内容或认证挑战,包括 CAPTCHA 场景。
这说明官方也承认两个核心风险:
第一,模型可能看错、想错、点错。
第二,模型能力可能被滥用。
这两个风险在桌面自动化中非常现实。
比如模型看到一个按钮,Thought 写得很合理:
Thought: 我需要点击确认按钮继续下一步。
Action: click(point='<point>820 620</point>')
但真实界面上这个位置可能是:
删除
付款
发送
确认覆盖
关闭不保存
只要执行层不拦截,就可能造成实际损失。
所以,UI-TARS 二次开发时必须加安全层。
二、安全层应该放在哪里?
很多人会把安全写在 Prompt 里,例如:
不要点击危险按钮。
不要删除文件。
不要付款。
这有用,但不够。
因为 Prompt 只是提醒模型,不是硬约束。
真正的安全边界应该放在执行前:
模型输出 Action
↓
Parser 解析 Action
↓
Safety 检查
↓
通过才执行
↓
否则暂停或拒绝
也就是说,安全层应该位于:
parse_action_to_structure_output
和
pyautogui 执行
之间
UI-TARS 官方 ui-tars 包的职责是把 VLM 生成的 GUI 动作解析成结构化动作,并生成 pyautogui 脚本;它本身不是完整安全执行沙箱。
所以二次开发时,我们要自己补上:
动作白名单
坐标边界检查
高风险关键词识别
敏感页面识别
人工确认
失败重试
强制停止
日志审计
三、先建立一个安全执行原则
做 GUI Agent,建议遵守一个原则:
模型只能建议动作,不能直接拥有最终执行权。
也就是说:
模型负责:
我觉得下一步应该点击这里。
系统负责:
这个动作能不能执行?
是否需要用户确认?
是否越界?
是否高风险?
执行后是否成功?
这比“模型输出什么就执行什么”安全得多。
更合理的分层是:
Model:
根据截图和任务生成 Thought + Action。
Parser:
把 Action 转成结构化 dict。
Safety:
判断动作是否允许。
Executor:
只执行 Safety 放行的动作。
Verifier:
执行后重新截图,判断是否成功。
四、风险一:误点击
误点击是 GUI Agent 最常见的失败场景。
原因可能有很多:
模型识别错了按钮;
坐标缩放错了;
窗口位置偏移;
DPI 缩放导致坐标错位;
截图和执行时界面变化;
弹窗突然出现;
页面加载导致布局移动;
滚动区域不对。
UI-TARS 的 parse_action_to_structure_output 会处理 point/start_box/end_box 的格式转换和坐标归一化;parsing_response_to_pyautogui_code 会再把归一化坐标乘以 image_width/image_height,还原成 pyautogui 坐标。
但这只解决了“坐标如何换算”的问题。
它不能保证:
模型选的目标一定对;
当前界面没有变化;
这个按钮可以安全点击。
所以,误点击必须靠 Safety 和 Verification 处理。
五、如何降低误点击风险?
建议至少做五层保护。
1. 坐标边界检查
任何 click、drag、scroll 坐标都必须在屏幕范围内。
def validate_point(x, y, width, height):
if not (0 <= x <= width and 0 <= y <= height):
raise ValueError(f"坐标越界: ({x}, {y})")
2. 点击前可视化
执行前在截图上画出红点,让用户确认。
模型准备点击的位置
必须能被人类肉眼看到
这对早期调试非常重要。
3. 低风险动作自动执行,高风险动作确认
例如:
scroll:可以自动执行
click 普通区域:可以半自动
type:建议确认
hotkey:建议确认
删除/付款/发送:必须确认
4. 执行后重新截图
不能认为 pyautogui 执行完就成功。
必须重新截图判断:
页面是否变化?
输入是否成功?
按钮点击后是否进入下一步?
是否出现错误弹窗?
5. 连续失败自动停止
如果连续 3 次界面没有变化,就停止。
if unchanged_count >= 3:
raise RuntimeError("连续多次执行后界面无变化,停止自动操作")
六、风险二:模型幻觉
UI-TARS 官方 README 提到,模型可能偶尔生成不准确描述、误识别 GUI 元素,或基于错误推断采取次优动作,尤其是在模糊或陌生环境中。
这就是 GUI Agent 里的“幻觉”。
文本模型的幻觉通常表现为:
编造事实;
说错知识;
引用不存在内容。
GUI Agent 的幻觉则表现为:
以为页面上有某个按钮;
以为当前已经登录;
以为文件已经保存;
以为弹窗已经关闭;
以为任务已经完成;
以为某个区域可以点击。
这种幻觉更危险。
因为它会转化成真实动作。
七、如何处理 GUI 幻觉?
最有效的方法不是让模型“别幻觉”,而是增加验证机制。
1. Thought 不能作为事实
模型说:
Thought: 当前已经打开设置页面。
不代表真的打开了。
要重新截图验证。
2. finished 必须验证
模型输出:
Action: finished(content='任务已完成')
不能直接相信。
应该检查:
目标页面是否出现?
目标文本是否存在?
文件是否生成?
表单是否提交成功?
3. 引入状态检查器
可以用简单规则,也可以用另一个视觉模型判断。
def verify_task_state(task, screenshot_path):
"""
判断当前截图是否真的满足任务目标。
MVP 阶段可以先人工确认。
"""
return "unknown"
4. 失败后不要无限重试
模型连续误判时,继续循环只会越错越远。
建议:
最多 10 到 15 步;
连续 3 次无明显变化;
连续 2 次解析失败;
连续 2 次点击相近坐标无效果;
立即暂停。
八、风险三:验证码 CAPTCHA
验证码是必须单独处理的场景。
UI-TARS 官方 Limitations 中明确提到,GUI 任务能力可能被滥用于认证挑战,包括 CAPTCHA 场景。
所以在二次开发中,验证码不应该作为“自动化能力展示点”。
正确策略是:
检测到验证码
↓
暂停自动化
↓
提示用户手动处理
↓
用户完成后继续
不要设计自动绕过验证码的逻辑。
因为验证码的目的就是区分真人和自动化程序,自动绕过可能违反网站规则,也可能带来账号风险。
九、如何检测验证码?
可以先用简单策略。
1. 关键词检测
如果模型 Thought 或截图 OCR 中出现:
验证码
captcha
CAPTCHA
人机验证
我不是机器人
滑块验证
选择所有包含
验证你是真人
security check
就暂停。
示例:
CAPTCHA_KEYWORDS = [
"验证码",
"captcha",
"人机验证",
"我不是机器人",
"滑块验证",
"security check",
"verify you are human",
]
def detect_captcha(text: str) -> bool:
lower = text.lower()
return any(k.lower() in lower for k in CAPTCHA_KEYWORDS)
2. Action 风险检测
如果模型输出:
drag(start_point=..., end_point=...)
同时 Thought 中出现“滑块”“验证”“captcha”,就停止。
3. 页面状态检测
如果连续出现验证页面,也停止。
十、验证码处理策略
推荐策略是:
1. 不自动解验证码;
2. 不调用第三方打码服务;
3. 不模拟滑块破解;
4. 不绕过访问控制;
5. 转人工处理;
6. 用户完成验证后,重新截图继续任务。
可以设计成:
def handle_captcha():
print("检测到验证码或人机验证,请手动完成。")
input("完成后按 Enter 继续...")
这样既保留了自动化流程,也避免越过安全边界。
十一、风险四:高风险操作
高风险操作包括:
删除文件
覆盖文件
清空数据
提交表单
发送消息
发送邮件
付款
下单
转账
授权登录
修改密码
安装软件
卸载软件
关闭安全软件
运行命令
上传隐私文件
这些动作不应该全自动执行。
即使模型很确信,也必须暂停确认。
因为 GUI Agent 一旦点错,后果可能不可逆。
十二、高风险动作如何识别?
可以从三层识别。
1. Action 类型风险
某些动作天然更危险:
hotkey
type
right_single
drag
例如:
hotkey(key='alt f4')
hotkey(key='shift delete')
type(content='...\n')
right_single(...)
这些都应该谨慎。
2. Thought 文本风险
模型 Thought 中如果出现:
删除
确认
提交
发送
付款
购买
覆盖
授权
密码
验证码
就标记为高风险。
3. 屏幕内容风险
如果截图中有高风险按钮:
Delete
Remove
Submit
Pay
Send
Confirm
Transfer
Authorize
也应该暂停。
MVP 阶段可以先依赖模型输出文本和 OCR,后续再做按钮区域识别。
十三、设计一个 SafetyPolicy
可以把安全策略封装成类:
class SafetyPolicy:
def __init__(self):
self.allowed_actions = {
"click",
"left_single",
"type",
"hotkey",
"scroll",
"wait",
"finished",
}
self.risky_hotkeys = {
"alt f4",
"ctrl w",
"ctrl q",
"shift delete",
}
self.risky_words = {
"删除",
"清空",
"付款",
"支付",
"转账",
"发送",
"提交",
"授权",
"密码",
"验证码",
"delete",
"remove",
"pay",
"transfer",
"send",
"submit",
"authorize",
"password",
"captcha",
}
def check(self, action: dict) -> dict:
action_type = action.get("action_type")
action_inputs = action.get("action_inputs", {})
text = str(action)
if action_type not in self.allowed_actions:
return {
"allow": False,
"reason": f"不允许的动作类型: {action_type}",
"need_confirm": False,
}
for word in self.risky_words:
if word.lower() in text.lower():
return {
"allow": False,
"reason": f"检测到高风险关键词: {word}",
"need_confirm": True,
}
if action_type == "hotkey":
key = action_inputs.get("key") or action_inputs.get("hotkey") or ""
if key.lower() in self.risky_hotkeys:
return {
"allow": False,
"reason": f"高风险快捷键: {key}",
"need_confirm": True,
}
return {
"allow": True,
"reason": "安全检查通过",
"need_confirm": False,
}
这只是最小版本。
真实产品里,SafetyPolicy 应该更细。
十四、风险五:直接执行 pyautogui 代码
UI-TARS 的 parsing_response_to_pyautogui_code 会返回 pyautogui 脚本字符串,官方 README 也说明这个函数会把结构化 actions 转换成 pyautogui script string。
这对学习和调试很方便。
但产品化时要谨慎:
exec(pyautogui_code)
这种方式风险很高。
原因有两个。
第一,字符串执行本身风险高。
第二,源码中部分坐标解析使用了 eval(start_box) / eval(end_box)。例如 drag、scroll、click 等分支会通过 eval 把字符串形式的 box 转成坐标列表。
研究代码里这样写很方便,但真实产品中,模型输出属于不可信输入,不建议直接 eval。
十五、如何替代 exec 和 eval?
建议自己写安全执行器。
不要执行模型生成的 Python 代码,而是执行结构化 action dict。
示例:
import ast
import pyautogui
def safe_parse_box(box: str):
try:
value = ast.literal_eval(box)
except Exception as e:
raise ValueError(f"坐标解析失败: {box}") from e
if not isinstance(value, (list, tuple)):
raise ValueError("坐标必须是 list 或 tuple")
if len(value) not in (2, 4):
raise ValueError("坐标长度必须是 2 或 4")
nums = [float(x) for x in value]
for n in nums:
if not (0 <= n <= 1):
raise ValueError(f"归一化坐标越界: {n}")
if len(nums) == 2:
x, y = nums
return [x, y, x, y]
return nums
def execute_click(action, image_width, image_height):
box = action["action_inputs"].get("start_box")
x1, y1, x2, y2 = safe_parse_box(box)
x = ((x1 + x2) / 2) * image_width
y = ((y1 + y2) / 2) * image_height
pyautogui.click(x, y, button="left")
这样你就可以保留 UI-TARS 的 Parser,但把执行层改成更安全的版本。
十六、风险六:type 自动输入敏感内容
UI-TARS 的 type 分支默认 input_swap=True,会生成 pyperclip.copy(...) 和 pyautogui.hotkey('ctrl', 'v'),如果内容以换行结尾,还会生成 pyautogui.press('enter')。
这对中文、长文本、特殊符号很友好。
但也带来风险:
可能覆盖用户剪贴板;
可能把内容输入到错误窗口;
可能自动提交表单;
可能输入密码、验证码、付款信息;
可能发送错误消息。
尤其是:
type(content='xxx\n')
末尾 \n 通常意味着输入后按 Enter。
这可能触发:
发送消息
提交搜索
提交表单
确认命令
所以 type 要比普通 click 更谨慎。
十七、type 的安全策略
建议:
1. 默认显示待输入内容;
2. 输入前确认当前焦点位置;
3. 如果 content 以 \n 结尾,必须额外确认;
4. 不允许自动输入密码、验证码、银行卡、支付信息;
5. 剪贴板输入前保存原剪贴板;
6. 输入后恢复剪贴板;
7. 输入后截图验证。
示例:
def validate_type_action(action):
content = action.get("action_inputs", {}).get("content", "")
sensitive_words = ["密码", "验证码", "password", "captcha", "银行卡", "支付"]
for word in sensitive_words:
if word.lower() in content.lower():
raise ValueError(f"禁止自动输入敏感内容: {word}")
if content.endswith("\\n") or content.endswith("\n"):
return {
"need_confirm": True,
"reason": "输入内容包含回车,可能触发表单提交或消息发送"
}
return {
"need_confirm": False,
"reason": "普通文本输入"
}
十八、风险七:hotkey 破坏当前状态
快捷键非常强大,也非常危险。
例如:
ctrl a:全选
ctrl x:剪切
ctrl s:保存
ctrl w:关闭标签页
alt f4:关闭窗口
shift delete:永久删除
UI-TARS 的 Prompt 要求 hotkey(key='ctrl c') 这类格式,按键用空格分隔且小写;源码执行层会把它拆成多个键并生成 pyautogui.hotkey(...)。
所以 hotkey 必须做风险分级。
建议:
低风险:
ctrl c
ctrl v
ctrl f
ctrl l
esc
中风险:
ctrl a
ctrl s
enter
高风险:
alt f4
ctrl w
ctrl q
shift delete
执行策略:
低风险:可自动执行
中风险:半自动确认
高风险:默认禁止
十九、风险八:scroll 和 drag 导致状态不可控
scroll 和 drag 看起来不像 delete、pay 那么危险,但它们也容易导致任务失控。
parsing_response_to_pyautogui_code 中,drag/select 会读取 start_box 和 end_box,计算起点终点后生成 pyautogui.moveTo(...) 和 pyautogui.dragTo(...);scroll 会根据 direction 生成 pyautogui.scroll(5) 或 pyautogui.scroll(-5),有 start_box 时会在指定位置滚动。
风险包括:
拖错文件;
拖错滑块;
滚动到错误区域;
页面滚过目标;
拖动窗口导致布局变化;
选中文本后误覆盖;
触发隐藏菜单。
所以 drag 建议先不要自动执行。
scroll 可以自动执行,但要限制次数。
例如:
MAX_SCROLLS_PER_TASK = 5
如果滚动多次仍找不到目标,就暂停。
二十、风险九:wait 和 finished 被滥用
Prompt 中包含:
wait()
finished(content='xxx')
wait() 表示睡眠 5 秒并重新截图检查变化,finished 表示任务完成。
这两个动作也需要检查。
wait 风险
模型可能反复 wait,导致任务卡住。
处理方式:
连续 wait 超过 2 次,暂停。
finished 风险
模型可能误以为任务完成。
处理方式:
finished 不能直接结束;
必须验证任务目标是否真的满足。
例如任务是:
下载文件
不能因为模型说 finished 就结束。
应该检查:
文件是否存在;
下载是否完成;
页面是否显示成功;
是否有错误提示。
二十一、失败场景分类
可以把失败场景分成 8 类。
1. Parse Failure:
模型输出格式不合法,无法解析。
2. Coordinate Failure:
坐标越界、缩放错误、窗口偏移、DPI 错误。
3. Target Failure:
模型找错目标元素。
4. State Failure:
执行后界面没有变化,或变化不符合预期。
5. Loop Failure:
反复点击、反复滚动、反复 wait。
6. Safety Failure:
动作涉及删除、付款、发送、验证码等高风险内容。
7. Environment Failure:
窗口不在前台、远程桌面异常、剪贴板失效、权限不足。
8. Model Failure:
幻觉、误识别、任务理解错误。
每类失败都应该有不同处理策略。
二十二、失败处理策略
建议做一个统一的失败处理表。
Parse Failure:
停止执行,保存原始模型输出,提示需要调整 Prompt。
Coordinate Failure:
停止执行,保存截图和坐标可视化图。
Target Failure:
重新截图,让模型反思一次;连续失败则停止。
State Failure:
允许重试一次;仍无变化则暂停人工接管。
Loop Failure:
超过最大步数或重复动作,停止。
Safety Failure:
拒绝执行或请求人工确认。
Environment Failure:
提示用户检查窗口、权限、DPI、远程桌面。
Model Failure:
降低自动化级别,切换半自动确认模式。
这比简单地 try-catch 更有用。
因为 GUI Agent 的失败不是一个异常,而是一个状态。
二十三、设计一个 Step Guard
可以给每一步执行增加 Guard。
class StepGuard:
def __init__(self, max_steps=15, max_waits=2, max_scrolls=5):
self.max_steps = max_steps
self.max_waits = max_waits
self.max_scrolls = max_scrolls
self.wait_count = 0
self.scroll_count = 0
self.last_actions = []
def check_step_count(self, step):
if step > self.max_steps:
raise RuntimeError("超过最大执行步数")
def check_repeated_action(self, action):
signature = str(action)
self.last_actions.append(signature)
self.last_actions = self.last_actions[-3:]
if len(self.last_actions) == 3 and len(set(self.last_actions)) == 1:
raise RuntimeError("检测到连续重复动作,停止执行")
def check_action_limit(self, action):
action_type = action.get("action_type")
if action_type == "wait":
self.wait_count += 1
if self.wait_count > self.max_waits:
raise RuntimeError("连续 wait 过多,停止执行")
if action_type == "scroll":
self.scroll_count += 1
if self.scroll_count > self.max_scrolls:
raise RuntimeError("scroll 次数过多,停止执行")
这个 Guard 可以防止 Agent 陷入循环。
二十四、建议的安全执行流程
综合起来,一个安全执行流程应该是:
Step 1:截图
Step 2:模型输出 Thought + Action
Step 3:解析 Action
Step 4:动作白名单检查
Step 5:验证码检测
Step 6:高风险关键词检测
Step 7:坐标边界检查
Step 8:重复动作检查
Step 9:必要时用户确认
Step 10:执行动作
Step 11:重新截图
Step 12:验证状态变化
Step 13:记录日志
伪代码:
for step in range(max_steps):
screenshot = capture_screen()
response = model.infer(task, screenshot, history)
actions = parse_response_to_actions(response)
for action in actions:
safety_result = safety_policy.check(action)
if not safety_result["allow"]:
if safety_result["need_confirm"]:
ask_user_confirm(action, safety_result["reason"])
else:
raise RuntimeError(safety_result["reason"])
step_guard.check_action_limit(action)
step_guard.check_repeated_action(action)
execute(actions)
new_screenshot = capture_screen()
verify_result = verify_state(task, screenshot, new_screenshot, actions)
log_step(step, response, actions, verify_result)
这才是适合真实桌面自动化的执行方式。
二十五、日志是安全系统的一部分
很多人把日志当成调试工具。
但对 GUI Agent 来说,日志也是安全系统的一部分。
每一步至少记录:
{
"step": 5,
"before_screenshot": "step_005_before.png",
"after_screenshot": "step_005_after.png",
"model_response": "Thought: ... Action: ...",
"parsed_actions": [],
"safety_result": {
"allow": true,
"reason": "安全检查通过"
},
"executed": true,
"execution_result": "success"
}
如果发生误点击,日志可以帮助你追查:
模型为什么点这里?
坐标怎么还原的?
执行前界面是什么?
执行后发生了什么?
安全检查为什么放行?
没有日志,就很难定位责任链。
二十六、半自动模式是最好的起点
如果你刚开始做 UI-TARS 二次开发,不要一上来全自动。
建议默认半自动:
模型输出 Thought + Action
↓
显示点击点
↓
显示动作说明
↓
显示风险评级
↓
用户确认
↓
执行
可以按风险级别逐步放开:
Level 0:
只展示动作,不执行。
Level 1:
低风险动作自动执行,高风险动作确认。
Level 2:
常见任务自动执行,敏感动作确认。
Level 3:
全自动执行,但保留强制停止和日志审计。
MVP 阶段建议停在 Level 0 或 Level 1。
二十七、安全边界的本质
UI-TARS 的价值在于,它把 GUI 操作抽象成了受控动作空间。
Prompt 中明确要求模型输出 Thought: ... Action: ...,并把桌面端动作限制在 click、drag、hotkey、type、scroll、wait、finished 等动作中。
但这只是第一层安全。
真正的安全边界应该是:
动作空间受控;
Parser 严格解析;
Executor 不直接 eval/exec;
Safety 拦截高风险;
Verifier 验证结果;
用户可随时接管。
换句话说:
安全不是一句 Prompt,而是一整套工程链路。
总结
这篇文章我们从安全角度分析了 UI-TARS 二次开发中的关键风险。
主要失败场景包括:
误点击;
模型幻觉;
验证码;
高风险操作;
坐标偏移;
快捷键破坏状态;
type 自动提交;
scroll/drag 失控;
finished 误判;
重复动作死循环。
对应的处理策略是:
动作白名单;
坐标边界检查;
验证码转人工;
高风险动作确认;
禁止自动输入敏感内容;
不用 exec 直接执行模型生成代码;
用 ast.literal_eval 或严格解析替代 eval;
执行后重新截图验证;
连续失败自动停止;
完整日志审计;
从半自动模式开始。
UI-TARS 官方代码提供了从模型输出到 pyautogui 脚本的基础能力,但真实产品不能只追求“能自动点”。官方也提醒模型可能出现误识别、幻觉、次优动作,以及 CAPTCHA 等滥用风险。
真正可靠的本地桌面 Agent,必须做到:
能执行,
也能暂停;
能点击,
也能拒绝;
能自动化,
也能让用户接管;
能完成任务,
也能在不确定时停下来。
这就是 GUI Agent 从 Demo 走向产品时最重要的一道安全边界。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)