上一篇文章论证了"为什么 Agent 不该持有可复用凭据"。这篇讲真正的机制:数据库、对象存储、缓存、主机是如何被登记而密码从不变成明文;受保护值如何通过系统认证的进程录入;跳板路由与钉住的主机身份如何工作;拓扑答案如何基于观测证据计算——而不是基于假设。
SAFA 的资产清单是一个加密、与传输方式无关的目录。"资源"是一个通用聚合模型——host(主机)、database(数据库)、object-storage(对象存储)、cache(缓存)、messaging(消息)、search(搜索)、graph(图)、service(服务)全部复用同一条记录。每条记录绑定一个不可变的模板,标明适配器/配置 schema,如 ssh@1、mysql@1、redis@1、s3@1、http@1。
登记一个类型不等于授予访问,也不等于声称适配器已存在。目录回答的是"用户登记了什么、受保护数据放在哪";真正执行某个类型的操作,由对应的适配器决定。
| 字段 | 可见性 | 说明 |
|---|---|---|
| canonical alias(规范别名) | 公开 | 小写逻辑名;Agent 唯一可用的选择器 |
| resource kind / type | 公开 | 如 database.mysql、object-storage.s3、host.linux |
| template | 公开 | 不可变的适配器/schema 绑定,如 mysql@1 |
| endpoint / username / route | 授权后 | 加密;仅授权详情视图披露 |
| typed metadata(类型化元数据) | 白名单 / 授权后 | 未知键一律默认私有 |
| credential bindings(凭据绑定) | 仅 Broker | 只有不透明 ID,永不出现在 Agent DTO |
| credential values / locators(凭据值) | 仅 Keychain / Broker | 永不作为资源元数据 |
首次录入密码是整个系统最敏感的时刻,所以 SAFA 把它与 Agent 能影响的一切隔离开。一个单独签名的 safa-trusted-setup 进程在 Mac 上以系统认证方式运行:受保护字段输入时关闭终端回显,值以类型化的 ProtectedResourceSetupPayload 经认证的本地 XPC 直达 Broker——绝不经过 Agent CLI 的 argv、环境变量、stdin、stdout 或 stderr。如果没有可信的控制终端,SAFA 会返回一条 safe_for_agent: false 的命令,由用户亲自执行。
最终产物是一个 CredentialReference(凭据引用):一个不透明 UUID,同时作为 Keychain 账户标识;加上类型(当前是 SSH;数据库密码、对象存储访问密钥、API token 无需改动保险箱结构即可扩展)、健康状态,以及最关键的一项——访问类别(access class):
automaticWithinPolicy —— 策略允许时,Broker 可直接使用该凭据。userPresenceRequired —— Broker 必须先获得 macOS 用户在场授权(Touch ID / 密码)才能使用。设备绑定的私钥没有任何导出路径;删除引用会在依赖资源停用后清除对应 Keychain 材料。秘密不是领域模型字段——保险箱 schema 本身就没有可泄露的东西。
一个录入到系统认证本地进程、以不透明 Keychain 引用存储、只在 Broker 边界内注入的密码,永远不会成为模型输入、工具输出或聊天记录。
连接元数据(endpoint、username、route)是加密的,不在 Agent 可见面内。需要路由时,资源模型携带 jumpRoute:一个有序、无环的 SSH 资源列表,Broker 依次穿越。也支持直连——包括指向本机已运行的 Core Tunnel 监听端口,隧道前置而不暴露任何东西。
每次执行都是一次全新的、隔离的 SSH 调用,基于每次请求的随机别名和一个临时 ssh_config + known_hosts(目录权限 0700):
StrictHostKeyChecking yes,用于查找的别名/端口固定,canonicalization 不可能产生分歧。GlobalKnownHostsFile /dev/null、HashKnownHosts yes、每请求独立的主机密钥别名——临时世界与系统完全隔离。SSH_ASKPASS(SSH_ASKPASS_REQUIRE=force)的密码、Secure Enclave agent socket、或已有 OpenSSH 身份文件/agent。StdinNull yes,强制 OpenSSH 走 SSH_ASKPASS 而不是从 stdin 读登录密码。主机身份记录守卫目的地:状态为 trusted(可信)、changed(已变更)或 revoked(已吊销),并记录验证方式。身份变更或缺失一律失败关闭——永远不存在原始 SSH 回退。
资源关系以有向、类型化、带属性的多重图存储——树结构表达不了"这台主机用了另一个安全域的路由、又依赖三个存储"。边携带层级(layer)与验证状态(verification):
desired/asserted —— 用户的意图或假设;提议的边永远不会成为证明。observed/verified —— 只能由签名适配器或有界探针创建,带时间上限。stale(过期)/ failed(失败)—— 可展示用于诊断,但不能证明路径。一次成功的有界 SSH 配置或执行,会刷新一条持续五分钟的 runtime.local can-reach <resource> 观测边;OpenSSH 的 exit 255 永远不会创建它。可达性只有在 Broker 找到一条完全由新鲜、已验证证据构成的路径时才返回 confirmed。
下面是真实命令的返回——在真实注册环境中录制(别名已脱敏,数值原样):
$ safa topology show data.home --limit 64 # 数据库主机在哪里?
schema: dev.safa.cli/v2
command: topology.show
status: completed
task: placement
answer:
outcome: found
source: data.home
count:
nodes: 6
edges: 5
nodes[6]{alias,kind,resource_kind}:
data.home,resource,host
site.home,site,null
desk.win,resource,host
router.home,resource,host
virt.host,resource,host
nas.primary,resource,host
edges[5]{id,from,relation,to}:
...,data.home,located-in,site.home
...
$ safa topology path data.home nas.primary --limit 64 # 数据库主机能到 NAS 吗?
schema: dev.safa.cli/v2
command: topology.path
status: completed
task: reachability
answer:
outcome: not-found
source: data.home
target: nas.primary
count:
nodes: 2
edges: 0
对比这两条输出:数据库主机与 NAS 同处一个站点(placement found,6 个节点 5 条 located-in 边),但 Broker 报告两者之间没有已验证的路径。这正是重点——同站不连通。Agent 若问"这个节点能到数据库吗",得到的是 not-found,且被禁止编造路由或尝试直连。同一个图版本还产出 topology impact(反向依赖搜索)与 topology link/unlink(变更关系,需 macOS 用户授权)。
默认的非交互摘要只暴露经过源码评审的白名单键:host.os.family、host.docker.available、database.engine、object-storage.provider、cache.engine、service.protocol。IP、内核版本、CPU/内存/磁盘细节、Docker 版本、路由以及一切未知键,都需要 resource show --details——而这条命令依赖 macOS 自有的用户在场提示。受保护详情与提权动作共用同一条规则:要么用户在 Mac 前,要么值保持隐藏。
授权在代码里不是模糊概念:待审批请求携带 TrustedApprovalPresentation(资源别名、特权级别、精确命令、意图、预期影响、风险级别、发现项),用户通过 safa-trusted-setup request approve 或拒绝来决策——sudo 还支持可选的作用域授权(见 spec 002)。Agent 的自我评估只是建议,永远不能替代这个决定。
当前可执行的配置是 SSH 主机 + 白名单只读诊断。数据库、对象存储、缓存与服务已作为类型化记录登记,其操作在评审前保持门控。通用目录、不透明凭据引用模型、跳板路由字段、访问类别与用户在场授权路径,都已经在设计与契约中——接下来交付的是各适配器,且每个都附带一致性测试夹具,而不是空头承诺。