Skip to content

fix(web): Map API トークンエンドポイントに Origin チェックを追加 (SOR-28)#180

Open
Sora4431 wants to merge 2 commits intomainfrom
fix/api-key-origin-check
Open

fix(web): Map API トークンエンドポイントに Origin チェックを追加 (SOR-28)#180
Sora4431 wants to merge 2 commits intomainfrom
fix/api-key-origin-check

Conversation

@Sora4431
Copy link
Copy Markdown
Collaborator

Summary

  • /api/google-maps/token, /api/mapbox/token, /api/mapbox/geocode に Origin/Referer ヘッダー検証を追加
  • tabitabi サイト経由以外のリクエスト(curl、別サイト等)を 403 で拒否
  • ALLOWED_ORIGINS 環境変数でカスタマイズ可能、dev モードではスキップ

Test plan

  • validateOrigin のユニットテスト 9件追加(全パス)
  • 本番デプロイ後、curl で直接 /api/google-maps/token を叩いて 403 が返ることを確認
  • ブラウザから地図が今まで通り表示されることを確認

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds server-side Origin/Referer validation to Map API proxy/token endpoints to reduce off-site usage, with an ALLOWED_ORIGINS allowlist and dev-mode bypass.

Changes:

  • Introduces validateOrigin(request) helper to enforce Origin/Referer checks (and allowlist via ALLOWED_ORIGINS).
  • Applies validateOrigin to /api/google-maps/token, /api/mapbox/token, and /api/mapbox/geocode.
  • Adds Vitest unit tests covering key allow/deny paths for validateOrigin.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
apps/web/src/routes/api/mapbox/token/+server.ts Calls validateOrigin before returning Mapbox token JSON.
apps/web/src/routes/api/mapbox/geocode/+server.ts Adds validateOrigin to geocode endpoint handler.
apps/web/src/routes/api/google-maps/token/+server.ts Calls validateOrigin before returning Google Maps API key JSON.
apps/web/src/lib/server/validate-origin.ts New shared validation utility for Origin/Referer allow/deny decisions.
apps/web/src/lib/server/validate-origin.test.ts New unit tests for validateOrigin.

const origin = request.headers.get('origin');
const referer = request.headers.get('referer');

const requestOrigin = origin ?? (referer ? new URL(referer).origin : null);
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new URL(referer) can throw (e.g., malformed/relative Referer header). That would bubble up as a 500 instead of a 403. Consider wrapping the URL parsing in a try/catch and treating invalid Referer as missing/forbidden (or ignore it and fall back to Origin).

Suggested change
const requestOrigin = origin ?? (referer ? new URL(referer).origin : null);
let requestOrigin = origin;
if (!requestOrigin && referer) {
try {
requestOrigin = new URL(referer).origin;
} catch {
requestOrigin = null;
}
}

Copilot uses AI. Check for mistakes.
Comment on lines +30 to +37
const host = request.headers.get('host');
if (!host) {
throw error(403, 'Forbidden');
}

const protocol = request.headers.get('x-forwarded-proto') ?? 'https';
const selfOrigin = `${protocol}://${host}`;

Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

x-forwarded-proto commonly contains a comma-separated list (e.g. "https,http"). Using it verbatim will produce an invalid selfOrigin and can cause false 403s. Consider normalizing it (take the first value and trim), or derive selfOrigin from new URL(request.url).origin (or event.url.origin) to avoid proxy/header quirks.

Suggested change
const host = request.headers.get('host');
if (!host) {
throw error(403, 'Forbidden');
}
const protocol = request.headers.get('x-forwarded-proto') ?? 'https';
const selfOrigin = `${protocol}://${host}`;
const selfOrigin = new URL(request.url).origin;

Copilot uses AI. Check for mistakes.
Comment on lines +13 to +26
const origin = request.headers.get('origin');
const referer = request.headers.get('referer');

const requestOrigin = origin ?? (referer ? new URL(referer).origin : null);

if (!requestOrigin) {
throw error(403, 'Forbidden');
}

const allowed = env.ALLOWED_ORIGINS;
if (allowed) {
const allowedList = allowed.split(',').map(o => o.trim());
if (allowedList.includes(requestOrigin)) return;
throw error(403, 'Forbidden');
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description says direct curl requests will be rejected, but this check relies on Origin/Referer values that any client (including curl) can spoof. If the intent is to enforce a real access boundary, consider adding an additional server-side control (e.g., session/auth check, signed nonce, or rate limiting) or update the description to clarify this is a best-effort browser-origin check rather than strong protection.

Copilot uses AI. Check for mistakes.
});
expect(() => validateOrigin(req)).toThrow();
});

Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing test case for malformed referer (e.g. referer: 'not-a-url' or relative URL). As implemented, new URL(referer) would throw and likely surface as a 500. Adding a test that asserts these inputs are handled as 403 (or otherwise gracefully rejected) would prevent regressions.

Suggested change
it('rejects requests with malformed Referer values', () => {
const malformedReferers = ['not-a-url', '/relative/path'];
for (const referer of malformedReferers) {
const req = createRequest({
referer,
host: 'example.com',
});
expect(() => validateOrigin(req)).toThrow();
}
});

Copilot uses AI. Check for mistakes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Sora4431
Copy link
Copy Markdown
Collaborator Author

Copilot コメント 3点対応しました:

  1. Referer パース安全化new URL(referer) を try/catch で囲み、不正な Referer は 403 として処理
  2. selfOrigin 取得改善x-forwarded-proto + host の組み立てを new URL(request.url).origin に変更
  3. スプーフィングについて — Origin/Referer チェックはブラウザレベルの best-effort 防御です。APIキー側のリファラー制限は SOR-31 として別途対応予定

malformed Referer のテストケースも追加済みです。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants