#82173active
net/http, mime/multipart: better control over form parsing
新規提案
要約
AIによる要約であり、誤りを含む場合があります。
概要
net/http のフォーム解析の挙動を開発者が細かく制御できるようにする提案です。目的は3つあります。URLクエリパラメータだけを安全に取り出せる新しいAPIを用意すること、意図しないmultipartフォーム解析を止められるようにすること、解析するデータのサイズ(メモリ・ディスク・値の個数)に上限を設定できるようにすることです。提案者はnet/httpのメンテナであるDamien Neil(@neild)です。
ステータス変更
(新規) → active
2026年10月12日に起票され、10月29日に @aclements がProposal Review Meetingの検討対象(active列)に追加しました。まだ承認されたわけではなく、これから週次の会議で議論が始まる段階です。起票から短期間でactiveになったのは、FormValue が招くリソース消費が実際にセキュリティ上の問題になっていることが背景にあると考えられます。コメントでは、関連する脆弱性 GO-2026-4341 の報告者も賛意を示しています。
技術的背景
現状の問題点
Request.FormValue は次の値をすべてまとめて扱います。
- URLクエリ
application/x-www-form-urlencodedのボディmultipart/form-dataのボディ
req.URL.Query().Get()は呼ぶたびにクエリを解析し直します。そのため、クエリを効率よく取り出す手段としてFormValueがよく使われています。
しかしFormValueは内部でボディの解析まで実行します。ボディのサイズを制限していないと、urlencodedでは最大10MiB、multipartでは値10MiBとファイル32MiBがメモリに読み込まれます。さらにファイルデータは上限なしで一時ファイルとしてディスクに書き込まれます。結果として、クエリパラメータを1つ読みたかっただけなのに、42MiBのメモリと無制限のディスクを消費する可能性があります。
提案された解決策
- クエリ専用API:
Request.Query url.Valuesフィールド、ParseQuery()、QueryValue(key)を追加します。既存のParseFormもQueryを設定するようにします。 - 暗黙のmultipart解析の無効化:
Server.DisableImplicitMultipartForm boolを追加します。有効にするとFormValueとPostFormValueはmultipartを解析しません。FormFile、MultipartReader、ParseMultipartFormは明示的な要求として扱われ、従来どおり解析します。 - サイズ制限の型:
http.QueryLimits{MaxValueCount}http.PostFormLimits{MaxMemory, MaxValueCount}multipart.FormLimits{MaxMemory, MaxFileMemory, MaxFileDisk, MaxPartCount, MaxHeaderValueCount}
いずれもゼロ値は既存のデフォルト(10000件、10MB、32MBなど)と同じ意味です。負の値は「解析を常に失敗させる」または「保存しない」を意味します。
- 設定する場所:
- サーバー全体:
Server.QueryLimits、Server.PostFormLimits、Server.MultipartFormLimits - ハンドラごとの上書き:
ParseQueryWithLimits、ParsePostForm、ParsePostFormWithLimits、ParseMultipartFormWithLimits - multipartの読み込み:
multipart.Reader.ReadFormWithLimitsを追加
- サーバー全体:
- ReverseProxy: 転送先URLのクエリパラメータ数がサーバーの上限を超える場合、URLを再エンコードする必要があると記載されています。
これによって何ができるようになるか
- API・JSONサーバー: multipartを扱わないサーバーは
DisableImplicitMultipartFormを設定するだけで、ディスクへの意図しない書き込みを防げます。 - 検索・一覧エンドポイント:
QueryValueを使うと、ボディの値が混ざる心配も過大な読み込みもなく、クエリだけを効率よく取得できます。 - ファイルアップロード:
MaxFileDisk: -1を指定すると一時ファイルを一切作らず、メモリ内だけで処理できます。エンドポイントごとに上限を変えることもできます。
コード例
// Before: クエリを読むだけのつもりでも、ボディのフォーム解析が走る
func handler(w http.ResponseWriter, r *http.Request) {
page := r.FormValue("page") // multipartボディなら最大42MiB+ディスク消費の可能性
_ = page
}
// After
srv := &http.Server{
DisableImplicitMultipartForm: true,
MultipartFormLimits: multipart.FormLimits{
MaxFileDisk: -1, // 一時ファイルを作らない
},
}
func handler(w http.ResponseWriter, r *http.Request) {
page := r.QueryValue("page") // URLクエリのみ。ボディは読まない
_ = page
}
func upload(w http.ResponseWriter, r *http.Request) {
err := r.ParseMultipartFormWithLimits(multipart.FormLimits{
MaxFileMemory: 8 << 20,
MaxPartCount: 10,
MaxFileDisk: -1,
})
// ...
}
議論のハイライト
- 一時ファイルの後片付け: @seankhliao は、
Request.WithContextなどでリクエストのシャローコピー(浅いコピー)を作ると一時ファイルが自動削除されない問題(#58809)を指摘しました。ParseMultipartFormWithLimitsがクリーンアップ関数を返し、defer cleanup()で後片付けする形を提案しています。@neild は、シャローコピーがハンドラごとの状態を参照する方式なら修正できる可能性があるとしつつ、「一時ファイルは非常に危険なので、ほとんどの人はディスク保存自体を無効にするのが最善だろう」と述べています。 URL.Query()のキャッシュ案は却下: @jub0bs はURL.Query()が解析済みのRequest.Queryを再利用する案を出しました。@neild は、URL.Query()が毎回新しいmapを返すことに依存するコードがあるため互換性を壊すと説明し、提案者も納得しています。- GODEBUGとの関係: @nicholashusin は、
QueryLimits.MaxValueCountがGODEBUG=urlmaxqueryparamsを上書きしない仕様は他の制限と一貫しないと指摘しました。代わりにQueryLimitsをnet/urlに移し、url.ParseQueryWithLimitsを追加して、ゼロ値ならGODEBUGに従い、非ゼロならGODEBUGを上書きする案を出しています。 - 複雑さへの懸念:
RequestにQuery、PostForm、Formと3つのurl.Valuesフィールドが並ぶことになります。提案者も「将来的にFormの非推奨化を検討する可能性がある」と認めています。@jub0bs はnet/httpがさらに複雑になることを心配し、v2の登場を望む声を上げています。 - 提案本文では、メソッド名が
ParsePostFormWithLimits(説明文)とParseFormWithLimits(シグネチャ)とで食い違っています。最終的な名前は今後の議論で決まると考えられます。