我机器上挂着十几个定时任务。前几天我翻设置,发现其中 5 个的权限一栏写着「完全允许访问」。
这五个字让我愣了一下。意思是:这几个任务每天定点跑的时候,不用问我,想读就读、想写就写、想跑命令就跑命令。而我,从开权限那天起,从来没逐条看过它们到底干了什么。
那天上午我把这 5 个任务审了一遍。不是让它自己汇报——AI 说自己「只做了 A、B、C」不算数,得看它留下的原始记录。这篇就把这套审计方法写下来,脚本不到一百行,不需要装任何东西,你也能照着跑一遍。
在客户端任务编辑面板的「权限」项,能看到这几个任务是 fullAccess。我想知道它是什么时候变成这样的。
每个自动化在 C:\Users\你的用户名\.workbuddy\automation-backups\ 下面都有一个 json 备份,重点看两个字段:createdAt 和 updatedAt。我这几个任务,两个时间完全相等,而 permissionMode 已经是 fullAccess——说明建任务那一刻就带着这个权限,不是后来被谁改的,也不是被别的程序动了手脚。
为什么建的时候就给?因为定时任务是无人值守的。如果设成「每步询问」,半夜十一点触发了没人点确认,任务就卡死或者失败。所以建的时候默认就给了完全权限。
这个结论挺重要:问题不在「有人偷偷改了我的设置」,在于我从开权限那天起就没审过它。
AI 每次跑完会给你一段总结,但那是它自己写的,可信度有限。要查,就查两处原始记录:
思路就一句话:先从数据库拿到「这个任务跑过哪些会话 id」,再拿这些 id 去 jsonl 里把每一次工具调用捞出来,然后分类统计。
import sqlite3, json
DB = r'C:\Users\你的用户名\.workbuddy\workbuddy.db'
IDS = ['automation-1788142020002', 'automation-1788142215361'] # 审几个填几个
con = sqlite3.connect(DB)
NAMES = dict(con.execute("SELECT id,name FROM automations"))
conv = {}
for aid in IDS:
for (rj,) in con.execute("SELECT runs_json FROM automation_runs WHERE automation_id=?", (aid,)):
try:
arr = json.loads(rj or '[]')
except Exception:
arr = []
for it in arr:
if it.get('conversationId'):
conv[it['conversationId']] = NAMES.get(aid)
print('任务数', len(IDS), '会话数', len(conv))
要点:automations 表存任务名,automation_runs 表里的 runs_json 是个 JSON 数组,每次运行一条,里面有 conversationId。这个 id 就是磁盘上 jsonl 文件的文件名。数据库只读打开,一个字节都不动它。
任务 id 怎么找?automations 表全查一遍,筛 permissionMode 字段是 fullAccess 的即可。
import glob, os, json, collections
PROJ = r'C:\Users\你的用户名\.workbuddy\projects'
files = glob.glob(os.path.join(PROJ, '**', '*.jsonl'), recursive=True)
hit = {}
for p in files:
cid = os.path.basename(p).split('.')[0]
if cid in conv:
hit[cid] = p
tools = collections.Counter()
calls = []
for cid, p in hit.items():
with open(p, encoding='utf-8', errors='ignore') as f:
for ln in f:
try:
o = json.loads(ln)
except Exception:
continue
if o.get('type') != 'function_call':
continue
nm = o.get('name')
args = o.get('arguments')
if isinstance(args, str):
try:
args = json.loads(args)
except Exception:
args = {'_raw': args}
tools[nm] += 1
calls.append((conv[cid], nm, args if isinstance(args, dict) else {}))
for t, n in tools.most_common():
print(' {:24s} {}'.format(str(t), n))
这里有两个坑:一是 arguments 有时候是 JSON 字符串、有时候已经是对象,两种都得接住;二是单行解析失败就跳过,别让整个脚本挂掉——几万行里混几行畸形很正常。
拿到 calls 这个列表之后,做四件事:
RISK = ['rm -rf', 'rmdir', 'del /f', 'del /s', 'format ', 'remove-item', 'shutdown',
'reg delete', 'taskkill', 'invoke-webrequest', 'curl ', 'wget ', 'netsh',
'schtasks', 'takeown', 'icacls', 'git push', 'send-mail', 'agent_mail']
WS = 'd:\\workbuddy\\你的项目名' # 换成你自己的工作区,必须小写
bad = []
dirs = collections.Counter()
out_files = []
for a, n, v in calls:
s = json.dumps(v, ensure_ascii=False).lower()
if any(k in s for k in RISK):
bad.append((a, n, s[:150]))
if n in ('Write', 'Edit', 'MultiEdit', 'NotebookEdit'):
fp = str(v.get('file_path') or v.get('path') or '')
if fp:
dirs[os.path.dirname(fp).lower()] += 1
if not fp.lower().startswith(WS):
out_files.append((a, fp))
print('危险命中', len(bad))
print('工作区外写入', len(out_files))
注意路径比较一定要统一转小写。Windows 上盘符和目录名大小写不一致,不转小写会漏判,本来越界的写操作会被当成安全。
危险词表不用写得多,覆盖这几类就够:删文件、格式化、改注册表、建计划任务、杀进程、关机、联网下载、往外发数据、调邮箱工具。
我本机跑完的输出长这样(节选):
被审会话 23 个,工具调用 591 次
=== 危险关键词扫描:命中 1 条 ===
[签到补漏] Bash :: {"command": "curl http://127.0.0.1:11434/api/tags"}
=== 写操作目录 ===
[工作区内] d:\workbuddy\你的项目名 ×203
[区外] c:\users\你的用户名\.workbuddy ×5
逐条看下来:
看到这个结果我松了口气。但心里也清楚:这只能说明过去没出事,不等于以后不会出事。
审计过程里翻到两件更值得注意的事。
第一件:自动化线程被当成普通聊天窗口续用了。有个签到任务的线程,后面接着聊了别的事,还调了技能管理、连了资料库。这些线程同样带着完全权限——在里面随口说一句,也是「不问就干」。风险不在定时任务本身,在这类续用窗口缺少二次确认。
第二件:有任务失败过,但原因不是权限。报错是「会话在任务完成前结束」,那是免费模型被限流掐断,跟权限没关系,别往安全上瞎怀疑。
所以结论很明确:权限不是问题,提示词的边界才是问题。
第一件:权限维持现状,不动。
降成「每次询问」听起来更安全,但定时触发时没人确认,任务直接跑不完——拿安全性换来的可能是任务全废。这笔账不划算。
第二件:给 5 个任务的提示词各加一段「安全硬约束」,并声明最高优先级(与提示词其他内容冲突时,以本节为准):
加完回数据库核验一遍:5 条记录的提示词里都有这段,状态和触发时间一点没变。改提示词不影响自动执行,代价几乎为零,但边界清楚了。
第三件:改掉一个习惯——不在自动化任务的线程里接着聊别的活儿,要聊就新开一条对话。
顺带提醒一句:改权限的正规途径是客户端任务编辑面板的「权限」项。自动化管理接口改不了这个字段,直接改数据库有被客户端缓存覆盖的风险,别干这种事。
这回审计给我最大的触动不是「AI 干了什么坏事」——它一件都没干。触动在于:我给出去一个很高的权限,然后大半年没回头看过一眼。安全这件事,最怕的不是别人动手,是自己忘了。
脚本我已经留在本地,隔段时间重跑一次,几十秒出结果。你如果手上也有挂着「完全访问」的定时任务,建议照着这套跑一遍。查完心里有底,比什么都强。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。