Repository navigation
fix(authz): enforce current role, token revocation, S3 permissions - #92
Conversation
…d user-root isolation checkAdminAuth: no longer trusts the JWT role claim. Logout and demotion previously had no effect on admin routes because only payload.role was read and jti was never checked; it now re-checks the revocation list, the disabled flag and the current database role. authUserFromReq: reject disabled users. JWT only proves the identity was valid when issued; every resolver must re-check current status. Core APIs went through getUserFromContext (which checks), but the S3 gateway reused this function and did not. S3 gateway: PutObject now requires WRITE_CONTENT and DeleteObject requires DELETE; object paths are scoped to the caller's base_path and bucket/key are validated. Previously any authenticated user, including read-only and disabled accounts, could write anywhere. getActualPath: fold dot segments before joining base_path so a user cannot escape their root via plain or percent-encoded traversal. verify: 225 tests pass.
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
openlist-work | 8121c8e | Oct 15 2026, 03:12 PM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
openlist-tsworkers | e7c16a0 | Oct 15 2026, 02:46 PM |
…onfig guard, upload limits, WebAuthn hardening Permission bits: rename/batch_rename/regex_rename use RENAME, remove/remove_empty_directory use DELETE, move/recursive_move use MOVE, copy uses COPY, instead of all sharing WRITE_CONTENT. meta.write_users now enforced at write time; archive read shares the /fs/get meta+password judgement; archive list/meta require READ_ARCHIVES and decompress requires DECOMPRESS plus write, with the destination scoped to the caller root and ZIP entry names restricted to relative-only so ../ cannot escape. Uploads re-check the actual received byte count (Content-Length could be omitted to bypass the limit). map format now throws on damaged JSON instead of returning null, so corruption is no longer mistaken for a fresh deployment that reopens public setup. Storage: key format writes before deleting, saveDb rolls memory/cache back on failed persistence, D1 batches retry per chunk and report partial commits, S3/WebDAV scope paths to the user root, archive fetch follows redirects safely. Sessions: revocation list uses the unified storage backend with bounded reload, multipart sessions are owner-bound, WebAuthn challenges are server-issued and single-use with origin/UP/UV checks and disabled-user rejection. verify: 285 tests pass; SSRF probe 22 block / 8 allow; WebAuthn probe all green.
There was a problem hiding this comment.
[OUTDATED] 此评审已过时,请参阅下方增量评审(v4)。
🙏 感谢 @PIKACHUIM 提交!
🤖 AI 自动审核声明:本评审报告由 AI 自动生成,当前使用 GLM 模型进行分析。
🎯 结论
🔄 Request Changes — 权限修复整体扎实,但上传大小限制的修复不完整,S3 网关完全缺失大小限制
📖 概要
一批授权加固:JWT 解析复核 disabled、OTP 失败纳入防爆破计数、目录级写 ACL 真正执行、归档读取走 meta ACL、ZIP 条目路径清理、上传大小按实际字节数复核、S3 网关补权限位与用户根目录隔离、WebAuthn challenge/origin/UV 校验。
🧭 整体方案
每个修复点都标注了「历史缺陷 + 实测复现」,用 isWithinUserRoot 统一收口路径越界,方案方向正确。问题在于大小限制这条线只修了一半。
📊 变更统计
8+ 个文件 | 功能 ⭐⭐⭐⭐ | 最小改动 ⭐⭐⭐⭐ | 前向兼容 ⭐⭐⭐⭐ | 方案设计 ⭐⭐⭐
🚨 关键问题
P1(建议修复后再合并):
⚠️ server/fs.ts:1655-1673(/fs/multipart/part)仍只做Content-Length预检,无该头时直接Buffer.from(await c.req.arrayBuffer())——readBodyWithinLimit想封死的「省略头绕过上限」路径在这里依然可用。该函数只接入了/fs/put(L974)和/fs/upload/part(L1104)。建议本路由同样改用readBodyWithinLimit(c, true),并把 L1655 的预检删掉换成统一入口。⚠️ server/s3.ts:206(PutObject)Buffer.from(await c.req.arrayBuffer())之前没有任何大小限制——MAX_UPLOAD/MAX_UPPART都不生效,超大请求体可整包读入内存。与/fs/put的防护形成明显不对称。建议复用getUploadSizeLimit(或新增MAX_S3_PUT)+ 实际字节数复核。
P2(可选):
- 💡
s3.ts:206PutObject 的ETag用Date.now().toString(16)生成,不是内容的真实哈希;对做分片续传/幂等判断的 S3 客户端可能产生误导,是否考虑至少返回空 ETag 或内容 md5? - 💡
safeArchiveEntryPath拒绝了../盘符/NUL,覆盖到位;/fs/other签发直传 URL 后的写入路径不经过本 PR 的大小限制(预失效 #75 已单独处理失效),边界行为建议在 PR 描述中注明。
📂 逐文件分析
src/backend/server/fs.ts
改动意图:写 ACL 执行点 + 归档 ACL + 条目清理 + 上传限流。
问题分析:deniedByMetaWriteAcl / deniedByMetaReadAcl 管理员放行、meta 缺失时放行的语义与 pkg/meta 约定一致;readBodyWithinLimit 的实际字节数复核思路正确——只是接入点不全(见 P1)。
src/backend/server/s3.ts
改动意图:S3 权限位 + base_path 隔离。
问题分析:canWrite/canRemove 权限位、isWithinUserRoot 收口、authUserFromReq 复检 disabled 都已到位;s3VirtualPath 对受限用户把 bucket 解释为自己根下的一层目录,与 /api/fs 视图一致。缺的只有大小限制(见 P1)。
src/backend/pkg/permission.ts
改动意图:normalizeSegments + isWithinUserRoot。
问题分析:.. 越根钳制、重复 / 合并正确;建议补一条「normalizeSegments('/a/../../b') === '/b'」的显式测试。
其余文件(auth.ts / webauthn.ts 等)无重大问题。
✅ 待处理清单
- [P1]
/fs/multipart/part改用readBodyWithinLimit,封死无 Content-Length 绕过 - [P1] S3 PutObject 增加大小限制 + 实际字节数复核
- [P2] S3 ETag 语义(可选)
- [P2]
normalizeSegments补越根钳制测试(可选)
🎯 结论:🔄 Request Changes — 权限加固值得合并,但上传限流修复不完整,两条 P1 补齐后即可通过。
…ser-role guards
1) 目录级访问控制补齐(消除跨协议权限旁路):/fs/dirs、/fs/search、WebDAV(PROPFIND/GET/HEAD/PUT/MKCOL/DELETE/MOVE/COPY)、S3(GET/HEAD/PUT/DELETE) 现在与 /fs/list /fs/get 使用同一套 meta 判定(密码 + read_users + write_users + meta.write 绕过开关)。/fs/dirs 的 force_root 只对管理员开放。
2) /fs/get_direct_upload_info 对齐 Go FsGetDirectUploadInfo:file_name 单层相对路径校验、meta 写 ACL、存储挂载边界校验、Overwrite:false 已存在探测。
3) JWT 增加 pwd_ts 密码指纹(对齐 Go UserClaims.PwdTS):登录/SSO/WebAuthn 三处签发,authUserFromReq/getUserFromContext/checkAdminAuth 三处校验 —— 改密后已签发令牌立即失效(历史缺陷:改密不注销旧会话)。
4) 用户管理对齐 Go:create 禁止 admin/guest;update 禁止变更 role、禁止禁用管理员;delete 按角色保护 admin/guest(不再只硬编码 id===1)。
5) WebDAV Basic Auth 接入登录防爆破(失败计数 + 指数退避锁定,与 /api/auth/login 共用),OPTIONS 匿名放行(对齐 Go WebDAVAuth)。
6) 删除(按评审决定):bcrypt/无盐单层哈希兼容与登录时自动迁移(只保留 Go 双层哈希);user/create 自动生成随机密码并回显;TS 专有 /fs/upload/{create,part,complete} 端点(分片只保留官方 /fs/multipart/*)。
7) 顺带修复 5 个改动前就存在的 tsc 报错(db_cipher.test.ts 重复 DbCipher 导入、/form 的 getUploadSizeLimit 少传参、multipart/init 的 user 空值收窄)。
验证:tsc 0 error;server 179 / model+pkg 50 / store 12 / drivers 131 全绿;新增 security_hardening.test.ts 覆盖角色保护与改密失效。
…th-isolation # Conflicts: # src/backend/server/webdav.ts
回应 PR #92 评审(Request Changes): P1-1 /fs/multipart/chunk 此前只做 Content-Length 预检,省略该头即可绕过单片上限并把整片读入内存;现改用 readBodyWithinLimit(c, true),并删掉重复的预检分支。(评审提到的 /fs/upload/part 已在上一轮删除,分片只保留官方 /fs/multipart/*。) P1-2 S3 PutObject 完全没有体积限制(MAX_UPLOAD/MAX_UPPART 都不生效,超大请求体整包读入内存);现接入共享限额实现,超限返回 S3 风格 EntityTooLarge(400)。为此把限额辅助函数抽到 pkg/upload-limit.ts,fs.ts 与 s3.ts 共用同一份。 P2-3 PutObject 不再返回伪造的 ETag(此前是 Date.now(),并非内容摘要,会误导依赖 ETag 做幂等/续传/校验的客户端)。真实 MD5 所需的算法 WebCrypto 不提供、纯 JS 实现要整包二次扫描、hash-wasm 的 WASM 在边缘运行时可能被禁用,故选择不返回 ETag(宁缺勿错),真实摘要留作后续跟进。 P2-4 /fs/other 签发的直传 URL 由客户端直连对象存储、不经过本站,MAX_UPLOAD/MAX_UPPART 覆盖不到——已在代码注释与 PR 描述中注明该边界。 P2-5 新增显式回归测试:normalizeSegments 越根钳制、getActualPath/isWithinUserRoot 边界、readBodyWithinLimit 的「省略 Content-Length 也按实际字节数拒绝」。 另修正 main 上既有失败测试:drivers/github_releases 的 gh_proxy 断言写成「代理值+完整链接」,而实现(与 Go strings.Replace 一致)是「替换 https://github.com 前缀」——纯测试改动。 验证:tsc 0 error;server 195 / model+pkg 81 / store 12 / drivers 151 全绿。
There was a problem hiding this comment.
[OUTDATED] 此评审已过时,请参阅下方增量评审(v5)。
🙏 感谢 @PIKACHUIM 提交!
🤖 AI 自动审核声明:本评审报告由 AI 自动生成,当前使用 Claude Sonnet 5 模型进行分析。
🔄 增量评审:v3 → v4
本轮自上次评审(2026-10-09)起新增 4 个有效 commits:
3ab8361fMerge origin/main(含 #93:EdgeOne 产物改为部署期生成)9bc81758第三轮:meta ACL 跨协议打通、JWT 密码指纹、用户角色保护、删除 4 项历史 TS 专有功能c6a5c226Merge origin/main8d9c339b上传限额覆盖/fs/multipart/chunk与 S3 PutObject,移除伪造 ETag
CI:Build and verify artifacts ✅ / Workers Builds (×2) ✅ — 全绿。
新增改动的问题(按 P0/P1/P2 分级):
P2(可选优化):
-
💡
webdav.ts(webdavAuth重构后) — 普通已登录用户访问 meta 密码保护路径时,metaPassword强制为""(仅 guest 账号可将 Basic 密码透传为 meta 密码,与 Go 对齐)。这是正确的安全语义,但 WebDAV 客户端收到 403 时没有明确指引如何传递 meta 密码(S3 侧有?pwd=/x-meta-password机制,WebDAV 侧完全没有)。建议在文档或 403 响应体中补一句说明,避免用户以为是账号权限问题反复尝试。 -
💡
pkg/upload-limit.ts:36(新模块)—exceedsUploadLimit在!contentLength(无 Content-Length 头 或值为0)时直接返回null(跳过预检),随后readBodyWithinLimit再按实际字节数判定——逻辑是对的。但如果调用方只调用exceedsUploadLimit而不用readBodyWithinLimit(当前没有,但日后可能出现),会误以为通过了检查。建议在函数 JSDoc 中明确标注:"本函数只做 Content-Length 预检,必须配合readBodyWithinLimit才能提供完整保护",防止被独立误用。
无新增 P0/P1 问题。
旧问题解决情况:
-
✅
[P1] server/fs.ts:1655-1673(/fs/multipart/part→ 现为/multipart/chunk)Content-Length 可绕过上限 →8d9c339b已改用readBodyWithinLimit(c, true)并移除旧预检,修复到位;security_hardening.test.ts补充了专项测试(含省略 Content-Length 场景)。 -
✅
[P1] s3.ts:206(PutObject)完全没有大小限制 →8d9c339b抽出pkg/upload-limit.ts模块,fs.ts/s3.ts共用;S3 侧改用标准EntityTooLarge错误码,不对称问题彻底消除。 -
✅
[P2] s3.ts:206ETag 用Date.now().toString(16)伪造内容摘要 →8d9c339b移除 ETag,响应体注释详细说明原因(边缘运行时无便宜的 MD5 通道),宁缺毋误,处理得当。 -
✅
[P2] pkg/permission.tsnormalizeSegments缺越根钳制测试 →8d9c339b在security_hardening.test.ts补充了normalizeSegments("/a/../../b") === "/b"等 5 条越根用例,以及getActualPath/isWithinUserRoot的完整矩阵覆盖。
🎯 结论:✅ Approve — 4 条旧问题全部修复,第三轮 meta ACL / JWT 密码指纹大改动结构清晰、测试覆盖到位(372 项全绿)、CI 全绿;两条 P2 是文档级建议,不阻塞合并。
回应第二轮评审(v3→v4,Approve)的两条 P2: P2-1 WebDAV 侧 meta 判定失败时只返回裸 403,用户容易误判成账号权限问题。现统一改为 metaForbidden():403 + 一句指引(WebDAV 无法携带目录密码,请改用 Web 界面或 /api/fs/* 带 password),措辞刻意不区分密码/read_users/write_users,不泄露具体限制类型。共 8 处(PROPFIND、GET/HEAD、PUT、MKCOL、DELETE、MOVE、COPY×2)统一使用。 P2-2 pkg/upload-limit.ts 的 exceedsUploadLimit 只做 Content-Length 预检,被独立调用会误判为「已通过检查」。现补充 JSDoc 明确它不是完整保护,并取消导出(模块内仅供 readBodyWithinLimit 使用),从接口层面消除误用。 新增回归测试:WebDAV 受 meta 保护路径返回带指引的 403(不触发存储层)。 验证:tsc 0 error;server 196 / model+pkg 81 / store 12 / drivers 151 全绿。
pikachuren
left a comment
There was a problem hiding this comment.
🙏 感谢 @PIKACHUIM 提交!
🤖 AI 自动审核声明:本评审报告由 AI 自动生成,当前使用 Claude Sonnet 5 模型进行分析。
🔄 增量评审:v4 → v5
本轮新增 1 个 commit(e7c16a01),改动文件:src/backend/pkg/upload-limit.ts、src/backend/server/webdav.ts、src/backend/server/security_hardening.test.ts,CI 全绿。
新增改动的问题:无新增问题。
旧问题解决情况(v4 两条 P2):
-
✅
webdav.tsWebDAV meta 403 缺客户端指引 → 新增metaForbidden()辅助函数,将所有 WebDAV meta 拦截点(PROPFIND/GET/PUT/MKCOL/DELETE/MOVE/COPY,共 8 处)统一换为含说明的响应体,明确告知调用方 WebDAV 无法携带 meta 密码的原因及正确操作路径(web UI //api/fs/*);措辞刻意不区分具体限制类型(密码/read_users/write_users),避免信息泄露。配套在security_hardening.test.ts新增 65 行回归用例,断言响应体含directory-level access control、meta password、web UI|api/fs,覆盖到位。 -
✅
pkg/upload-limit.tsexceedsUploadLimit()应标注"必须配合readBodyWithinLimit使用" → 函数改为非导出(去掉export),同时补充 JSDoc 明确说明"仅做 Content-Length 预检,Content-Length 可被省略或伪造,缺失时直接放行,单独调用会重新引入绕过缺陷;刻意不导出以防止日后被独立误用"。实现与注释两层防护,处理比建议更彻底。
🎯 结论:✅ Approve — v4 两条 P2 全部修复,且均有测试覆盖,CI 全绿,无遗留阻塞项。
AI 代码评审(补充意见)基于对本 PR 关键源码的审查( 结论:系统性的安全加固 PR,覆盖 token 吊销、路径越权、跨协议权限旁路(S3/WebDAV/dirs)、SSRF、上传限额等多个真实问题,代码质量和自证(历史缺陷均附实测复现方式)比较扎实,建议合并。 已确认做得好的地方
建议关注的风险
以上基于当前最新 commit 的代码审查,仅供参考,最终请以人工评审结论为准。 |
…th-isolation 冲突仅 src/backend/server/fs.ts 一处(来自 #75 文件树/链接缓存),解决口径: 1) /fs/upload/{create,part,complete}:保留本分支的删除(按评审决定,分片只保留官方 /fs/multipart/*)。main 在该端点内新增的 invalidatePaths 随之移除;其余缓存失效点全部保留,写操作的失效集中在 internal/op/storage.ts(6 处),不受影响。 2) /fs/get_direct_upload_info:main 新增的预失效代码引用 reqPath/file_name,而本分支已把校验后的目录/文件名归一化为 dirPath/dstPath(沿用 main 的内联拼接会指向未归一化路径)。改为 invalidatePaths([dirPath, dstPath]),语义等价且沿用已校验路径。 缓存隔离性核对(不削弱本分支的安全不变量): - 缓存键为 (storage.id, 存储内相对路径),与用户无关,不缓存任何按用户计算的数据; - 所有缓存读取都在鉴权/ACL 之后:/fs/* 的 meta 判定先于 listItems/getItem,WebDAV/S3 的 meta 判定同样先于存储调用,/d、/p 的链接缓存(raw.ts)在验签之后; - meta.hide 过滤在 route 层逐项执行,缓存不改变「按请求过滤」的语义。 验证:tsc 0 error;server 197 / cache 24 / model 39 / pkg 42 / store 12 / drivers 151 全绿(共 465 项)。
|
忽略风险1、2、3,均不存在 |
- 冲突解决:采用上游 OpenListTeam#92 的 isRestrictedIpv4 + parseIpv6Groups 数值分组实现, 删除本分支与其重复的 isSafeIpv4 / isSafeIpv6Literal if-链版本 - 回归测试 http_ssrf.test.ts 保留全部原有用例,并补上 OpenListTeam#92 新覆盖的 6to4 (2002::/16) / NAT64 (64:ff9b::/96) 内嵌 IPv4 与组播 ff00::/8 用例 - http.ts 顺手按仓库 prettier 规范格式化 3 处(无语义变更)
fix(authz): 授权校验、路径隔离与元信息 ACL 全面对齐 Go
一、背景与动机
一次针对本仓库的安全评审(代码走查 + 隔离环境 PoC 实测)确认了若干授权、
隔离与协议一致性问题。它们的共同特征是:
在 7 天有效期内继续生效;
base_path,各协议入口各自为政;
/api/fs/*有 meta 判定,而/dav、/s3、/fs/dirs、/fs/search、/fs/archive/*完全没有,形成跨协议权限旁路;WRITE_CONTENT权限位,与实际权限模型不符。这些缺陷不会把数据泄露到公网,但会在多用户 / 多协议部署下破坏权限模型。
本 PR 在不改接口结构、不需要前端改动的前提下逐项修复;需要兼容性设计的
问题(S3 SigV4、任务队列、归档格式等)留作后续,见 §七。
二、三轮修复总览
e3a95c222dd1979bc81753ab8361origin/main(#93:EdgeOne 产物改为部署期生成)2.1 第一轮(
e3a95c2)要点checkAdminAuthpayload.role,从不检查jti—— 注销/降权后旧令牌仍可访问后台(实测 200)jti未吊销 + 用户存在未禁用 + 当前 role 仍是管理员authUserFromReqdisabled判断(S3 网关直连复用本函数)if (!user || user.disabled) return null/bucket/key—— 只读/禁用用户可写、受限用户可跨目录读写PutObject需WRITE_CONTENT、DeleteObject需DELETE;路径映射为<base_path>/bucket/key并isWithinUserRoot复核;bucket/key语法校验getActualPath..留给下游折叠 ——/photos/alice+/../bob/x→/photos/bob/x(用户根被越过)normalizeSegments()(栈折叠、空栈钳制在根),先折叠再拼接;导出isWithinUserRoot()供网关复检/login、/login/hash的 OTP 失败分支直接return,不计数 —— 6 位 TOTP 可无限爆破recordLoginFailure,与密码失败共用 IP+用户名 / 全局计数与指数退避2.2 第二轮(
22dd197)要点READ_ARCHIVES/DECOMPRESS位;解压目标未做base_path映射;ZIP 条目../可逃出目标目录READ_ARCHIVES;decompress 需DECOMPRESS+ 写权限;读取与/fs/get共用 meta+密码判定;目标经getActualPath+isWithinUserRoot;新增safeArchiveEntryPath限定条目为「只能向下展开的相对路径」map格式把 JSON 解析失败转成null,随后/api/public/init/setup重新开放初始化并覆盖配置Content-Length,缺失该头即不拦截readBodyWithinLimit,按实际收到字节复核;/put、/form、分片全部接入isSafeUrl放过 IPv4-mapped IPv6 私网地址(http://[::ffff:127.0.0.1]/)isRestrictedIpv4复用rename/remove/move/copy统一使用canWriteRENAME/DELETE/MOVE/COPYwrite_usersACL 只影响列表write提示,写接口不复查deniedByMetaWriteAcl,在全部写入入口执行path+size复用,无 owner;status无需认证owner_id+owner_root;chunk/complete/status 全部校验所有者key格式先删后写、saveDb失败仍更新缓存、D1 分批无重试key改为先写后删;持久化失败回滚内存与请求缓存;D1 分批单片重试并报告已提交片数补充加固:WebDAV/S3 源路径与
Destination均映射到调用者根目录并复核;归档下载改用与
/d、/p相同的safeProxyFetch(禁用自动跟随 + 逐跳 SSRF 断言);WebAuthn 挑战改为服务端签发、单次消费、5 分钟有效,新增 origin / UP / UV 校验
并拒绝已禁用账号。
三、第三轮(
9bc8175)细节3.1 目录级访问控制(meta ACL)跨协议打通 —— 本次核心
缺陷:meta 定义的密码保护、
read_users/write_users白名单只在/api/fs/list、/api/fs/get生效;下列入口完全不做 meta 判定,构成跨协议绕过:
/fs/dirs、/fs/search、WebDAV、S3。实测:直连被密码保护而401 的目录,能经这些入口枚举/读取。
修复:所有入口统一复用
pkg/meta的判定(getNearestMeta+canAccess/canRead/canWrite/canWriteContentBypassUserPerms)。/fs/dirsread_users(403 文案对齐 Go:password is incorrect or you have no permission);force_root仅管理员/fs/searchsearch()新增user/password选项,按父目录最近 meta 逐项过滤(对齐 GoSearchFiltered的 filter 闭包)PROPFIND/GET/HEADread_usersPUT/MKCOLWRITE_CONTENT或meta.write强制开放 +write_usersDELETEwrite_usersMOVEMOVE位(跨目录)/RENAME位(改名)+ 源/目标目录写判定COPYread_usersCOPY位 + 目标目录写判定GET/HEAD?pwd=/x-meta-password)+read_usersPUTWRITE_CONTENT或meta.write+write_usersDELETEwrite_usersWebDAV meta 密码通道对齐 Go
WebDAVAuth:仅 guest 账号会把 Basic密码继续作为 meta 密码(
if user.IsGuest() { MetaPassKey = password }),普通用户一律为空 —— 目录密码不能靠账号密码顺带绕过。
3.2
/fs/get_direct_upload_info对齐 Go原实现只有
canWrite(user)一道门槛,其余四重校验全部缺失。现补齐:validateFileName(file_name):拒绝空值、/、\、.、..—— 防止签发指向目标目录之外的直传凭证(如 S3 预签名 URL);
WRITE_CONTENT或meta.write强制开放(此前write_content_bypass只在响应里展示、实际不生效);meta.write_users白名单;可能命中嵌套挂载点);
Overwrite: false时目标已存在 → 403file exists。3.3 JWT 密码指纹(
pwd_ts)—— 改密即全端下线缺陷:JWT 只有
{id, username, role, exp, jti},没有密码指纹。Go 每次请求都用
PwdTS与数据库当前值比对(改密 = 旧令牌立即失效);TS 侧改密后旧令牌在有效期内(7 天)继续可用,除非用户显式登出。
修复(对齐 Go
common.UserClaims.PwdTS+middlewares.Auth):pkg/password.ts新增passwordStampOf()/isPasswordStampValid();setUserPassword本就刷新pwd_update_at;pwd_ts:/api/auth/login、/api/auth/login/hash、SSO(
sso.ts)、WebAuthn(webauthn.ts);authUserFromReq、getUserFromContext、checkAdminAuth。3.4 管理员用户管理对齐 Go
user/createrole可随意传1/2,可批量制造管理员/guestadmin or guest user can not be createduser/updaterole、disabled无限制 —— 可提权、可禁用唯一管理员把自己锁死role(role can not be changed);拒绝禁用管理员(admin user can not be disabled)user/deleteid===1,guest(id=2)可被删,id≠1 的管理员不受保护cannot delete admin or guest3.5 WebDAV 认证加固
/api/auth/login共用同一套机制(对齐 Go 的
model.LoginCache在登录与 WebDAV 间共用),超出阈值返回 429;此前
/dav可无限次尝试密码。OPTIONS匿名放行(对齐 Go):客户端在提供凭据前需要探测 DAV 能力。3.6 顺带修复 5 个既有
tsc报错db_cipher.test.ts重复type DbCipher导入;fs.ts/form的getUploadSizeLimit(c)少传参;multipart/init的user空值收窄。(这些是改动前就存在的问题,本次一并修掉,使
pnpm lint通过。)四、按评审决定的功能增删
4.1 删除(4 项)
salt+saltedHash,无salt一律不可验证)。保留 F-11「不静默重置遗留哈希」的保护user/create自动生成随机密码并回显/fs/upload/{create,part,complete}/fs/multipart/*(Go 有的才有)Face404动画组件4.2 保留(TS 独有,评审确认保留)
CSRF 签发接口、审计日志系统与查询/统计/清理、全局安全响应头
(CSP/HSTS/X-Frame-Options 等)、
ip_limit/traffic_limit进程内限流、管理员用户列表的
otp布尔字段、env_check/kv/status/health(z)自检接口、插件管理系统、逐跳 SSRF 校验 + 受信主机白名单、
POST /task/refresh(Cron 触发的存储 Token 刷新)、/fs/archive/decompress的 zip-slip + 用户根双重防护。
五、验证
5.1 自动化测试
security_hardening.test.ts)新增
src/backend/server/security_hardening.test.ts覆盖:user/create拒绝 admin/guest 角色;user/update拒绝改角色、拒绝禁用管理员(并允许合法的权限位/base_path/禁用普通用户);user/delete按角色保护(含 id≠1 的管理员),普通用户仍可删除;pwd_ts指纹)。更新
default_credentials.test.ts:随机密码断言改为 Go 语义(空密码、不回显、哈希仍为 64-hex + 盐)。
5.2 静态检查
npx tsc -p tsconfig.json --noEmit→ 0 error(本轮之前存在 5 个历史报错,已一并修复)。5.3 PoC 口径
评审阶段的行为验证全部在隔离环境完成:合成密钥、注入式内存后端、
临时文件、被禁止联网的
fetch;未触碰真实账号、真实网盘、生产数据库或私网资源,临时测试文件未进入提交。
六、行为变化与兼容性
6.1 需要关注的变化(第三轮)
PwdTS;上线时全体用户会重新登录一次ADMIN_PASS重置或管理员改密;不会静默重置user/create不传密码/fs/dirs、/fs/search、WebDAV、S3 访问受保护路径read_users/write_users生效?pwd=或x-meta-password/fs/upload/*调用方/fs/multipart/*OPTIONS/fs/get_direct_upload_infoOverwrite:false且已存在 → 403WRITE_CONTENT6.2 明确未改动的契约(避免误伤)
/d、/p、/sd增加登录要求(签名直链需保持可被播放器/下载器匿名消费);base_path(官方前端已在 URL 中携带);customize_head/customize_body(这是功能);base_path="/"用户的 S3 / WebDAV 路径映射完全不变。七、遗留 / 后续批次
(避免破坏现有客户端);如需启用建议单独评估与灰度。
/fs/put、/fs/form的Overwrite/As-Task/X-File-Md5|Sha1|Sha256/Last-Modified请求头;move/copy 的
overwrite/skip_existing/merge冲突策略;跨存储 move/copy 显式报错或流式转移;
driver/list改为按实际驱动动态生成;set_guangyapan;分享的运行期二次校验(创建者权限 /base_path)与访问计数去抖;archive_extensions如实返回当前支持格式。LDAP 登录、S3 SigV4 / ListObjectsV2 / 多段上传、种子 API 路径与 Go 对齐。
八、提交信息
第三个提交(本轮)正文:
九、建议的验收要点
base_path用户的签名下载继续正常(不得 401);read_users保护的目录:/fs/dirs、/fs/search、WebDAV、S3 一律拒绝(带正确密码时放行);
write_users白名单外的用户经 WebDAV / S3 写入 → 403;base_path="/"用户的 S3 / WebDAV 路径映射与修复前完全一致;/fs/multipart/*分片上传流程正常(/fs/upload/*已移除);附:关联前端 PR
OpenListTeam/OpenList-Frontendfix/remove-face404/1888afechore: remove the 404 face animation (Face404)src/components/Face404.tsx;移除components/index.ts导出;Error组件去掉show404Face属性;pages/home/Obj.tsx不再传该属性(共 4 文件,−179 行)Face404/show404Face残留引用