严重漏洞:roster_import 使用 $_FILES 的原始扩展名拼 roster_YYYYMMDD_HHMMSS.<ext>
落在 Web 根目录内的 data/uploads/,既无扩展名白名单(可上传 .php),文件名又
可按秒枚举;而 bootstrap 只写了 data/.htaccess,nginx 不读 .htaccess,
生产环境(nginx + PHP-FPM)下可直接远程代码执行。
修复(api.php roster_import 四层防护):
- 扩展名白名单:仅 xlsx / xlsm / csv / txt
- 随机文件名:bin2hex(random_bytes(16)),杜绝枚举
- 解析完成后 @unlink 立即删除,Web 目录下不留存任何上传文件
- 20MB 大小限制 + UPLOAD_ERR 检查
加固(bootstrap.php):data 及 cache/uploads/snapshots 每个目录都写
.htaccess(Deny from all)+ index.html 占位;README 与文档补充 nginx
location ^~ /data/ { deny all; return 404; } 规则。
测试:新增 e2e tools/test-upload-guard.js(11 断言,覆盖 .php/.phtml 被拒、
无残留、合法 csv 导入成功且即删、无可枚举文件名),并入 tests/run-all.sh。
全套 90 项通过。
docs: 新增 VitePress 文档站(18 页)
- 使用指南 12 页:简介/部署/快速上手/系统设置/名单管理/统计口径/试卷设置/
快照趋势异常/导出打印/数据看板/随机抽取/常见问题
- 安全 2 页:安全总览(含本次漏洞复盘)与部署加固清单(P0-P2)
- 开发 4 页:架构与目录/接口一览/SQLite 存储/测试
- 配图迁移至 docs/public/images/;.gitignore 忽略文档构建产物
2.3 KiB
2.3 KiB
试卷设置(满分 / 合格率 / 免考)
不同试卷满分往往不同(100 分制、150 分制、50 道题每题 2 分……),合格率要求也可能不同。 所以满分的每卷配置放在主页面「试卷设置」里,而不是系统设置——设置里只保留全局默认值。
打开方式
主页面工具栏 → 试卷设置(按钮为高亮主色)。配置只对当前选中的试卷生效。
优先级
本卷配置 > 全局默认(系统设置)
未设置某项时显示"跟随全局(X)",保存后卡片标题与统计口径立即更新。
满分
- 用于合格率阈值计算和卡片展示;
- 留空或填
0= 清除本卷配置,回落全局默认; - 全局默认满分在 设置 → 全局默认满分 中修改。
合格率
- 百分比,含义是「成绩 ≥ 满分 × X%」记合格。例如满分 150、合格率 60 → 90 分及格;
- 留空 = 跟随全局;录入范围会被夹到
1 ~ 100; - 保存后统计卡片标题会变成
合格率 (≥60%满分),与实际口径一致。
::: tip 为什么不再写死 60% 早期版本把「≥60% 满分」硬编码在代码里,一旦满分不是 100 就会产生误导。 现在阈值可配、可每卷覆盖,且界面上直接显示当前阈值。 :::
免考名单
某些人因故不参加本卷考试(缓考、免修、已单独补考等),如果直接把他们从名单里删除, 其它试卷的统计也会受影响。免考名单解决的就是这个:
- 填写学号列表,支持逗号、中文逗号、空格、换行分隔;
- 生效范围:仅当前试卷;
- 效果:这些人不计入名单总人数,也不计入缺考,彻底从本卷的分母中移除;
- 若他们实际上也交了卷,答卷仍会出现在成绩明细中。
示例输入:
2024012001, 2024012002
2024012015
清除本卷设置
弹窗左下角 清除本卷设置 会删除该试卷的全部独立配置(满分、合格率、免考), 之后完全跟随全局。
存储
| 环境 | 位置 |
|---|---|
启用 pdo_sqlite |
data/ny.db 表 ny_project_config(主键 project_id) |
| 未启用 | data/project_config.json |