claude-mem Server 安全机制解析:API 密钥认证、local-dev 回环旁路与密钥存储模型
Claude-Mem 的 server 运行时默认采用 API 密钥认证来保护记忆读写接口,其安全文档 security.md 定义了三层关键约定:默认 API-key 认证、local-dev 回环开发旁路的启用条件,以及 BullMQ 队列模式下的存储边界。读完本文,你可以理解 claude-mem 中认证模式的环境变量组合、cmem_ 前缀 API 密钥的生成/存储/校验全链路,以及部署时避免将服务端口裸暴露在公网的具体约束,并能结合仓库源码定位到每一项安全约定的实现位置。
认证模式总览:默认 api-key,显式才可降级
从 SettingsDefaultsManager 的默认值可以确认,CLAUDE_MEM_AUTH_MODE 的出厂默认值是 api-key,即 server beta 运行时开箱即要求每个请求携带密钥,没有任何隐式放行路径。
认证中间件 requireServerAuth 的密钥提取逻辑为:
- 首选
Authorization: Bearer <key>(canonical 形式); - 回退到
X-Api-Key: <key>请求头,以兼容使用@better-auth/api-key默认约定的客户端(例如 Windows-canary 线发布的 worker bundle)。
Bearer 解析由 parseBearerToken 完成,采用 /^Bearer\s+(.+)$/i 正则并做 trim 处理。缺失密钥时中间件直接返回 401 及提示 Missing API key (Authorization: Bearer <key> or X-Api-Key: <key>);密钥无效或权限不足则返回 403 Invalid API key or insufficient scope。
local-dev 回环旁路:双环境变量 + 三重网络判据
安全文档明确了一条强约束:CLAUDE_MEM_AUTH_MODE=local-dev 只有在同时设置 CLAUDE_MEM_ALLOW_LOCAL_DEV_BYPASS=1 时才启用回环开发旁路,且不得置于反向代理之后或绑定在公网可达的地址上。源码中这个"双开关"设计在 auth.ts 体现为五个必须同时满足的条件:
- 请求未携带任何 API 密钥(
!rawKey); authMode === 'local-dev'(来自环境变量,默认api-key);CLAUDE_MEM_ALLOW_LOCAL_DEV_BYPASS === '1'显式打开;- 客户端 IP 是回环地址——isLocalhost 只接受
127.0.0.1、::1、::ffff:127.0.0.1、localhost四种取值; Host请求头是回环主机名——hasLoopbackHostHeader 仅接受127.0.0.1、localhost、::1(支持[::1]:port这类带方括号的 IPv6 写法);- 且不存在任何代理转发头——hasForwardedClientHeaders 会检查
forwarded、x-forwarded-for、x-forwarded-host、x-real-ip四个头,只要出现任意一个就拒绝旁路。
正是第 6 条从机制上解释了文档为何警告"不要放在反向代理之后使用":反代会在请求上注入 X-Forwarded-For 等头,本地旁路随之失效,攻击者即使能从外部打进来也无法借道"本地开发模式"免密访问。这套判定逻辑由 tests/server/auth-api-key.test.ts 中的用例逐条验证:localhost 无 Bearer 通过、未显式 opt-in 时返回 401、出现转发头时返回 401、方括号 IPv6 回环 Host 被接受、未设置 CLAUDE_MEM_AUTH_MODE 时默认走 API-key 认证。
旁路通过后,请求上下文被标记为 mode: 'local-dev'、scopes: ['local-dev'],与真实密钥路径的 AuthContext 结构(auth.ts#L13-L21)保持同构,下游路由无需感知认证来源。
API 密钥生成:cmem_ 前缀与一次性展示
文档指出 API 密钥以 cmem_ 前缀生成、仅展示一次。生成实现非常简洁,见 createRawServerApiKey:
export function createRawServerApiKey(): string {
return `cmem_${randomBytes(32).toString('base64url')}`;
}
即固定前缀 cmem_ 加 32 字节密码学随机数的 base64url 编码(约 43 字符熵载体),总熵约 256 bit。"仅展示一次"是密钥模型的自然结果——数据库里从不落盘明文,创建接口(createServerApiKey)返回的 CreatedServerApiKey 结构中,rawKey 与入库记录 record 分离,调用方只在创建瞬间拿到明文,之后任何途径都无法再从存储中还原。
密钥存储模型:从 SHA-256 到加盐 scrypt 的演进
security.md 对存储的描述是:"Claude-Mem 只把 SHA-256 哈希、前缀元数据、scopes、状态与时间戳存入 SQLite,不存明文。" 结合源码可以看到这一约定在实现上又向前演进了一步:
新密钥采用加盐、慢速、计时安全的 scrypt 派生(hashServerApiKey,对应内部工单 #2541):
const SCRYPT_COST = 16384; // N — memory-hard,必须是 2 的幂
const SCRYPT_KEYLEN = 64;
const SCRYPT_SALT_BYTES = 16;
export function hashServerApiKey(rawKey: string): string {
const salt = randomBytes(SCRYPT_SALT_BYTES);
const derived = scryptSync(rawKey, salt, SCRYPT_KEYLEN, { N: SCRYPT_COST });
return `scrypt$${SCRYPT_COST}$${salt.toString('hex')}$${derived.toString('hex')}`;
}
存储格式为 scrypt$<N>$<saltHex>$<derivedHex>:每把密钥独立 16 字节随机盐(抗彩虹表)、scrypt 的内存硬化特性(N=16384)抬高离线爆破成本、比较使用 crypto.timingSafeEqual 抗时序侧信道(safeEqualHex)。
遗留的无盐 SHA-256 路径被保留但只读:hashServerApiKeyLegacySha256 仅用于验证既有旧密钥,不再写入任何新键。verifyRawKeyAgainstStoredHash 通过 scrypt$ 前缀识别存储格式并分派到对应验证分支;当旧密钥被成功验证、明文在手中时,upgradeLegacyKeyHashIfNeeded 会透明地将其升级为 scrypt 格式——即旧密钥在首次使用时自动完成强哈希迁移。
SQLite 表结构与文档所述字段一一对应,见 api_keys 表映射:
| 列 | 说明 |
|---|---|
key_hash |
密钥哈希(scrypt 新格式或 SHA-256 旧格式) |
prefix |
明前 10 字符(rawKey.slice(0, 10)),非机密,用于候选收敛 |
scopes |
权限范围 JSON 数组 |
status |
active / revoked |
last_used_at_epoch / expires_at_epoch |
最近使用时间 / 过期时间(epoch 毫秒) |
created_at_epoch / updated_at_epoch |
创建与更新时间戳 |
创建与吊销操作还会写入审计日志(api_key.create / api_key.revoke,见 sqlite-api-key-service.ts、L233-L248)。
密钥校验链路:前缀收敛 + 常量时间比较 + 过期与 scope 检查
verifyServerApiKey 的完整校验顺序为:
- 候选收敛:加盐哈希不再具有"同一明文恒定哈希"的性质,因此无法直接按哈希查库。校验先用密钥前 10 位前缀(非机密)通过 listActiveApiKeysByPrefix 缩小候选集——本地密钥数量小且前缀区分度极高;
- 逐候选常量时间验证:对每个候选执行 scrypt(或 SHA-256)派生并用
timingSafeEqual比较; - 过期检查:
expiresAtEpoch存在且不晚于当前时间即拒绝; - scope 检查:hasRequiredScopes 要求授权 scope 覆盖所需 scope,
*通配符可覆盖一切(*管理员通配是显式 opt-in,默认不授予); - 副作用:命中后透明升级旧哈希、
markApiKeyUsed刷新last_used_at_epoch。
本地 SQLite 后端新建密钥的默认 scope 是 memories:read + memories:write(DEFAULT_LOCAL_API_KEY_SCOPES),因为本地 V1 路由按这两个 scope 分别门控读与写接口;此前"无显式 scope 即 []"会导致默认密钥静默无权访问任何路由,这一修复保证了默认密钥开箱即用。
队列与存储边界:BullMQ 模式下 Redis 只是传输层
security.md 最后一段给出 BullMQ 模式的两条约束:
- BullMQ 模式依赖 Redis 或 Valkey;队列 payload 被刻意限定为"恢复观察处理所需的最小工作",SQLite 仍然是规范(canonical)记忆存储——即使 Redis 数据丢失,也只是丢失待处理任务,不会丢失记忆本体;
- 可部署示例应启用 Redis 持久化,且避免在无认证的情况下把 server 端口暴露到公网。
这一边界与 CLAUDE_MEM_QUEUE_ENGINE 的默认值 sqlite(SettingsDefaultsManager)以及 CLAUDE_MEM_REDIS_URL / CLAUDE_MEM_REDIS_HOST 等 Redis 连接配置项相互印证:默认走 SQLite 队列,显式切到 BullMQ 时才需要外部 Redis/Valkey,且密钥认证(前文所述的 CLAUDE_MEM_AUTH_MODE=api-key 默认路径)是暴露网络边界的最后一道防线。
部署要点小结
把文档约定与源码证据对齐后,可以归纳出 claude-mem server 的部署安全清单:
- 生产环境保持默认
CLAUDE_MEM_AUTH_MODE=api-key,为客户端签发cmem_前缀密钥并以Authorization: Bearer传递,注意密钥只展示一次; - 本地开发如需免密,必须同时设置
CLAUDE_MEM_AUTH_MODE=local-dev与CLAUDE_MEM_ALLOW_LOCAL_DEV_BYPASS=1,且服务只绑定回环地址、不加任何反向代理(转发头会使旁路自动失效); - 公网部署绝不可跳过认证直接暴露端口;使用 BullMQ 时为 Redis/Valkey 开启持久化,并理解 SQLite 才是记忆数据的最终落点;
- 密钥治理依赖
api_keys表的expires_at_epoch、status与 scopes 列:定期轮换、吊销(revoked状态即时生效)、按需最小化 scope,避免无谓授予*通配。
以上每一条都能在 src/server/middleware/auth.ts、src/server/auth/sqlite-api-key-service.ts、src/storage/sqlite/auth.ts 与 tests/server/auth-api-key.test.ts 中找到对应实现与回归测试,可作为进一步审计与二次开发的入口。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00