审计与覆盖
每次闸门决策都会写入日志。受信任的工作流可在策略层豁免。紧急停止开关用于事故响应,始终留下操作记录。
上周一,你尝试运行的某个脚本被拦截了。隐约记得点过"取消",但具体是哪个脚本?又或者,你有一条从头到尾经过审查的自动化流程,每次运行时却仍会弹出同样的确认问题。能不能让闸门直接放行?
本页介绍三个操作员级别的控制手段:查看历史决策、配置单工作流豁免,以及紧急停止开关。
设计理念:可见、可拦截、始终审计
以下三条属性贯穿闸门的每条代码路径:
- 可见。 每次闸门决策(拒绝 / 确认 / 放行)都会写入一条审计记录,无一例外。操作员可随时回溯。
- 可拦截。 受信任的工作流可通过
script_risk_policy预先批准特定脚本体。豁免本身也会写入一条审计记录,可区分"因策略获批"与"脚本本身干净"这两种情况。 - 始终审计。 就算在事故响应期间设置了
DISABLE_SCRIPT_RISK_GATE=1,每次被旁路的调用也会写入一条标记bypassed_by="env_global"的记录。任何使用模式都不会让闸门保持沉默。
为什么这样设计? 信任来自透明,而非单纯的严格。只会拦截的闸门最终会被绕过:有人设置了那个环境变量然后忘记了。一个提供清晰旁路路径、同时记录每次使用的闸门,既足够灵活(真实工作流可以顺利运行),也保持问责(事故发生后可以还原现场)。
查看决策记录:minara script-risk list / show
先从"今天刚发生了什么"入手:
# 所有工具最近 50 条决策
minara script-risk list
# 最近 24 小时内的拒绝记录
minara script-risk list --verdict=reject --since=2026-05-14
# 特定会话的所有决策
minara script-risk list --session=abc-123
# 特定工作流定义内的所有决策
minara script-risk list --workflow=wf_daily_rebalance
# 按工具过滤
minara script-risk list --tool=execute_code
# 查看某条决策的完整详情(所有 finding、所有证据行)
minara script-risk show <decision-id>
# 机器可读格式
minara script-risk list --verdict=confirm --json各标志可组合使用。目的是取证:当你想了解昨天哪些脚本被拦截时,从宽泛的 --verdict=reject 开始,再逐步缩小范围(--session、--workflow、--tool、--since)。
HTTP 等效接口提供相同的数据结构:
GET /v1/admin/script-risk/decisions?verdict=reject&tool=terminal&since=…&limit=…
GET /v1/admin/script-risk/decisions/<id>如果你把网关暴露给除自己以外的人,请在反向代理层对该接口加以保护。
审计记录包含什么、不包含什么
每条决策都会记录:
- 决策时间戳、工具名称、工具调用 ID(已知时)、会话 ID
- 裁定(拒绝 / 确认 / 放行)及 findings 数组;每个 finding 包含类别、单行描述、屏蔽后的证据文本,以及在脚本体中的行号
body_sha256:被分析脚本体的哈希值user_decision(execute / cancel / timeout):当流程经过确认弹窗时记录bypassed_by(env_global / workflow_policy / null):当流程通过显式策略放行时记录
明确不记录:
- 脚本体本身
- 匹配到脱敏规则的原始私钥、Authorization 头、Cookie 值或 Bearer token
- 脚本的 stdout / stderr
为什么不记录完整脚本体? 含有私钥的脚本一旦持久化,审计日志就变成了第二条泄漏通道。存储
sha256可以回答"之前是否见过这个脚本?"(是/否);屏蔽后的证据可以回答"哪条规则触发了、匹配的内容大致是什么?";两者都不需要把密钥复制到长期存储的表中。代价是:事后无法逐字节回放脚本体。换来的是一份可以安全备份、共享和 grep 的审计日志。
工作流豁免:script_risk_policy
典型场景是这样的:你有一个定投 (DCA) 工作流,每天调用 minara swap USDC ETH 100。脚本是你自己写的,也经过了审查,体内容不会变化。每次运行都要确认,纯属摩擦。
在工作流定义内配置:
script_risk_policy:
approved_body_sha256:
- "abc123…" # 你审查过的脚本的 sha256
allowed_categories:
- fund_moving_cli # 该工作流被明确允许
# 调用 minara swap通过 workflow_activate 激活工作流(该操作本身是涉及资金的两步确认)后,策略即注册生效。此后,当该工作流运行匹配的脚本体时,YELLOW 级别的 finding 会静默降级。审计记录中会写入 bypassed_by="workflow_policy",事后仍可看出闸门未拦截的原因。
适用场景:
- 体内容稳定的预审脚本(哈希校验将你锁定在特定版本)
- 特定业务工作流中,某类已知操作本就是其核心目的(闪兑的定投工作流;调用 approve 的再平衡工作流)
不适用场景:
- 通用的万能工作流。策略应足够精确,一句话就能说清楚"这条豁免是为什么而设"。
- 对所有类别全部放行。那等于对一个工作流彻底关闭闸门。
- 每次运行时动态生成体内容的脚本。体内容变化后 sha256 不匹配,策略会静默失效(失败时保持关闭,本身没问题,但你增加了复杂度却毫无收益)。
为什么策略可以旁路 YELLOW 而不能旁路 RED? YELLOW 表示"合理但有风险":
minara swap是真实的操作,确实有人可能想自动化它。策略正是用来表达"是的,这个特定工作流被允许执行该类操作"的。RED 表示"没有合理的业务理由"。批量删除、IMDS 数据外泄、内联私钥,这些情况不存在合法的工作流场景。如果你的工作流触发了 RED,解决办法是修改工作流脚本,而非放宽策略。RED 被设计为策略不可旁路。
全局紧急停止开关:DISABLE_SCRIPT_RISK_GATE
设置环境变量 DISABLE_SCRIPT_RISK_GATE=1 会完全跳过闸门,RED 和 YELLOW 均直接放行。每次旁路仍会写入一条审计记录,标记 bypassed_by="env_global"。
每年应仅使用数次的场景:
- 生产事故响应。 一个真实的误报正在阻断正常业务,需要十分钟时间发布修复。设置环境变量、完成恢复操作、取消设置,然后提交 issue 收紧触发规则。
- 完全隔离的 CI。 回测、端到端测试、回归测试套件;所有调用方可证明为非交互式,测试环境处于沙盒中。
永远不应使用的场景:
- "确认弹窗太烦了":那正是安全机制在发挥作用。
- "我在给团队运行 agent,不想审查策略":这会让整个团队暴露在攻击者能绕过第一层防御的风险中。
- 写进 shell profile 然后忘记。下一个无关的会话会继承它。
从审计角度,可以这样发现滥用:
minara script-risk list --json \
| jq '.decisions[] | select(.bypassed_by == "env_global")'如果在你记忆中的时间窗口之外看到 env_global 旁路记录,说明环境变量在某处被设置了,而你并没有意识到。
完整环境变量参考见环境变量 → DISABLE_SCRIPT_RISK_GATE。
操作员职责
第 4 层(闸门)、第 3 层(涉及资金的确认)和第 2 层(OS jail)承担了大量工作,但无法替代一些不可省略的操作员规范:
- 每个 Minara 实例对应一名操作员。 Minara 为单用户交易场景设计,不支持多租户 SaaS。团队共享的 Web UI 不在支持范围内;每位操作员应运行自己的实例。
- 私钥存放在
.env中,不进代码库。 且.env应设置为chmod 600。闸门可以在运行时阻止第 4 层读取未授权文件,但无法阻止你在 shell 中执行cat .env。 - 看到 RED 时,不要尝试旁路。 停下来想想:为什么自己运行的脚本触发了高置信度的恶意模式?95% 以上的情况不是误报,那是闸门在履职。
- 看到 YELLOW 时,阅读证据。 花两秒钟读一下匹配行,这正是弹窗存在的意义。凭肌肉记忆点"执行",只会让自己成为最薄弱的一环。
- 永远不要把
MINARA_SKIP_FUND_CONFIRM=1或DISABLE_SCRIPT_RISK_GATE=1写入.zshrc、.envrc或 systemd unit。 如果某个脚本确实需要,就为那条命令单独内联设置,完成后立即取消。 - 审计日志的备份很重要。
script_risk_decisionsSQLite 表是取证链的一部分,应像对待日志一样对待它。