Autentikasi dan otorisasi kita sudah solid (Bagian 3, Bagian 4). Sekarang kita pastikan lapisan berikutnya: data yang MASUK ke sistem tidak bisa dipakai untuk menyerang sistem itu sendiri. Ini tentang Injection, ancaman yang dibahas konsepnya di Bagian 2.
Membuktikan SQL Injection Tidak Bekerja di Kode Kita
Sejak awal seri FastAPI, kita selalu memakai SQLModel untuk berinteraksi dengan database — bukan menulis SQL mentah dengan string concatenation. Ini bukan kebetulan; ini yang membuat kita otomatis kebal dari SQL injection, TANPA perlu menulis kode pertahanan tambahan apa pun.
Mari buktikan langsung — coba "serang" API kita sendiri dengan payload SQL injection klasik lewat field title:
curl -X POST localhost:8000/api/tasks -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"title\": \"'; DROP TABLE task; --\"}"
{"id":2,"title":"'; DROP TABLE task; --","done":false,"priority":"medium","created_at":"..."}
Cek langsung ke database setelahnya:
SELECT id, title FROM task;
id | title
----+------------------------
1 | Task rahasia alice
2 | '; DROP TABLE task; --
Tabel task masih utuh, datanya masih ada semua. Payload yang tadinya dirancang untuk menghapus seluruh tabel cuma tersimpan sebagai STRING BIASA — persis seperti judul task lain mana pun. Ini membuktikan konsep dari Bagian 2: SQLModel/SQLAlchemy secara otomatis memakai parameterized query di balik layar.
Kenapa Ini Bisa Terjadi (dan Kenapa Kita Sudah Aman)
Di balik layar, ketika kita menulis:
task = Task(title=payload.title, priority=payload.priority, owner_id=current_user.id)
session.add(task)
session.commit()
SQLModel/SQLAlchemy TIDAK PERNAH menyisipkan payload.title langsung ke dalam teks perintah SQL. Sebagai gantinya, perintah SQL yang sebenarnya dikirim ke database kurang lebih seperti ini:
INSERT INTO task (title, priority, owner_id) VALUES ($1, $2, $3)
Dengan $1, $2, $3 sebagai placeholder — nilai sesungguhnya dikirim TERPISAH dari teks perintahnya, lewat protokol database. Database sendiri yang menjamin $1 SELALU diperlakukan sebagai satu nilai data, apa pun isinya — termasuk kalau isinya kebetulan terlihat seperti perintah SQL. Ini beda fundamental dengan menyusun string SQL manual (f"... WHERE title = '{title}'"), yang membiarkan isi variabel ikut "dibaca" sebagai bagian dari perintah.
Aturan praktis: selama kamu memakai ORM/query builder (SQLModel, SQLAlchemy, Django ORM, dan sejenisnya) dan TIDAK PERNAH menyusun SQL manual dengan string formatting/concatenation dari input user, kamu sudah otomatis terlindungi dari SQL injection. Bahaya baru muncul kalau ada kebutuhan khusus yang memaksa menulis raw SQL — kalau itu terjadi, WAJIB memakai parameter binding yang disediakan library-nya, bukan format string Python.
Validasi Pydantic Sebagai Lapisan Pertahanan, Bukan Formalitas
Ingat kembali Field constraints dari seri FastAPI — di konteks keamanan, ini bukan cuma soal "data rapi", tapi lapisan pertahanan pertama sebelum data sempat diproses:
class TaskCreate(BaseModel):
title: str = Field(min_length=1, max_length=200)
priority: Priority = Priority.medium
max_length=200 bukan cuma kosmetik. Tanpa batas ini, seseorang bisa mengirim title sepanjang berjuta-juta karakter berulang kali — membebani database, memory, dan bandwidth, salah satu bentuk Unrestricted Resource Consumption yang sudah disinggung di Bagian 2. Validasi ini terjadi SEBELUM path operation function kita dijalankan sama sekali — request yang melanggar aturan ditolak 422 di lapisan paling luar, tidak pernah sempat menyentuh business logic ataupun database.
curl -X POST localhost:8000/api/tasks -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" -d '{"title": ""}'
# 422 — ditolak sebelum sempat diproses
Soal XSS — Kenapa Belum (dan Tidak Perlu) Ditangani di Sini
task-tracker-api kita murni API JSON — tidak pernah merender HTML. Cross-Site Scripting (XSS) terjadi ketika data yang bisa dikontrol user dirender sebagai HTML TANPA di-escape, memungkinkan penyerang menyisipkan <script> yang dieksekusi di browser korban. Karena kita tidak pernah mengembalikan HTML, XSS bukan ancaman langsung di lapisan API ini.
Tapi ini bukan berarti "sudah aman selamanya". Kalau suatu saat data dari API ini ditampilkan di frontend (React, Vue, atau apa pun) TANPA output encoding yang benar, title seperti <script>alert('hacked')</script> bisa saja dieksekusi di browser user lain yang melihatnya. Kabar baiknya, hampir semua framework frontend modern (React, Vue) secara default melakukan escaping otomatis saat me-render data sebagai teks — celah XSS biasanya muncul justru ketika developer secara eksplisit BYPASS proteksi itu (misalnya dangerouslySetInnerHTML di React) tanpa sanitasi tambahan.
Prinsip pentingnya: pertahanan terhadap XSS ada di titik RENDER (output encoding di frontend), bukan di titik SIMPAN (API/database). Menyaring <script> saat data masuk itu rapuh dan gampang dilewati (ada puluhan cara menulis ulang payload XSS); output encoding yang benar di titik render jauh lebih andal karena tidak bergantung menebak semua kemungkinan payload.
Rangkuman
Field constraints) adalah lapisan pertahanan aktif, bukan formalitas — mencegah data berlebihan sampai ke business logic sama sekali.