AI生成APIにおけるCORSワイルドカード:Cursorがやってしまうこと

Dev.to / 2026/4/4

💬 オピニオンDeveloper Stack & InfrastructureTools & Practical Usage

要点

  • Express/Fastify のAI支援による雛形生成は、設定なしで `app.use(cors())` をデフォルトにすることが多く、その結果 `Access-Control-Allow-Origin: *` のような許容範囲の広いCORSになってしまいます。
  • ワイルドカードのCORSは、ブラウザの同一オリジンポリシーを事実上無効化し、任意のWebサイトが別オリジンでのリクエストを行えて、特に認証クレデンシャルがある場合にはレスポンスを読み取れる可能性があります。
  • セキュリティ上のリスクは、セッションクッキーまたはローカルストレージ内のトークンでリクエストが認証されると、悪意のあるページによる情報のサイレントな持ち出し(データ抽出)が可能になるため、より深刻になります。
  • 記事では、この不安全なパターンが残り続ける理由として、チュートリアルやボイラープレートで一般的であること、またAIコーディングツールの学習データにおいて「CORS設定」として関連付けられてしまっていることを説明しています。
  • 推奨される対処は、明示的なオリジン許可リストを実装し、CORSレスポンスで `Origin` ヘッダを反映する前にその値を検証することです。

TL;DR

  • AIエディタは、ほぼすべてのExpress/Fastifyの雛形で、設定ゼロのままデフォルトでcors()を使う
  • ワイルドカードCORSはブラウザの同一オリジン保護を無効化し、APIをあらゆるウェブサイトに公開してしまう
  • 修正: 明示的なオリジン許可リストを定義し、反映する前にリクエストのOriginヘッダを検証する

先月、友人たちの3つのサイドプロジェクトをレビューしました。いずれもCursorを使ってバックエンドを足場(スキャフォールド)作成していました。そのうち2つは、本番でワイルドカードCORSが有効になっていました。開発だけではありません。本番環境で、実在のユーザーデータを提供していました。

コードはまったく問題なさそうに見えました。Cursorが生成したExpressのセットアップは綺麗でした。ルーティングは整理され、エラーハンドリングも堅実でした。けれども、すべてのapp.jsの先頭には同じ行がありました:

// CWE-942: Permissive Cross-domain Policy
app.use(cors()); // wildcard - every origin allowed

この1行、設定ゼロのままでも、ブラウザにこう伝えます。つまり、あなたのログイン中のユーザの代わりとして、このAPIに対して認証付きリクエストを行うことが、世界中のあらゆるウェブサイトに許されると。

ワイルドカードCORSが実際に意味すること

ブラウザはデフォルトで同一オリジンポリシーを強制します。evil.com のページは、APIがそのオリジンを明示的に許可しない限り、your-api.com へのfetchのレスポンスを読み取れません。

引数なしでcors()を呼び出すと、Expressはこのヘッダをすべてのレスポンスに追加します:

Access-Control-Allow-Origin: *

アスタリスクは「任意のオリジンからのリクエストを受け付ける」という意味です。ブラウザのブロックが止まります。すると、ユーザが開いているどのタブもあなたのAPIにアクセスしてレスポンスを読み取り、(ユーザが認証済みであることを前提に)データを持ち出せます。

これは、セッションクッキーやローカルストレージに保存されたJWTと組み合わせたときに最も危険です。悪意のあるページがこっそり読み込まれ、認証済みユーザとしてリクエストを送り、結果を読み取ります。誰もアラートを受け取りません。

なぜAIエディタはこれを出し続けるのか

このパターンは至るところにあります。チュートリアルはもちろん、Stack Overflowで最も投票数の多いExpressのCORS回答では、app.use(cors()) が「“すべてのオリジンを許可する”」というコメント付きで紹介されています。同じ行を含むMERNのボイラープレートはGitHub上のいたるところにあります。

このデータで学習したモデルは、「ExpressでCORSをセットアップする」ことを、引数なしのcors()と結び付けます。そうするとリクエストが動きます。開発者は先に進みます。許容的なCORSが原因でデモに失敗することはありません。悪用されるまで、このバグは静かに潜んだままです。

修正: 明示的な許可リスト

// CWE-942 mitigated
const ALLOWED_ORIGINS = [
  'https://yourapp.com',
  'https://staging.yourapp.com'
];

app.use(cors({
  origin: function (origin, callback) {
    // allow server-to-server requests (no Origin header)
    if (!origin) return callback(null, true);
    if (ALLOWED_ORIGINS.includes(origin)) return callback(null, true);
    callback(new Error('Not allowed by CORS policy'));
  },
  credentials: true
}));

いくつか、特に注目すべき点があります:

!origin のチェックにより、サーバー間通信(curl、Postman、他のバックエンドサービス)を通過させられます。これにより、統合テストが壊れません。フロントエンドがクッキーやAuthorizationヘッダを送る場合は、credentials: true フラグが必要です。これがないと、ブラウザはクレデンシャルを黙って破棄します。

絶対に使ってはいけない組み合わせが1つあります。credentials: trueorigin: '*' の組み合わせです。ブラウザは、ワイルドカードオリジンに対してクレデンシャルを送信することを拒否します。この設定があると、認証が黙って壊れているのに、CORSは依然として大幅にオープンのままです。1つの設定ミスで、2つのバグが手に入るというわけです。

いま確認すべきこと

app.js または server.ts を開いて cors( を検索してください。どこかに空の括弧(空のパーレン)があれば、この問題が起きています。

あわせて確認してください:

  • レスポンスに実際に含まれているAccess-Control-Allow-Originヘッダ(開発者ツールのNetworkタブ)
  • セッションミドルウェアやCookie認証の背後にあるルート(重要なのはこれらです)
  • あなたのAPIゲートウェイまたはCDNのCORS設定 - アプリレベルの設定を、こっそり上書きしてしまう可能性があります

私はこのためにSafeWeaveを運用しています。これはCursorとClaude Codeに、MCPサーバとしてフックし、先に進む前にこれらのパターンをフラグ付けします。それでも、semgrepとgitleaksを使った基本的なpre-commitフックでも、この投稿に含まれる内容のほとんどは検知できます。重要なのは、どのツールを使うにせよ、早い段階で検知することです。