Custom UI sayfaların kötü niyetli sitelerce çerçevelenip (clickjacking) tenant oturumunun istismarını engellemek için iki kural zorunludur: (1) CSP frame-ancestors ile yalnız panel origin'ine izin ver, (2) App Bridge mesajlaşmasını origin'e pinle ve gelen event.origin'i doğrula.
Custom UI sayfanı çerçeveleyen (iframe'e gömen) Restomenum paneli — DEV panel dahil — şu iki origin'den yüklenir. Hem CSP frame-ancestors'ta hem App Bridge origin doğrulamasında ikisine birden izin ver:
• https://app.restomenum.com • https://test-restomenu.web.app
Custom UI sayfanın HTTP yanıtında bu header bulunmalı. Sürüm onayında otomatik denetlenir:
Content-Security-Policy: frame-ancestors https://app.restomenum.com https://test-restomenu.web.app
✓ GEÇER: frame-ancestors var + panel origin'lerini içerir + '*' yok ✗ REDDET: header yok · '*' · 'none' · panel origin'i yok → onay bloklanır
postMessage'ı wildcard '*' ile değil panel origin'ine pinli gönder ve gelen mesajlarda event.origin'i yukarıdaki listeyle doğrula. Panel iki origin'den açılabildiği için üst pencerenin origin'ini document.referrer'dan tespit edip listeyle karşılaştır (aşağıdaki referans). Bu JS içinde olduğundan reviewer manuel teyit eder.
// Panel origin'leri — Custom UI iframe'ini çerçeveleyen üst pencere (DEV panel dahil):
// https://app.restomenum.com · https://test-restomenu.web.app
// 1) Custom UI sayfan SADECE panel tarafından çerçevelenebilmeli (anti-clickjacking).
// Sayfanın HTTP yanıtında şu header ZORUNLU ve İKİ origin'i de içermeli (onayda otomatik denetlenir):
Content-Security-Policy: frame-ancestors https://app.restomenum.com https://test-restomenu.web.app
// ❌ header yok · ❌ frame-ancestors '*' · ❌ 'none' · ❌ panel origin'i yoksa → onay reddedilir.
// 2) App Bridge postMessage'ı wildcard '*' DEĞİL, panel origin'ine pinli gönder + gelen mesajda
// event.origin doğrula (manuel review'da teyit edilir). Panel iki origin'den açılabildiği için
// üst pencerenin origin'ini referrer'dan tespit et ve listeyle doğrula:
const PANEL_ORIGINS = ["https://app.restomenum.com","https://test-restomenu.web.app"];
const PARENT = (() => { try { return new URL(document.referrer).origin; } catch { return ''; } })();
if (!PANEL_ORIGINS.includes(PARENT)) throw new Error('panel dışında açıldı');
window.parent.postMessage({ type: 'ready' }, PARENT); // hedef origin pinli (wildcard yok)
window.addEventListener('message', (e) => {
if (!PANEL_ORIGINS.includes(e.origin)) return; // gelen origin doğrula
// ... güvenli: e.data işle
});Content-Security-Policy: frame-ancestors https://app.restomenum.com https://test-restomenu.web.app — iki origin de var.frame-ancestors '*' veya 'none' içermiyor.postMessage(data, PARENT) — hedef, panel origin listesinden tespit edilmiş; wildcard yok.if (!PANEL_ORIGINS.includes(event.origin)) return; — gelen origin listeyle doğrulanıyor.