Alt Kaynak Bütünlüğü, CORS ve Referrer Policy ile harici alt kaynakların güvenliğini sağlama

Üçüncü taraf CDN'lerden veya harici kaynaklardan komut dosyaları, stil sayfaları, yazı tipleri ya da resimler yüklerken Alt Kaynak Bütünlüğü (HTML'de integrity ve CSS'de integrity() url()) ile bunların kriptografik bütünlüğünü doğrulayın, çapraz kaynak kimlik bilgisi işlemeyi (crossorigin ve cross-origin()) yapılandırın ve giden Referer üstbilgilerini (referrerpolicy ve referrer-policy()) kısıtlayın. Belge düzeyinde İçerik Güvenliği Politikası ve giden bağlantı izin verilenler listeleri için Web Güvenliği ve Bağlantı İzin Verilenler Listeleri ile giden ağ bağlantılarını kısıtlama başlıklı makaleleri inceleyin.

1. Sabit öğeler için kriptografik bütünlük özetleri oluşturma

Alt Kaynak Bütünlüğü (SRI), tarayıcının yalnızca baytları uygulamanız tarafından beyan edilen kriptografik karma ile eşleşen harici öğeleri yürütmesini veya oluşturmasını sağlar.

  • Özetleri derleme veya yayınlama sırasında hesaplayın, hiçbir zaman canlı güvenilmeyen yanıtlardan hesaplamayın: Derleme veya yayınlama adımınız sırasında bilinen iyi yerel öğe baytlarından bütünlük karmaları oluşturun (veya paket yazarı tarafından yayınlanan resmi özeti kopyalayın) ve bunları HTML, CSS veya JavaScript'inize sabit kodlayın. Kendi integrity karma değerini hesaplamak için çalışma zamanında tarayıcıda üçüncü taraf URL'si getirmeyin. Bu işlem, uzak sunucunun döndürdüğü tüm baytlara güvenir ve SRI'yi geçersiz kılar.
  • Varsayılan olarak sha384- kullan: Bütünlük meta verilerini sha384- (önerilen varsayılan), sha512- veya sha256- kullanarak <algorithm>-<base64-digest> olarak biçimlendirin.
  • Harici alt kaynakları sabit, sürümlü URL'lere sabitleyin: Üçüncü taraf öğelerine her zaman, sitenizi sessiz tedarik zinciri değişikliklerine maruz bırakan ve uzak dosya güncellenir güncellenmez SRI'yi bozan değişken kayan URL'ler (latest/app.min.js) yerine sabit, sürüme sabitlenmiş veya içerik adresli URL'ler (https://cdn.example.com/ui@2.4.0/app.min.js gibi) kullanarak referans verin. Yerinde güncellenen değişken bir üçüncü taraf komut dosyası (ör. tedarikçi firma analiz etiketi) yüklemeniz gerekiyorsa bu URL'de integrity parametresini atlayın ve bunun yerine İçerik Güvenliği Politikası (strict-dynamic/tek seferlik rastgele sayılar) veya iframe korumalı alanıyla kısıtlayın.
  • Bir uç nokta birden fazla geçerli yük sunabildiğinde birden fazla özeti boşlukla ayırma: SRI'yi destekleyen tüm tarayıcılar sha256-, sha384- ve sha512-'yi uygular. Bir URL, bilinen birkaç iyi yükten birini meşru olarak döndürebiliyorsa (ör. A/B testi, aşamalı CDN sunumu veya içerik anlaşmalı derleme sırasında) aynı algoritmanın birden fazla özetini boşlukla ayırarak listeleyin (ör. integrity="sha384-variantA... sha384-variantB..."). Tarayıcı, mevcut en güçlü algoritmanın listelenen özetlerinden herhangi biriyle eşleşirse yanıtı kabul eder.
  • Çapraz kaynak alt kaynaklarını CORS başlıklarıyla sunun: Tarayıcılar, çapraz kaynak yanıtının bütünlüğünü yalnızca CORS tarafından okunabilir olduğunda (anonim istekler için Access-Control-Allow-Origin: * veya kimlik bilgisi içeren istekler için Access-Control-Allow-Credentials: true ile tam istek kaynağı) doğrulayabilir.
# Generate a sha384- SRI hash at build time from a known-good local file
printf "sha384-%s\n" "$(openssl dgst -sha384 -binary app.min.js | openssl base64 -A)"
// Build-time / tooling helper (Node.js 19+, Deno, Bun) to compute an SRI string from known-good local bytes
export async function computeIntegrityMetadata(bytes) {
  const rawBytes =
    typeof bytes === 'string' ? new TextEncoder().encode(bytes) : bytes;
  const digestBuffer = await crypto.subtle.digest('SHA-384', rawBytes);
  const binary = Array.from(new Uint8Array(digestBuffer), (b) =>
    String.fromCharCode(b),
  ).join('');
  return `sha384-${btoa(binary)}`;
}

2. Güvenli HTML komut dosyaları, stil sayfaları ve önceden yüklemeler

Belge düzeyinde bir Referrer-Policy: strict-origin-when-cross-origin HTTP yanıt başlığı ayarlayın (veya <head> içine <meta name="referrer" content="strict-origin-when-cross-origin"> ekleyin). Böylece, istek başına yönlendiren değiştiriciyi atlayan tüm alt kaynaklar veya CSS url(), çapraz kaynak isteklerde hassas yol ve sorgu parametrelerini kaldırmaya devam eder.

Ardından, harici <script>, <link rel="stylesheet">, <link rel="preload"> ve <link rel="modulepreload"> öğelerinde her zaman integrity, crossorigin ve referrerpolicy öğelerini birlikte ayarlayın:

  • integrity="sha384-...": Yanıt baytları beklenen özetle eşleşmiyorsa kaynağın yürütülmesini veya uygulanmasını engeller (engellenen bir <link rel="stylesheet"> için linkElement.sheet, null olarak kalır ve bir error etkinliği tetiklenir).
  • crossorigin="anonymous" (herkese açık CDN öğeleri için önerilen varsayılan) veya crossorigin="use-credentials": integrity, çapraz kaynaklı bir kaynakta ayarlandığında gereklidir. crossorigin parametresinin atlanması, tarayıcının kaynağı no-cors modunda getirmesine (karma doğrulaması yapılamayan opak bir yanıt oluşturur) ve kaynağı engellemesine neden olur.
  • referrerpolicy="no-referrer" (üçüncü taraf CDN'ler için önerilen varsayılan) veya referrerpolicy="strict-origin-when-cross-origin": Referer istek başlığındaki kaynak veya yol bilgilerinin üçüncü taraf ana makineye sızmasını önlemek için doküman yönlendiren politikasını geçersiz kılar.
<!-- Document-level baseline referrer policy in <head> (or via Referrer-Policy HTTP header) -->
<meta name="referrer" content="strict-origin-when-cross-origin" />

<!-- External stylesheet with SRI, anonymous CORS, and no referrer -->
<link
  rel="stylesheet"
  href="https://cdn.example.com/ui@2.4.0/styles.css"
  integrity="sha384-bs0EksOYBbU/g0IAHMXbSyH1Mgw83GKn+EZlSI+VE+zVchXmSbLwhVLrPzsOWnc5"
  crossorigin="anonymous"
  referrerpolicy="no-referrer"
/>

<!-- External script with SRI, anonymous CORS, and no referrer -->
<script
  src="https://cdn.example.com/ui@2.4.0/bundle.min.js"
  integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
  crossorigin="anonymous"
  referrerpolicy="no-referrer"
></script>

fetch() ile harici alt kaynakları programatik olarak getirirken eşleşen integrity, mode, credentials ve referrerPolicy seçeneklerini iletin (özet uyuşmazlığı, TypeError ile vaadi reddeder):

const response = await fetch('https://cdn.example.com/ui@2.4.0/manifest.json', {
  integrity: 'sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC',
  mode: 'cors',
  credentials: 'omit',
  referrerPolicy: 'no-referrer',
});

3. url() değiştiricileriyle CSS resimlerini, yazı tiplerini ve içe aktarılan stil sayfalarını güvenli hale getirme

CSS url() istek değiştiricileri (cross-origin(), referrer-policy() ve integrity()); background-image, content, mask-image, border-image-source, filter, @font-face src ve @import kuralları dahil olmak üzere CSS ile yüklenen kaynakların getirme modunu, yönlendiren üstbilgisini ve alt kaynak bütünlüğü doğrulamasını doğrudan CSS'den kontrol eder:

Değiştirici işlevi Bağımsız değişken söz dizimi ve izin verilen değerler Amaç
cross-origin(<ident>) Tırnak içine alınmamış anahtar kelime: anonymous (önerilen varsayılan) veya use-credentials CORS kullanarak CSS kaynağını getirir ve kimlik bilgilerinin gönderilip gönderilmeyeceğini kontrol eder. Çapraz kaynak CSS kaynaklarında her zaman integrity() ile birlikte ekleyin.
referrer-policy(<ident>) Tırnak içine alınmamış anahtar kelime: no-referrer (üçüncü taraf öğeler için önerilen varsayılan), strict-origin-when-cross-origin, origin, same-origin, strict-origin, origin-when-cross-origin, no-referrer-when-downgrade veya unsafe-url Bu CSS isteği için doküman yönlendiren politikasını geçersiz kılar.
integrity("<string>") Tırnak içine alınmış dize: "sha384-<base64>" (veya boşlukla ayrılmış özetler "sha384-... sha384-...") Alınan CSS kaynağını oluşturmadan veya uygulamadan önce beklenen kriptografik özete göre doğrular.

url() değiştiricileri oluştururken aşağıdaki CSS söz dizimi ve güvenlik kurallarına uyun:

  • URL dizesini url("...") içine alın: İstek değiştiriciler yalnızca url(...) içindeki URL tırnak içine alınmış bir <string> olduğunda geçerlidir (tırnak içine alınmamış URL'ler değiştiricileri kullanamaz).
  • Her niteleyici türünü url() başına en fazla bir kez belirtin: url("..." ...) içinde cross-origin(), referrer-policy() ve integrity() niteleyicilerini istediğiniz sırayla birleştirebilirsiniz ancak aynı niteleyici türünü iki kez tekrarlamak (ör. iki integrity() niteleyicisi) geçersizdir. Birden fazla karma sağlamak için bunları tek bir integrity("sha384-... sha384-...") dizesi içinde boşlukla ayırın.
  • Çapraz kaynak integrity() ile cross-origin(anonymous) her zaman birlikte kullanılmalıdır: @font-face src ve mask-image varsayılan olarak anonim cors modunda getirilirken background-image, content, border-image-source ve @import varsayılan olarak no-cors moduna (karma doğrulaması yapılamayan opak bir yanıt oluşturur) ve harici SVG filter varsayılan olarak same-origin moduna (çapraz kaynak URL'leri tamamen engeller) ayarlanır. cross-origin(anonymous) (veya cross-origin(use-credentials)) eklemek, her CSS mülkünde tutarlı CORS doğrulaması sağlar.
  • Değiştiricileri @import ve @font-face kurallarında url("...") içine yerleştirin: cross-origin(), referrer-policy() ve integrity()'yi url("..." ...) içinde tutarken format("woff2") ve tech(...) gibi yazı tipi tanımlayıcılarını url(...) dışında tutun.
@import url(
  "https://cdn.example.com/theme@1.2.0/tokens.css"
  cross-origin(anonymous)
  referrer-policy(no-referrer)
  integrity("sha384-bs0EksOYBbU/g0IAHMXbSyH1Mgw83GKn+EZlSI+VE+zVchXmSbLwhVLrPzsOWnc5")
);

@font-face {
  font-family: "BrandSans";
  src: url(
    "https://cdn.example.com/fonts@1.0.0/brand-sans.woff2"
    cross-origin(anonymous)
    referrer-policy(no-referrer)
    integrity("sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC")
  ) format("woff2");
  font-display: swap;
}

.verified-graphic {
  background-image: url(
    "https://cdn.example.com/media@1.0.0/badge.svg"
    cross-origin(anonymous)
    referrer-policy(no-referrer)
    integrity("sha384-Iw/0p59dP96nGTzHvf2XIfqcBwBW7RM6JfOij8uZxyg5azfNaOOUW8SQy6H/hU91")
  );
}

Yedek stratejiler

Temel hedefiniz henüz CSS url() istek değiştiricilerini (cross-origin(), referrer-policy() veya integrity()) desteklemiyorsa alt kaynakların işlevsel ve korumalı kalması için belge düzeyindeki politikaları basamaklı CSS bildirimleriyle birleştirin:

  1. Tarayıcılar arası SRI gerektiğinde CSS @import yerine HTML <link rel="stylesheet"> tercih edin: <link rel="stylesheet"> üzerindeki subresource-integrity ve referrer-policy tüm tarayıcılarda yaygın olarak kullanılabildiğinden, her tarayıcıda bütünlük doğrulamasının zorunlu kılınması gerektiğinde harici stil sayfalarını @import yerine <link rel="stylesheet" href="..." integrity="sha384-..." crossorigin="anonymous" referrerpolicy="no-referrer"> ile yükleyin.
  2. 2. adımdaki belge düzeyinde Referrer-Policy başlığına veya <meta name="referrer"> öğesine güvenin: CSS url() içinde referrer-policy() özelliğini desteklemeyen tarayıcılarda, belge düzeyinde Referrer-Policy: strict-origin-when-cross-origin başlığı veya <meta name="referrer" content="strict-origin-when-cross-origin"> öğesi, çapraz kaynak CSS isteklerindeki hassas yol ve sorgu parametrelerini otomatik olarak kaldırır.
  3. Progresif geliştirme için ayrı CSS bildirimlerini basamaklama: url("..." ...) içindeki bir değiştiriciyi tanımayan tarayıcılar, CSS ayrıştırma sırasında bildirimin tamamını siler. Önce basit bir url("..."), ardından cross-origin() ve referrer-policy() bildirimi (bu iki değiştiriciyi destekleyen ancak integrity()'yi desteklemeyen tarayıcılar için) ve son olarak integrity("sha384-...") dahil tam bildirimi beyan edin:

    • Doğrulanmamış bir url() değerini asla aynı virgülle ayrılmış @font-face src listesine koymayın: src: url("font.woff2" cross-origin(anonymous) integrity("sha384-...")), url("font.woff2"); değerini tek bir src tanımlayıcısına yazmak, çalışma zamanında Alt Kaynak Bütünlüğü'nü atlar. Bir CDN, integrity() kontrolünü geçemeyen, üzerinde oynanmış bir yazı tipi sunarsa tarayıcı bunu yükleme hatası olarak değerlendirir ve otomatik olarak virgülle ayrılmış ikinci url("font.woff2") değerine geri döner. Ayrı src: bildirimler kullanmak, destekleyen tarayıcıların CSS ayrıştırma sırasında doğrulanmamış bildirimin üzerine yazmasını ve çalışma zamanında değiştirilmiş yazı tiplerini engellemesini sağlar.
    @font-face {
      font-family: "BrandSans";
      /* 1. Parse-time fallback for browsers without url() modifier support */
      src: url("https://cdn.example.com/fonts@1.0.0/brand-sans.woff2") format("woff2");
      /* 2. Parse-time fallback for browsers supporting cross-origin() and referrer-policy() */
      src: url(
        "https://cdn.example.com/fonts@1.0.0/brand-sans.woff2"
        cross-origin(anonymous)
        referrer-policy(no-referrer)
      ) format("woff2");
      /* 3. Full protection in browsers supporting cross-origin(), referrer-policy(), and integrity() */
      src: url(
        "https://cdn.example.com/fonts@1.0.0/brand-sans.woff2"
        cross-origin(anonymous)
        referrer-policy(no-referrer)
        integrity("sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC")
      ) format("woff2");
      font-display: swap;
    }
    
    .verified-graphic {
      background-image: url("https://cdn.example.com/media@1.0.0/badge.svg");
      background-image: url(
        "https://cdn.example.com/media@1.0.0/badge.svg"
        cross-origin(anonymous)
        referrer-policy(no-referrer)
      );
      background-image: url(
        "https://cdn.example.com/media@1.0.0/badge.svg"
        cross-origin(anonymous)
        referrer-policy(no-referrer)
        integrity("sha384-Iw/0p59dP96nGTzHvf2XIfqcBwBW7RM6JfOij8uZxyg5azfNaOOUW8SQy6H/hU91")
      );
    }
    
  4. JavaScript'te CSS.supports() ile CSS url() değiştirici desteğini özellik algılama:

export function getCssUrlModifierSupport() {
  const supportsCrossOriginAndReferrer =
    typeof CSS !== 'undefined' &&
    typeof CSS.supports === 'function' &&
    CSS.supports(
      'background-image',
      'url("https://example.com/a.svg" cross-origin(anonymous) referrer-policy(no-referrer))',
    );

  const supportsUrlIntegrity =
    typeof CSS !== 'undefined' &&
    typeof CSS.supports === 'function' &&
    CSS.supports(
      'background-image',
      'url("https://example.com/a.svg" cross-origin(anonymous) referrer-policy(no-referrer) integrity("sha384-Iw/0p59dP96nGTzHvf2XIfqcBwBW7RM6JfOij8uZxyg5azfNaOOUW8SQy6H/hU91"))',
    );

  return { supportsCrossOriginAndReferrer, supportsUrlIntegrity };
}