团队知识共享有两条边界:哪些内容被批准离开个人机器,哪些主体可以对共享实例执行哪些操作。ChatCrystal 的云模式已经支持令牌和远端归一化导入,但固定提交没有为每位成员建立独立账号或项目级读写角色。把一个共享 token 发给所有人,不等于实现权限隔离。
当前云模式的真实能力
server/src/runtime/cloud.ts 通过 CHATCRYSTAL_CLOUD_MODE=true 或非空 CHATCRYSTAL_API_TOKEN 判定云模式。server/src/routes/setup.ts 为受保护 API 路由安装 Bearer token 检查;health、setup 状态/完成和 auth verify 属于公开例外,setup、verify 与 token rotation 另有各自校验。rotate 路由用请求体的 currentToken 验证,不能概括为所有路由只检查 Bearer。server/src/services/auth.ts 支持环境令牌或 scrypt 保存的单令牌及失败尝试控制;API 只轮换持久化令牌,环境令牌要在部署环境更改。这不是多用户账号、逐成员撤销或 RBAC。
server/src/routes/import.ts 在云模式禁用服务端本地扫描,开放 ingest 路径;server/src/cli/commands/import.ts 从有源历史的设备解析并上传归一化载荷,非交互需明确 –yes。server/src/runtime/watcherPolicy.ts 云模式不启动 watcher。因此“共享主机自动扫描所有人的桌面文件”和“只有单人本地导入,没有远端入口”都不是当前行为。多个客户端可以提交,不表示同时导入压力、隔离或冲突恢复已经实测。
MCP 的七工具通过客户端使用同一 API 连接,见MCP 协议边界。一套工具名称不会证明接的是哪个团队数据集,也不会替共享网关制定成员权限。
选择共享形态前明确信任范围
| 形态 | 保留的边界 | 需要额外解决 |
|---|---|---|
| 个人库与经审查的可读导出 | 私人历史先留在本人设备 | 导出版本、脱敏、来源和更新记录 |
| 一个共同信任域内的共享实例 | 当前单令牌访问 | 所有持令牌者的能力范围、轮换影响、并发恢复 |
| 身份网关后的共享实例 | 可在外围制定逐人/逐角色规则 | 路由与方法完整验证、阻断直连、网关维护和审计 |
| 不同信任域分别部署 | 数据与凭据分开 | 多实例更新与检索策略 |
这些是部署方案,不是当前 UI 内置团队功能,也没有人数或内存推荐实测。tag、project_key 和 scope 是检索或知识语义,不是 token 的权限分区。手动全局知识和自动项目知识的区别,不能用来承诺某成员无法读另一个项目。
共享前先审查内容,读权限按操作定义
源对话可能包含路径、代码、凭据或未审定结论。共享流程应先得到数据所有者批准,标记可共享范围、脱敏、记录原始来源,再上传;自动摘要不能被当作自动脱敏保证。服务端 ingest 是数据入口,命令确认不是组织审批机制。
只允许 GET 会阻断 POST /api/memory/recall 这类读性质的操作,又可能放行带敏感配置的 GET。角色规则必须按准确方法和路径设计,再测实际客户端功能;同时从网络层禁止绕过网关直连持共享令牌的后端。下面是拟议网关策略和整批审查的合成验收,不是 ChatCrystal 的鉴权实现。角色作为参数传入,没有身份认证、HTTP 代理、真实脱敏器、真实导入或后端。
用 Node 24 保存为 team.mjs,运行 node team.mjs。
import assert from 'node:assert/strict';
// Proposed gateway policy; this is not ChatCrystal's token hook.
const rules = new Map([
['reader', new Set(['POST /api/memory/recall', 'GET /api/notes'])],
['importer', new Set(['POST /api/import/ingest'])],
]);
function authorize(role, method, rawPath, target, approvedTarget) {
assert.equal(target, approvedTarget);
const url = new URL(rawPath, 'https://gateway.invalid');
if (url.pathname.includes('%') || url.pathname.includes('..')) throw new Error('ambiguous path');
if (!rules.get(role)?.has(`${method} ${url.pathname}`)) throw new Error('denied');
}
const target = 'reviewed-instance-a';
let stored = [];
function ingest(role, items) {
authorize(role, 'POST', '/api/import/ingest', target, target);
for (const item of items) {
if (item.approval !== 'approved' || item.classification !== 'team-shareable')
throw new Error('unapproved input');
if (/secret|password|token/i.test(item.content)) throw new Error('redaction required');
}
stored.push(...items.map(({ id, content }) => ({ id, content })));
}
authorize('reader', 'POST', '/api/memory/recall', target, target);
assert.throws(() => authorize('reader', 'POST', '/api/import/ingest', target, target), /denied/);
assert.throws(() => authorize('reader', 'GET', '/api/notes', 'other-instance', target));
assert.throws(() => authorize('reader', 'DELETE', '/api/notes/1', target, target), /denied/);
assert.throws(() => authorize('reader', 'POST', '/api/queue/cancel', target, target), /denied/);
assert.throws(() => authorize('reader', 'GET', '/api/config', target, target), /denied/);
assert.throws(() => authorize('unknown', 'GET', '/api/notes', target, target), /denied/);
const approved = {
id: 'reviewed-1',
content: 'use a scoped transaction',
approval: 'approved',
classification: 'team-shareable',
};
ingest('importer', [approved]);
assert.equal(stored.length, 1);
assert.throws(
() => ingest('importer', [approved, { ...approved, id: 'private-2', approval: 'pending' }]),
/unapproved/
);
assert.equal(stored.length, 1);
assert.throws(
() => ingest('importer', [{ ...approved, content: 'password=example' }]),
/redaction/
);
assert.equal(stored.length, 1);
console.log('proposed gateway roles and whole-batch review gate pass on fixtures');
权限与审批验收必须有拒绝样本
删除规则匹配保护,reader 的 ingest 拒绝断言应失败;把 stored.push 移到所有输入审查之前,包含 pending 内容的批次会污染合成存储,数量断言应失败。恢复后运行。关键词检查只证明样本被拦截,不是可靠脱敏或敏感信息检测;URL 和方法规则也不是完整代理安全实现。
上线共享方案要分别测缺/错 token、轮换后旧 token、reader 的 recall 与禁止写入、未批准载荷、错误目标实例、绕过网关直连、恢复后检索。当前 server/src/services/memory/writeback.ts 的写回 receipt 与 server/src/services/experience/reviews.ts 的质量反馈记录有业务用途,但不能当作完整逐成员操作审计;共享 token 也不能可靠归因到个人。身份、审计和内容审批若由外围系统承担,应把负责人和保留周期写入部署约定。
同步与备份不靠复制活动数据库
不要把多人桌面同时指向同一活动 chatcrystal.db,也不要用文件同步覆盖运行中的库。当前 sql.js 在内存中操作并导出,vectra-index 又是独立数据集,详见WASM 持久化和向量索引内部边界。停止所有写入者或取得一致性快照,再复制约定的数据目录并在隔离环境恢复验证。读取源工具的活动 SQLite 还需要主库/WAL 一致性条件,不能靠复制一个 state.vscdb 宣布备份完整。
共享不是把所有历史集中存放。先对相同任务测试 approved 内容的导入、来源追溯、检索、拒绝与恢复,再决定哪些知识需要汇入共同库。自动化步骤见CLI 知识流水线。