TREK 插件管理完全指南:从信任模型到安装、更新与退出治理
2026/9/20 21:38:42 网站建设 项目流程

TREK 插件管理完全指南:从信任模型到安装、更新与退出治理

【免费下载链接】TREKA self-hosted travel/trip planner with real-time collaboration, interactive maps, PWA support, SSO, budgets, packing lists, and more.项目地址: https://gitcode.com/GitHub_Trending/nomad22/TREK

如果你要在生产环境管理 TREK 插件系统,最该先问的不是"怎么装",而是"这套隔离机制到底能信几分"。本篇以 TREK 插件安全为纲,沿"信任模型 → 准入 → 运行 → 变更 → 退出 → 问责"的完整生命周期,讲清楚 TREK 插件安装、权限再同意、签名密钥轮换、出口管控与卸载清理的每一步背后是什么机制、边界在哪里。文中机制描述均来自服务端 plugins/ 模块的源码实现,可直接对照验证。

先看地基:TREK 插件系统的信任模型

管理面板上的一切按钮都建立在一个前提上:你批准的权限清单是真实边界,而不是一行装饰文字。理解这一点,后续所有操作才有意义。

进程级隔离:插件能做什么、不能做什么

每个活动插件运行在独立的操作系统子进程里,由 Node 的权限模型(--permission)启动,文件系统读取被限定在它自己的代码目录内(plugin-supervisor.ts、plugin-sdk.ts)。具体边界如下:

  • 拿不到的东西JWT_SECRET、数据库连接、TREK 的任何机密——对插件进程物理不可达,不是"被拦截",是根本不在它的地址空间里。
  • 做不到的动作:打开trek.db、写文件、派生子进程、使用 worker 线程、加载原生模块,全部禁止。插件自己的数据放在独立的 SQLite 文件中,且只能通过 TREK 中转访问。
  • 唯一的通信通道是内部 RPC,且 TREK 只应答 manifest 中声明、并且你已批准的能力;未授予的调用会被拒绝,而不是静默忽略。
  • RPC 通道本身对插件代码是封死的:即便插件跑在 fork 出的进程里,其原始 IPC 原语(process.sendprocess.on('message'))在代码加载前就被吊销。插件既无法伪造宿主消息,也无法窃听其他在途请求,所有交互被迫经过能力校验的 SDK。
  • 插件的页面/组件 UI 跑在密封的浏览器 frame中,读不到会话 cookie,也碰不到外围 TREK 页面。
  • 崩溃、挂起或内存耗尽时,死的只是它自己的进程——TREK 继续运行,管理员可以重启或停用该插件。

徽章意味着什么:Reviewed、Signed、Unsigned

注册表卡片上的三类徽章,各自保证的范围必须分清楚:

徽章保证什么不保证什么
Reviewed(已审查)TREK 维护者对每一个版本做了恶意软件扫描不承诺质量、可用性,也不是"无害"担保
Signed(已签名)文件已对照作者的签名密钥校验,且密钥在安装时钉住(TOFU)签名只证明"字节来自作者",不证明作者意图良性
Unsigned(未签名)文件与注册表担保的字节一致(SHA-256 校验)没有任何机制把字节与作者绑定——少一层保证,但注册表里大多数插件尚未签名,故仅是提示而非警报

最坏情况是什么

权限清单界定的是插件能触及什么,不约束它在授权范围内的意图:一个被允许读取行程、又声明了某个出站主机的插件,完全可以把行程数据发往那个主机。这就是为什么 TREK 把"先读权限清单和出口主机,再点安装"当作硬性纪律,而不是可选项。

准入闸口:运行时总开关与管理员前提

所有插件管理操作有两道前置闸口,任一不满足就无法工作:

  • 管理员身份 + 运行时开启:面板与全部端点挂在@Controller('api/admin/plugins'),同时使用JwtAuthGuardAdminGuard(plugins.controller.ts)。运行时关闭时,installuploadactivateupdaterescan等操作统一返回503
  • 总开关是"每次调用实时读取"的:判定逻辑在 kill-switch.ts,只要TREK_PLUGINS_ENABLED的取值不是false0offno(大小写不敏感),系统即视为开启,默认开启。改环境变量并重启后立即生效,无需其他配置。

要整体关闭系统,只需:

environment: - TREK_PLUGINS_ENABLED=false

关键语义:关闭运行时是"休眠"而非"删除"——已安装插件保留在磁盘上、处于停用状态,无害地待命;重新打开后恢复可用。注意"运行时开启"与"某个插件在运行"是两回事:所有插件必须逐个手动激活,激活之前没有任何第三方代码执行。

如何从注册表安装 TREK 插件并完成预安装审查

安装路径有三条:注册表、上传旁路、直接放到磁盘(把插件目录丢进插件代码目录server/data/pluginsTREK_PLUGINS_DIR卷,再点Rescan或重启,即以 inactive 状态被发现并注册)。以注册表为主线展开:

  1. Discover视图浏览社区注册表。每张卡片展示图标、名称、作者、描述、类型、Reviewed / Signed / Unsigned 徽章、最新版本号与下载量。
  2. 点击卡片打开预安装审查对话框——安装前必须读完的四块内容:
    • 它能访问什么:manifest 声明的权限以平实语言逐条呈现;不请求任何权限的插件会明确显示Needs no special access.
    • 连接目标(egress):所有声明的可出站主机,等宽字体展示。
    • Setup:插件要求填写的配置项,标注 Instance-wide / Per user 与是否必填。
    • Details:版本、体积、所需 TREK 版本范围、审查时间、下载量。
  3. 点击Install

版本兼容是怎么卡住的

"能装最新版"不等于"能装某个旧版"。服务端在安装前用assertHostCompatible/hostSatisfies把插件声明的trek版本范围与当前宿主比对(host-compat.ts),且所有安装入口都过这道闸——显式指定版本、裸装最新版、带约束解析,任何路径都绕不过去。结果分三种:

  • 最新版兼容 → 正常Install
  • 最新版超前于当前 TREK,但存在仍兼容的旧版 → 按钮变为Install {version},安装那个旧版;
  • 没有任何版本兼容 → 按钮变为Incompatible并禁用,琥珀色提示条直接解释原因,而不是藏在 tooltip 里。

下载与落盘同样层层设防:SSRF 安全下载 → SHA-256 校验 → 作者签名校验(如有)→ 防 zip-slip/zip-bomb 的安全解压到 staging → 重新解析并校验归档内自带的 manifest(归档自身声明的trek范围才是权威兼容依据,注册表索引元数据只是下载前的廉价预筛)→ 原生二进制扫描 → 原子性移入代码目录 → 以inactive状态注册(registry.service.ts)。整个安装流程不执行任何插件代码——激活是独立动作。

注册表缓存与 Rescan 按钮

注册表索引缓存 30 分钟(CACHE_TTL),拉取的是聚合后的dist/index.json而非逐插件 GitHub API 调用;拉取失败时软降级(返回缓存或空列表),不影响面板其他功能。Rescan同时做两件事:重新发现磁盘上的本地插件,并强制绕过 30 分钟缓存与 GitHub CDN 边缘缓存(max-age=300)拉取注册表——这样刚发布的插件立即可见,而不是最长等约 35 分钟。

激活一个插件时会发生什么:级联、依赖与拒绝错误码

激活(行上的 Enable 开关)不是一个简单开关,它先跑一遍只读预检,再按依赖图顺序拉起进程(plugin-runtime.service.ts):

  • 预检是只读的,从最严重到最不严重依次检查:宿主版本兼容 → 权限再同意 → 必需 addon 是否启用 → 插件依赖是否满足。任何一项不满足都不会留下"半激活"状态。
  • 依赖优先排序:启用插件前会计算enableOrder,先拉起全部依赖再拉起目标;已安装但停用的依赖会被自动级联启用,并 toast 告知你哪些被顺带打开了。

三类激活拒绝,各有对应错误码与补救路径:

错误码含义补救
ADDON_DISABLED必需的 addon 处于关闭状态在 Admin → Addons 中开启后重试
DEPENDENCY_MISSING插件依赖缺失或版本不匹配对话框逐条列出依赖,一键 Download / Update(拉最新兼容版本后自动重试)
TREK_VERSION_INCOMPATIBLE/TREK_VERSION_UNKNOWN宿主版本不在插件声明范围内 / 插件未声明支持范围升级 TREK 或放弃该插件

此外还有两组级联语义需要记住:

  • addon 被关闭时,依赖它的插件及所有传递依赖它的插件被自动停用(deactivateForDisabledAddon)。
  • 停用一个被依赖的插件,所有依赖它的插件一并停用——插件不能在依赖缺失时继续运行(deactivateWithDependents,依赖方先停、依赖后停)。

出口管控:谁能连出去、私网边界与操作者主机

插件的出站能力由权限http:outbound:<host>逐主机声明,校验规则在 manifest 侧与 egress-policy.ts 中一致:不允许裸*、不允许整 TLD 通配(*.com)、不允许带协议前缀。除此之外还有一层SSRF 兜底:即便某个已声明主机解析到回环地址、私网段(10/8172.16/12192.168/16)、链路本地(含169.254.169.254云元数据 IP)、CGNAT 或组播地址,连接也会被拒绝——插件无法借此转向 TREK 自己的数据库主机或内网服务,IPv6 的 NAT64/6to4/Teredo 隧道写法也被展开后同样拦截。

操作者主机(operatorEgress):给"只有你知道地址的服务"开口子

自托管的 Gotify、ntfy 这类服务,manifest 在发布时不可能预知你的主机名。为此存在operatorEgress声明:

  • manifest 声明operatorEgress的插件,审查对话框会出现"+ hosts you add"提示;安装后在⋯ → Allowed hosts中逐个添加主机名。
  • 添加前插件行显示琥珀色Add allowed host芯片——此时它一个主机也到不了,不提示会像"静默故障";有主机后芯片变蓝并显示数量。
  • 保存后插件会重启。这不是体验折中:出口白名单在子进程初始化时安装一次、且明确拒绝二次init,运行中进程不可能热扩容白名单——唯一生效方式就是重新拉起子进程。
  • 三条硬边界:未声明operatorEgress的插件永远拿不到额外主机(安装时的同意仍是上限);只有管理员能添加主机,普通用户即便凭据是自己的也不行;删除某主机后插件立即失去该出口(同样伴随重启)。
  • 卸载时这些主机被无条件删除——否则后来者复用同一 id 会悄悄继承前人的出口权限。

什么时候需要放开私网出口

如果目标服务与 TREK 同机或同局域网(localhost192.168.x.x),必须再设TREK_PLUGIN_ALLOW_PRIVATE_EGRESS=on。默认策略是私网出口一律拒绝。要注意这个变量放宽的是所有已安装插件的私网出口策略,只有当你信任全部插件时才该开启。

变更治理:TREK 插件更新、权限再同意与签名密钥

当注册表存在更新的版本时,插件行出现Update → v{version},列表上方汇总提示条与Update all批量按钮。更新流程的核心承诺是:更新永远不会静默扩大权限

update()的流程是:先安装新代码(失败时运行中的旧子进程原样保留,继续以内存中的旧代码服务)→ 对比新版本的声明权限与已授予权限的差集newGrants→ 无新增则透明重启到新代码;有任何新增(权限或出站主机)则保持插件关闭并返回差集,由管理员在对话框中选择"同意并开启"或"暂不开启"。批量更新时这些同意提示排队依次出现,一个都不跳过。

更新挑的是"能跑的最新版",不是"最新版"

resolveUpdateTarget会挑选当前 TREK 能运行的最新版本。盲取最新版是典型的更新事故:新版放弃了对当前宿主的支持,替换成功后激活闸又拒绝拉起——插件反而比不更新更糟。若算出的"目标版本"并不比已装版本更新(即只会构成降级),更新直接以NO_COMPATIBLE_UPDATE拒绝。

签名密钥轮换:唯一可覆盖的更新阻塞

作者签名密钥与安装时钉住的密钥不匹配时,更新被拒绝,插件行显示Update blocked — {reason}Review入口,对话框并排展示钉住密钥与新密钥的指纹。四种签名失败状态中,只有SIGNATURE_KEY_CHANGED(密钥变更)存在覆盖路径

  • SIGNATURE_MISSING(曾有签名现在没有)、SIGNATURE_INCOMPLETE(密钥与签名只存在其一)、SIGNATURE_INVALID(签名校验不过)——只有解释,没有任何覆盖按钮,服务端同样拒绝(signature-status.ts)。
  • 覆盖由POST :id/retrust端点承担(plugins.controller.ts),且强制三重条件:仅当错误码是SIGNATURE_KEY_CHANGED;调用方必须回显对话框中展示的完整公钥(防止对话框渲染后注册表条目又被换一次钥,管理员批准了自己没见过的密钥);重信任与更新在同一次调用内完成——要么新密钥通过校验、插件落到新版本并钉住新密钥,要么什么都不变,杜绝"钉住未验证密钥"的中间窗口。即便被管理员背书的新密钥,也必须真实签过这份产物才能落盘,且密钥钉只会被替换、永不清空为 NULL。

旁路加载:上传插件与本地开发链接

两条绕过注册表的路径,都以"没有注册表条目担保"为代价,换来更大的来源自由度。

Upload plugin:50MB 上传旁路

工具栏的Upload按钮或直接拖拽.zip/.tar.gz到面板。服务端sideload()的执行顺序是:解压到 staging 并执行与注册表安装相同的硬性防护(防 slip/bomb 安全解压、严格 manifest 校验、拒绝原生二进制)——仅 SHA-256/签名校验不适用,因为没有注册表条目可比对。上传上限50 MB(与 SDKpack的打包上限一致,另留 4KB 压缩开销)。

两个安全语义值得注意:

  • 新归档保持 inactive注册,标记为SideloadedUploaded manually — not from the registry, unsigned and unreviewed),无自动更新、无 Source repository 链接。激活仍需显式同意权限。
  • 用相同 id 覆盖上传时,旧代码先被强制停止并停用,替换后的代码绝不可能是"未经重新激活仍在运行"的状态。

Link a local plugin:仅限开发的本地链接

一个路径输入框,从本地构建目录注册插件并对真实数据热重载。入口仅在TREK_PLUGINS_DEV_LINK=1(恰好为1,其他任何值都视为关闭)时出现,且叠加管理员与总开关闸口(dev-link.ts)。实现要点:

  • 对插件代码目录创建符号链接而非复制,校验 manifest、拒绝原生二进制,注册为 inactive;已存在同 id 的正式安装会被明确拒绝,不会覆盖真实插件。
  • 通过fs.watch监听server/构建输出,重建后防抖 400ms自动重新 fork;manifest 若放宽了权限,热重载同样走权限再同意,不能借开发通道偷权限。
  • 行标记为Dev-Link

对自己诚实:Sideloaded 与 Dev-Link 都只是卡片上的来源标签——它们不做额外检查、不同沙箱、不受额外限制,以声明的权限原样运行,且没有经过恶意软件扫描与签名。徽章的意义只有一句话:除了你,没有人为这份代码背书。

退出治理:卸载插件会清理什么

⋯ → Delete的确认文案很直白:停止插件、移除代码、删除其全部数据,不可撤销。uninstall的清理分无条件项与按选项删除项(plugin-runtime.service.ts):

无条件清理(无论是否保留数据):

  • 停止子进程、移除代码目录(dev-link 只解符号链接,绝不删进作者源码);
  • plugins注册表行、设置字段、设置页按钮(plugin_actions);
  • 出口主机与定时任务——理由相同:留着会让后续复用同一 id 的插件悄悄继承前人的出口权限,或让已不存在插件的定时回调被触发;
  • 若插件曾作为通知渠道,一并退役该渠道(清掉用户按事件的退订记录),避免复用 id 时继承旧配置。

仅当 deleteData=true 时额外删除:

  • 插件自己的数据目录(私有 SQLite)与错误日志;
  • 实例级设置(plugin:{id}:%键)、实体元数据;
  • 每用户配置(含加密的 API 密钥)、OAuth 令牌与状态、迁移台账、能力审计日志、待处理的 GDPR 用户数据擦除队列——最后这几项若遗漏,同 id 插件重装时会静默"收养"这些残留。

保留数据(deleteData=false)时的一个反直觉点:待处理的用户数据擦除队列刻意保留——数据目录还在,被删用户的行可能仍在里面,擦除义务必须随之存续;同 id 插件重装后会继续兑现这些队列条目。

问责:权限边界内的操作如何被审计

TREK 的插件审计基于哈希链(防篡改),有两个视图,分工明确:

  • 用户侧(不设管理员门槛):每个用户在Settings → Plugins的活动日志中,能看到插件以其名义执行的全部宿主中介操作——读取了哪些行程/费用、写入了哪些地点、TREK 代发的每一次出站调用。这是问责的关键设计:即使插件持有的是较宽的读取授权,读取行为也对其数据的所有者透明。
  • 管理员侧(Admin → Plugins):按插件维度的视图,配合每插件的View error log(崩溃/失败请求日志,每个插件保留最近 500 行、管理端展示最新 200 行)。

审计之外还有限流与配额兜底,防止单个插件拖垮宿主:RPC 调用有每秒速率与突发额度(TREK_PLUGIN_RPC_PER_SEC默认 20、TREK_PLUGIN_RPC_BURST默认 60、并发上限 16),出站内存有每插件上限(TREK_PLUGIN_MAX_RSS_MB默认 300MB,超限即停进程),AI 与通知代理按插件有每日配额。

配置速查:TREK 插件相关环境变量一览

完整参考见 Environment-Variables.md,与日常治理相关的主要项:

变量作用默认值
TREK_PLUGINS_ENABLED插件系统总开关;取值非false/0/off/no即开启开启
TREK_PLUGINS_DEV_LINK开发链接(本地目录注册 + 热重载)开关,仅值为1时生效关闭
TREK_PLUGIN_ALLOW_PRIVATE_EGRESS设为on时允许插件出口解析到私网/内网地址(放宽全部插件)关闭(默认拒绝私网出口)
TREK_PLUGIN_REGISTRY_URL覆盖 Discover 页浏览的注册表索引地址(可指向自建镜像)TREK 官方注册表
TREK_PLUGINS_DIR/TREK_PLUGINS_DATA_DIR插件代码目录 / 插件私有数据目录(建议都挂持久卷)<data>/plugins/<data>/plugins-data
TREK_PLUGIN_PERMISSIONS设为off可关闭 OS 级权限沙箱(不推荐)on
TREK_PLUGIN_MAX_RSS_MB单插件进程内存上限(MB)300
TREK_PLUGIN_AI_PER_DAY/TREK_PLUGIN_NOTIFY_PER_DAY单插件每日 AI / 通知代理调用上限,0禁用200 / 100
TREK_PLUGIN_RPC_PER_SEC/TREK_PLUGIN_RPC_BURST/TREK_PLUGIN_RPC_INFLIGHT插件 RPC 速率/突发/并发20 / 60 / 16
TREK_PLUGIN_AUDIT_MAX_ROWS单插件审计日志保留行数,0禁用清理20000

延伸阅读

  • Plugins.md — 插件系统全貌:类型、隔离模型、依赖、活动日志
  • Plugin-Permissions.md — 每条权限的确切授予范围与http:outbound细节
  • Plugin-Development.md — SDK 与 manifest 编写
  • Plugin-Publishing.md — 注册表提交流程与trek-pluginCLI
  • Admin-Addons.md — 插件可能依赖的 addon 管理
  • Security-Hardening.md — 安全加固建议

【免费下载链接】TREKA self-hosted travel/trip planner with real-time collaboration, interactive maps, PWA support, SSO, budgets, packing lists, and more.项目地址: https://gitcode.com/GitHub_Trending/nomad22/TREK

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询