- 人工智能
- AI Agent
- AI 应用
- 交互助手
- 桌面应用
- 多智能体
- Agent 记忆
- 工具调用
【免费下载链接】OpenHanako
可作为日常办公助手,无需复杂配置即可让 AI 记住信息、操作电脑、搜索浏览、执行代码等。具备记忆系统、人格塑造、多 Agent 协作、安全沙盒等核心功能,支持多平台接入和插件扩展。
本文以 SECURITY.md 为骨架,结合 OpenHanako 仓库中的安全模块实现,系统讲解该项目的漏洞报告流程、安全关注范围、本地凭证存储的权限契约(owner-only 读写)、每次启动都会执行的文件权限自愈机制,以及迁移备份的 90 天保留策略。读完你不仅能理解这份安全文档的全部承诺,还能掌握这些承诺在代码中是如何落地与验证的,例如凭证文件写入、权限修复、跨平台差异等具体实现细节。
1. 项目背景与安全文档定位
OpenHanako 是一款可作为日常办公助手的 AI 桌面应用,具备记忆系统、人格塑造、多 Agent 协作、安全沙盒等核心能力,支持多平台接入和插件扩展。项目已更名为HanaAgent,但出于过渡期兼容考虑,仓库路径与安全报告 URL 仍保留在旧版openhanako名称上——这一点在安全文档开篇即有说明。
该项目的安全文档承担了三类职责:
- 外部沟通:明确漏洞发现者如何报告问题、响应时限;
- 边界声明:界定哪些安全问题是项目关注范围(Scope);
- 存储契约:详细说明本地凭证(Provider 密钥、OAuth Token、设备记录)在数据目录中的存放方式、权限保证与已知边界。
2. 漏洞报告流程(Reporting a Vulnerability)
2.1 报告渠道
安全文档给出的报告方式分为两个优先级:
- 首选:在仓库 Security 标签页打开 Private vulnerability report(私有漏洞报告),路径为
https://github.com/liliMozi/openhanako/security; - 备选:若私有报告不可用,则在
https://github.com/liliMozi/openhanako/issues提交 Issue。
2.2 报告需要包含的信息
为了让维护者能快速复现与评估,报告应包含:
- 漏洞的描述(Description of the vulnerability);
- 复现步骤(Steps to reproduce);
- 潜在影响(Potential impact)。
2.3 响应承诺
维护者承诺在72 小时内响应,并先与报告者协作修复,再对外公开披露(coordinated disclosure)。这是一个典型的负责任的漏洞披露流程,意味着漏洞细节在修复完成前不会被公开展示。
3. 安全关注范围(Scope)
SECURITY.md 明确了四个重点关注的安全风险类型,这些也是仓库中安全代码模块实际防范的对象:
| 风险类型 | 英文原文 | 仓库中的相关机制 |
|---|---|---|
| 沙盒逃逸 | Sandbox escape(PathGuard / Seatbelt bypass) | lib/sandbox/下的沙盒策略模块 |
| 凭证泄露 | Credential leakage | core/credential-file-healer.ts、shared/secret-fs.ts等凭证保管模块 |
| 远程代码执行 | Remote code execution | 执行边界与策略模块(如 core/execution-boundary.ts、core/execution-router.ts) |
| Electron 渲染进程 XSS | Cross-site scripting in the Electron renderer | 桌面端 CSP 配置(vite.csp-profiles.ts)及配套的 csp-sync.test.ts |
从源码结构看,这份 Scope 列表与仓库安全基础设施的划分一一对应,属于“有实际防御实现支撑的边界声明”,而非空泛的承诺。
4. 本地凭证存储契约(Local credential storage)
4.1 数据目录与存放位置
Provider 密钥、OAuth Token 和设备记录均存放在数据目录中,该目录由环境变量HANA_HOME指定,默认值为~/.hanako(即用户主目录下的.hanako隐藏目录)。
4.2 项目承诺的三项保证
安全文档明确承诺以下三点,每一项都有对应的源码实现:
保证一:凭证文件仅所有者可读写,数据目录仅所有者可访问。
代码层面的落地位于 shared/secret-fs.ts,其中定义了两个核心常量:
SECRET_FILE_MODE = 0o600:凭证文件,所有者可读可写,其他任何账户无任何权限;SECRET_DIR_MODE = 0o700:凭证目录,仅所有者可进入和列出。
该模块特意将权限固化在函数内部而非作为参数传入,原因在模块注释中说明得很清楚:一个把 mode 作为可选参数的通用原子写函数,每一个新调用点都可能是"忘记传权限参数"从而写出全机器可读凭证的隐患。这里权限不是参数,调用方根本不可能写错,静态扫描也能仅凭函数名区分凭证写入与普通写入。
凭证写入统一走writeSecretFileSync(filePath, content):先清除可能残留的临时文件(.tmp),再以0o600创建临时文件并写入内容,随后做一次 best-effort 的权限收紧(tightenBestEffort),最后原子rename到目标位置。之所以要先清残留临时文件,是因为"以 mode 创建文件"对已存在的文件不生效——若一次崩溃留下了旧临时文件,它会把旧权限带给下一个写入。
从调用面来看,writeSecretFileSync被 core/migrations.ts、core/provider-catalog.ts、core/device-registry.ts、core/local-user-account.ts、core/web-session-store.ts、core/plugin-config.ts、core/first-run.ts 等大量模块共用,构成了整个数据目录的凭证写入统一入口。
保证二:每次启动都验证权限,发生漂移时自动修复并记录日志。
新写入文件带正确权限,只能覆盖"升级后写入的文件";磁盘上已有的文件会保留其创建时的权限,且永远不会被重写的文件会一直带着旧权限。因此每次启动都执行自愈,而非只做一次性迁移——因为权限会在之后再次漂移:恢复备份、跨机器拷贝数据目录、外部同步都会重新引入原始权限。
实现位于 core/credential-file-healer.ts 的healCredentialFileModes({ hanakoHome, log }),其工作范围包括:
- 数据目录本身(收紧为
0o700); - 顶层凭证文件清单
TOP_LEVEL_SECRET_FILES,共 10 个:provider-catalog.json、models.json、added-models.yaml、auth.json、device-credentials.json、devices.json、pairing-sessions.json、local-user-auth.json、users.json、web-sessions.json; - 每个 Agent 目录下的
config.yaml(可能含 Provider 密钥),以及迁移过程中产生的config.yaml.pre-scope-migration备份、config.yaml.tmp崩溃残留; - 三个整树凭证目录(
SECRET_TREES):migration-backups(迁移备份)、本地 Provider 插件目录(其定义文件携带 Provider 密钥)、security目录(签名密钥与授权记录); - 插件数据目录中每个插件的
config.yaml(可能持有连接器与服务凭证),但只修配置文件本身,不动插件数据目录里属于其他 store 的下载文件、任务状态与产物; - 会话清单迁移检查点(
checkpoints/session-manifest/)内完整拷贝的 agents 目录。
每次修复都会记录日志(如[credential-custody] tightened <相对路径>);修复失败的文件会被归入failed列表并上报错误总线(errorBus,以credential-custody:<路径>去重),但不会中断应用启动——因为部分文件系统(可移动介质、网络挂载)会直接拒绝 chmod,若因此打断用户反而小题大做。
保证三:迁移产生的备份同样持有凭证副本,超过 90 天即被清理,且仅在"存活目录可读且非空"时清理。
实现位于 core/credential-backup-retention.ts 的pruneStaleCredentialBackups({ hanakoHome, now, maxAgeMs, log }):
- 保留窗口常量
CREDENTIAL_BACKUP_MAX_AGE_MS = 90 * DAY_MS,即 90 天; - 删除(不可逆)必须同时满足两个条件:备份超过保留窗口,且当前
provider-catalog.json可读、可解析、含非空 providers; - 若存活目录缺失、无法解析或为空,无论备份多旧都全部保留——这正是回滚存在的目的;
- 每个决定(删除或保留)都记录日志,保证策略可审计而非静默执行;
- 健康检查刻意直接读取并解析目录文件,而不是走 catalog store 的加载器——因为 store 的加载器在目录缺失时会回退触发旧文件迁移,让一次"提问"产生改写状态的副作用(对应测试 credential-backup-retention.test.ts 专门验证了"缺失的目录必须保持缺失")。
4.3 自愈与保留在启动序列中的位置
这两个启动步骤在 core/engine.ts 中以runBestEffortStartupMigrationStep形式顺序执行(见 core/engine.ts):
- 先
pruneStaleCredentialBackups(步骤credential-backup-retention)清理过期迁移备份; - 再
healCredentialFileModes(步骤credential-custody)收紧凭证权限。
代码注释解释了放置顺序的用意:放在所有数据迁移之后,让本轮迁移刚写出的文件也被覆盖到。两个步骤互相独立,任何一步出意外都不会连累另一步——"清理"和"矫正"解耦。
另外值得注意的工程细节:目录名全部从所属模块导入常量,而非各处重复拼写。例如 core/security-dir.ts 是唯一命名security目录的地方,core/migration-backups.ts 是唯一命名migration-backups的地方。模块注释记录了一次真实事故:某列表手写了providers而 store 实际写的是provider-plugins,导致自愈 pass 走过一条不存在的路径、静默地什么都没修。这个教训直接促成了"目录名单一来源"的约定。
5. 保护的边界:什么不是承诺
SECURITY.md 诚实且具体地划出了权限保护的三个边界:
5.1 同用户程序不可防
权限位决定的是"哪些账户可以打开文件",它无法限制以同一用户身份运行的程序——这类程序可以读取该用户能读取的一切。要收窄这一点,需要由操作系统凭证存储(如 macOS Keychain、Windows Credential Locker)键控的加密,而本项目目前没有做("this project does not do today")。
5.2 Windows 平台不执行任何权限操作
NTFS 不实现 POSIX 权限位,平台文件 API 也无法表达它们,因此应用在 Windows 上完全不执行权限工作(secret-fs.ts中SUPPORTS_POSIX_MODE = process.platform !== "win32",Windows 分支直接跳过 chmod)。在 Windows 上真正保护数据的是数据目录从父目录继承的 ACL:
- 默认位置(用户配置文件内)已排除其他标准账户;
- 但通过
HANA_HOME把数据目录放到别处时,它继承的权限取决于目标位置的授权,应用既不调整也不报告。
因此文档给出的 Windows 建议是:尽量让数据目录留在默认位置。
5.3 凭证以明文存储
凭证文件是明文存放的,权限保护只是"谁可以打开",不提供内容加密。安全文档据此给出操作纪律:不要把数据目录放进版本控制、共享盘或转发的压缩包里。
6. 测试覆盖:契约如何被验证
这份安全契约不仅有文档与实现,还有成体系的测试印证:
- tests/credential-file-healer.test.ts(POSIX 平台运行)验证了:
- 数据目录本身被收紧为
0o700; - 10 个顶层凭证文件全部收紧为
0o600并进入healed列表; - 每个 Agent 的
config.yaml、scope 迁移备份config.yaml.pre-scope-migration、崩溃残留config.yaml.tmp(含检查点内副本)都被修复; migration-backups目录树、本地 Provider 插件目录树、security目录树(签名密钥resource-ticket-key、授权记录grants.json)整体收紧;- 插件数据目录只修配置文件、不动邻居文件(负向断言同样构成契约);
- 已合规时保持静默(
healed为空、无日志); - 缺失数据目录时抛错而非假装干净通过;
- 某个文件 chmod 失败时记入
failed,其余文件照常处理。
- 数据目录本身被收紧为
- tests/credential-backup-retention.test.ts 验证了:
- 超过 90 天且存活目录健康时删除备份;
- 窗口内(如 89 天)保留,理由为
within-retention-window; - 存活目录缺失/无法解析/无 providers 时无条件保留所有备份(
live-catalog-unusable); - 健康检查不会触发目录重建;
- 保留窗口常量恰好等于 90 天。
这些测试直接对应 SECURITY.md 中"每次启动验证并在漂移时恢复、每次修正都记录日志、备份 90 天后清理且仅当回滚目标健康时清理"的每一句承诺,属于可执行的契约定义。
7. 实操要点速查
将本文内容沉淀为可直接照做的检查清单:
- 数据目录位置:确认
HANA_HOME指向可信的本地路径;默认~/.hanako即可,不要为了省事把它放到共享盘或网盘同步目录。 - Windows 用户:保持数据目录在默认位置,不要通过
HANA_HOME挪到权限不受控的目录;不要期待应用会帮你收紧权限。 - 权限巡检:在 Linux/macOS 下可以自行抽查——
ls -l ~/.hanako应为drwx------(0o700),其中的auth.json、provider-catalog.json等应为-rw-------(0o600)。若发现漂移,重启应用即可触发自愈并产生[credential-custody] tightened ...日志。 - 备份纪律:迁移备份最多保留 90 天且存活目录健康时才会清理;涉及回滚时不要手动删除
migration-backups。 - 明文凭证意识:任何情况下都不要把数据目录纳入版本控制、打包或传输;同用户下的其他程序仍可读取这些明文文件。
- 漏洞报告:遵循私有报告优先的流程,报告需包含描述、复现步骤与潜在影响,响应承诺为 72 小时内。
8. 总结
SECURITY.md 是一份"承诺 + 边界"双轨并行的安全文档:它既给出了外部可依赖的漏洞报告流程与响应时限,又用精确的语言划定了本地凭证保护的保证范围与三个明确边界(同用户程序、Windows ACL 继承、明文存储)。而仓库源码把每一句承诺都落成了可执行、可测试的模块——统一入口的 owner-only 写入(secret-fs.ts)、每次启动幂等的权限自愈(credential-file-healer.ts)、90 天可审计的备份保留(credential-backup-retention.ts),并由 credential-file-healer.test.ts 与 credential-backup-retention.test.ts 固化为契约。理解这份文档,等于同时理解了该应用在本地凭证安全上"做了什么、不做什么、以及为什么"。
- 人工智能
- AI Agent
- AI 应用
- 交互助手
- 桌面应用
- 多智能体
- Agent 记忆
- 工具调用
【免费下载链接】OpenHanako
可作为日常办公助手,无需复杂配置即可让 AI 记住信息、操作电脑、搜索浏览、执行代码等。具备记忆系统、人格塑造、多 Agent 协作、安全沙盒等核心功能,支持多平台接入和插件扩展。
相关推荐
Craft Agents 安全机制解析:从漏洞报告策略到凭证加密存储与传输鉴权的源码级实现
Craft Agents 安全机制解析:从漏洞报告策略到凭证加密存储与传输鉴权的源码级实现 本文基于 Craft Agents(craft agents oss
人工智能大模型AI AgentMCP Clients工具调用交互助手Open WebUI 安全策略深度解读:漏洞报告规则、CVE 协调机制与默认安全权限设计
Open WebUI 安全策略深度解读:漏洞报告规则、CVE 协调机制与默认安全权限设计 Open WebUI 的安全策略( docs/SECURITY.md
人工智能大模型AI 应用RAGAI Agent本地部署交互助手后端前端Taro 安全策略全解读:受支持版本、漏洞报告渠道与响应机制
Taro 安全策略全解读:受支持版本、漏洞报告渠道与响应机制 本篇指南基于 Taro 开源仓库根目录下的 SECURITY.md https://link.gi
前端小程序跨平台移动开发
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考