隐私配置与 Skill 隐私提取/加载
本文介绍 OpenViking 的隐私配置能力,以及它与 Skill 写入(提取)和读取(加载还原)的协作机制。
目标
隐私配置用于把敏感值(如 api_key、token、base_url)从技能正文中分离出来,减少已识别敏感值在 SKILL.md 中的明文存储,并支持版本查询和切换。抽取依赖模型,只替换识别并匹配成功的片段,不能保证发现所有敏感信息。
核心目标:
- 写入时自动抽取敏感值并占位符化
- 读取时按当前生效配置自动还原
- 支持版本查询、切换与审计
核心对象与存储结构
隐私配置按二级键组织:category + target_key。
category:配置类别(当前 Skill 使用skill)target_key:目标标识(通常是 skill 名)
用户空间下的存储路径:
viking://user/{user_id}/privacy/{category}/{target_key}/
├── .meta.json # 元信息(active_version/latest_version/labels 等)
├── current.json # 当前生效版本快照
└── history/
├── version_1.json
├── version_2.json
└── ...current.json 与 history/version_x.json 都是完整快照(values 整包)。
版本语义
upsert(写入)
- 每次 upsert 都以传入
values作为“新快照候选” - 如果与当前版本
values完全一致,则不新建版本,直接返回当前版本 - 否则创建新版本并自动设为当前生效版本
- 允许新增 key(不会因“未知 key”报错)
values是完整快照,不是部分更新;省略的旧 key 不会保留在新版本中
activate(激活)
- 将历史版本设置为当前生效版本(写回
current.json) - 更新
.meta.json中的active_version - 不会轮换或撤销第三方服务中的密钥;这里只切换 OpenViking 使用的值
Skill 隐私提取(写入链路)
当通过 add_skill 写入 Skill 时,会走隐私提取与占位符化流程。
add_skill
-> SkillProcessor._sanitize_skill_privacy
-> extract_skill_privacy_values (LLM 抽取 values)
-> placeholderize_skill_content_with_blocks (明文替换为占位符)
-> privacy.upsert(category="skill", target_key=skill_name, values=...)
-> 写入占位符化后的 SKILL.md关键点
- 抽取结果来源:模型返回 JSON,读取
values字段作为隐私键值。 - 内容替换:原文中命中的敏感片段会替换为占位符:
{{ov_privacy:skill:{skill_name}:{field_name}}}
- 保留块映射:会同时记录:
original_content_blocksreplacement_content_blocks
- 写盘结果:匹配成功的片段以占位符保存,未匹配内容保持原样。该流程处理技能正文,不等于扫描并清理所有附件。
Skill 加载还原(读取链路)
读取 SKILL.md 时,FSService.read 会尝试自动还原占位符。
fs.read(uri)
-> get_skill_name_from_uri(uri)
-> privacy.get_current(category="skill", target_key=skill_name)
-> restore_skill_content(content, skill_name, current.values)URI 匹配
当前支持通过后缀识别 Skill:/skills/{name}/SKILL.md,可兼容 user-scoped skill 路径,例如:
viking://~/skills/{name}/SKILL.mdviking://user/{user_id}/skills/{name}/SKILL.md
restore 规则
restore_skill_content 的行为如下:
content 中有占位符,且 privacy value 存在且非空
-> 直接替换为对应值。content 中有占位符,但 privacy value 缺失或为空
-> 保留占位符不替换,并记入unresolved_entries。privacy 中存在 key 且非空,但 content 中没有对应占位符
-> 记入“额外配置”提示(Configured but not referenced in content)。当存在
unresolved_entries或“额外配置”时,会在内容末尾追加:[Privacy Config Notice]Missing config: ...(如有)Configured but not referenced in content: ...(如有)
仅当该 skill 已有
current配置时才会进入 restore。若没有当前配置,不会追加 notice。
read 返回的是还原后的值;“额外配置”提示也会包含配置值。Privacy API 和历史快照同样可以返回完整值,该功能不等于响应脱敏或存储加密。需要加密存储时,另见存储加密。
与 CLI/API 的关系
- 管理面:通过 Privacy API/CLI 管理版本(查询、写入、回滚)
- 内容面:通过
read自动恢复占位符 - 两者解耦:内容文件负责占位符,隐私服务负责敏感值与版本
PrivacySanitizer 不根据字段名猜测敏感性。当前 active Privacy 中长度不少于 6 个 Unicode 字符的字符串值会参与正文自动扫描与引用替换;更短的值仍可保存、引用和显式读取,但不会参与全文替换,以降低普通正文误命中。Python 负责目录、引用和缓存策略,匹配器构建、最长匹配与正文重写由 ragfs 的 Rust Aho-Corasick 实现完成,并通过 PyO3 复用不可变 matcher、扫描时释放 GIL。匹配目录按用户和本进程 revision 缓存,本进程修改会立即失效,另外设有 3600 秒缓存 TTL 以覆盖多 worker/多节点修改。
以保存 GitHub Personal Access Token(PAT)为例:
# 首次写入或轮换 GitHub PAT;credential/github 是 target,pat 是 field
ov privacy upsert credential github --field-pat 'github_pat_...'
# 盘点当前目标和字段,不暴露值
ov privacy tree --category credential --references
# target 只有 pat 一个字段时可以省略 field
ov privacy reference credential github
ov privacy resolve credential github
# 显式指定 field 的等价形式
ov privacy reference credential github pat
ov privacy resolve credential github pat
ov privacy resolve '{{ov_privacy:alice:viking://~/privacy/credential/github#/values/pat}}'
# 查看/切换历史版本,或删除 pat 字段(最后一个字段删除后 target 也会删除)
ov privacy versions credential github
ov privacy activate credential github 1
ov privacy delete credential github pat --yes
# 扫描一个明确的 URI;默认仅报告,--fix 才会替换明文 PAT
ov privacy scan --uri viking://~/resources/project-a设计收益
- 降低明文敏感信息在技能正文中的暴露风险
- 为外部密钥轮换后的配置切换保留版本记录
- 对上层调用透明:
read即可拿到还原后的可执行技能文本 - 对不完整配置提供可观测提示,便于排障