如何在静态网站(github pages)上提供无需账号的文件上传页面?
可以实现:页面继续托管在 GitHub Pages 或 Codeberg Pages,文件由独立的上传服务接收。没有预算时,优先考虑借用已有的 EU 托管 Nextcloud,提供“仅上传”的文件投递链接。需要自定义界面时,再增加一个小型上传接口。
不建议把 bot 的长期 token 放进网页。任何访客都能提取它,绕过上传页面,直接使用这个 token 的全部权限。
静态网站为什么需要另一个服务接收文件?
GitHub Pages 托管 HTML、CSS、JavaScript 等静态文件,Codeberg Pages 也是静态网站托管服务。浏览器可以运行上传代码,但接收和保存文件需要服务端接口。在仓库中添加 upload.html 并不会创建这样的接口。GitHub Pages 文档、Codeberg Pages 文档
文件可以按这个流程提交:
协作者打开静态页面
→ 上传服务接收文件
→ 文件进入待审核区
→ 维护者审核后导入项目仓库
上传者无需 GitHub 或 Codeberg 账号。维护者则需要配置接收服务,并管理最终导入仓库的文件。
bot 账号和公开 token 有哪些问题?
token 放进浏览器后就不再是秘密
无论 token 写在 JavaScript 中、由另一个接口返回,还是通过构建环境变量注入页面,只要浏览器能使用它,访客就能从代码、运行时或网络请求中取得它。压缩和混淆代码也无法保密。
攻击者拿到 token 后,可以自行构造请求,修改文件路径、分支和内容,绕过页面上的文件大小检查、扩展名限制和提交按钮频率限制。
“指定分支”和“仅写入”需要重新确认
以 GitHub 为例,fine-grained personal access token 可以限制到指定仓库和权限,但这不等于“只能向某个分支追加文件”。Contents 的写权限包含读权限,也允许修改或删除文件。GitHub token 权限说明
上传 API 请求中的 branch 参数只指定本次操作的目标分支:
{
"branch": "incoming"
}
调用者可以修改这个参数。操作最终能否执行,取决于服务端权限和分支规则。GitHub Contents API 文档
分支保护可以限制推送,但需要检查规则的覆盖范围、例外和绕过权限。它也不会自动实现“允许新增文件、禁止修改别人文件”的投递箱功能。GitHub 分支保护文档
迁移到 Codeberg 后,应按当时的 Forgejo 实例配置重新核对权限,不能直接套用 GitHub 的 token scope 或分支规则。
即使确实只能写入接收分支,仍有风险
限制写入分支可以缩小影响范围,但接收分支仍可能遭到滥用:
- 大量垃圾文件和提交会消耗存储、API 配额与审核时间。即使删除文件,内容仍可能留在 Git 历史中。
- 如果权限允许修改或删除文件,别人已经提交的材料也可能被破坏。
- 公开仓库中的接收分支无法私密接收文件,尚未审核的材料也可能被他人读取。
- 如果提交会触发 CI、预览或部署,不可信文件就可能进入自动处理流程。具体风险取决于工作流如何读取和执行这些内容。
- 所有请求共用同一个凭据,难以单独撤销某个滥用者的权限,提交记录也不能可靠证明实际上传者的身份。
bot 可以在服务端自动将文件导入仓库,凭据应保存在服务端。
方法一:使用 Nextcloud 的文件投递链接
如果项目成员、合作组织或社区已经有位于 EU 的 Nextcloud,使用它最省维护工作。Nextcloud 的匿名上传功能允许访客在没有 Nextcloud 账号、无需登录的情况下提交文件,同时禁止访客浏览接收目录中的文件。Nextcloud 匿名上传文档
可以这样设置:
- 创建一个专门接收投稿的文件夹。
- 创建公开分享链接,选择
File drop (upload only)或对应的仅上传选项。 - 按需设置有效期或分享密码。
- 在静态网站中添加上传入口。
- 维护者审核文件后,再将其导入仓库。
可用选项可能受 Nextcloud 版本和管理员配置影响。Nextcloud 分享权限文档
静态页面添加一个链接即可:
<p>无需注册账号。上传的文件将在审核后加入项目。</p>
<a href="https://cloud.example.org/s/REPLACE_WITH_SHARE_ID">
打开文件上传页面
</a>
域名和分享 ID 都是占位符,需要替换为实际地址。
这个投递链接的权限有限,但仍可能被转发或用于垃圾上传,因此需要能随时关闭链接、清理文件,并控制配额。
Nextcloud 软件免费,托管却未必免费。零预算的前提是已有可用资源,或有人愿意提供托管。
方法二:静态表单连接小型上传接口
如果必须使用自己的上传页面,可以在已有的 EU 服务器上运行上传接口。网站仍由 Pages 托管,上传请求发送到另一个域名。
表单可以这样写,向自建接口提交一个文件:
<form
action="https://uploads.example.org/upload"
method="post"
enctype="multipart/form-data"
>
<label for="file">选择文件</label>
<input id="file" name="file" type="file" required>
<button type="submit">上传</button>
</form>
使用前,必须将 https://uploads.example.org/upload 替换为实际实现的接收端点。上传接口应返回成功或失败页面。
服务端至少需要完成以下工作:
- 在接收过程中限制请求大小,同时设置上传频率、总容量和文件数量上限。
- 按项目需求检查文件类型,不能只信任扩展名或客户端提供的
Content-Type。 - 由服务端生成文件名,避免路径穿越、同名覆盖,也不允许用户指定保存位置。
- 将文件存入非公开的待审核区,审核前不自动执行、发布或送入构建流程。
- 提供清理机制,并能在遭到滥用时暂停接收。
这些限制都必须在服务端落实。前端检查能改善体验,却无法约束自行构造的请求。OWASP 文件上传指南
普通 HTML 表单可以跨域提交,浏览器随后跳转到接收端的结果页。如果使用 fetch() 上传,并在原页面读取响应,接收端还需要正确配置 CORS。CORS 控制浏览器的跨域访问行为,无法阻止脚本或其他 HTTP 客户端直接调用接口。MDN CORS 文档
需要自动提交到 Git 时,应由后台固定仓库、分支和目标路径,并在服务端保存最小权限凭据。最好使用独立的接收仓库,与项目源码和部署流程隔离。
方法三:对象存储配合短期预签名上传
文件较大,或已有对象存储资源时,可以让浏览器直接上传到存储服务:
- 浏览器向后台申请上传授权。
- 后台检查限流和配额,并决定、生成对象名。
- 后台签发短期预签名上传 URL,限定可执行的操作和目标对象。
- 文件进入私有待审核区,审核通过后再导入项目。
这样无需向访客公开长期存储凭据。不过,预签名 URL 本身也是凭据。以 Amazon S3 为例,它在到期前可以重复使用,向同一个对象 key 上传还会覆盖已有对象,因此不能默认将其视为一次性链接。S3 预签名 URL 文档
不同存储服务对大小限制、上传策略和覆盖保护的支持各不相同,需要分别核对。浏览器声明的文件大小也不能代替服务端或存储端的限制。
这个方案仍需要签名服务,也会产生存储和流量成本。服务采用 S3 兼容协议,并不代表它位于 EU。
零预算和 EU 偏好下怎么选?
优先寻找已有的 EU 托管文件投递服务,例如合作方的 Nextcloud。这样可以减少自行维护上传接口的工作,以后更换 Pages 托管平台时,也能继续使用原来的上传入口。
如果已有 EU 服务器,再考虑自建上传接口。选择有免费额度的服务前,需要确认它支持文件上传、允许匿名接收,且容量和地域符合要求。
核对 EU 要求时,要分别确认服务商注册地、文件实际存储地,以及备份和日志所在地。如果要求所有相关数据都留在 EU,还需要同时检查网站托管、验证码和其他第三方请求。
无需账号上传,也可能留下个人信息,例如服务端日志、文件名和文件元数据。上传页应说明谁能访问文件、保存多久、审核后是否公开,以及如何申请删除。
备注:内容仅供参考。
