Bab 49 — Keamanan: Menjaga Aplikasi dari Kebocoran dan Serangan Umum

Seri A1 — Rekayasa Perangkat Lunak Profesional

CatatanDi bab ini, Anda akan:
  • Menangani rahasia dan kata sandi dengan benar (nilai tiruan saja di buku ini)
  • Mengenali dan menangkal injeksi SQL, path traversal, dan deserialisasi tak aman
  • Memindai kode dengan bandit dan dependensi dengan pip-audit
  • Membaca temuan alat keamanan dengan kepala dingin (mana yang nyata, mana yang tidak)
  • Menutup Seri A1 dengan audit atas proyek bersambung

Semua skrip bab ini ada di folder kode/bab49/. Di versi web, Anda juga bisa mengunduh semuanya sekaligus (zip).

Aplikasi Anda kini sudah di server. Itu berarti ia terbuka bagi siapa pun yang bisa menjangkaunya, termasuk orang yang iseng atau jahat. Bayangkan gudang hasil kebun: Anda tidak hanya mengunci pintu depan. Anda juga menyimpan kunci gudang di tempat aman (bukan di bawah keset), memeriksa karung yang masuk, dan tidak membiarkan sembarang orang membawa pulang catatan harga. Keamanan berarti banyak lapisan kecil, karena tidak ada satu gembok yang cukup.

Keamanan berlapis: rahasia, dependensi, kode, masukan.
PeringatanAWAS

Semua “rahasia” di bab ini adalah nilai tiruan (misalnya [disamarkan]). Jangan pernah menaruh kata sandi atau kunci sungguhan di kode, di naskah, atau di Git. Contoh “serangan” di bab ini hanya menyerang data tiruan di memori komputer Anda sendiri.

Lapisan 1: rahasia

Rahasia (kata sandi, kunci API, token) yang tertulis di kode akan ikut tersimpan di Git, di salinan rekan kerja, dan di mana pun kode itu menyebar. Bila bocor, mengubah kode tidak cukup: Anda harus mengganti rahasianya. Kebiasaan yang benar:

01_rahasia.py
# Bab 49 - Rahasia: jangan ditulis di kode, baca dari lingkungan, dan bandingkan dengan aman
import hmac
import os
import secrets

# JANGAN begini (nilai tiruan, hanya untuk menunjukkan bentuk yang salah):
#   KUNCI_API = "sk-contoh-tiruan-123"
# Begini: baca dari variabel lingkungan; kalau tidak ada, hentikan dengan pesan jelas.
os.environ["APP_KUNCI_API"] = "[disamarkan]"        # demo saja; di server diisi oleh sistem


def ambil_rahasia(nama):
    nilai = os.environ.get(nama)
    if not nilai:
        raise RuntimeError(f"variabel lingkungan {nama} belum diisi")
    return nilai


def samarkan(teks, tampil=2):
    """Menampilkan hanya sedikit awal teks; sisanya bintang."""
    return teks[:tampil] + "*" * max(len(teks) - tampil, 0)


kunci = ambil_rahasia("APP_KUNCI_API")
print("kunci terbaca, panjang:", len(kunci), "| tampilan aman:", samarkan(kunci))

try:
    ambil_rahasia("APP_TIDAK_ADA")
except RuntimeError as galat:
    print("galat jelas:", galat)

# Membuat token acak yang kuat dengan modul secrets (bukan random!)
token = secrets.token_urlsafe(32)
print("panjang token acak:", len(token), "karakter | dua token sama?", token == secrets.token_urlsafe(32))

# Membandingkan rahasia: compare_digest tahan serangan pengukuran waktu
print("cocok:", hmac.compare_digest(kunci, "[disamarkan]"), "| salah:", hmac.compare_digest(kunci, "tebakan"))
kunci terbaca, panjang: 12 | tampilan aman: [d**********
galat jelas: variabel lingkungan APP_TIDAK_ADA belum diisi
panjang token acak: 43 karakter | dua token sama? False
cocok: True | salah: False
  • Rahasia dibaca dari variabel lingkungan, dan bila tidak ada, program berhenti dengan pesan jelas (bukan diam-diam memakai nilai bawaan yang berbahaya).
  • Saat menampilkan, tampilkan sedikit saja ([d**********). Panjang nilai aslinya bisa dicetak, isinya jangan.
  • Untuk token, pakai modul secrets, bukan random (yang bisa ditebak). Dua token dari secrets.token_urlsafe(32) hampir pasti berbeda (43 karakter).
  • Membandingkan rahasia dengan == bisa membocorkan informasi lewat selisih waktu; hmac.compare_digest tahan terhadap hal itu.

Kata sandi pengguna: simpan hash, bukan teksnya

Jangan pernah menyimpan kata sandi apa adanya. Simpan hash (sidik jari satu arah) yang diberi garam (salt, bumbu acak per pengguna):

02_kata_sandi.py
# Bab 49 - Kata sandi: simpan HASH berbumbu (salt), jangan teks aslinya
import hashlib
import hmac
import os


def buat_hash(kata_sandi):
    garam = os.urandom(16)                              # "bumbu" acak, berbeda tiap pengguna
    hasil = hashlib.scrypt(kata_sandi.encode(), salt=garam, n=2**14, r=8, p=1)
    return garam, hasil


def periksa(kata_sandi, garam, hasil):
    percobaan = hashlib.scrypt(kata_sandi.encode(), salt=garam, n=2**14, r=8, p=1)
    return hmac.compare_digest(percobaan, hasil)


garam1, hash1 = buat_hash("contoh-sandi-tiruan")
garam2, hash2 = buat_hash("contoh-sandi-tiruan")

print("panjang garam:", len(garam1), "byte | panjang hash:", len(hash1), "byte")
print("sandi benar diterima :", periksa("contoh-sandi-tiruan", garam1, hash1))
print("sandi salah ditolak  :", not periksa("tebakan", garam1, hash1))
print("dua pengguna, sandi sama, hash sama?", hash1 == hash2)
print("teks asli tersimpan di hash?", b"contoh" in hash1)
panjang garam: 16 byte | panjang hash: 64 byte
sandi benar diterima : True
sandi salah ditolak  : True
dua pengguna, sandi sama, hash sama? False
teks asli tersimpan di hash? False

Dua pengguna dengan kata sandi sama menghasilkan hash berbeda karena garamnya berbeda, sehingga penyerang tidak bisa menandai “semua yang hash-nya sama pasti sandinya sama”. Teks aslinya tidak ada di hash. Fungsi scrypt di hashlib memang sengaja dibuat lambat dan boros memori supaya menebak massal menjadi mahal. Parameter n, r, p di skrip hanyalah contoh; angka yang direkomendasikan saat ini belum kami verifikasi, jadi ikuti panduan keamanan terbaru dan pertimbangkan pustaka khusus untuk produksi.

Injeksi SQL

Bila Anda menyambung masukan pengguna langsung ke teks SQL, pengguna bisa menyisipkan perintah mereka sendiri:

03_injeksi_sql.py
# Bab 49 - Injeksi SQL: menyambung teks ke perintah SQL itu berbahaya, parameter itu aman
import sqlite3

db = sqlite3.connect(":memory:")
db.execute("CREATE TABLE petani (nama TEXT, plot TEXT, rahasia TEXT)")
db.executemany("INSERT INTO petani VALUES (?, ?, ?)", [
    ("Budi", "A", "catatan-pribadi-1"),
    ("Siti", "B", "catatan-pribadi-2"),
])

masukan_jahat = "A' OR '1'='1"       # pengguna jahat mengetik ini di kolom "plot"


def cari_rawan(plot):
    sql = f"SELECT nama, rahasia FROM petani WHERE plot = '{plot}'"      # JANGAN begini
    return sql, db.execute(sql).fetchall()


def cari_aman(plot):
    sql = "SELECT nama, rahasia FROM petani WHERE plot = ?"              # begini
    return sql, db.execute(sql, (plot,)).fetchall()


sql, hasil = cari_rawan(masukan_jahat)
print("RAWAN SQL  :", sql)
print("RAWAN hasil:", hasil, "<- semua baris bocor!")

sql, hasil = cari_aman(masukan_jahat)
print("AMAN  SQL  :", sql)
print("AMAN  hasil:", hasil, "<- tidak ada plot bernama itu")
print("AMAN, plot normal:", cari_aman("A")[1])
RAWAN SQL  : SELECT nama, rahasia FROM petani WHERE plot = 'A' OR '1'='1'
RAWAN hasil: [('Budi', 'catatan-pribadi-1'), ('Siti', 'catatan-pribadi-2')] <- semua baris bocor!
AMAN  SQL  : SELECT nama, rahasia FROM petani WHERE plot = ?
AMAN  hasil: [] <- tidak ada plot bernama itu
AMAN, plot normal: [('Budi', 'catatan-pribadi-1')]

Masukan A' OR '1'='1 mengubah perintah menjadi WHERE plot = 'A' OR '1'='1': kondisinya selalu benar, sehingga semua baris bocor. Dengan parameter (tanda ? dan nilai dikirim terpisah), masukan jahat itu hanya dianggap sebagai teks nama plot yang tidak ada, jadi hasilnya kosong. Aturannya mutlak: jangan pernah membentuk SQL dengan f-string atau + dari masukan pengguna. Di Bab 47, t-string melakukan hal yang sama dengan lebih nyaman.

Path traversal

Bila nama berkas datang dari pengguna, ../ bisa membuat program membaca berkas di luar folder yang dimaksud:

04_path_traversal.py
# Bab 49 - Path traversal: nama berkas dari pengguna bisa "naik" keluar folder dengan ../
from pathlib import Path

akar = Path(__file__).parent / "_tmp" / "laporan"
akar.mkdir(parents=True, exist_ok=True)
(akar / "panen.txt").write_text("hasil panen blok A: 245,5 kg", encoding="utf-8")
(akar.parent / "rahasia.txt").write_text("INI DI LUAR FOLDER LAPORAN", encoding="utf-8")


def baca_rawan(nama):
    return (akar / nama).read_text(encoding="utf-8")                      # JANGAN begini


def baca_aman(nama):
    jalur = (akar / nama).resolve()
    if not jalur.is_relative_to(akar.resolve()):                          # begini
        raise PermissionError(f"jalur di luar folder laporan: {nama!r}")
    return jalur.read_text(encoding="utf-8")


serangan = "../rahasia.txt"
print("RAWAN:", baca_rawan(serangan), "<- bocor!")
try:
    baca_aman(serangan)
except PermissionError as galat:
    print("AMAN ditolak:", galat)
print("AMAN, nama normal:", baca_aman("panen.txt"))
RAWAN: INI DI LUAR FOLDER LAPORAN <- bocor!
AMAN ditolak: jalur di luar folder laporan: '../rahasia.txt'
AMAN, nama normal: hasil panen blok A: 245,5 kg

Versi rawan membaca rahasia.txt yang ada di luar folder laporan. Versi aman memakai resolve() (mengubah jalur menjadi bentuk mutlak tanpa ..), lalu memastikan hasilnya masih berada di dalam folder yang diizinkan dengan is_relative_to.

Deserialisasi tak aman

Membuka data berformat pickle dari sumber yang tak Anda percaya sama dengan menjalankan kode orang itu. Contoh berikut tidak berbahaya (hanya mencetak teks), tetapi membuktikan intinya:

05_deserialisasi.py
# Bab 49 - Deserialisasi tak aman: pickle bisa MENJALANKAN kode saat dibuka; json hanya data
import json
import pickle


class Jahat:
    """Contoh tak berbahaya: saat dibuka, pickle memanggil print (penyerang sungguhan memanggil hal lain)."""

    def __reduce__(self):
        return (print, ("!!! kode dari data pickle berjalan saat pickle.loads()",))


data_asing = pickle.dumps(Jahat())
print("membuka pickle dari sumber tak tepercaya...")
pickle.loads(data_asing)                      # JANGAN begini dengan data dari luar

teks_asing = '{"blok": "A", "hasil_kg": 245.5}'
print("membuka JSON:", json.loads(teks_asing))

# JSON tidak bisa menjalankan kode; data aneh hanya jadi galat biasa
try:
    json.loads("print('x')")
except json.JSONDecodeError as galat:
    print("JSON menolak kode:", type(galat).__name__)
membuka pickle dari sumber tak tepercaya...
!!! kode dari data pickle berjalan saat pickle.loads()
membuka JSON: {'blok': 'A', 'hasil_kg': 245.5}
JSON menolak kode: JSONDecodeError

Baris !!! kode dari data pickle berjalan... muncul saat membuka pickle: data itu memuat perintah, bukan sekadar isi. JSON hanya data; teks aneh menghasilkan galat biasa. Untuk data dari luar, pakai JSON (atau format data lain yang tidak bisa menjalankan kode), dan gunakan pickle hanya untuk data yang Anda tulis sendiri dan simpan di tempat tepercaya.

bandit: pemindai keamanan kode

bandit membaca kode Python tanpa menjalankannya (analisis statis) dan menandai pola berisiko. Kita pindai satu berkas rawan dan satu berkas yang sudah diperbaiki:

06_bandit.py
# Bab 49 - bandit: pemindai keamanan kode Python (analisis statis, tanpa menjalankan kode)
import json
import subprocess
import sys
from pathlib import Path

tmp = Path(__file__).parent / "_tmp"
tmp.mkdir(exist_ok=True)

rawan = tmp / "contoh_rawan.py"
rawan.write_text('''import pickle

password = "contoh-tiruan-123"


def cari(db, plot):
    return db.execute(f"SELECT * FROM pohon WHERE plot = '{plot}'").fetchall()


def muat(data):
    return pickle.loads(data)
''', encoding="utf-8")

aman = tmp / "contoh_aman.py"
aman.write_text('''import json
import os


def cari(db, plot):
    return db.execute("SELECT * FROM pohon WHERE plot = ?", (plot,)).fetchall()


def muat(data):
    return json.loads(data)


kunci = os.environ.get("APP_KUNCI_API")
''', encoding="utf-8")


def pindai(berkas):
    hasil = subprocess.run([sys.executable, "-m", "bandit", "-f", "json", "-q", str(berkas)],
                           capture_output=True, text=True, encoding="utf-8")
    return json.loads(hasil.stdout)["results"]


for berkas in (rawan, aman):
    temuan = pindai(berkas)
    print(f"{berkas.name}: {len(temuan)} temuan")
    for t in sorted(temuan, key=lambda t: t["line_number"]):
        print(f"  baris {t['line_number']:2} {t['test_id']} [{t['issue_severity']}/{t['issue_confidence']}] {t['issue_text'][:60]}")
contoh_rawan.py: 4 temuan
  baris  1 B403 [LOW/HIGH] Consider possible security implications associated with pick
  baris  3 B105 [LOW/MEDIUM] Possible hardcoded password: 'contoh-tiruan-123'
  baris  7 B608 [MEDIUM/MEDIUM] Possible SQL injection vector through string-based query con
  baris 11 B301 [MEDIUM/HIGH] Pickle and modules that wrap it can be unsafe when used to d
contoh_aman.py: 0 temuan

Empat temuan pada berkas rawan sesuai dengan yang sudah kita pelajari: impor pickle (B403), kata sandi tertulis (B105), SQL dari f-string (B608), dan pemakaian pickle.loads (B301). Berkas yang diperbaiki bersih (0 temuan). Tiap temuan punya tingkat keparahan (LOW, MEDIUM, HIGH) dan keyakinan; baca keduanya sebelum panik.

pip-audit: dependensi yang bermasalah

Kode Anda mungkin aman, tetapi pustaka yang Anda pakai punya lubang. pip-audit mencocokkan nama dan versi paket Anda dengan basis data kerentanan yang diketahui. Kita sengaja memilih dua paket versi lama sebagai contoh:

07_pip_audit.py
# Bab 49 - pip-audit: mencari kerentanan pada paket yang kita pakai (contoh: versi lama sengaja dipilih)
import subprocess
import sys
from pathlib import Path

tmp = Path(__file__).parent / "_tmp"
tmp.mkdir(exist_ok=True)
daftar = tmp / "requirements-lama.txt"
daftar.write_text("requests==2.19.0\npyyaml==5.3\n", encoding="utf-8")   # versi lama, untuk contoh

hasil = subprocess.run(
    [sys.executable, "-m", "pip_audit", "-r", str(daftar), "--no-deps", "--disable-pip"],
    capture_output=True, text=True, encoding="utf-8", timeout=100,
)
print(hasil.stdout.strip())
print("kode keluar pip-audit:", hasil.returncode)
Name     Version ID              Fix Versions
-------- ------- --------------- ------------
requests 2.19.0  PYSEC-2018-28   2.20.0
requests 2.19.0  PYSEC-2018-28   2.20.0
requests 2.19.0  PYSEC-2023-74   2.31.0
requests 2.19.0  PYSEC-2023-74   2.31.0
requests 2.19.0  PYSEC-2026-1873 2.32.0
requests 2.19.0  PYSEC-2026-1872 2.32.4
requests 2.19.0  PYSEC-2026-2275 2.33.0
requests 2.19.0  PYSEC-2026-1873 2.32.0
requests 2.19.0  PYSEC-2026-1872 2.32.4
requests 2.19.0  PYSEC-2026-2275 2.33.0
pyyaml   5.3     PYSEC-2020-96   5.3.1
pyyaml   5.3     PYSEC-2021-142  5.4
pyyaml   5.3     PYSEC-2021-142  5.4
pyyaml   5.3     PYSEC-2020-96   5.3.1
kode keluar pip-audit: 1

Keluaran ini diambil sekali pada 2026-10-10 (arahan @statis), karena pip-audit menghubungi basis data di internet dan isinya berubah tiap hari; hasil di komputer Anda bisa berbeda. Cara membaca: tiap baris berisi paket, versi Anda, nomor kerentanan (PYSEC-...), dan versi perbaikan (Fix Versions). Untuk requests 2.19.0, perbaikan terakhir yang disebut ada di versi 2.33.0; untuk pyyaml 5.3, di 5.4. Beberapa nomor tampil dua kali di tabel; kami tidak menelusuri sebabnya. Kode keluar 1 berarti “ada kerentanan”, dan itulah yang membuat CI gagal sehingga Anda terdorong memperbarui. Langkah nyatanya: naikkan versi di requirements.txt, jalankan tes, ulangi audit.

Proyek Bersambung A1: audit proyek

Sekarang kita memeriksa kode proyek kita sendiri dari Bab 45, 46, dan 48: bandit atas ketiganya, ditambah pemindai rahasia sederhana. Pemindainya diuji dulu dengan contoh buatan agar kita yakin ia memang bisa mendeteksi:

08_proyek_audit.py
# Bab 49 - Proyek bersambung A1: audit keamanan atas kode proyek (bandit + pemindai rahasia)
import json
import re
import subprocess
import sys
from pathlib import Path

kode = Path(__file__).parent.parent
sasaran = {
    "paket (Bab 45)": kode / "bab45" / "proyek_paket" / "src",
    "arsitektur (Bab 46)": kode / "bab46" / "lapangan_v2",
    "server (Bab 48)": kode / "bab48" / "app",
}

POLA_RAHASIA = [
    ("kata sandi/kunci tertulis", re.compile(r"""(?i)(password|passwd|secret|token|api_?key)\s*=\s*["'][^"'\s]{8,}["']""")),
    ("kunci akses gaya AWS", re.compile(r"AKIA[0-9A-Z]{16}")),
    ("kunci privat", re.compile(r"-----BEGIN [A-Z ]*PRIVATE KEY-----")),
]


def pindai_rahasia(teks):
    return [nama for nama, pola in POLA_RAHASIA if pola.search(teks)]


# Uji pemindainya dulu dengan contoh buatan (nilai tiruan, disambung agar tidak terbaca sebagai kunci sungguhan)
contoh = 'password = "tiruan-12345678"\nkunci = "AKIA' + 'ABCDEFGHIJKLMNOP"\n'
print("pemindai menemukan di contoh buatan:", pindai_rahasia(contoh))
print()

total_bandit = total_rahasia = 0
for nama, folder in sasaran.items():
    hasil = subprocess.run([sys.executable, "-m", "bandit", "-r", "-f", "json", "-q", str(folder)],
                           capture_output=True, text=True, encoding="utf-8")
    temuan = json.loads(hasil.stdout)["results"]
    rahasia = [(p.name, jenis) for p in sorted(folder.rglob("*.py"))
               for jenis in pindai_rahasia(p.read_text(encoding="utf-8"))]
    total_bandit += len(temuan)
    total_rahasia += len(rahasia)
    print(f"{nama:22} bandit: {len(temuan)} temuan | rahasia tertulis: {len(rahasia)}")
    for t in temuan:
        print(f"    -> {t['test_id']} [{t['issue_severity']}] {Path(t['filename']).name} baris {t['line_number']}: {t['issue_text'][:55]}")

print()
print("TOTAL temuan bandit:", total_bandit, "| TOTAL rahasia tertulis:", total_rahasia)
pemindai menemukan di contoh buatan: ['kata sandi/kunci tertulis', 'kunci akses gaya AWS']

paket (Bab 45)         bandit: 1 temuan | rahasia tertulis: 0
    -> B311 [LOW] cli.py baris 9: Standard pseudo-random generators are not suitable for 
arsitektur (Bab 46)    bandit: 0 temuan | rahasia tertulis: 0
server (Bab 48)        bandit: 1 temuan | rahasia tertulis: 0
    -> B311 [LOW] rekap.py baris 8: Standard pseudo-random generators are not suitable for 

TOTAL temuan bandit: 2 | TOTAL rahasia tertulis: 0

Pemindai rahasia menemukan kedua pola di contoh buatan, dan tidak ada rahasia tertulis di kode proyek. bandit memberi dua temuan, keduanya B311 tingkat LOW: penggunaan modul random di cli.py dan rekap.py. Apakah ini masalah? Tidak: random di sini hanya membuat data contoh yang bisa ditebak dengan benih tetap, bukan untuk token atau kata sandi. Inilah triase: tidak semua temuan harus diperbaiki, tetapi tiap temuan harus dibaca dan diputuskan. Bila ada pemakaian random untuk token, itu barulah salah dan harus diganti dengan secrets.

Gabungan seluruh Seri A1 pada proyek kita:

Bab Hasil pada proyek
44 rekap nilai kayu dipercepat dengan data per kolom dan numpy
45 dikemas sebagai paket yang lolos twine check dan terpasang di lingkungan bersih
46 ditata berlapis (domain, layanan, adapter, antarmuka), aturannya diperiksa otomatis
47 ditulis dengan gaya Python 3.14 (generik, t-string untuk SQL berparameter)
48 disajikan sebagai server dengan health check, log, dan metrik; Dockerfile dan CI sebagai berkas (tidak dijalankan)
49 diaudit: tanpa rahasia tertulis, temuan bandit sudah ditriase

Latihan

  1. Tambahkan pada 01_rahasia.py fungsi yang menolak rahasia kurang dari 12 karakter. Uji dengan nilai pendek.
  2. Ubah 04_path_traversal.py agar menyerang dengan "/etc/passwd" (jalur mutlak). Apakah baca_aman menolaknya? Mengapa? (Petunjuk: apa yang dilakukan operator / dari pathlib bila bagian kanan adalah jalur mutlak?)
  3. Tambahkan satu contoh rawan lagi ke 06_bandit.py, misalnya memanggil eval() pada masukan, dan lihat ID temuan baru dari bandit.
  4. Tantangan: tulis tes pytest yang memastikan fungsi cari_aman di skrip 03 tidak membocorkan baris untuk lima masukan jahat berbeda (misalnya yang memuat --, ;, dan ').

Ringkasan Bab

  • Keamanan itu berlapis: rahasia, dependensi, kode, dan masukan.
  • Rahasia dari lingkungan, tidak pernah di kode; token dari secrets; bandingkan dengan compare_digest; kata sandi disimpan sebagai hash berbumbu.
  • Tangkal injeksi SQL dengan parameter, path traversal dengan resolve() plus is_relative_to, dan hindari pickle untuk data luar.
  • bandit memindai kode, pip-audit memindai dependensi; baca temuan, lakukan triase, perbaiki yang nyata.
  • Hasil pip-audit bergantung tanggal; ulangi secara berkala, idealnya di CI.

Penutup Seri A1

Selamat! Anda telah menyelesaikan Seri A1: Rekayasa Perangkat Lunak Profesional. Anda kini bisa mengukur dan mempercepat program, mengemasnya menjadi paket, menatanya dengan pola dan arsitektur berlapis, memakai fitur Python modern sampai 3.14, membawanya ke server dengan Docker dan CI/CD (sebagai berkas yang siap dipakai), memantaunya, dan mengauditnya dari sisi keamanan.

Sistem Anda siap produksi, tetapi datanya masih kecil. Seri A2 menangani data skala besar: format kolumnar, Polars dan DuckDB, data geospasial cloud-native, sensor lapangan, dan komputasi untuk data yang tak muat di satu komputer.

Selanjutnya, di Bab 50: rekayasa data modern, dengan Parquet, Polars, DuckDB, dan alur data yang rapi.