Aplikasi yang terpasang di perangkat pengguna berada di luar kendali pengembangnya, sehingga asumsi bahwa kode tidak dapat dibaca harus dibuang sejak awal. Topik ini merangkum pengamanan aplikasi catatan pada titik-titik yang paling sering menjadi celah, yaitu secret (rahasia seperti API key dan token) yang ditanam di kode, webview yang terbuka terlalu lebar, masukan platform channel yang tidak divalidasi, build rilis tanpa obfuscation, dan dependensi yang tidak dirawat. Setelah topik ini dibaca, setiap titik tersebut dapat diperiksa satu per satu sebelum aplikasi dirilis.
Secret tidak pernah ditanam di kode#
String yang ditulis di Dart ikut terbawa ke dalam biner aplikasi, dan string literal mudah ditemukan dengan peralatan yang tersedia untuk umum, bahkan setelah kompilasi AOT. Contoh berikut adalah pola yang harus dihindari.
// tidak-dijalankan
// TIDAK AMAN: kunci dapat dibaca dari biner aplikasi.
const apiKey = 'KUNCI-CONTOH-YANG-DAPAT-DIBACA';Siapa pun yang memasang aplikasi dapat membedah binernya, mengambil kunci tersebut, lalu menggunakannya dengan identitas layanan milik pengembang. Kebocoran tidak hanya terjadi melalui biner. Pesan log yang mencetak kunci ikut tersimpan pada perangkat, dan konfigurasi yang dikirim melalui parameter URL ikut tercatat pada log server, sehingga seluruh jalur penulisan dan pengiriman kunci harus diperiksa, bukan hanya tempat kunci itu disimpan. Pembedaan yang tepat adalah memisahkan dua jenis data. Pengenal yang memang dimaksudkan untuk dibaca publik, seperti ID peta yang tampil di layar, boleh tetap disimpan di aplikasi, sedangkan kunci yang menimbulkan biaya atau memberi akses ke data pengguna wajib keluar dari aplikasi dan dipindahkan ke server.
–dart-define dan batasnya#
Konfigurasi yang perlu berubah antar-build, misalnya alamat server pengembangan dan alamat server rilis, diteruskan melalui opsi --dart-define dan dibaca dengan String.fromEnvironment.
// tidak-dijalankan
const alamatServer = String.fromEnvironment(
'ALAMAT_SERVER',
defaultValue: 'https://catatan.example.com/api',
);# tidak-dijalankan
# meneruskan konfigurasi saat build
flutter build apk --dart-define=ALAMAT_SERVER=https://catatan.example.com/apiMekanisme ini nyaman, tetapi batasannya harus dipahami dengan benar sebelum mekanisme ini dipercaya berlebihan.
Menyembunyikan kunci pihak ketiga di balik backend#
Pola yang aman untuk kunci layanan pihak ketiga adalah backend perantara milik pengembang sendiri. Aplikasi memanggil backend dengan autentikasi pengguna, backend memanggil layanan pihak ketiga dengan kuncinya, lalu hasilnya diteruskan kembali ke aplikasi. Kunci tidak pernah meninggalkan server, laju permintaan dapat dibatasi melalui rate limit (pembatasan jumlah permintaan) pada backend, dan kunci dapat diganti tanpa menerbitkan pembaruan aplikasi. Untuk aplikasi catatan, fitur penyimpanan cukup dibangun di atas backend milik sendiri tanpa menyentuh layanan luar, sedangkan fitur yang memang membutuhkan layanan pihak ketiga diarahkan melalui backend tersebut.
Webview dengan batasan#
Webview menjalankan halaman web di dalam aplikasi, dan kekuatan sekaligus risikonya terletak pada JavaScript. Mode JavaScript yang terbuka penuh berarti halaman yang dimuat dapat menjalankan kode apa pun, termasuk membaca nilai yang diteruskan aplikasi ke halaman tersebut. Karena itu, dua pengaturan dijaga, yaitu pembatasan navigasi melalui navigationDelegate dan pengaktifan JavaScript hanya apabila halaman itu benar-benar membutuhkannya.
// tidak-dijalankan
WebViewWidget(
controller: WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setNavigationDelegate(
NavigationDelegate(
onNavigationRequest: (request) {
final url = request.url;
if (url.startsWith('https://panduan.catatan.example.com')) {
return NavigationDecision.navigate;
}
return NavigationDecision.prevent;
},
),
),
)Pada contoh tersebut, hanya domain panduan milik aplikasi yang boleh dimuat di dalam webview, sedangkan tautan lainnya ditolak. Tautan yang berada di luar daftar yang diizinkan lebih baik dibuka di browser perangkat, sehingga konten luar tetap berada di luar aplikasi dan tidak mewarisi akses apa pun dari webview. Prinsip yang sama berlaku pada arah sebaliknya, yaitu data yang diteruskan aplikasi ke halaman web hanya berupa data yang memang ditujukan untuk ditampilkan, bukan token maupun pengenal internal, karena halaman tersebut dapat membaca seluruh URL yang membawa data itu.
Validasi masukan platform channel#
Platform channel adalah jalur masuk yang terbuka dari luar aplikasi. Handler di sisi native menerima nama metode dan argumen dari Dart, dan pada perangkat yang dimodifikasi, pesan tersebut dapat datang dari pihak di luar aplikasi yang ditulis pengembang. Handler yang baik memperlakukan argumen seperti masukan pengguna, yaitu memeriksa tipenya, membatasi panjangnya, dan menolak sisanya.
// tidak-dijalankan
when (call.method) {
"simpanPiket" -> {
val judul = call.argument<String>("judul")
if (judul.isNullOrBlank() || judul.length > 100) {
result.error(
"ARGUMEN_TIDAK_VALID",
"judul wajib terisi dan maksimal 100 karakter",
null,
)
} else {
simpanPiket(judul)
result.success(true)
}
}
else -> result.notImplemented()
}Validasi yang sama berlaku pada arah sebaliknya. Hasil yang diterima Dart dari native juga diperiksa tipenya sebelum digunakan, karena pengecualian di tengah perjalanan pesan dapat menghasilkan data yang tidak lengkap. Dengan dua arah yang sama-sama divalidasi, platform channel tidak lagi menjadi pintu masuk tanpa pemeriksaan.
Obfuscate dan pembaruan dependensi#
Dua kebiasaan terakhir melengkapi titik-titik sebelumnya. Pertama, build rilis di-obfuscate, yaitu nama simbol Dart diacak sehingga hasil pembacaan terhadap biner lebih sulit dihubungkan dengan kode asli. Opsi --split-debug-info memindahkan informasi debug ke berkas terpisah yang disimpan untuk membaca stack trace kelak.
# tidak-dijalankan
# build rilis dengan obfuscation dan pemisahan info debug
flutter build apk --release --obfuscate --split-debug-info=~/debug-info/aplikasi_catatanKedua, dependensi dirawat secara berkala. Package yang usang sering membawa celah yang sudah diketahui publik, dan penyerang justru menargetkan versi lama tersebut. Jalankan flutter pub outdated secara berkala, baca catatan perubahannya sebelum menaikkan versi, dan utamakan package yang perawatannya aktif. Kedua kebiasaan ini tidak menggantikan pengamanan di titik lain, tetapi keduanya memastikan pengamanan pada titik-titik lain tidak lepas melalui sisi yang terlupakan.
Ringkasan#
- Secret tidak ditanam di Dart karena string literal tetap terbaca dari biner aplikasi, dan kunci pihak ketiga disembunyikan di balik backend milik sendiri.
--dart-definemengelola konfigurasi per build, tetapi nilainya juga tertanam, sehingga hanya cocok untuk data yang bukan rahasia.- Webview dibatasi melalui
navigationDelegate, dan mode JavaScript hanya diaktifkan apabila halaman itu membutuhkannya. - Masukan platform channel divalidasi pada kedua sisi, build rilis di-obfuscate dengan
--obfuscate, dan dependensi diperbarui secara berkala.
Referensi#
- Security — Google — Ringkasan resmi ancaman keamanan pada aplikasi Flutter beserta mitigasinya.
- Obfuscate Dart code — Google — Dokumentasi resmi opsi obfuscate dan split-debug-info beserta penyimpanan simbolnya.
- Write platform-specific code with platform channels — Google — Dokumentasi platform channel yang menjadi jalur masuk pada pembahasan validasi.