23 KiB
许可证链路安全评估
历史快照提示:本文是 2026-03-12 的安全评估和后续局部复核记录,不代表当前代码、部署脚本和许可证服务的完整最新状态。当前接入和部署以
docs/许可证服务接入说明.md、docs/deployment/部署说明.md、docker-compose.prod.yml和实际代码为准。
评估日期:2026-03-12
复核更新:2026-05-13。本文件仍是安全评估快照,不是操作入口;执行整改前必须按当前代码、部署文件和实际环境复核。
1. 评估范围
本次评估基于当前仓库代码静态核对,重点覆盖以下链路:
- 主应用本地许可证缓存、激活、验证、心跳
- 主应用启动初始化、默认管理员创建
license-server的管理员认证、发证、验证、心跳、免费注册、API Keyflnxi-agent与flnxi-agent-go的安装、更新、容器控制能力
不包含:
- 线上服务器真实配置核验
- 第三方网络链路抓包
- 动态渗透测试
2. 总体结论
当前这套方案可以拦住普通误用和低水平滥用,但还不能算“硬防破解”。
核心问题不是单一算法不安全,而是:
- 本地可控环境里承载了过多关键判断
- 高权限 Agent 控制面暴露过大
- 初始化和默认配置偏宽松
license-server虽已完成项目隔离主链路,但仍有少量历史壳路由和文档口径需要继续清理
简单评价:
- 防普通误用:中等偏上
- 防低水平破解:中等
- 防有经验的定向破解:偏弱
- 运维默认安全性:偏弱
2.1 整改状态更新(截至 2026-03-12)
本轮已完成的收口:
- 已补齐已确认的许可证执法漏口:部门相关
POST / PATCH / DELETE与自定义模板PUT / DELETE / migrate已补许可证校验 - 已收紧本地许可证缓存信任边界:本地读取统一改为“解密 -> 解析签名许可证 -> 验签 -> 从签名原文恢复配置”
- 已修正
/api/license/check:不再只凭本地缓存结构恢复激活,而是先走本地许可证验签 - 已给
flnxi-agent与flnxi-agent-go增加基础管理认证;Node 版同时补了安全头和 Socket Origin 校验 - 已把 Agent 认证从“可选启用”收紧到“默认关闭管理面”:未配置
AGENT_AUTH_USERNAME/AGENT_AUTH_PASSWORD时,仅保留/api/health - 已收紧系统初始化入口:
/api/system/init现在在“无管理员”场景下要求SYSTEM_INIT_TOKEN,有管理员后只允许管理员执行 - 已取消固定初始口令:不再内置
88888888,改为“环境变量提供或运行时生成一次性密码”,并在首次登录后强制改密 - 已去掉主应用与 Node Agent 对许可证服务的明文 HTTP 默认回退,默认改为
https://license.flnxi.com - 已把主应用和 Node Agent 接到项目维度公开接口:通过
LICENSE_PROJECT_CODE命中license-server的/api/license/{projectCode}/...与/api/version/{projectCode}/... - 已收紧 Agent 安装链路里的内部凭据:Node / Go 两个 Agent 在安装时都会为 PostgreSQL、MinIO、
NEXTAUTH_SECRET生成随机值,不再写死弱口令 - 已收紧
license-server部署默认值:docker-compose.yml改为强制读取关键环境变量,deploy.ps1在缺少.env时会生成随机密钥和一次性管理员初始密码 - 已把 Go Agent 一键安装脚本和 Docker 示例改为默认只发布到
127.0.0.1,需要局域网直连时必须显式放开 - 已完成
license-server后台管理链路的项目隔离:管理员后台真实读写已切到/api/admin/{projectCode}/... - 已完成
license-server公开验证链路的项目隔离:激活、验证、心跳、免费注册、发证、版本检查都已有项目维度入口 - 旧的无项目后台接口和无项目公开接口已改为
410或跳转,不再默认落到某个项目库 - API Key 已收口到“超管 + 项目维度”路径:
/api/admin/{projectCode}/api-keys
本轮已验证的结果:
flnxi-agent已通过tsc --noEmitflnxi-agent-go已补齐go.sum,并通过容器内最小编译验证:go test ./internal/api -run TestDoesNotExist、go test . -run TestDoesNotExist- 主应用新增测试已通过:
app/activate/__tests__/page.test.tsx、lib/__tests__/system-init.test.ts
本轮仍未解决的重点:
- 仍有部分部署文档和非主路径脚本保留历史示例值,需要继续清理到统一口径
- 2026-05-25 复核:
docker-compose.prod.yml中NEXTAUTH_SECRET已改为必填校验;客户交付仍必须显式配置强随机值 - Go Agent
install.sh当前启动容器时没有传入AGENT_AUTH_USERNAME/AGENT_AUTH_PASSWORD,因此默认只保留/api/health,不能把该脚本理解为“安装后管理面已可安全使用” - 私有化部署场景下,本地宽限期和本地缓存仍只能视为容灾,不应再被当作“硬防破解”
- 历史旧入口目前仍保留壳路由,虽不再承载真实逻辑,但后续仍建议物理删除
2026-05-30 补充:主应用到 license-server 的 activate / verify / heartbeat / register-free / premium-modules 主链路已要求 LICENSE_REQUEST_SIGNING_SECRET,请求体携带 timestamp、nonce 和 HMAC signature。本文早期关于“主应用请求缺少签名/重放防护”的旧结论已过期,只能作为历史问题来源,不再作为当前事实。
2026-06-01 补充:intranetPenetration 已在内网穿透启动、状态、停止入口做服务端许可证拦截,并且要求付费模块凭证;apiAccess 当前没有作为素材系统客户功能交付,标准版和专业版默认配置已改为 false,对应展示文案调整为预留能力。此前“apiAccess 已定义但未见生产执法点”的判断,对当前代码应理解为“暂不交付、不可售卖”,而不是“已售卖但缺拦截”。
2026-06-03 补充:apiAccess 仍是预留能力,但服务端门禁已统一到 paid-core。如果未来许可证把 apiAccess 打开,也必须同时通过本地许可证、授权模块凭证、模块运行状态和 paid-core 的 apiAccess:use 动作授权。
2026-06-01 补充:自定义模板不只在管理维护入口做许可证拦截,普通读取和使用入口也已按许可证收口。/api/templates、/api/upload-templates、/api/material-types 在当前许可证不包含 customTemplates 时只返回内置模板,不继续暴露历史自定义模板,避免客户降级后仍使用付费模板能力。
2026-06-02 补充:付费模块链路已进一步拆成“模块授权台账 + 短期镜像拉取凭证”。license-server 新增 LicenseModuleGrant 和 ModulePullTokenAudit,premium-modules 清单来源改为授权台账;客户侧获取模块核心镜像前必须先拿 15 分钟有效的 pullToken,Registry 前置代理再通过 /api/registry/auth 校验 token、token 审计记录、仓库路径、清单 digest、许可证状态、激活实例和模块授权状态。主应用只缓存授权凭证和短期拉包凭证,不直接挂 Docker Socket;Go Agent 已提供最小模块同步入口,负责拉取模块镜像、校验 digest、启动模块容器并在健康检查失败时尝试恢复旧模块容器。
3. 链路概览
当前大致链路如下:
- 主应用激活页调用本地
/api/license/activate或/api/license/activate-free - 主应用后端根据
LICENSE_PROJECT_CODE代理请求到远端license-server的项目维度公开接口 license-server返回签名许可证- 主应用把许可证写入本地
system_configs - 主应用后续通过本地缓存、宽限期和心跳维持运行状态
- 主应用的部分功能开关和数量限制,直接依赖本地缓存中的许可证配置
- Agent 负责安装、升级、容器操作和环境初始化,并通过同一个项目编码获取版本信息
这意味着:
- 远端服务端负责“发证和远端判定”
- 本地主应用负责“落地缓存和日常执法”
当前最薄弱的就是后半段。
4. 已确认风险
4.1 高风险(已部分整改):本地许可证缓存信任边界曾经过宽
相关文件:
lib/license/license-service.tsapp/api/license/verify/route.tslib/license/server-config.tsapp/api/license/check/route.ts
关键现象:
- 本地保存的
license数据里包含cachedConfig和lastVerified - 功能判断、用户上限、素材上限会直接读取
cachedConfig - 验证失败后,只要还在宽限期内,就会继续信任本地缓存
/api/license/check只要看到本地结构像许可证,就会恢复已激活状态
状态更新(2026-03-12):
- 已修复:本地许可证读取已改为先验签,再从签名原文恢复
LicenseConfig - 已修复:
/api/license/check现在通过getLocalLicense()验签后才恢复激活 - 剩余风险:宽限期、本地时钟和客户自控主机这一前提没有改变,因此这里仍不应按“本地硬防破解”理解
直接影响:
- 只要有人能改本地数据库里的
system_configs,就有机会伪造本地授权状态 - 只要再配合阻断外联,就可能让系统在本地“带假证运行”
- 宽限期字段同样落在本地,可被人为延长
风险判断:
这不是理论问题,是当前信任边界设计过宽的问题。
建议优先级:P0
建议方向:
- 本地不要直接信任
cachedConfig - 功能判断优先使用“服务端签名原文重新验签后的结果”
- 宽限期不能只依赖本地可改时间字段
- 本地缓存至少要加入篡改检测和签名校验失败后的硬降级
4.2 高风险(已部分整改):两套 Agent 仍是高权限控制面,但认证、默认开放面和安装凭据都已做第一轮收口
相关文件:
flnxi-agent/src/server.tsflnxi-agent/src/system-manager.tsflnxi-agent-go/main.goflnxi-agent-go/internal/api/handlers.go
关键现象:
- Node 版 Agent 当前已接入 Basic Auth、基础安全头,以及 Socket 连接时的 Origin / 认证校验
- Go 版 Agent 当前已接入 Basic Auth,中间件默认放行
/api/health - Node / Go 两个 Agent 当前都会在安装时生成随机的数据库、MinIO 和
NEXTAUTH_SECRET,不再在源码里写死固定安装凭据 - 两个 Agent 仍然都提供安装、更新、重启、停止、容器管理等高权限能力
- 当前已不再“未配置就直接开放”;如果未配置
AGENT_AUTH_USERNAME和AGENT_AUTH_PASSWORD,管理面会直接关闭,仅保留/api/health - Go Agent 的一键安装脚本当前默认把宿主机端口只发布到
127.0.0.1;需要局域网直连时,必须显式改成0.0.0.0 - Go Agent 的一键安装脚本当前没有传入管理账号密码;安装后如不额外配置认证,除
/api/health外管理面会关闭 - Go 版默认仍监听所有网卡,也没有像 Node 版那样对浏览器来源做额外限制
- Go 版里的
authToken仍只是用于镜像仓库认证,不是请求调用者认证
直接影响:
- 这类接口本质上仍接近“远程 Docker 控制台”
- 当前虽然已补基础口令认证,但只要部署时没配账号密码、或继续直接暴露管理端口,风险仍然很高
- 一旦被利用,风险级别仍高于普通业务接口漏洞
风险判断:
这是当前整条链路里最危险的面之一。
建议优先级:P0
建议方向:
- 更理想的方式仍是统一到
127.0.0.1/ 内网反代 / 更窄的 ACL 访问 - 对安装、更新、容器操作做审计日志
- 严格减少 Agent 可操作面,不要默认暴露完整容器控制能力
4.3 高风险(已部分整改):系统初始化入口已收口,但默认部署安全仍需继续推进
相关文件:
app/api/system/init/route.tslib/license/license-service.tsapp/activate/page.tsxdocker-compose.prod.yml
关键现象:
/api/system/init现在在“尚未有管理员”的场景下要求一次性SYSTEM_INIT_TOKEN- 一旦系统里已有管理员,后续初始化操作只允许管理员登录后执行
- 初始管理员密码不再固定写死;若未提供
INIT_ADMIN_PASSWORD,系统会在初始化时生成一次性密码 - 初始管理员首次登录后会被强制修改密码,不再依赖“默认密码是否等于某个固定值”
- 生产编排里已去掉
INIT_ADMIN_PASSWORD=88888888这种弱默认回退
直接影响:
- 新装环境“任何人都能直接打初始化接口”的风险已明显下降
- 固定弱口令被大规模复用的风险已下降
- 但如果
SYSTEM_INIT_TOKEN配得过弱、或初始化响应被不当保存,仍然会留下新的管理面风险
风险判断:
这一项已经从“公开高危入口”下降到“已收口但仍需继续收紧”。
建议优先级:P0
建议方向:
- 部署脚本应默认生成强随机
SYSTEM_INIT_TOKEN - 初始化成功页/接口返回的一次性密码不应再被额外持久化或二次暴露
- 后续可继续考虑把“手动初始化”进一步收口到仅部署脚本或仅内网入口
- 需要同步清理 Go Agent 静态页面里仍残留的旧默认账号文案
4.4 高风险(已部分整改):默认通信已开始切 HTTPS,部署弱默认值也已缩小,但仍有部分高价值密钥待继续清理
相关文件:
app/api/license/activate/route.tsapp/api/license/verify/route.tsapp/api/license/heartbeat/route.tsapp/api/license/activate-free/route.tslib/license/server-config.tslib/license/license-service.tsflnxi-agent/src/system-manager.tsflnxi-agent-go/internal/api/handlers.golicense-server/docker-compose.ymllicense-server/deploy.ps1
关键现象:
- 主应用和 Node Agent 已把
LICENSE_SERVER_URL默认值改为https://license.flnxi.com - Node / Go Agent 安装链路里原先写死的数据库密码、MinIO 密码、
NEXTAUTH_SECRET已改为运行时随机生成 - Go Agent 读取 Harbor 版本列表时,已不再内置固定管理员账号密码,而是改为可选环境变量
license-server/docker-compose.yml已不再回退到默认 JWT / 数据库口令,缺关键环境变量会直接拒绝启动license-server/deploy.ps1和scripts/generate-keys.ts已改为生成随机密钥和一次性管理员初始密码
直接影响:
- HTTP 链路更容易被中间人拦截、篡改、重放
- 明文默认口令会导致环境一旦直接复用默认值就进入高危状态
- 写死的仓库管理员口令影响面甚至超出当前项目
风险判断:
这是代码安全和部署安全同时存在的问题。
建议优先级:P0
建议方向:
- 默认全部改为 HTTPS 地址或要求显式配置
- 清除所有硬编码口令、默认密钥、仓库管理员账号密码
- 部署失败也不能自动回退到弱默认值
4.5 中高风险:license-server 的 API Key 管理权限过宽
相关文件:
license-server/app/api/admin/api-keys/route.tslicense-server/app/api/admin/[projectCode]/api-keys/route.tslicense-server/lib/project-auth.ts
关键现象:
- 旧的无项目 API Key 接口已废弃,不再承载真实逻辑
- 当前真实 API Key 管理入口已改为项目维度,并要求
verifySuperAdmin() - 项目级 API Key 前端页面也已补上,但还缺更细的审计与生命周期管理
直接影响:
- 普通项目管理员已不能再接触 API Key 管理能力
- 但 API Key 仍属于高价值发证凭据,如果缺少审计、轮换和用途隔离,后续仍可能形成运维风险
风险判断:
这一项已从“明确权限边界错误”下降为“已收口但仍需继续完善”。
建议优先级:P2
建议方向:
- 保持 API Key 为超管专属
- 继续完善项目级 API Key 隔离
- 增加创建、禁用、查看的审计记录
4.6 中低风险:多项目改造主链路已收口,但仍有历史壳接口待物理清理
相关文件:
license-server/lib/db.tslicense-server/app/api/admin/dashboard/route.tslicense-server/app/api/admin/licenses/route.tslicense-server/app/api/admin/alerts/route.tslicense-server/app/api/admin/versions/route.tslicense-server/app/api/admin/customers/route.tslicense-server/app/api/admin/[projectCode]/dashboard/route.ts
关键现象:
license-server的真实后台管理链路已经切到/api/admin/{projectCode}/...- 旧的无项目后台接口已不再执行真实业务逻辑,而是返回
410 - 旧的无项目后台页面也已改为跳转到项目选择页
- 真实运行链路里,项目隔离现在由
mainPrisma + getProjectDb(projectCode)明确承担
直接影响:
- 当前不会再因为旧后台接口误打到默认项目库
- 但保留历史壳接口会继续增加维护噪音,也容易让后续阅读代码的人误判真实入口
风险判断:
这项风险已明显下降,更偏维护性和清理优先级问题。
建议优先级:P2
建议方向:
- 后续物理删除旧单项目后台接口
- 继续保持所有许可证后台读写统一走项目维度
- 文档中明确哪些旧入口已废弃,避免继续被当正式入口使用
4.7 中风险:主应用 CORS 当前是“反射任意 Origin”
相关文件:
middleware.ts
关键现象:
- 虽然定义了
ALLOWED_ORIGINS - 但真正返回头时直接使用
requestOrigin || origin - 允许名单没有真正参与判断
直接影响:
- 现在未必马上能构成完整攻击链
- 但它会放大以后新增接口时的跨域风险
风险判断:
属于应尽早收口的基础安全配置问题。
建议优先级:P2
建议方向:
- 只对白名单来源返回
Access-Control-Allow-Origin - 不要对任意来源做反射
4.8 中风险:许可证服务端公开接口缺少真正的请求签名校验
相关文件:
license-server/lib/types.tslicense-server/app/api/license/[projectCode]/activate/route.tslicense-server/app/api/license/[projectCode]/verify/route.tslicense-server/app/api/license/[projectCode]/heartbeat/route.tslicense-server/app/api/license/[projectCode]/register-free/route.tslicense-server/app/api/license/[projectCode]/generate/route.ts
关键现象:
- 类型里定义了
signature - 但实际
activate / verify / heartbeat的 schema 没有要求请求签名 - 当前主要依赖时间戳和硬件指纹
- 免费注册接口会根据
instanceId直接返回已有激活码
直接影响:
- 接口更容易被脚本化探测和重放
- 设备级身份校验强度不足
风险判断:
它还不是当前最致命的问题,但后面如果系统规模变大,会越来越明显。
建议优先级:P2
建议方向:
- 给客户端到
license-server的请求增加签名或短期令牌 - 免费注册和已有激活码返回逻辑需要进一步收紧
4.9 补充结论:按私有化部署场景重看后,当前更大的问题仍是“本地执法过重”,但已确认漏口已先补齐一轮
相关文件:
docs/项目目标与目标环境.mdREADME.mdmiddleware.tsapp/api/departments/route.tsapp/api/admin/upload-templates/route.tsapp/api/admin/upload-templates/[id]/route.tsapp/api/admin/upload-templates/[id]/migrate/route.tslib/license/check-limits.tslib/license/license-service.tsapp/api/license/check/route.ts
关键现象:
- 当前项目真实定位是企业私有化部署,目标设备是 NAS / Linux 服务器,普通最终用户只通过浏览器访问
- 这意味着客户往往实际控制容器、数据库、环境变量和本地缓存,本地许可证判断天然难以做到“硬防破解”
middleware.ts已明确不再承担许可证快速检查,当前主要依赖各个 API 自己做许可证判断- 已确认的部门隔离漏口已补齐:
POST / PATCH / DELETE现在都要求同级许可证 - 已确认的自定义模板漏口已补齐:模板编辑、删除、迁移现在都要求同级许可证
apiAccess当前仍是预留能力,标准版和专业版默认不出售;若未来开启,已要求走paid-core的apiAccess:use门禁/api/license/check已改为先做本地验签,再恢复激活状态
直接影响:
- 当前已确认的几处直接漏口已经关闭,许可证执法一致性比之前好一些
- 但整体上仍更接近“拦普通误用”和“提高破解成本”,还不是“能扛有主机控制权用户的定向破解”
- 已声明但未落地的能力执法仍会继续制造新的不一致风险
- 在客户自控服务器场景下,只要本地缓存和本地数据库还能被直接接触,远端发证的价值就会被削弱
风险判断:
这不是单点漏洞,而是产品部署模型与当前执法架构共同造成的边界问题。
建议优先级:P0
建议方向:
- 不再以“本地绝对防破解”为目标,转为“统一执法 + 提高成本 + 留痕 + 可追责”
- 所有付费功能改为集中式检查,避免散落在零碎 API 中各自判断
- 本地缓存只保留容灾价值,不应继续承担最终真相
- 优先补齐已确认的许可证漏口,再考虑更复杂的防篡改设计
5. 后续修复优先级建议
P0:必须优先处理
- 清除剩余的硬编码口令和默认弱密钥
- 继续把 Agent 从“默认安全”收口到“默认内网 / 反代优先”
- 继续把许可证执法点集中化,并补齐尚未落地的已声明能力
- 复核宽限期、本地时钟和本地容灾策略,避免再次形成过宽信任边界
- 把初始化令牌的生成、传递和审计流程纳入正式部署脚本
P1:应在下一阶段完成
- 给所有已声明的许可证能力补齐真实生产执法点
- 继续清理历史壳接口和旧文档入口
- 收紧项目级 API Key 的审计、轮换与用途隔离
- 明确公开接口的接入方约束和项目编码管理流程
P2:作为体系完善
- 修正 CORS 白名单逻辑
- 给许可证公开接口补请求签名、限流、审计
- 补全安全相关监控和异常告警
6. 建议的整改顺序
建议按下面顺序推进:
- 先清理剩余硬编码密钥,并把初始化令牌流程接进正式部署入口
- 再继续收紧 Agent 的默认暴露面、认证与审计
- 再处理
license-server权限边界和多项目收口 - 最后处理 CORS、接口签名、限流和审计完善
原因:
- 前两项仍是当前最直接的高危入口
- 后两项更偏体系长期稳定性和可维护性
7. 当前结论摘要
当前系统不是完全没有保护,而是“远端有一定保护,本地执法仍偏重,且默认部署安全性还不够”。
如果按当前项目真实目标来看,它是部署在客户自己控制的 NAS / Linux 服务器上的私有化产品。在这种前提下,真正意义上的“硬防破解”天然就比较弱。
本轮收口之后,方向已经比之前更合理,但后续正确方向依然不应是单纯追求“本地绝对防住”,而应是:
- 统一许可证执法入口
- 收紧本地缓存信任边界
- 把高价值能力尽量建立在更难被本地完全替代的链路上
- 同时加强审计、留痕、异常检测和运营管控
如果面对的是普通客户环境,这套方案暂时还能工作;如果面对的是懂容器、懂数据库、懂抓包、懂改配置的人,当前风险暴露仍然比较明显。
后续若继续做新版本功能移植,建议把“安全收口”列为并行主任务,不要继续在当前信任边界上叠更多功能。