Hoppscotch 安全架构解析:威胁模型、安全控制机制与源码级实现验证
本文为 Hoppscotch 官方安全策略(SECURITY.md)的深入解读,覆盖桌面端、Hoppscotch Agent 与自托管实例三类部署形态的威胁模型,并结合仓库源码验证包签名校验、脚本沙箱、限流与 GraphQL 复杂度限制等核心安全控制的实际实现。读完后你将能准确判断哪些行为属于设计意图而非漏洞,并掌握自托管部署时必须配置的安全参数。
策略范围与安全边界
官方安全策略覆盖 hoppscotch 仓库 中的以下组件:
- 桌面端(Desktop app):基于 Tauri 的桌面客户端,包括独立使用以及连接自托管或云实例的场景;
- Hoppscotch Agent:运行在用户本机的本地中继服务,负责代理 Web 客户端发出的请求;
- Hoppscotch CLI:用于运行集合与测试的命令行客户端,可面向本地集合文件或实例;
- 自托管后端:由自托管组织部署的 Node.js 后端、PostgreSQL 数据层及相关服务;
- 自托管 Web 客户端与管理面板:自托管实例提供的前端界面与管理员仪表盘。
不在范围内(属于独立安全边界):
- hoppscotch.io 云平台与 hoppscotch.com 官网;若发现跨边界漏洞,仍然应当报告,官方会跨边界协调分诊;
- Hoppscotch 浏览器扩展(独立仓库与分发渠道);
- 第三方代理或社区分支(fork)。
架构与威胁模型
Hoppscotch 是一个客户端侧的 API 开发与测试工具,不同部署形态的威胁模型存在本质差异。
桌面端:用户即操作者
用户就是操作者(operator)。 发送 API 请求的人,正是配置了该工具、输入凭据的同一个人。这与"不受信用户向共享后端提交输入"的多租户 Web 服务有根本区别。由此衍生出一系列被文档明确认定为设计意图的行为:
- 本地存储是有意为之。 独立模式下,桌面端会把集合、环境、请求历史与凭据(含令牌、API 密钥等敏感数据)持久化到本地存储,依赖操作系统级访问控制,以及在启用时的全盘加密(FileVault、BitLocker、LUKS)保护。
- 标记为 secret 的环境变量只保存在本地、绝不与服务器同步,其安全姿态与其他本地凭据一致。
- 中继可访问用户指定的任意 URL(包括 localhost、内网 IP 段、云元数据端点);实时功能(WebSocket、SSE、Socket.IO、MQTT)同样允许连接用户指定的端点——连接由用户发起并受用户控制。
- 按域名粒度的 TLS 配置由用户掌控:支持自定义 CA 证书、客户端证书(PEM 与 PKCS#12),并可针对特定域名关闭主机/对端校验,用于配合自签名证书或企业 PKI 环境。
- 默认开启 debug 级别日志,写入滚动本地日志文件,日志文件与应用数据存放在同一应用数据目录中。
- 自动更新经过签名校验:更新清单在与公钥校验通过前不会应用任何二进制,且该检查为只读,不上传用户数据。
- 版本变更时创建本地备份(最多保留 3 份),备份与主数据遵循同样的安全姿态。
Hoppscotch Agent:localhost 中继服务
Agent 是独立的本地服务,为 Web 客户端提供浏览器沙箱所限制的能力(自定义请求头、localhost 访问、客户端证书、绕过 CORS)。其关键安全设计为:
- 绑定 localhost:9119,CORS 策略宽松:任何来源在网络层面都能触达该端口,访问控制下沉到应用层的注册握手——用户在 Agent UI 中输入 6 位一次性密码(OTP)建立加密通信通道(X25519 密钥交换 + AES-256-GCM)。OTP 不设过期时间、注册尝试也不做速率限制,安全假设是"用户在 Agent UI 可见时有意发起注册"。
- 与桌面端相同的中继信任模型:向用户指定的任意 URL 发请求,TLS、代理、证书配置按域名由用户控制。
- 数据本地存储:注册密钥、按域名的设置(代理、客户端/CA 证书、TLS 校验开关)与日志,均遵循桌面端同样的本地数据安全姿态。
这一描述与源码完全一致。Agent 的 HTTP 服务在 server.rs 中实现:
let cors = CorsLayer::permissive();
let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 9119));
可以看到服务确实绑定 127.0.0.1:9119,并挂载了 CorsLayer::permissive() 的宽松 CORS 层,与文档"网络层开放、应用层鉴权"的威胁模型相互印证。
自托管实例:管理员是操作者,用户是租户
部署后端后安全模型发生变化:
- 实例管理员是操作者、用户是租户。实例支持多用户、团队、基于角色的访问控制(RBAC)与共享集合,认证与授权边界必须在用户之间严格成立。
- 数据同时存储在服务端与本地。集合、环境、请求历史与团队数据持久化在 PostgreSQL;连接自托管后端的桌面端用户还保留一份本地副本。自托管组织负责数据库加密、备份安全与访问控制。
- 集合可通过公共 URL 发布:以 UUID 形式生成的 slug 发布的文档无需认证即可公开访问,是否发布由自托管组织控制。
- 管理面板拥有更高权限:实例管理员可查看和管理所有用户、发送邀请、配置全实例设置,管理操作虽受角色检查约束,但作用域覆盖全部团队与用户。
- 基础设施令牌(infra token)提供程序化访问,支持配置过期时间,应像管理凭据一样谨慎保管。
- 会话管理:用户会话使用可配置的 Cookie 名称与自动生成的会话密钥;生产部署必须显式设置
INFRA.SESSION_SECRET,自动生成的值不适合生产使用。 - 可选的分析遥测:仅当
INFRA.ALLOW_ANALYTICS_COLLECTION开启时,后端才向 PostHog 发送聚合遥测(用户数、工作区数、版本)。默认关闭,且不包含任何请求内容、凭据或按用户划分的数据。
后端 main.ts 中的 CORS 策略也呼应了这一边界划分:生产模式按 WHITELISTED_ORIGINS 白名单精确放行来源,开发模式才开启 origin: true;同时全局挂载 ValidationPipe(whitelist + forbidNonWhitelisted)做输入净化。
核心安全控制机制(含源码验证)
包签名校验:Ed25519 + BLAKE3
用户添加自托管实例时,桌面端会下载该实例编译好的 Web 应用包(HTML、JS、CSS)并在内嵌 webview 中运行。远端包在加载前经过 Ed25519 签名与逐文件 BLAKE3 完整性哈希校验,签名无效或哈希不匹配的包会被拒绝加载。相关实现在 tauri-plugin-appload 的校验模块 中。
文档同时明确了该机制的保护范围边界,这是理解其价值的关键:
- 签名密钥通过实例连接从提供实例处获取(强烈建议使用 TLS/HTTPS);
- 它能防住:本地缓存中的包被损坏、可信连接下的传输篡改;
- 它不能防住:已被攻陷的实例(实例同时提供密钥与包)、在非可信传输上获取密钥时的主动中间人攻击(攻击者可同时替换两者)。
- 因此信任边界是"用户与实例之间的连接"以及"用户主动添加该实例"这一信任决策——与安装浏览器扩展类似。
脚本沙箱:QuickJS WASM 为默认
预请求/后置请求脚本与宿主环境隔离。默认在所有平台运行于 QuickJS WebAssembly 沙箱(由 hoppscotch-js-sandbox 包实现),与浏览器上下文、Tauri IPC 层(桌面端)及宿主操作系统隔离:
- 脚本通过
pw、hopp、pmAPI 命名空间受控访问请求数据; - 网络访问经受控的 fetch 钩子中介,脚本无法发起任意系统调用或访问文件系统;
- 从外部集合文件导入的脚本与本地编写的脚本遵循完全相同的默认/传统执行路径与约束。
退出到传统兼容模式(legacy mode)的机制按平台区分:
| 平台 | 开关方式 | 传统模式实现 |
|---|---|---|
| 桌面端 / Web | 设置中的 "Experimental scripting sandbox" 开关(默认开启) | 专用 Web Worker + Function 构造器,隔离弱于 QuickJS,且只暴露 pw 命名空间,不中介 fetch 等 Worker 全局 |
| CLI | --legacy-sandbox 标志 |
isolated-vm V8 隔离区,具备 V8 隔离级保障,但 API 面与 QuickJS 路径不同 |
传统模式是为依赖 QuickJS 未暴露的宿主 JS 语义的脚本保留的向后兼容路径。
更新签名校验
桌面端自动更新器在应用任何更新前,将更新清单与公钥做校验,被篡改的清单或二进制会被拒绝。更新检查为只读,不传输用户数据、凭据或使用信息。
请求限流:INFRA.RATE_LIMIT_TTL 与 INFRA.RATE_LIMIT_MAX
自托管后端通过 @nestjs/throttler 对 REST 与 GraphQL 端点实施限流。app.module.ts 中的装配方式:
ThrottlerModule.forRootAsync({
inject: [ConfigService],
useFactory: async (configService: ConfigService) => [
{
ttl: +configService.get('INFRA.RATE_LIMIT_TTL'),
limit: +configService.get('INFRA.RATE_LIMIT_MAX'),
},
],
}),
GraphQL 侧由 GqlThrottlerGuard 承接,其 getTracker 方法优先取 req.ips[0](适配反向代理后的真实客户端 IP)、否则回退 req.ip,实现按 IP 的限流计数。默认阈值定义在 infra-config/helper.ts:
name: InfraConfigEnum.RATE_LIMIT_MAX,
value: '100', // 100 requests per IP per RATE_LIMIT_TTL
即默认 每个 IP 在每个 TTL 窗口内最多 100 次请求,两个值均可通过环境变量覆盖。需要注意文档的补充说明:部分已认证变更(mutations)会有意退出限流——在限流会干扰正常交互使用的场景下,且这些退出项限定在已认证会话内。
GraphQL 查询复杂度限制
为防止深度嵌套或高开销查询造成拒绝服务,后端在 GQLComplexityPlugin 中基于 graphql-query-complexity 库实施复杂度上限:
const COMPLEXITY_LIMIT = 50;
// ...
if (complexity > COMPLEXITY_LIMIT) {
throw new GraphQLError(
`Query is too complex: ${complexity}. Maximum allowed complexity: ${COMPLEXITY_LIMIT}`,
);
}
细节上,内省字段(__ 前缀)被定制估算器计为 0 复杂度,普通字段按 simpleEstimator 默认复杂度 1 累加,并叠加 fieldExtensionsEstimator 支持字段级覆盖。超过 50 的查询会在 didResolveOperation 阶段被直接拒绝。
报告安全漏洞的正确姿势
官方通过 GitHub Security Advisories(GHSA)管理漏洞报告;未收到响应时可通过 support@hoppscotch.io 并附 GHSA 链接跟进。对评估有异议时,直接在 advisory 中回复补充上下文或证据,官方会重新评估。跨仓库问题(如 UI 组件的 XSS 可能归属 @hoppscotch/ui)如不确定,直接提交到 hoppscotch/hoppscotch 的 GHSA 即可。不要创建 GitHub issue 报告安全漏洞。
报告的前提要求很明确:必须证明你理解本文档描述的架构与威胁模型。把本文档已明确记录为设计意图的行为再次标记为漏洞,或套用通用漏洞分类(SSRF、不安全存储、CORS 配置不当等)却不解释该发现如何绕过所述信任模型的报告,会被直接关闭——这一要求同样适用于 AI 工具、LLM 或自动扫描器生成的报告。
哪些不算漏洞(按设计意图与范围分类)
官方文档给出了详尽的"非漏洞"清单,这是与通用漏洞扫描结果对话时最重要的参照。
桌面端与 Agent 的有意行为:
- 中继/Agent 向内网 IP 段、localhost 或云元数据端点发送请求——这是产品的核心功能;
- 凭据、令牌、API 密钥存储于用户本机的本地存储、应用数据目录、本地日志或本地备份——本地数据受操作系统级访问控制保护;
- 含请求详情(含头部与认证数据)的 debug 级日志输出——同样数据本就存在于本地数据目录中;
- 通过签名校验后的自托管实例包可访问桌面端应用数据——添加实例本身即一次显式信任决策;
- 用户针对特定域名关闭 TLS 主机/对端校验——面向自签名/内部证书的操作员级按域名设置;
- WebSocket、SSE、Socket.IO、MQTT 连接触达用户指定的内网端点——与 HTTP 中继同一信任模型的独立实时功能;
- 导入集合中的预/后置请求脚本在沙箱中执行——沙箱对导入与本地脚本一视同仁;
- 桌面端向 releases.hoppscotch.com 检查更新——不传输用户数据,清单经签名校验;
- Agent 在 localhost:9119 接受任意来源连接——CORS 宽松是设计,访问控制由注册握手与加密通道承担;
- Agent 注册 OTP 无过期、注册尝试不限速——它不是远程认证端点,因此不构成 CWE-307(认证尝试限制不当),安全假设是用户在可见的 Agent UI 上有意发起;
- 需要"事先已获得用户本机访问权限"的假想攻击——攻击者本就可以通过操作系统访问同样的数据。
自托管的有意行为:
- 首次初始化(bootstrap)前配置端点可无认证访问——初始引导阶段刻意未认证,管理员创建后即门控;这不是 CWE-306。对已完成初始化的实例,bootstrap 相关问题属于另一类应当报告的问题;
- 已发布集合经公共 URL 免认证访问——发布是操作员的显式动作,不是 CWE-284;
- 未设置
INFRA.SESSION_SECRET时自动生成会话密钥——威胁模型已注明该值不适合生产,自托管组织负责显式配置; - 部分已认证 GraphQL 变更退出限流——有意为之且限定于已认证会话。
超出范围的发现:
- 依赖库中无已证实针对 Hoppscotch 的实际攻击路径的漏洞;
- 未对照本威胁模型验证过的自动扫描输出、AI 生成的报告或通用安全评估——报告必须指出"哪一项具体安全控制在本上下文中缺失或可被绕过",而不是标记与已知漏洞类匹配的代码模式;
- 对本文档已解释为有意行为套用通用分类:向用户指定的内网 IP 发请求是产品核心功能而非 CWE-918(SSRF);把凭据存在用户自己的机器上是单用户开发者工具的预期数据模型而非 CWE-312;localhost 服务上带应用层鉴权的宽松 CORS 策略不是 CWE-942;
- 自托管 Web 客户端缺少 HTTP 安全头(CSP、HSTS、X-Frame-Options)且无已证实"该头能阻止的具体攻击"的发现;
- 针对 hoppscotch.io / hoppscotch.com 的发现需走平台自身的安全渠道;跨"自托管组件 + 云平台"边界的报告可以在此提交并会得到协调。
小结
Hoppscotch 的安全文档价值在于它没有罗列通用检查清单,而是先按部署形态建立信任模型(谁发起、谁受控、数据落在哪),再逐项解释每一项"看似漏洞"的行为为何是设计意图,并配套可验证的工程控制:Agent 的 127.0.0.1:9119 + 应用层注册握手(server.rs)、Ed25519/BLAKE3 包签名、QuickJS WASM 脚本沙箱、Throttler 限流(app.module.ts)与 50 复杂度的 GraphQL 上限(GQLComplexityPlugin.ts)。对自托管运维者,最直接的行动项是:生产环境显式设置 INFRA.SESSION_SECRET、按流量调整 INFRA.RATE_LIMIT_TTL / INFRA.RATE_LIMIT_MAX、通过 WHITELISTED_ORIGINS 收紧 CORS,并妥善保管 infra token 与已发布集合的范围。
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 StartedRust0622
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