TypeScriptとEdge Runtimeの境界を安全に実装する確認手順
TypeScriptの型が消える実行時境界、Edge Runtimeの制約、入力検証、認証・キャッシュ、ヘッダーを脅威ごとに確認する実装ガイドです。
読了目安:5分

TypeScriptが通ったことは、ネットワークから届くJSON、Cookie、ヘッダー、環境変数の値が安全だという証明にはなりません。TypeScriptの型はJavaScript出力時に消えるため、実行時の境界では別の検証が必要です。TypeScript公式資料も、型注釈が消去されてJavaScriptが生成される仕組みを説明しています。
この記事は「これを設定すれば安全」と断定するものではありません。アプリ固有の資産、攻撃者、データ、認証方式を整理したうえで、実装とテストを対応させるための確認手順です。
1. 信頼境界を先に列挙する
コードを書く前に、外部から値が入る場所を一覧にします。
| 境界 | 例 | 失敗時の影響 |
|---|---|---|
| URL・本文 | query、JSON、FormData | 不正処理、負荷増大 |
| 認証情報 | Cookie、Authorization | なりすまし、権限昇格 |
| キャッシュキー | path、header、user state | 他ユーザーへの応答混入 |
| 外部API | JSON、status、redirect | 誤処理、SSRF等の候補 |
| 秘密情報 | 環境変数、鍵 | 漏えい、署名偽造 |
| ブラウザ出力 | HTML、script、header | XSS等の候補 |
「内部APIだから信頼する」ではなく、境界ごとに入力元、許可形式、最大サイズ、失敗時の応答を決めます。
2. unknownから実行時に絞り込む
外部入力を型アサーションだけで確定させません。次の例は依存ライブラリを使わず、必要なキーと値を実行時に検査します。
type SignupInput = {
email: string;
displayName: string;
};
export function parseSignupInput(value: unknown): SignupInput {
if (typeof value !== "object" || value === null || Array.isArray(value)) {
throw new Error("invalid body");
}
const input = value as Record<string, unknown>;
if (
typeof input.email !== "string" ||
input.email.trim().length < 1 ||
input.email.length > 254 ||
typeof input.displayName !== "string" ||
input.displayName.trim().length < 1 ||
input.displayName.length > 80
) {
throw new Error("invalid fields");
}
return { email: input.email, displayName: input.displayName };
}
長さ制限や許可文字は業務要件に合わせます。OWASPの入力検証ガイドが説明するように、構文上正しいことと業務上妥当であることも分けて確認します。
3. Edge Runtimeの対応APIを実測する
Edge RuntimeはNode.jsランタイムと同一ではありません。VercelのEdge Runtime資料で利用可能なWeb APIと制限を確認し、実際のproduction相当環境で次をテストします。
- 使用する暗号・ストリーム・エンコーディングAPI
- 依存パッケージがNode.js専用APIを要求しないか
- タイムアウト、レスポンスサイズ、地域設定
- 失敗時に機密値がログや応答へ出ないか
地理的に分散していることだけでDDoS耐性や安全性が保証される、とは扱いません。
4. 認証とキャッシュを同じレビューで扱う
認証済み応答を共有キャッシュへ保存すると、入力検証が正しくても情報が混ざる可能性があります。各ルートで次を確認します。
- 認証と認可を別々に実施する
- ユーザー固有応答を共有キャッシュしない
- キャッシュキーに必要な差分が含まれる
- Cookie属性と有効期間を脅威モデルに合わせる
- トークン失効、鍵ローテーション、時計ずれをテストする
「アクセストークンは必ず15分」「Cookieなら安全」のような一律値は置きません。HttpOnlyはJavaScriptからの読出しを制限しますが、すべてのXSSやCSRFを解決する設定ではありません。
5. 秘密情報とデプロイ設定を検査する
秘密鍵が未設定のまま処理を続けないよう、起動またはリクエストの早い段階で明示的に失敗させます。
export function requireSecret(name: string): string {
const value = process.env[name];
if (!value) throw new Error(`Missing required secret: ${name}`);
return value;
}
秘密値そのものをログへ出さず、PreviewとProductionで別の値と権限を使います。Vercelを利用する場合はConformanceのルール一覧も補助検査として使えますが、自動ルールだけでレビューを完了させません。
6. CSPとヘッダーをReport-Onlyから検証する
CSPはブラウザ側の多層防御の一つです。OWASPのCSPガイドは、まずContent-Security-Policy-Report-Onlyで違反を観測してから強制する方法を説明しています。
外部スクリプトやnonceを含むサイトでは、実際のHTMLとロード対象を一覧化し、代表ページで違反と動作を確認します。CSPを付けたという事実だけで「XSSを防止した」と評価しません。
レビュー完了条件
- 各信頼境界に、許可形式・上限・失敗応答のテストがある
- 認証済み・未認証・権限不足を別ケースで試した
- ユーザー固有応答のキャッシュ挙動を確認した
- 必須秘密情報が欠けると安全側に失敗する
- ログとエラー本文へ秘密・個人情報が出ない
- CSPと主要ヘッダーを実HTMLで確認した
- Node.jsとEdge Runtimeの差をproduction相当で実行した
入力例と拒否理由を対応させる
上の関数は型と長さの境界を示す最小例で、メールアドレスの妥当性・所有確認・登録権限を完了する実装ではありません。空のメールや空白だけの表示名も拒否するよう補っていますが、業務上の許可文字や正規化は別途決めます。
| テスト入力 | 期待する結果 |
|---|---|
null、配列 | invalid body |
| emailが数値、空文字、254文字超 | invalid fields |
| displayNameが空白だけ、80文字超 | invalid fields |
| 必要な文字列が範囲内 | 2項目を返す。本人確認済みとは扱わない |
これらは設計用の期待値です。本番相当のAPIでは、不正入力を400系へ変換し、例外のスタックや秘密情報を返さないことまで別にテストします。
結論
TypeScript、Edge Runtime、CSPは、それぞれ異なる層の道具です。型チェックを入力検証の代わりにせず、エッジ配信を攻撃耐性の証明にせず、ヘッダーを安全性の保証にしないことが出発点です。脅威と境界を列挙し、各対策に失敗テストを対応させて初めて、変更後も再確認できる実装になります。
確認した一次情報
主要な主張については、本文中にも対応する資料へのリンクを残しています。
- Content Security Policy Cheat SheetOWASP Foundation · security-guidance · 確認: 2026-07-26
- Input Validation Cheat SheetOWASP Foundation · security-guidance · 確認: 2026-07-26
- Conformance rulesVercel · official-documentation · 確認: 2026-07-26
- Edge RuntimeVercel · official-documentation · 確認: 2026-07-26
- TypeScript for the New ProgrammerMicrosoft · official-documentation · 確認: 2026-07-26