跳到主要内容

SkillSec V2

SkillSec 扫描 Skill 内容、签名扫描结果、保留可恢复的版本,并向 SkillFS 发布选定版本。 内置扫描全部在本地执行,不调用模型。Rust CLI 使用 agent-sec-cli skillsec 入口, 由一个 root daemon 执行业务操作。skill-ledger 保留为兼容别名,子命令、参数、输出和退出码一致。 现有 Hook 适配器和脚本可继续使用旧名,新命令建议使用 skillsec。能力 ID、 SKILL_LEDGER_* 环境变量、RPC 方法及数据存储路径保持不变。

本文说明 V2 核心及 Hook 适配器。Skill Ledger 指南保留 V1 参考和 各宿主的匹配、策略控制规则。适配器沿用现有语言、默认策略和启用状态,通过 CLI 调用 Rust daemon。

安装边界​

正式发布组件的常规入口为 sudo anolisa --install-mode system install sec-core,Alinux 用户还可使用 安装指南中的 RPM 方式。这些入口选择已发布产物,不会选择尚未发布的 V2 迁移分支。安装正式版本不代表已获得 SkillSec V2。

V2 RPM 需要在 Linux 上按仓库配方从本分支构建。完整配方还会构建保持现状的 sandbox 和插件包, 需要 Node.js 22.14 或更新版本、Rust 1.93 或更新版本、RPM 工具、systemd RPM 宏、C 编译器、 pkg-config 和 OpenSSL 开发头文件:

./scripts/rpm-build.sh agent-sec-core-v2
sudo yum install ./scripts/rpmbuild/RPMS/x86_64/agent-sec-cli-*.rpm

aarch64 使用对应架构目录。本阶段安装核心 CLI 包;元包还会拉入 Agent 集成。插件包应与本次 Hook 迁移使用相同修订, 仅升级 CLI 不会更新进程中已加载的插件。 V1 和 V2 CLI 使用相同包名和可执行文件路径,安装 V2 会替换 V1。先停止 V1 写入者并保留匹配备份, 具体见下文部署回退。

仅从源码安装核心时,使用 Linux 和 Rust 1.93 或更新版本,在仓库根目录执行:

make -C src/agent-sec-core build-cli-v2
sudo make -C src/agent-sec-core install-core-v2

产物为 src/agent-sec-core/target/v2/bin/agent-sec-cli 和 agent-sec-daemon。 核心二进制不依赖 Python Ledger 运行时。install-core-v2 将两个二进制安装到 /usr/bin, 系统 unit 安装到 /usr/lib/systemd/system,初始配置以 0600 安装到 /etc/agent-sec/skillsec.json。 重装保留已有配置;RPM 使用 %config(noreplace)。安装不启用 Agent Hook;服务启动后,对已发现 Skill 的后台扫描可能初始化签名密钥。

V2 systemd 服务单元名为 agent-sec-core.service,可执行文件/进程名为 agent-sec-daemon。 V1 使用同名单元作为用户服务,V2 将其作为系统服务运行。

在运行 systemd manager 的主机上,RPM 会在首次安装和 V1 升级到 V2 时启用并启动 root 系统服务。安装前先检查配置,随后验证 daemon 是否可用:

sudo systemctl status agent-sec-core.service
agent-sec-cli skillsec status

如果 RPM 安装在没有 systemd manager 的镜像或容器中,或者服务后来被停止或禁用,请显式启动:

sudo systemctl enable --now agent-sec-core.service

使用系统级 systemctl,不带 --user。unit 创建运行、状态和日志目录,保持密钥和日志私有, 向 daemon 授予 CAP_DAC_OVERRIDE、CAP_CHOWN、CAP_FOWNER 和 CAP_SYS_PTRACE。 前三项支持 SkillSec 目录访问与恢复;CAP_SYS_PTRACE 支持进程发现读取受保护的 procfs 可执行文件和 namespace 链接。 保留 NoNewPrivileges、SystemCallFilter=@system-service、native syscall 架构、 MemoryDenyWriteExecute 和内核保护,同时保留 HOME、/tmp、系统 Skill 目录及共享挂载的访问能力, 以完成扫描、元数据发布和回滚。daemon 不具有 CAP_SYS_ADMIN。自定义 SkillFS 挂载须对服务可见。

评估时使用隔离部署;V1 和 V2 不得同时写同一份 Skill 元数据。 实现状态与实际 Linux 交付验收分别记录在迁移文档。

V2_CARGO_TARGET_DIR 指定共享 Cargo 构建缓存,V2_BIN_BUILD_DIR 指定二进制暂存目录。 构建和安装时传入相同的暂存目录设置。已有 Linux 构建可用以下命令检查暂存安装布局及配置保留:

bash src/agent-sec-core/tests/packaging/test-skillsec-install.sh

该暂存检查不会启动 systemd,也不代表真实安装验收通过。

以前台进程运行开发 daemon 时,先创建 root 所有、模式 0755 的运行目录,以及 root 所有、模式 0700 的空状态目录。通过 agent-sec-daemon serve --socket /run/agent-sec-core/daemon.sock 指定绝对 socket 路径,可用 --skillsec-config /etc/agent-sec/skillsec.json 指定配置。 进程必须以 root 运行。运行目录必须预先存在;私有状态目录不存在时由 daemon 创建。

原位 SkillFS backing 挂载要求内核支持 open_tree、move_mount,并可访问 /proc/self/fd。 操作不支持或被拒绝时直接失败;daemon 无需增加挂载 capability。

Agent Hook 接入​

安装配套插件或修改可信启动环境后,重启 Agent。使用自定义 socket 时,确保 AGENT_SEC_DAEMON_SOCKET 与 daemon 一致。加载 Skill 前先运行 agent-sec-cli skillsec status,若启动尚未建立 V2 信任,再显式扫描该 Skill;V1 密钥或 manifest 不能建立 V2 信任。

宿主查询命中 Skill 时的初始化默认策略
Codexcheck幂等 init --no-baselineask,在 UserPromptSubmit 以警告呈现
Qodercheck无ask
Qwen Codeshow幂等 init --no-baselineask
Cosh-NGshow幂等 init --no-baselineask
OpenClawshow幂等 init --no-baselineask
Hermesshow无observe

四类执行初始化的适配器在每次命中调用时询问 daemon,不检查 HOME 密钥文件,不扫描 Skill, 不重置不安全的密钥,也不换钥。OpenClaw 不在插件注册时初始化,不缓存进程级就绪结果。 禁用或未命中的 Hook 不调用 CLI。Qoder、Hermes 保持只读,启动尚未完成扫描时需显式建立信任。 宿主确认仅允许当前操作继续,不写入 Ledger 人工决策。

check 消费者接受退出码 1 的有效风险结果,不把它当作通信失败。show 消费者要求 退出码 0 及有效发布摘要,再按 message 应用原有策略。已激活的 warn 版本或显式人工决策 可能没有消息。hidden 由 SkillFS 发布视图执行;Hook 不根据该字段单独拒绝直接文件系统访问。 未受管 Skill 不在这一受管发布路径内。

CLI 缺失、daemon 不可用、超时或响应无效时记录诊断。Codex、Cosh-NG、Qwen Code、OpenClaw、 Hermes 保持执行错误时 fail-open;Qoder 与原来一样把错误交给配置策略处理。 初始化失败时,本次调用在 check/show 之前停止,不回退 Python。诊断不复制 CLI 原始 stderr。

Cosh-NG 为整个 SkillSec Hook 预留 10 秒:初始化最多 3 秒,show 最多 5 秒, 另留 2 秒用于启动及结果处理。capabilities 环境变量视图仍显示单次查询的 5 秒预算, 不表示宿主 manifest 的整体超时。

适配器到 daemon 的测试与真实 Agent 验收分别记录。当前证据及宿主限制见 迁移文档。

系统配置与信任​

daemon 读取 root 所有的 /etc/agent-sec/skillsec.json;--skillsec-config 可指定其他绝对路径。 父目录必须可信,配置文件不得允许组或其他用户写入,也不得是符号链接或有多个硬链接。 默认文件缺失时使用以下默认值;格式错误或权限不安全会导致启动失败。

{
"stateDir": "/var/lib/agent-sec/skillsec",
"enableDefaultSkillDirs": true,
"managedSkillDirs": []
}

默认目录自动启用,旧配置未填写 enableDefaultSkillDirs 时也默认开启。daemon 启动时授权:

位置发现范围
/usr/share/anolisa/skills/*、/usr/local/share/anolisa/skills/*直接 Skill 子目录
<home>/.local/share/anolisa/skills/*ANOLISA 用户安装目录
<home>/.openclaw/skills/*、<home>/.copilot-shell/skills/*直接 Skill 子目录
<home>/.hermes/skills/**递归 Skill;根目录自身有 SKILL.md 时也包含自身
<home>/.qoder/skills/*、<home>/.codex/skills/*、<home>/.agents/skills/*、<home>/.qwen/skills/*直接 Skill 子目录

<home> 来自本地 /etc/passwd 的绝对 HOME 字段,包含 root 和服务账户。重复 HOME 合并; 非法 HOME、/、符号链接 HOME 及 /dev/null 等已存在的非目录会跳过并记录诊断。 尚不存在的 HOME 保留授权范围。账户变更后需重启 daemon。仅由 NSS 提供的账户、非标准 XDG_DATA_HOME、CODEX_HOME、QWEN_HOME 及项目目录需显式配置或通过已认证 SkillFS 接入; 调用者环境变量不能扩大授权范围。

managedSkillDirs 用于追加管理员授权的绝对路径:

配置项授权及发现的 Skill
/srv/skills/demo该精确 Skill
/srv/skills/*直接、非隐藏 Skill 子目录
/srv/skills/**递归 Skill;根目录自身有 SKILL.md 时也包含自身

用 sudoedit /etc/agent-sec/skillsec.json 编辑现有配置,保留其他设置,再执行 sudo systemctl restart agent-sec-core.service 生效。enableDefaultSkillDirs: false 仅关闭 默认目录;显式配置和已认证 SkillFS 挂载继续有效。显式列表为空不会关闭已启用的默认目录。 关闭范围不会删除既有 Skill 元数据或重置信任。不支持其他 glob 语法或 ~;发现跳过隐藏后代, 不跟随符号链接。所有本地用户仍可操作全部受管 Skill,本次不引入用户 ACL。

init、scan --all、check --all、status 统一由 daemon 发现,合并默认目录与 managedSkillDirs。调用端显式路径在 Skill I/O 前校验;一个越界路径即以 PermissionDenied 拒绝整个请求。init --no-baseline 和单路径请求不发现目录。

启动扫描在现有 worker 中异步执行,与已到期的 SkillFS 通知交替处理,socket 无需等待扫描。 挂载内 Skill 来自认证通知和配置中的精确身份,直接经已认证 resolver 解析,不遍历 FUSE。 已有标准范围内新增 Skill 立即获得授权, 在下次聚合命令或 daemon 重启时发现;不增加目录 watcher,也不在普通 Skill 调用时自动扫描。 宿主 Hook 继续按原策略控制调用;安装本身不保证拦截所有直接文件读取。

单次默认发现最多读取 10,000 个目录项、加入 1,024 个新 Skill 身份;这些限制不截断显式配置。 单目录失败或预算耗尽时保留已完成的发现,返回含 root、error 的 discoveryErrors,退出码为 1, 状态不能显示健康。启动时记录发现不完整并继续扫描成功的部分。发现超时仍直接失败。

scan 和 certify 成功后,包括内容未变化的 noop,将 canonical 精确 Skill 路径记入 daemon 实际使用的配置文件。普通目录已被 managedSkillDirs 模式覆盖时不重复添加;SkillFS 路径始终 保存精确项,使重启后 无需遍历 FUSE 就能解析该目录。这些项同时授予普通目录访问权限:移除 SkillFS 挂载或关闭 默认目录发现,都不会撤销已保存路径;还须移除对应的 managedSkillDirs 项并重启 daemon。 保存失败在 warnings 中提示,不丢弃扫描结果、findings 或激活结果。修复配置错误后重试扫描即可补存发现信息。

启动扫描、scan --all 和 init baseline 对 /usr/share/anolisa/skills 或 /usr/local/share/anolisa/skills 的直接宿主 Skill 子目录复用只读跳过规则:daemon 无法写入 账本位置(包括只读挂载)时返回 status: skipped、reasonCode: readonly_system_skill、 persisted: false;启动任务不再尝试激活该项。跳过不表示安全或认证成功。 root 仍可写的 0555 目录正常扫描。显式扫描、用户目录写入失败、SkillFS 失败及其他存储错误 仍报错;跳过的 Skill 需放入可写的受管副本后才能建立信任。

状态目录归 root 所有,模式为 0700;signing-key.pk8 为 0600。第一阶段允许所有本地用户操作所有 受管 Skill,文件归属不作为用户访问 ACL。所有用户均不支持换钥。系统密钥签名的是 daemon 计算的记录, 不是调用者提交的任意字节。

不导入 V1 历史、加密密钥或 keyring。新 manifest 绑定 Skill 的规范绝对路径身份。 移动到另一个身份后,需要重新建立信任;不同位置的同名 Skill 具有独立身份。

核心流程​

以下命令由管理该 Skill 的普通用户执行。init --no-baseline 仅初始化共享密钥。 scan 也会初始化缺失的密钥;两者均不会静默替换损坏的密钥。

agent-sec-cli skillsec init --no-baseline
agent-sec-cli skillsec list-scanners
agent-sec-cli skillsec analyze /path/to/skill
agent-sec-cli skillsec scan /path/to/skill
agent-sec-cli skillsec check /path/to/skill
agent-sec-cli skillsec show /path/to/skill
agent-sec-cli skillsec audit /path/to/skill --verify-snapshots
agent-sec-cli skillsec status --verbose

analyze 要求路径在授权范围内,但仍只读,不初始化密钥,也不写账本。默认内置扫描器为 code-scanner 和 static-scanner。 可用 scan --scanners code-scanner,static-scanner 显式选择。内容变化时创建新版本; 内容不变时补齐缺少的扫描结果。scan --force 可替换相同内容的扫描结果,不凭空增加内容版本。

完整性状态共有六种:

状态含义
none内容尚无通过认证的扫描判定
pass通过认证的内容具有通过扫描结果
warn通过认证的内容具有警告发现项
deny通过认证的内容具有拒绝发现项
drifted当前内容与通过认证的记录不一致
tampered账本元数据、签名或版本绑定未通过认证

执行错误和 unmanaged 诊断不属于额外的完整性状态。扫描成功表示扫描完成,不表示安全判定一定为 pass。

首次初始化密钥前,检查没有账本的 Skill 仍返回 none、退出 0,审计其空历史也成功。 两种查询均不创建密钥或元数据。已有账本但密钥缺失或无效时,不会按空历史处理。

外部结果、人工决策与恢复​

通过 CLI 导入外部 JSON findings 报告。自定义 skill、cli 或 api 扫描器仅用于导入结果; root daemon 不会执行其配置的命令。

agent-sec-cli skillsec certify /path/to/skill --findings /path/to/findings.json --scanner skill-vetter
agent-sec-cli skillsec decide /path/to/skill --action allow --reason 'Reviewed this version'
agent-sec-cli skillsec decide /path/to/skill --action always_allow
agent-sec-cli skillsec decide /path/to/skill --action block
agent-sec-cli skillsec decide /path/to/skill --action rollback --version v000001
agent-sec-cli skillsec decide /path/to/skill --clear
agent-sec-cli skillsec export /path/to/skill --version v000001 --output /path/to/empty-export
agent-sec-cli skillsec activate /path/to/skill

allow 批准选定版本;always_allow 保存持续的人工批准。block 隐藏暴露内容。 rollback 恢复选定的已认证快照并记录决策;--clear 清除人工决策。 回滚后的文件归 Skill 根目录所有者所有,普通用户仍可继续编辑;特权文件模式位会被清除。

导出向调用者所有的空目录写入 snapshot/、manifest.json 和 findings.json。 缺少的导出目录由 CLI 以调用者身份按 0700 创建;已有目录权限保持不变,非空目标目录会被拒绝。 --delete-findings 仅在认证成功且确认本地报告未变化后,以 CLI 调用者权限删除该报告。 成功删除返回 findingsDeleted: true;清理失败返回 findingsDeleted: false 和 findingsDeleteError,保留已提交的认证及其成功退出码。daemon 失败或响应格式错误时不会删除输入。

show 展示 active、pending 或 hidden 选择。在 pass_warn_only 下,符合条件的已认证 pass 或 warn 版本可以自动暴露;最新内容不安全时可保留符合条件的旧版本,或呈现待审视图。 人工决策参与版本选择。activation.activationPending=true 表示账本已提交,但发布尚未完成。 检查 show,修复所报告的原因后用 activate 重试。daemon 重启会恢复支持的中断发布和回滚状态, 不承诺持久化通知队列。

普通操作保留逐 Skill 的回滚恢复和校验。启动采用配置发现,无法再被发现的 Skill 不保证自动恢复; 恢复路径后可对其重试显式的已授权操作。不再用独立私有登记表补充全局恢复覆盖。

SkillFS 绑定​

SkillFS 与普通 CLI 共用公共 socket。notify 握手和消息保留现有 HMAC 合同,普通 V2 JSON 仍严格解析。 公共 socket 必须归 root 所有、模式为 0666,直接父目录归 root 所有、模式为 0755。 SkillFS 还会检查祖先目录安全性与真实内核对端 UID。权限调整不允许明文降级或绕过 HMAC。

daemon 显式配置规范路径与 backing 路径的映射:

{
"stateDir": "/var/lib/agent-sec/skillsec",
"managedSkillDirs": ["/home/alice/.openclaw/skills/demo"],
"skillfs": {
"authKeyFile": "/etc/agent-sec/skillfs-hmac.key",
"mounts": [{
"controlSocket": "/run/user/1000/skillfs/control.sock",
"canonicalRoot": "/home/alice/.openclaw/skills",
"liveRoot": "/home/alice/.openclaw/live-skills",
"peerUid": 1000
}]
}
}

使用独立的随机 HMAC 密钥,长度 32–4096 字节,不得使用签名密钥。 daemon 的副本必须归 root 所有、模式 0600;向 SkillFS 用户提供内容相同、归其所有的独立 0600 文件, 不要将 daemon 的私有文件改为所有人可读。以上 UID 1000 示例中,SkillFS notify 的 socket_path 设为 /run/agent-sec-core/daemon.sock,auth_key_file 指向其用户私有副本。 control socket 仍归 UID 1000 所有,父目录 0700、socket 0600;control 的 trusted_peer_uid=0,trusted_peer_key_file 指向同一用户私有副本。必须使用 HMAC 认证, 不设置 trusted_peer_exe,因为可执行文件认证是另一种互斥的认证模式。 按 SkillFS 运行时参考 配置 activation 和 backing root;其中旧示例的 daemon 端点和对端身份应替换为上述 V2 配置。

两个进程必须能看到配置中的绝对路径及相同 backing 文件系统对象,因此容器部署需要匹配的共享卷路径。 同一普通非原地挂载的 canonical/live 根可以完全相等,否则必须互不包含。 规范根目录和 live 根目录不得与其他映射交叠。resolver 失败时不会回退为扫描 FUSE 视图; 目录 inode 不匹配时会拒绝操作,不会给替换后的内容签发记录。

通知按 Skill 合并,然后依次触发扫描与激活。notify 确认仅表示已接受入队。 启用 SkillFS 时,status 包含 skillfs 的 queue/running/processed/failed 计数。 通过计数、公共审计和真实 FUSE 读取,分别确认受理、处理完成和可见发布;启动时会重新调度配置中保存的精确挂载 Skill。

输出、审计与限制​

CLI 向 stdout 输出业务 JSON。check 遇到 deny、tampered 或执行错误时返回 1; 已完成的 scan/certify 和完整 analyze 即使发现风险也返回 0。 analyze 覆盖不足返回 1,输入无效返回 2。传输失败写入 stderr 并返回 1。 必须同时检查 verdict、activation 字段和进程退出码。

用 --socket 或 AGENT_SEC_DAEMON_SOCKET 指定端点。SkillSec 默认超时 60 秒; --timeout-ms 在服务端最大为 120 秒。超时不能证明写入是否已提交,重试修改前先查询状态和历史。 响应过大时返回 ResponseTooLarge 和 operationMayHaveCommitted=true,不会截断业务数据。

公共审计使用共享 Action Runtime 和私有 /var/log/agent-sec 事件存储。 投影包含操作、计数、判定、版本和错误类型,排除源代码、原始 findings、本地路径、人工原因和密钥。 CLI 响应保留完整业务数据。内置扫描限制为 2,000 个文件、总计 50 MiB、深度 32; findings 导入限制为 2 MiB。覆盖失败会明确呈现,不会为部分内容签发完整扫描结论。

系统密钥与部署回退​

V2 初始化一把系统签名密钥并持续复用。不支持密钥轮转:rotate-keys 和 init --force-keys 在目录发现、扫描或密钥修改前失败,直接 RPC 请求也会被拒绝。普通 init --no-baseline 保持 幂等,已有密钥不安全或损坏时不会自动替换。

不再维护私有 managed-skills.json 登记表或 key-rotation.json 事务。未发布实现留下的文件 不读取、不迁移、也不删除。不要通过手动删除签名密钥模拟轮转;已有签名历史依赖该密钥。

替换已有部署前,停止旧 daemon 和 SkillFS 写入者,保留匹配的二进制、配置,并备份私有密钥和状态, 以及每个 Skill 的内容和 .skill-meta。使用副本开展迁移评估。 回退 V1 时先停止 V2,恢复对应的 V1 文件、密钥、配置和元数据,再启动 V1/SkillFS。 不要让 V1 读取 V2 manifest,也不要混用旧元数据与已修改的源内容。 仅保留二进制不等于可恢复状态,同时启动两个 daemon 也不是升级方式。