Sepanjang section ini kita telah membedah satu per satu ancaman terhadap aplikasi PHP — dari injeksi SQL, XSS, CSRF, hingga pengelolaan secret dan dependensi. Masalahnya, pengetahuan yang tersebar di kepala lima belas chapter tidak otomatis hadir saat paling dibutuhkan: detik-detik sebelum menyetujui sebuah pull request. Checklist menyelesaikan masalah tersebut dengan mengubah pengetahuan menjadi pemeriksaan tertulis yang bisa ditandai satu per satu. Chapter penutup ini merangkum seluruh section menjadi checklist siap pakai, memetakan setiap butir ke chapter pembahasannya, lalu menutup dengan cara mengotomatiskan sebagian pemeriksaan dan menjadikannya kebiasaan tim.

Mengapa Checklist Berhasil #

Otak pengembang saat review kode sedang memikirkan banyak hal sekaligus: logika bisnis, gaya kode, performa, tenggat rilis. Dalam kondisi beban kognitif seperti itu, langkah-langkah yang tidak selalu relevan — “apakah input ini sudah divalidasi?” — mudah terlewat meskipun pengetahuannya ada. Psikologi penerbangan menemukan hal yang sama puluhan tahun lalu, dan solusinya bukan pilot yang lebih pintar, melainkan daftar periksa tertulis yang dibacakan sebelum lepas landas.

Checklist keamanan bekerja dengan cara yang sama: butirnya pendek, bisa dijawab ya/tidak, dan tidak memerlukan konteks besar untuk memutuskan. Yang tidak dilakukan checklist adalah menggantikan pemahaman — daftar “centang kotak” tanpa tahu alasannya justru berbahaya karena memberi rasa aman palsu. Gunakan setiap butir sebagai pintu masuk: ketika ragu pada satu butir, tautan rujukannya mengantar Anda kembali ke chapter yang membahas topik tersebut secara mendalam.

Checklist Input dan Output #

Dua titik batas aplikasi — tempat data masuk dan keluar — menyerap sebagian besar butir pemeriksaan karena di sanalah data tidak lagi bisa dipercaya:

PemeriksaanRujukan mendalam
Semua input divalidasi di sisi server, termasuk hidden field dan headervalidasi server-side
Validasi memakai allowlist (yang boleh), bukan blocklist (yang dilarang)sanitasi vs validasi
Nilai yang ditampilkan ke HTML di-escape dengan htmlspecialchars() sesuai konteksoutput escaping
Output HTML yang membangun atribut, URL, dan JavaScript di-escape sesuai konteksnyacross-site scripting
Form penting dilindungi token CSRF dan memakai POSTCSRF , token CSRF
Form sukses redirect dengan pola POST-Redirect-GET agar tidak terkirim ulangpost-redirect-get

Checklist Database, Perintah, dan Sistem Berkas #

Layer penyimpanan dan interaksi dengan sistem operasi adalah tempat kesalahan berakibat paling mahal — kebocoran massal atau eksekusi perintah:

PemeriksaanRujukan mendalam
Semua query memakai prepared statements dengan parameter bindingprepared statements
Tidak ada nilai input pernah dirangkai langsung ke string SQLSQL injection
User database aplikasi diberi hak seminimal mungkin (bukan superuser)database deployment
Argumen ke exec()/shell_exec() lolos escapeshellarg() — atau lebih baik dihindaricommand injection
Nama berkas dari input dibersihkan basename() + verifikasi realpath()path traversal
Upload dibatasi ekstensi/MIME yang diizinkan, disimpan di luar web root, tanpa eksekusifile upload security

Checklist Autentikasi, Otorisasi, dan Sesi #

Kesalahan di area ini tidak membocorkan satu data — kesalahan ini memberikan kunci ke seluruh aplikasi:

PemeriksaanRujukan mendalam
Password disimpan dengan password_hash() (bcrypt/argon2id), tanpa algoritma lainpassword hashing
Login membatasi percobaan gagal (rate limiting / penundaan)authentication security
ID sesi diregenerasi setelah login sukses dan perubahan hak aksessession regeneration , session fixation
Otorisasi dicek ulang di server untuk setiap request, tanpa percaya kondisi dari klienauthentication security
Kontrol akses deny-by-default: tanpa peran/izin yang cocok, akses ditolakrole-based access control
Cookie sesi memakai HttpOnly, Secure, dan SameSitecookie options , session security

Checklist Header dan Secret #

Lapisan pertahanan pada respons HTTP dan pengelolaan kredensial sering luput karena tidak tampak di alur fitur:

PemeriksaanRujukan mendalam
Security headers dasar terpasang: X-Content-Type-Options, X-Frame-Options, Referrer-Policysecurity headers
CSP membatasi sumber script dan style yang boleh dieksekusiContent Security Policy
Konfigurasi CORS hanya membuka origin yang memang dipercaya, tanpa wildcard untuk kredensialCORS
Halaman sensitif melarang pembingkaian oleh situs lainclickjacking
Tidak ada secret di kode; kredensial dibaca dari variabel lingkungansecrets management
composer.lock terkomit dan composer audit berjalan di CIdependency security , dependency auditing

Checklist Operasional #

Sisa butir menyangkut perilaku aplikasi saat terjadi kesalahan dan saat berjalan di produksi — saat ketenangan aplikasi justru sedang diuji:

PemeriksaanRujukan mendalam
display_errors mati di produksi; pesan galat tidak membocor path, query, atau stack traceproduction error handling
Galat tidak tertangani tetap tercatat ke log dengan konteks yang cukup untuk investigasilogging errors
Konfigurasi produksi diverifikasi terpisah dari developmentproduction configuration
Seluruh trafik dilayani lewat HTTPSproduction configuration
Secret dirotasi berkala dan meninggalkan jejak auditsecrets management

Mengotomatiskan Pemeriksaan #

Butir yang jawabannya bisa dibaca dari konfigurasi atau sistem berkas tidak perlu diperiksa manusia — manusia justru komponen yang paling mudah lupa. Fungsi audit kecil berikut memeriksa konfigurasi aplikasi dan melaporkan setiap penyimpangan:

php
<?php
function auditKonfigurasiProduksi(array $config): array
{
    $temuan = [];

    if (($config["display_errors"] ?? true) !== false) {
        $temuan[] = "display_errors masih aktif";
    }
    if (($config["session_httponly"] ?? false) !== true) {
        $temuan[] = "cookie sesi tanpa HttpOnly";
    }
    if (($config["session_secure"] ?? false) !== true) {
        $temuan[] = "cookie sesi tanpa Secure";
    }
    if (!in_array($config["password_algo"] ?? "md5", ["bcrypt", "argon2id"], true)) {
        $temuan[] = "algoritma hashing password lemah";
    }
    return $temuan;
}

$configLama = ["display_errors" => true, "session_httponly" => false];
$configAman = [
    "display_errors"   => false,
    "session_httponly" => true,
    "session_secure"   => true,
    "password_algo"    => "argon2id",
];

$temuan = auditKonfigurasiProduksi($configLama);
echo "configLama: " . count($temuan) . " temuan\n";
foreach ($temuan as $i => $t) {
    echo "  " . ($i + 1) . ". $t\n";
}
echo "configAman: " . count(auditKonfigurasiProduksi($configAman)) . " temuan\n";

Output yang Dihasilkan:

text
configLama: 4 temuan
  1. display_errors masih aktif
  2. cookie sesi tanpa HttpOnly
  3. cookie sesi tanpa Secure
  4. algoritma hashing password lemah
configAman: 0 temuan

Perhatikan pola ?? default di setiap pemeriksaan: kunci yang hilang diperlakukan sebagai konfigurasi paling tidak aman, bukan “belum diatur”. Aplikasi tua dengan configLama pun ditangani dengan cara yang sama seperti aplikasi yang belum menyetel apa pun — keduanya mendapat empat temuan lengkap dengan penomorannya, sementara configAman bersih. Fungsi seperti ini bisa dipanggil dari health check atau skrip deployment dan menggagalkan rilis ketika temuannya tidak kosong.

Pemeriksaan berbasis sistem berkas juga bisa diaudit otomatis — misalnya memastikan berkas kredensial tidak dapat ditulis oleh pengguna lain di server bersama:

php
<?php
function auditPermissionBerkas(string $path): string
{
    $mode = fileperms($path) & 0777;
    if (($mode & 0007) !== 0) {
        return sprintf("%s: world-writable (%o) — bisa diubah pengguna lain", basename($path), $mode);
    }
    return sprintf("%s: aman (%o)", basename($path), $mode);
}

$dir = sys_get_temp_dir() . "/audit-budi";
if (!is_dir($dir)) {
    mkdir($dir, 0700, true);
}

$berkasSandi = "$dir/kredensial.json";
file_put_contents($berkasSandi, "{}");
chmod($berkasSandi, 0666);

$berkasAman = "$dir/konfigurasi.json";
file_put_contents($berkasAman, "{}");
chmod($berkasAman, 0600);

echo auditPermissionBerkas($berkasSandi) . "\n";
echo auditPermissionBerkas($berkasAman) . "\n";

Output yang Dihasilkan:

text
kredensial.json: world-writable (666) — bisa diubah pengguna lain
konfigurasi.json: aman (600)

Operasi bitwise adalah inti fungsinya: fileperms() & 0777 mengambil sembilan bit perizinan, lalu & 0007 mengisolasi tiga bit terakhir yang mengatur hak “orang lain” (other). Nilai bit tersebut nol berarti hanya pemilik dan grup yang boleh menyentuh berkasnya. Latihan di atas sengaja memakai berkas kosong di direktori sementara — di produksi, sasaran pemeriksaannya adalah berkas kredensial nyata, dan audit ini dijalankan oleh skrip deployment, bukan dari browser.

Checklist dalam Code Review #

Checklist paling berguna pada momen review, dan caranya sederhana: buka pull request, jalankan mata baris per baris, dan untuk setiap perubahan tanyakan butir mana yang relevan. Perubahan pada query SQL memicu pemeriksaan prepared statements; perubahan pada template memicu pemeriksaan escaping; perubahan pada form memicu pemeriksaan CSRF. Reviewer tidak perlu menanyakan semuanya setiap kali — hanya butir yang menyentuh kode yang berubah.

Penulis kode juga memakai checklist yang sama sebelum mengirim: perlakukan setiap butir sebagai pertanyaan prasimpan diri. Kebiasaan ini memindahkan temuan dari tahap review (mahal, menghalangi orang lain) ke tahap penulisan (murah, hanya menyentuh diri sendiri). Ketika satu butir berulang kali terlewat oleh banyak orang, itu sinyal untuk mengotomatiskannya — pindahkan dari checklist manusia ke pipeline CI, lalu ganti butirnya dengan pemeriksaan yang belum bisa diotomatisasi.

Review Keamanan Triwulanan #

Aplikasi berubah, dan begitu pula ancamannya: dependensi menua, advisori keamanan baru terbit, dan fitur baru menambah permukaan serangan perlahan-lahan. Karena itu checklist juga perlu dipakai dalam ritme berkala, bukan hanya saat ada perubahan kode. Jadwalkan review triwulanan yang menjalankan tiga agenda: audit dependensi (composer audit) terhadap versi yang terpasang produksi, penelusuran setiap butir checklist terhadap konfigurasi produksi yang sebenarnya, dan pembahasan insiden kecil kuartal ini — galat aneh, log mencurigakan, laporan dari pengguna — sebagai calon butir checklist baru.

Ritme ini juga saatnya menimbang daftar dependensi: paket yang sudah setahun tidak dirawat dicari alternatifnya sebelum menjadi beban, dan fitur internal yang tidak terpakai dibersihkan beserta dependensinya. Hasil setiap review dicatat singkat — temuan, keputusan, penanggung jawab — agar review berikutnya bisa membandingkan apakah keputusan sebelumnya benar-benar dijalankan.

Ringkasan #

Checklist mengubah pengetahuan keamanan yang tersebar di seluruh section menjadi prosedur yang bisa dijalankan di bawah tekanan: butir pendek berjawaban ya/tidak, masing-masing berpaut ke chapter pembahasannya, diperiksa selektif saat code review, dan diotomatiskan begitu pola kelalaiannya terlihat. Ritme triwulanan menjaga daftar itu tetap hidup mengikuti perubahan aplikasi dan ancaman. Dengan ini, perjalanan kita di Section Web Security genap — kebiasaan-kebiasaan yang sama akan kita pakai lagi ketika mengendalikan respons HTTP secara halus di Section Cookies, Headers, dan Caching .

Referensi #