本文へ移動
aapp-hacks
← 記事一覧へ
著者
app-hacks
公開日
更新日
確認日

Cloudflare Pages / Functions / セキュリティ

Cloudflare Pages Functionsのお問い合わせ入力検証

Cloudflare Pages FunctionsでJSON、文字数、Origin、リクエストサイズ、エラー応答を検証するお問い合わせAPIの境界設計を解説します。

Cloudflare Pages Functionsのお問い合わせ入力検証

お問い合わせフォームでは、ブラウザ側の入力制限だけを信頼できません。リクエストは直接APIへ送れるため、Pages Functionの入口でメソッド、Origin、Content-Type、本文サイズ、JSONの形、各フィールドを順に検証します。メール送信やTurnstileの確認より前に入力境界を作ることで、不正な値を後段のサービスへ渡さないようにします。

お問い合わせリクエストをメソッドとOrigin、本文サイズ、入力項目の順で検証し、その後に認証とメール送信へ進める流れ

最初に受け付けるリクエストを絞る

APIはフォームの想定どおりのPOSTだけを受け取ります。許可しないメソッドや別Originからのリクエストは、本文を読んだり外部サービスへ接続したりする前に止めます。

if (request.method !== 'POST') {
  return json({ ok: false }, 405);
}

if (request.headers.get('Origin') !== siteOrigin) {
  return json({ ok: false }, 403);
}

if (!request.headers.get('Content-Type')?.includes('application/json')) {
  return json({ ok: false }, 415);
}

Originの確認はすべての不正送信を防ぐ仕組みではありませんが、サイトのフォームとして受けるリクエストの境界を明確にします。エラー時に詳細な内部条件を返しすぎると、攻撃者に検証ルールを与えるため、応答は最小限にします。

本文サイズは読み込み中にも制限する

Content-Lengthは参考になりますが、ヘッダー値だけを信用して本文を無制限に読み込んではいけません。ストリームから読むたびに合計サイズを確認し、上限を超えたら中止します。

let size = 0;
const chunks: Uint8Array[] = [];

for await (const chunk of request.body ?? []) {
  size += chunk.byteLength;
  if (size > 24_000) return json({ ok: false }, 413);
  chunks.push(chunk);
}

上限値は、受け付ける項目の最大文字数とエンコーディングの余裕を考えて決めます。フォーム本文を画像や添付ファイルの経路にしない場合は、別のアップロードAPIと混ぜないことも重要です。

JSONの形とフィールドを個別に確認する

JSONとして解析できても、配列、null、想定外の型を受け入れるべきではありません。オブジェクトだけを受け、文字列は空白を除去してから長さと形式を確認します。

const value = await request.json();
if (!value || Array.isArray(value) || typeof value !== 'object') {
  return json({ ok: false }, 400);
}

const name = typeof value.name === 'string' ? value.name.trim() : '';
const message = typeof value.message === 'string' ? value.message.trim() : '';

if (!name || name.length > 100 || message.length < 10 || message.length > 5_000) {
  return json({ ok: false }, 400);
}

メールアドレス、件名、Turnstileトークンにも、用途に応じた形式と上限を設けます。検証はブラウザとFunctionの両方に置けますが、信頼境界になるのはFunction側です。

応答を一定にして保存させない

成功・失敗ともJSONとして返し、Content-TypeとCache-Control: no-storeを付けます。入力エラー、認証失敗、外部メールサービスの失敗を、利用者向けのメッセージと内部ログで分けます。レスポンスに認証情報、メールAPIの応答本文、受信先アドレスを含めません。

入力検証は迷惑送信対策やメール配信の代わりではありません。Turnstileのサーバー側検証、レート制限、送信先の認証情報管理とは別に、入口のデータを小さく正しい形に保つ役割です。各段階を分けることで、失敗時の原因も切り分けやすくなります。

関連記事