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:
| Pemeriksaan | Rujukan mendalam |
|---|---|
| Semua input divalidasi di sisi server, termasuk hidden field dan header | validasi server-side |
| Validasi memakai allowlist (yang boleh), bukan blocklist (yang dilarang) | sanitasi vs validasi |
Nilai yang ditampilkan ke HTML di-escape dengan htmlspecialchars() sesuai konteks | output escaping |
| Output HTML yang membangun atribut, URL, dan JavaScript di-escape sesuai konteksnya | cross-site scripting |
| Form penting dilindungi token CSRF dan memakai POST | CSRF , token CSRF |
| Form sukses redirect dengan pola POST-Redirect-GET agar tidak terkirim ulang | post-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:
| Pemeriksaan | Rujukan mendalam |
|---|---|
| Semua query memakai prepared statements dengan parameter binding | prepared statements |
| Tidak ada nilai input pernah dirangkai langsung ke string SQL | SQL injection |
| User database aplikasi diberi hak seminimal mungkin (bukan superuser) | database deployment |
Argumen ke exec()/shell_exec() lolos escapeshellarg() — atau lebih baik dihindari | command injection |
Nama berkas dari input dibersihkan basename() + verifikasi realpath() | path traversal |
| Upload dibatasi ekstensi/MIME yang diizinkan, disimpan di luar web root, tanpa eksekusi | file upload security |
Checklist Autentikasi, Otorisasi, dan Sesi #
Kesalahan di area ini tidak membocorkan satu data — kesalahan ini memberikan kunci ke seluruh aplikasi:
| Pemeriksaan | Rujukan mendalam |
|---|---|
Password disimpan dengan password_hash() (bcrypt/argon2id), tanpa algoritma lain | password hashing |
| Login membatasi percobaan gagal (rate limiting / penundaan) | authentication security |
| ID sesi diregenerasi setelah login sukses dan perubahan hak akses | session regeneration , session fixation |
| Otorisasi dicek ulang di server untuk setiap request, tanpa percaya kondisi dari klien | authentication security |
| Kontrol akses deny-by-default: tanpa peran/izin yang cocok, akses ditolak | role-based access control |
Cookie sesi memakai HttpOnly, Secure, dan SameSite | cookie options , session security |
Checklist Header dan Secret #
Lapisan pertahanan pada respons HTTP dan pengelolaan kredensial sering luput karena tidak tampak di alur fitur:
| Pemeriksaan | Rujukan mendalam |
|---|---|
Security headers dasar terpasang: X-Content-Type-Options, X-Frame-Options, Referrer-Policy | security headers |
| CSP membatasi sumber script dan style yang boleh dieksekusi | Content Security Policy |
| Konfigurasi CORS hanya membuka origin yang memang dipercaya, tanpa wildcard untuk kredensial | CORS |
| Halaman sensitif melarang pembingkaian oleh situs lain | clickjacking |
| Tidak ada secret di kode; kredensial dibaca dari variabel lingkungan | secrets management |
composer.lock terkomit dan composer audit berjalan di CI | dependency security , dependency auditing |
Checklist Operasional #
Sisa butir menyangkut perilaku aplikasi saat terjadi kesalahan dan saat berjalan di produksi — saat ketenangan aplikasi justru sedang diuji:
| Pemeriksaan | Rujukan mendalam |
|---|---|
display_errors mati di produksi; pesan galat tidak membocor path, query, atau stack trace | production error handling |
| Galat tidak tertangani tetap tercatat ke log dengan konteks yang cukup untuk investigasi | logging errors |
| Konfigurasi produksi diverifikasi terpisah dari development | production configuration |
| Seluruh trafik dilayani lewat HTTPS | production configuration |
| Secret dirotasi berkala dan meninggalkan jejak audit | secrets 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
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:
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 temuanPerhatikan 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
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:
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 .