TL;DR
- AIエディタはほぼ常に、設定なしで
cors()をデフォルト適用しがちです -- これによりAccess-Control-Allow-Origin: *が設定されます - 認証が必要なAPIに対するワイルドカードCORSは、ユーザーをクロスサイトリクエスト攻撃にさらします
- 修正:ワイルドカードを、環境変数で制御する明示的なオリジン許可リストに置き換えます
私はCursorだけで開発されたサイドプロジェクトをレビューしていました。Expressのバックエンドはきれいに見えました -- 構造化されたルート、しっかりしたエラーハンドリング、適切な認証ミドルウェア。そこで次にCORSの設定を確認しました。
app.use(cors()); // defaults to Access-Control-Allow-Origin: *
1行です。Cursorがスターターテンプレートから自動提案したもの。フロントエンドが開発中に文句を言わなくなるので本番に残していました。が、これは本番でした。
ワイルドカードCORSはSQLインジェクションに比べると無害に感じられます。すぐにデータが漏れるわけではありません。でも、クッキーやセッショントークンを使うAPIであれば、ワイルドカードCORS設定は「あなたのログイン中のユーザーになりすまして」認証済みリクエストを、ユーザーに気づかれない形で実行できることを意味します。攻撃者は、任意のウェブサイト(フィッシングページや悪意ある広告iframeなど)を用意するだけで可能です。
危険なパターン(CWE-942)
CursorやClaude Codeは、動くExpressバックエンドを求めるとほとんどの場合、次のようなものを提案します:
// CWE-942: Permissive Cross-domain Policy
const express = require('express');
const cors = require(' cors');
const app = express();
app.use(cors()); // Allows all origins -- no questions asked
app.use(express.json());
app.get('/api/user/profile', authenticate, (req, res) => {
res.json(req.user);
});
引数なしでの cors() の呼び出しは、すべてのレスポンスに Access-Control-Allow-Origin: * を設定します。するとフロントエンド側で、fetch呼び出しに credentials: true を追加した場合、モダンブラウザはリクエストをブロックし、CORSエラーを投げます。そこで多くの開発者は、次のように「直したつもり」になります:
// Even worse
app.use(cors({ origin: '*', credentials: true }));
ブラウザはこの組み合わせもブロックします(認証情報を扱う場合、ワイルドカードのオリジンは使えません)。ただし重要なのは意図です。開発者は、コンソールのエラーを黙らせるために、最大限に許容的な設定へと段階的に誘導されているのです。
AIエディタがこれを生成し続ける理由
学習データは「動く」ではなく「安全である」に偏っていません。「これで動く」を示す情報が「安全である」を示す情報より多いのです。何百ものチュートリアル、Stack Overflowの回答、READMEファイルでは、フロントエンドをバックエンドと通信させるためのワンライナーとして app.use(cors()) が紹介されています。モデルはそれを学習しています。Cursorがバックエンドのスターターを生成するときは、すぐに動くことを最適化します -- ワイルドカードCORSはそれを実現するので、生成されてしまいます。
セキュリティ上の影響は、ずっと後になってようやく、あるいはまったく表面化しないことさえあります。ほとんどのプロジェクトでは、セキュリティレビューが行われません。
実際の修正
ワイルドカードを明示的なオリジン許可リストに置き換えます。リストは環境変数に保持して、ステージングと本番の設定を分離したままにしてください:
// Explicit origin allowlist
const allowedOrigins = process.env.ALLOWED_ORIGINS
? process.env.ALLOWED_ORIGINS.split(',')
: [];
app.use(cors({
origin: (origin, callback) => {
// Allow server-to-server requests (no Origin header)
if (!origin) return callback(null, true);
if (allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error(`CORS blocked: ${origin}`));
}
},
credentials: true // safe -- origin is explicitly verified beforethis applies
}));
あなたの .env:
ALLOWED_ORIGINS=https://yourdomain.com,https://www.yourdomain.com
cors ライブラリを使っていない場合:
// 手動のヘッダー -- 明示的で監査可能
app.use((req, res, next) => {
const origin = req.headers.origin;
if (allowedOrigins.includes(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Credentials', 'true');
}
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
if (req.method === 'OPTIONS') return res.sendStatus(204);
next();
});
重要な違いはこれです。ワイルドカードをブロードキャストするのではなく、検証済みの origin をそのまま返していることです。すべての CORS リクエストに対して、明示的な回答が返ります。
もう一点 -- semgrep の設定にこれを追加して、コードレビューに到達する前にワイルドカード CORS を検知できるようにしてください:
rules:
- id: cors-wildcard
patterns:
- pattern: cors({ ..., origin: '*', ... })
- pattern: cors()
message: Wildcard CORS detected -- replace with explicit allowlist
severity: WARNING
languages: [javascript, typescript]
私はこれのために SafeWeave を動かしています。Cursor と Claude Code に MCP サーバーとして組み込み、私が次に進む前にこれらのパターンをフラグ付けします。とはいえ、semgrep を使った基本的な pre-commit フックでも、ワイルドカード CORS の設定の大半は数秒で検知できます。重要なのは、どのツールを使うにせよ、早い段階で見つけることです。



