# 2026年10月9日 固定资产系统问题与隐患审查 本轮列出 26 项整改问题:11 项 P1、15 项 P2,另有 3 项依赖配置或历史数据的条件性风险。优先处理误操作资产、虚假可用库存、HR 空源停用、打印状态一致性,以及本地修复未上线的问题。 代码基准为 `main@843babd` 加当前未提交工作区,包含 10 月 7 日的本地整改。生产证据采自 10 月 9 日固定资产系统的只读核查。本轮新增审计文档,未修改业务代码、数据库数据或部署,未补发消息。 P1 表示可能造成错误资产操作、数据损坏、核心流程中断或明显安全风险,应优先整改;P2 表示影响一致性、安全、恢复能力和使用体验。代码缺陷与“已经在线上造成损失”分别判断,后者不能仅凭 mock 推断。 ## 当前生产状态 | 项目 | 10 月 9 日核查结果 | 含义 | | --- | --- | --- | | 维修快照 | `maintenance_record.original_tree_snapshot` 列不存在 | 本地快照整改尚未在生产生效 | | 健康检查 | 已部署文件中没有新 `healthController.js`;健康接口仍固定返回 `Server is running` | HTTP 200 不能证明业务表、迁移和数据库可用 | | 数据库 | 应用使用 root;3306 绑定宿主机 IPv4 和 IPv6 全网卡 | 最小权限和端口收敛尚未完成;当前电脑直连 3306 超时,不能据此断言公网已开放或所有来源都被阻断 | | 运行时 | Node.js `v20.20.2`,进程 UID 为 0,时区 UTC,MySQL 默认隔离级别 `REPEATABLE-READ` | 日期偏移条件已具备;并发审查需考虑旧快照;运行账号权限过大 | | 通知 | 通知中心 70 条已读、12 条未读,没有 failed;另有 5 条旧通道停用记录 | 暂无新的通知中心失败;历史旧通道记录不应当作需要补发的新故障 | | 证书 | 严格 TLS 校验通过,到期为北京时间 2027 年 3 月 23 日 07:59:59 | 上次证书替换有效,当前没有过期问题 | | 打印 | 162 条完成、15 条排队,最早排队任务创建于 3 月 30 日 | 存在长期积压;排队状态本身不证明已领取任务卡死 | | 备份权限 | 最新标准备份目录及所有祖先目录为 755,数据库和上传归档为 644 | 同服务器其他本地账号可读取备份 | 生产当前不使用公开默认数据库密码。本轮不把上次已消除的默认密码问题再次认定为现存事实。 ## P1 问题 ### 1 维修和健康检查的本地整改尚未上线 生产没有维修快照字段,也没有新健康检查控制器;已部署维修、报废控制器与本地构建文件的 SHA256 不同。本地构建通过并不等于生产使用了这套代码,至少维修快照及真实健康检查仍未生效。 应把已完成的本地修复纳入可审查的发布版本,执行备份、迁移预检、增量迁移和发布验收。历史维修记录不能推测回填快照。相关文件:[维修控制器]()、[健康控制器]()、[快照迁移]()。 ### 2 切换资产详情后显示旧资产却操作新资产 详情只在 `onMounted` 加载,没有监听路由 ID;同组件复用时,页面继续显示资产 A,删除和维修却取新 URL 中资产 B 的 ID。顶部全局搜索从 A 进入 B 即可触发,存在删除错误资产、错误登记维修和保存错位资料的风险。 Vue 内存路由复现结果:URL 为 `/equipment/edit/2`,显示 ID 为 1,删除目标为 2,加载次数仍为 1。应在 ID 变化时重载并清空旧状态,操作目标与当前展示对象保持一致。[详情加载]()、[删除目标]()、[路由容器]()。 ### 3 损坏或丢失归还仍增加可用库存 归还状况只记在借用单,库存更新统一加 1 并恢复“可用”,没有让损坏或丢失进入对应资产状态。内存 mock 中,两种归还均成功,可用库存变为 1、可用价值恢复,借用单却仍记录损坏或丢失。 应先确定损坏、丢失的正式业务状态,再统一普通和批量归还规则;不能用备注替代库存状态。[归还状态计算]()、[普通归还]()、[批量归还]()。 ### 4 维修中卸载原损坏配件会直接变可用 “维护中”父资产允许卸载,卸载后的子树只能设置为“可用”,没有核查未完成维修单及送修前状态。原损坏配件整树送修后被拆下,会绕过维修成为可借资产;之后取消维修又因树关系变化返回 409。 内存 mock 已复现父资产仍维护中、配件脱离后可用。应限制维修期间的资产树变更,或建立可审计的维修拆装流程并保留配件状态。[允许状态]()、[拆下后状态]()、[卸载入口]()。 ### 5 完成维修可能覆盖已卸载并重新借出的配件 完成维修没有锁定整棵资产树,随后对子项仅按 ID 覆盖状态。在可重复读事务先建立快照、另一事务卸载并重新借出配件的交错下,完成事务可能从旧快照读出原配件,将其覆盖为可用,而新借用单仍未归还。 已用内存 mock 模拟该交错;生产默认隔离级别确为 `REPEATABLE-READ`,但尚未做隔离实库并发验证。应采用一致的整树锁顺序,并在写入时核对关系、状态和版本。[维修完成]()、[子项无条件更新]()。 ### 6 HR 成功空源可能停用全部同步人员 源查询成功但人员结果为空时,流程仍用 `source_userid notIn []` 停用所有同步人员,再删除没有借用或盘点历史的人员。上游 ETL 空窗、错误筛选和异常数据都可能被误认为人员整体离职。 原函数内存调用已捕获空源仍执行停用和清理的条件;未据此认定线上已经发生。应在写入前校验源完整性、空集和人数骤减,并把大批停用作为需要复核的变更。[批量停用]()、[执行顺序]()、[清理删除]()。 ### 7 打印状态并发回写可把完成任务重新排队 打印回写先读取并检查状态,随后只按任务 ID 更新。完成与可重试失败同时读到 `printing` 时,完成先提交,失败仍可把任务改回 `queued`;完成路径还可能已清理标签图片。 mock 最终状态为 `queued`,两次更新条件都只有 ID。应使用包含原状态、领取终端和版本的条件更新,保持完成状态不可倒退,并协调图片清理。[回写入口]()、[更新条件]()。 ### 8 打印领取后缺少失联恢复 领取后终端断网或崩溃,任务停在 `claimed/printing`;领取入口只读取 `queued`,管理员也只能取消 `queued`,没有租约到期回收或领取任务的人工核实入口。 应增加领取租约和心跳、失联状态及人工恢复入口。对可能已经出纸的任务,恢复时必须区分结果未知和明确未打印,避免盲目重新排队。[领取]()、[取消限制]()、[任务模型]()。 ### 9 .NET 打印助手把结果回写失败误报为打印失败 实际打印和 `completed` 请求位于同一个 try 中。打印已提交,但完成回写请求未到服务器时,catch 又上报 `failed`,使“已打印但回写未知”变成“打印失败”;管理员补发可能重复出纸。 应分别处理实际打印与状态回写,持久化待回写结果,重启后继续对账。[打印与完成回写]()、[失败回写]()。此项为代码控制流证据,未用实体打印机触发。 ### 10 本地登录缺少限流且允许极弱密码 应用的本地登录没有账号或 IP 的失败计数、退避和锁定;后端创建及重置密码只要求非空,单字符密码可被接受。生产当前使用 local 模式,因此该登录路径启用;是否另有外围 WAF 限流没有核实。 应在应用层建立密码强度规则及登录限流,保留审计,同时避免把攻击者的失败尝试变成永久锁死正常账号。[登录]()、[重置校验]()。 ### 11 发布覆盖旧源码后才做部署备份 发布脚本先删除并替换服务端 backend、frontend、scripts 和配置,再调用 deploy.sh 中的“发布前备份”。因此标准备份保存的是新配置,旧源码已被删除;后续构建或上线失败没有自动回到上一版本的步骤。 应在覆盖前保存旧版本和配置,保留可回退的镜像标识,再采用原子切换;发布失败需有明确恢复路径。[先覆盖文件]()、[后调用部署]()、[备份配置内容]()。 ## P2 问题 | 编号 | 问题与影响 | 证据与整改方向 | | --- | --- | --- | | 12 | 重置密码不撤销旧会话;被盗会话可在改密后继续使用 | [密码更新]()仅改密码,[会话认证]()不校验认证版本。mock 确认旧会话仍通过。引入认证版本或按用户撤销会话 | | 13 | 会话过期后,前端在登录与首页之间循环 | [路由守卫]()捕获失败但不清 Pinia 认证状态。真实守卫提取复现循环。统一 401 和会话失效的清理流程 | | 14 | 跨页批量借出和归还静默漏掉前页所选资产 | [编码查询]()只查当前页,选中 ID 却跨页保留。源码提取复现只提交第 2 页资产。维护完整选中对象或按 ID 批量获取 | | 15 | 预计归还时间在生产偏移 8 小时,影响逾期判断 | [前端提交]()传无时区日期,[后端解析]()按服务器时区解释。生产为 UTC;北京时间 12 点会保存为 12:00Z,回显 20 点。提交含明确时区的 ISO 时间 | | 16 | Excel 数量 0 或非数字被改成 1,错误行被丢弃且行号错位 | [导入转换]()使用默认数量和 filter。源码提取复现 3 行仅提交 2 行。保留原始行号,逐行报告错误,禁止静默改数 | | 17 | 已关闭盘点任务仍可分配责任人,改动已结束结果 | [分配入口]()查询任务仅选 ID,未核查状态。closed 任务 mock 分配成功。事务内校验并锁定任务状态 | | 18 | 不同参与人并发盘点会覆盖汇总计数 | [汇总]()先聚合后覆盖,[调用]()缺少任务级串行保护。mock 明细 checked=2 而汇总为 1;尚未隔离实库验证。采用一致锁序与正确隔离读,或安全增量计数 | | 19 | 借出时无配件的普通归还跳过后来新增配件的确认 | [差异检查]()遇 null 快照直接认为无差异。mock 更新根和新配件却不要求确认。区分可靠空集合与缺失快照,对当前资产树做完整比对 | | 20 | 打印图片只校验 Data URL,缺少真实 PNG、尺寸和单任务数量限制 | [校验]()接受伪 PNG,后续落盘;[请求解析]()在认证前允许 50 MB。应限制解码字节、像素和标签数量,并适度限制公开端点请求体 | | 21 | 数据库运行账号权限过大,生产端口仍绑定全网卡 | [Compose]()仍使用 root;生产实际映射 3306 与 3000。应用故障或入侵会扩大数据库影响面;公网直连未证实。分离迁移与运行账号,取消无必要端口暴露 | | 22 | 生产数据库及上传备份可被其他本地账号读取 | 最新标准备份数据文件 644,全部父目录 755;[备份脚本]()无限制 umask。设置目录 700、文件 600,核查并修正存量归档权限 | | 23 | 标准备份不足以独立恢复整套服务 | [备份内容]()不含环境配置、证书、对应版本源码或镜像。只持有标准备份时不能独立重建服务。分开保管加密配置和证书,记录版本及镜像,并做隔离恢复演练 | | 24 | KEEP_BACKUPS=0 被接受并删除全部标准备份,包括刚生成的一份 | [参数校验]()接受 0,[清理]()删除全部。纯 mock 替换 rm 后已复现。要求正整数并在清理前确认至少保留一份完整备份 | | 25 | 容器缺少内存限制和日志轮转,可能耗尽共享主机资源 | 生产 equipment 容器 Memory=0,json-file 日志配置为空;[Compose]()没有资源或日志限制。应按实际容量配置限额和 max-size/max-file,并监测磁盘;本轮未证明已经耗尽 | | 26 | 依赖与运行时需要安全更新 | 本地生产依赖扫描后端报告 27 项包级告警、前端 10 项;告警数不等于可利用漏洞数。xlsx 0.18.5 在实际导入路径被使用,生产 Node 20 已处于官方 EOL。需逐条判断可达性、升级并回归;不要盲目采用 audit 建议的跨主版本降级 | 第 26 项的直接证据:[xlsx 锁定版本]()、[实际读取文件]()。SheetJS 官方确认读取特制文件时的[原型污染](https://cdn.sheetjs.com/advisories/CVE-2023-30533)及[正则拒绝服务](https://cdn.sheetjs.com/advisories/CVE-2024-22363)问题覆盖该版本。Node.js 官方[版本状态表](https://nodejs.org/en/about/previous-releases)已将 Node 20 标为 EOL。 ## 条件性风险 1. **Keycloak 用户名映射可能继承本地高权限身份。** [身份映射]()按用户名匹配,没有绑定 issuer 和 subject。只有启用 Keycloak 且非可信用户能注册或改成对应本地用户名时才成立。当前生产是 local 模式,不能说线上已发生该越权。 2. **旧 legacy 盘点共享链接可选择他人身份。** [旧身份确认]()依赖 token 和参与人 ID。当前生产唯一 legacy 任务已关闭,活跃任务为 dingtalk 模式;若将历史任务重新开放或恢复为可操作状态,需要先迁移身份验证。 3. **打印助手允许 HTTP 携带长期令牌。** [Electron 地址配置]()和 [.NET 地址配置]()未强制 HTTPS,客户端随后附带 Bearer。纯 mock 已证实此行为;默认 HTTPS 不触发,当前实际终端配置未核实。生产地址应拒绝 HTTP,开发例外单独控制。 ## 修复顺序与验收 1. 先处理资产详情错位、损坏或丢失归还、维修拆装与完成并发、HR 空源保护。验收必须覆盖错误资产 ID、损坏库存、树关系变更、并发借出以及空源和人数骤减。 2. 修正打印回写条件、失联恢复和 .NET 结果回写,区分已打印、未打印、结果未知;增加持久化恢复和重复上报场景。 3. 补齐密码规则、登录限流及改密撤销会话,同时修复前端 401 循环。 4. 收敛备份权限、修正覆盖前备份和回退流程,再把已有本地维修、权限、健康检查及部署整改发布到生产。新增数据库字段走增量迁移,不自动猜测旧记录。 5. 处理跨页选择、日期时区、导入错误行、盘点状态和计数,以及依赖与容器资源配置。并发问题在隔离 MySQL 环境验证,不向生产注入测试数据。 ## 已执行验证及范围 - 前端 `npm run build`、后端 `npm run build` 均通过。 - 后端现有 251 项测试通过;执行时数据库地址设为不可用的本机审计地址,避免连接真实数据库。 - Electron 打印助手现有 18 项测试通过。 - 备份保留、报废迁移预检、数据库部署配置 3 个 shell 测试脚本分别通过。 - 资产路由复用、跨页选择、认证循环、导入转换,以及归还、维修、HR、打印、盘点相关触发路径使用源码提取或内存 mock 验证。并发 mock 不能替代隔离实库验证,具体限制已随条目标明。 - 生产只读核查限定固定资产目录、equipment 容器及 equipment_management 数据库,未操作同机其他系统。 本轮未完成实体打印验收、带真实登录态的浏览器流程验收和隔离实库并发压测。现有测试通过说明已覆盖场景正常,不能排除清单中的未覆盖分支。前端构建中统计相关共享 chunk 约 1.12 MB,移动端加载性能也值得进一步测量;构建体积本身不证明页面已经卡顿。