Pipeline CI untuk audit header keamanan web secara otomatis membantu tim mendeteksi regresi konfigurasi sebelum perubahan mencapai produksi. Pendekatan ini berguna ketika aplikasi, reverse proxy, CDN, atau ingress berubah dan tanpa sengaja menghapus atau melemahkan header seperti Content-Security-Policy, Strict-Transport-Security, X-Content-Type-Options, Referrer-Policy, dan proteksi framing.

Intinya, Anda tidak perlu menunggu pemindaian manual setelah rilis. Definisikan baseline header yang diharapkan, jalankan pengecekan terhadap environment target di CI/CD, lalu buat aturan fail yang realistis agar deployment berhenti jika ada header kritis yang hilang atau nilainya melemah. Referensi praktiknya selaras dengan dokumentasi keamanan web MDN, tetapi implementasinya harus mengikuti arsitektur aplikasi Anda sendiri.

Mengapa audit header keamanan perlu masuk ke CI/CD

Header keamanan sering dikelola di beberapa lapisan sekaligus: aplikasi backend, Nginx/Apache, load balancer, CDN, sampai platform hosting. Itu membuat regresi mudah terjadi. Contoh yang umum:

  • Tim menambah domain pihak ketiga dan mengubah CSP terlalu longgar.
  • Ingress baru tidak meneruskan Strict-Transport-Security.
  • Respons HTML utama punya header aman, tetapi halaman login atau error page tidak.
  • Staging dan production berbeda tanpa dokumentasi, lalu perubahan staging ikut bocor ke production.

Audit otomatis di CI/CD tidak menggantikan review keamanan, tetapi efektif untuk menjaga guardrail pada hal-hal yang bisa diverifikasi secara deterministik: ada atau tidaknya header, pola nilainya, dan konsistensi antar endpoint penting.

Header yang sebaiknya diaudit

Untuk audit awal, fokus pada header yang paling sering dipakai dan dampaknya jelas. Mengacu pada praktik umum di MDN, beberapa header yang layak dijadikan baseline adalah:

1. Content-Security-Policy (CSP)

CSP membatasi sumber konten yang boleh dijalankan atau dimuat oleh browser. Dalam audit CI, jangan hanya cek keberadaannya. Nilai CSP juga perlu diverifikasi secara minimum, misalnya memastikan tidak ada unsafe-inline atau * pada konteks yang seharusnya ketat, kecuali memang ada alasan yang terdokumentasi.

2. Strict-Transport-Security (HSTS)

HSTS memaksa browser memakai HTTPS untuk domain tersebut. Header ini relevan hanya jika situs memang disajikan melalui HTTPS. Karena itu, audit biasanya dijalankan terhadap endpoint HTTPS yang representatif. Untuk staging, header ini kadang sengaja dilonggarkan atau tidak diaktifkan jika domain staging tidak stabil atau belum siap.

3. X-Content-Type-Options

Nilai yang umum diharapkan adalah nosniff. Header ini sederhana, stabil, dan cocok dijadikan aturan fail keras karena jarang membutuhkan pengecualian.

4. Referrer-Policy

Header ini mengatur informasi referrer yang dikirim browser. Di audit CI, fokus pada keberadaan dan apakah nilainya masih dalam kebijakan yang diterima tim, misalnya tidak terlalu permisif.

5. Proteksi framing

Proteksi framing biasanya diterapkan dengan Content-Security-Policy: frame-ancestors .... Di sistem lama, Anda mungkin masih melihat X-Frame-Options. Untuk audit modern, lebih aman menganggap frame-ancestors sebagai mekanisme utama, tetapi jika aplikasi lama masih mengandalkan X-Frame-Options, masukkan juga ke baseline sampai migrasi selesai.

Catatan: Tidak semua endpoint membutuhkan header identik. File statis, API JSON, halaman HTML, dan callback pihak ketiga bisa punya kebutuhan berbeda. Audit yang baik memakai baseline per kategori endpoint, bukan satu aturan untuk semua.

Mulai dari baseline yang realistis

Kesalahan paling umum adalah langsung membuat aturan terlalu ketat, lalu pipeline gagal di banyak tempat dan akhirnya dimatikan. Mulailah dengan baseline yang kecil tapi bernilai.

Tentukan endpoint yang diuji

Jangan audit seluruh situs di tahap pertama. Pilih beberapa endpoint representatif:

  • / atau halaman HTML utama
  • /login jika ada autentikasi
  • /dashboard atau halaman aplikasi internal
  • /api/health atau endpoint API JSON jika header juga diatur di sana
  • Halaman error kustom bila dikelola reverse proxy

Definisikan aturan minimum

Contoh baseline awal yang praktis:

  • HTML utama harus punya Content-Security-Policy
  • Endpoint HTTPS production harus punya Strict-Transport-Security
  • Semua endpoint web harus punya X-Content-Type-Options: nosniff
  • Semua halaman HTML harus punya Referrer-Policy
  • Halaman HTML harus punya frame-ancestors di CSP atau X-Frame-Options

Simpan baseline sebagai file konfigurasi

Jangan hardcode aturan di pipeline YAML. Simpan dalam file agar mudah direview dan diubah bertahap. Contoh:

{
  "environments": {
    "production": {
      "baseUrl": "https://example.com",
      "paths": ["/", "/login", "/dashboard"],
      "rules": {
        "content-security-policy": { "required": true },
        "strict-transport-security": { "required": true },
        "x-content-type-options": { "required": true, "equals": "nosniff" },
        "referrer-policy": { "required": true },
        "frame-protection": { "required": true }
      }
    },
    "staging": {
      "baseUrl": "https://staging.example.com",
      "paths": ["/", "/login"],
      "rules": {
        "content-security-policy": { "required": true },
        "strict-transport-security": { "required": false },
        "x-content-type-options": { "required": true, "equals": "nosniff" },
        "referrer-policy": { "required": true },
        "frame-protection": { "required": true }
      }
    }
  }
}

Pengecualian untuk staging dibuat eksplisit, bukan diam-diam. Ini penting agar tim tahu bahwa perbedaan tersebut disengaja.

Opsi implementasi: curl cepat atau skrip Node.js yang lebih fleksibel

Ada dua pendekatan umum. curl cocok untuk validasi sederhana dan cepat ditambahkan ke pipeline. Node.js lebih cocok jika Anda butuh parsing header, aturan per environment, laporan yang rapi, dan exit code yang konsisten.

Opsi 1: audit sederhana dengan curl

Untuk kasus awal, Anda bisa memakai curl -I atau curl -sD - untuk mengambil header respons.

set -e

URL="https://example.com/"
HEADERS=$(mktemp)

curl -sS -D "$HEADERS" -o /dev/null "$URL"

grep -iq '^content-security-policy:' "$HEADERS" || {
  echo "FAIL: CSP tidak ditemukan pada $URL"
  exit 1
}

grep -iq '^x-content-type-options: nosniff' "$HEADERS" || {
  echo "FAIL: X-Content-Type-Options harus nosniff pada $URL"
  exit 1
}

grep -iq '^referrer-policy:' "$HEADERS" || {
  echo "FAIL: Referrer-Policy tidak ditemukan pada $URL"
  exit 1
}

if ! grep -iq '^content-security-policy:.*frame-ancestors' "$HEADERS" \
  && ! grep -iq '^x-frame-options:' "$HEADERS"; then
  echo "FAIL: frame protection tidak ditemukan pada $URL"
  exit 1
fi

echo "PASS: header keamanan minimum tersedia"

Mengapa pendekatan ini bekerja? Karena CI hanya perlu satu sinyal sederhana: exit code 0 untuk lulus, non-0 untuk gagal. Shell script mudah dijalankan di hampir semua runner.

Keterbatasannya juga jelas:

  • Sulit mengelola banyak endpoint dan banyak pengecualian.
  • Parsing dengan grep mudah rapuh jika aturan makin kompleks.
  • Validasi nilai CSP yang lebih detail cepat menjadi tidak nyaman di shell.

Opsi 2: audit yang lebih maintainable dengan Node.js

Jika tim Anda sudah memakai Node.js di CI, skrip kecil akan lebih mudah dirawat daripada shell yang terus membesar. Contoh berikut memakai modul bawaan agar tidak bergantung pada paket tambahan.

const fs = require('fs');

async function fetchHeaders(url) {
  const response = await fetch(url, { redirect: 'follow' });
  const headers = {};
  response.headers.forEach((value, key) => {
    headers[key.toLowerCase()] = value;
  });
  return { status: response.status, headers };
}

function hasFrameProtection(headers) {
  const csp = headers['content-security-policy'] || '';
  return csp.includes('frame-ancestors') || Boolean(headers['x-frame-options']);
}

function checkRule(name, rule, headers) {
  if (name === 'frame-protection') {
    if (rule.required && !hasFrameProtection(headers)) {
      return 'frame protection tidak ditemukan';
    }
    return null;
  }

  const value = headers[name];
  if (rule.required && !value) {
    return `${name} tidak ditemukan`;
  }

  if (rule.equals && value && value.toLowerCase() !== rule.equals.toLowerCase()) {
    return `${name} harus bernilai ${rule.equals}, aktual: ${value}`;
  }

  return null;
}

async function main() {
  const config = JSON.parse(fs.readFileSync('security-headers-baseline.json', 'utf8'));
  const envName = process.env.AUDIT_ENV || 'staging';
  const env = config.environments[envName];

  if (!env) {
    console.error(`Environment tidak ditemukan: ${envName}`);
    process.exit(2);
  }

  let failed = false;

  for (const path of env.paths) {
    const url = new URL(path, env.baseUrl).toString();
    const { status, headers } = await fetchHeaders(url);

    console.log(`Memeriksa ${url} [${status}]`);

    for (const [name, rule] of Object.entries(env.rules)) {
      const error = checkRule(name, rule, headers);
      if (error) {
        failed = true;
        console.error(`FAIL ${url}: ${error}`);
      }
    }
  }

  if (failed) {
    process.exit(1);
  }

  console.log('PASS: semua aturan baseline terpenuhi');
}

main().catch((err) => {
  console.error(err);
  process.exit(1);
});

Pendekatan ini cocok ketika Anda butuh:

  • Baseline per environment
  • Aturan yang bisa direview lewat pull request
  • Laporan yang lebih jelas untuk debugging
  • Ekstensi bertahap, misalnya pengecekan pola CSP minimum

Contoh integrasi ke pipeline CI/CD

GitHub Actions

Contoh berikut menjalankan audit terhadap staging setelah deployment preview atau environment uji tersedia.

name: security-header-audit

on:
  workflow_dispatch:
  pull_request:
  push:
    branches: [main]

jobs:
  audit-headers:
    runs-on: ubuntu-latest
    env:
      AUDIT_ENV: staging
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Run security header audit
        run: node scripts/audit-security-headers.js

Jika deployment staging dibuat di job sebelumnya, tempatkan job audit setelah job deploy, atau gunakan output URL dari job deploy sebagai input ke skrip audit.

GitLab CI

stages:
  - test
  - deploy
  - security

security_headers_audit:
  stage: security
  image: node:20
  variables:
    AUDIT_ENV: staging
  script:
    - node scripts/audit-security-headers.js
  allow_failure: false
  only:
    - merge_requests
    - main

Pola umumnya sama: jalankan audit setelah target environment tersedia dan bisa diakses runner. Jika environment internal hanya tersedia di jaringan privat, pastikan runner memiliki akses yang tepat.

Aturan pass/fail yang realistis

Audit yang berguna bukan audit yang paling ketat, tetapi yang konsisten dan bisa dipertahankan. Berikut pendekatan yang lebih realistis untuk tim developer.

Fail keras untuk header dasar yang stabil

  • X-Content-Type-Options: nosniff hilang atau berbeda
  • Referrer-Policy hilang
  • Tidak ada mekanisme proteksi framing

Header-header ini umumnya tidak sering berubah dan kecil risikonya untuk diuji secara otomatis.

Fail bertahap untuk CSP

CSP sering paling sulit karena aplikasi modern memuat skrip inline, aset CDN, atau integrasi pihak ketiga. Karena itu, hindari aturan terlalu agresif di awal. Contoh rollout:

  1. Tahap 1: hanya cek keberadaan CSP
  2. Tahap 2: larang pola yang jelas berbahaya pada halaman tertentu
  3. Tahap 3: audit direktif minimum seperti default-src atau frame-ancestors
  4. Tahap 4: review manual untuk penyempurnaan kebijakan

HSTS khusus environment yang tepat

Untuk Strict-Transport-Security, aturan fail keras biasanya diterapkan di production, bukan di semua environment. Alasannya:

  • Staging kadang memakai subdomain sementara atau sertifikat yang berubah
  • Runner CI mungkin menguji endpoint internal yang tidak merepresentasikan perilaku publik
  • Kesalahan rollout HSTS bisa berdampak lebih besar jika domain belum siap

Pengecualian untuk staging tanpa membuat audit kehilangan nilai

Staging sering berbeda dari production, tetapi perbedaannya harus seminimal mungkin. Jika Anda perlu pengecualian, terapkan prinsip berikut:

  • Dokumentasikan di baseline, jangan sembunyikan di skrip
  • Batasi scope pengecualian, misalnya hanya HSTS
  • Beri alasan teknis, misalnya domain staging tidak permanen
  • Review berkala, karena pengecualian yang dibiarkan terlalu lama cenderung menjadi kebiasaan

Contoh buruk adalah menonaktifkan seluruh audit pada staging karena satu header belum siap. Lebih baik nonaktifkan satu aturan dengan alasan yang jelas daripada mematikan semua sinyal.

Strategi rollout bertahap agar tidak merusak deployment

Menambahkan kontrol keamanan ke pipeline sering gagal bukan karena teknisnya sulit, tetapi karena transisinya terlalu mendadak. Rollout bertahap lebih aman.

Fase 1: mode observasi

Jalankan audit dan tampilkan hasilnya, tetapi jangan menggagalkan pipeline dulu. Tujuannya mengumpulkan baseline aktual dan menemukan endpoint yang tidak konsisten.

Fase 2: fail untuk kasus paling aman

Setelah noise berkurang, aktifkan fail untuk header yang paling stabil seperti X-Content-Type-Options dan proteksi framing.

Fase 3: perluas cakupan endpoint

Tambahkan halaman login, dashboard, error page, dan endpoint yang dilayani lapisan infrastruktur berbeda. Ini membantu menemukan gap antara aplikasi dan reverse proxy.

Fase 4: perketat aturan CSP dan production gate

Baru setelah aplikasi stabil, jadikan audit sebagai syarat wajib untuk deploy production. Dengan urutan ini, pipeline memberi perlindungan tanpa menghambat tim sejak awal.

Debugging ketika audit gagal

Saat pipeline gagal, fokus pertama adalah memastikan di lapisan mana header hilang atau berubah.

Periksa rantai respons sebenarnya

Gunakan curl dengan verbose atau dump header untuk melihat redirect dan header final.

curl -sS -D - -o /dev/null -L https://example.com/login

Masalah umum: header ada di respons awal, tetapi hilang setelah redirect; atau hanya ada pada / tetapi tidak pada /login.

Bandingkan antar lapisan

Jika aplikasi lokal benar tetapi environment CI salah, cek:

  • Konfigurasi Nginx/Apache/ingress
  • Aturan CDN atau edge platform
  • Perbedaan vhost, route, atau middleware antar environment
  • Halaman error yang dihasilkan proxy, bukan aplikasi

Jangan lupa header bisa berbeda menurut tipe konten

Beberapa sistem hanya menyetel header pada respons HTML, bukan JSON atau file statis. Itu tidak selalu salah, tetapi harus sesuai dengan baseline yang Anda definisikan.

Keterbatasan audit otomatis

Audit otomatis sangat berguna, tetapi jangan memberi rasa aman palsu. Ada beberapa hal yang tidak bisa dipastikan hanya dengan memeriksa header respons.

  • CSP bisa ada tetapi tetap lemah. Keberadaan header tidak menjamin kebijakannya aman.
  • Header yang benar tidak menutup celah aplikasi. XSS, CSRF, SSRF, dan masalah otorisasi tetap perlu pengujian terpisah.
  • Perilaku browser tidak sepenuhnya tercermin dari satu respons. Misalnya, interaksi antar halaman, iframe, atau aset pihak ketiga bisa butuh verifikasi manual.
  • Endpoint yang tidak diuji bisa lolos. Jika hanya mengaudit homepage, Anda bisa melewatkan halaman admin, callback, atau error page.

Kapan review manual tetap diperlukan

Review manual masih penting dalam beberapa situasi:

  • Saat merancang atau mengubah CSP secara signifikan
  • Saat menambahkan vendor script, iframe, widget, atau analytics baru
  • Saat memigrasikan reverse proxy, CDN, atau arsitektur deployment
  • Saat aplikasi memiliki halaman sensitif seperti login, pembayaran, atau area admin
  • Saat ingin memastikan kebijakan framing, referrer, dan mixed content sesuai model ancaman aplikasi

Praktiknya, gunakan audit otomatis sebagai pagar minimum di CI/CD, lalu lengkapi dengan review manual pada perubahan yang memengaruhi permukaan serangan browser.

Rekomendasi implementasi yang paling masuk akal

Jika Anda ingin mulai minggu ini tanpa membuat pipeline berisik, pendekatan berikut biasanya cukup aman:

  1. Pilih 2-4 endpoint HTML penting
  2. Simpan baseline per environment dalam file JSON
  3. Jalankan skrip Node.js di staging setelah deploy
  4. Fail keras untuk X-Content-Type-Options, Referrer-Policy, dan proteksi framing
  5. Untuk CSP, mulai dari cek keberadaan lalu perketat bertahap
  6. Terapkan aturan HSTS wajib di production, opsional di staging bila memang ada alasan teknis

Dengan pola ini, pipeline CI untuk audit header keamanan web secara otomatis memberi perlindungan yang nyata tanpa langsung menjadi hambatan deployment. Nilai utamanya bukan pada banyaknya aturan, tetapi pada konsistensi: setiap perubahan infrastruktur atau aplikasi harus mempertahankan baseline keamanan yang sudah disepakati tim.