Kebocoran state serverless: simpan data pengguna per permintaan
Pelajari bagaimana runtime serverless yang digunakan ulang dapat membuka data lama dan cara mengisolasi identitas serta state per permintaan.
💡 Penjelasan sederhana
Perlakukan autentikasi, identitas tenant, dan data khusus permintaan seperti kartu pengunjung: semuanya milik satu kunjungan dan tidak boleh tertinggal di meja bersama. Sebagian runtime serverless menggunakan ulang environment atau isolate untuk permintaan berikutnya. Jika kode menyimpan konteks pengguna dalam state mutable tingkat modul lalu permintaan lain membacanya, aplikasi dapat melanggar batas pengguna atau tenant sendiri.
Penggunaan ulang adalah perilaku implementasi, bukan jaminan bahwa pengguna berikutnya akan mencapai runtime yang sama atau runtime hidup selama waktu tertentu. Kebocoran memerlukan state eksekusi bersama dan jalur kode yang mempertahankan data permintaan atau membaca state lama. Simpan identitas dalam lingkup permintaan dan teruskan secara eksplisit ke operasi yang memerlukannya.
Konsep inti dan istilah
- Execution environment reuse
- Penggunaan ulang execution environment: platform dapat mempertahankan runtime atau isolate untuk pemanggilan berikutnya, tetapi juga dapat menghentikannya kapan saja.
- Module-level state
- State tingkat modul: nilai yang diinisialisasi di luar handler. Nilai itu dapat bertahan antar-pemanggilan pada runtime tersebut, sehingga jangan simpan data khusus pengguna atau tenant.
- AsyncLocalStorage
- AsyncLocalStorage: API Node.js untuk meneruskan store melalui konteks eksekusi asinkron. Ini melacak konteks, bukan mengautentikasi atau mengotorisasi pengguna.
- Cross-request data exposure
- Paparan data antar-permintaan: permintaan berikutnya membaca data permintaan sebelumnya pada konteks yang digunakan ulang. Penggunaan ulang saja tidak membuktikan kebocoran.
Alur paparan data langkah demi langkah
Satu permintaan membuat datanya sendiri
Permintaan A mengautentikasi dan memperoleh konteks pengguna atau tenant-nya. Bug terjadi bila kode menaruh identitas itu di variabel mutable tingkat modul, bukan di lingkup handler.
Runtime mungkin digunakan ulang
Setelah merespons, platform dapat mempertahankan environment tetap hangat. AWS mendokumentasikan penggunaan ulang; Cloudflare Workers dapat memakai ulang isolate, tetapi routing dan masa hidupnya bukan urutan tetap antar-pengguna.
Permintaan berikutnya mencapai runtime yang sama
Jika permintaan B ditangani proses atau isolate yang sama, nilai modul dari A tetap tersedia sampai ditimpa atau dihapus. Early return atau autentikasi yang terlewat dapat membuat state lama terbaca.
Aplikasi mengirim data dengan identitas yang salah
Jika handler memakai nilai lama untuk kueri database atau respons, B dapat menerima data A. Ini kegagalan isolasi aplikasi yang bersyarat, bukan sifat tak terhindarkan platform serverless.
Kode sumber: pola salah dan implementasi aman
// UNSAFE: request identity is mutable process-wide state.
import { NextResponse } from "next/server";
let cachedUserContext: { userId: string; role: string } | null = null;
export async function POST(req: Request) {
const token = req.headers.get("authorization");
if (token) {
cachedUserContext = await decodeUser(token);
}
// If token is missing, stale state can still be used.
return NextResponse.json({ actor: cachedUserContext?.userId });
}
// SAFE PATTERN: keep identity local and pass it to the service.
import { NextResponse } from "next/server";
export async function POST(req: Request) {
const user = await authenticate(req);
if (!user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const account = await loadAccountForUser(user.id);
return NextResponse.json({ actor: user.id, account });
}
// The service enforces ownership in its query:
async function loadAccountForUser(userId: string) {
return db.account.findFirst({ where: { ownerId: userId } });
}
Daftar periksa penguatan engineering
- Simpan autentikasi, identitas pengguna, ID tenant, dan data mutable khusus permintaan di handler atau konteks yang secara eksplisit terisolasi; jangan simpan di variabel global modul.
- Utamakan parameter eksplisit untuk meneruskan identitas terautentikasi ke layanan dan akses data. Di Node.js, gunakan
AsyncLocalStorage.run()hanya bila konteks asinkron implisit diperlukan; ini meneruskan konteks, bukan memberi otorisasi. - Jangan samakan singleton klien database dengan state pengguna. Klien dapat digunakan ulang, tetapi teruskan pengguna/tenant terautentikasi ke setiap kueri dan tegakkan kepemilikan dalam kondisi kueri.
- Gunakan
React cache()hanya untuk memoization per permintaan yang didokumentasikan pada React Server Components; jangan jadikan cache autentikasi antar-permintaan umum. - Tambahkan tes integrasi yang menyelang-nyeling permintaan pengguna berbeda dan satu permintaan tanpa autentikasi; pastikan respons dan operasi database tetap terikat ke identitas masing-masing.
- Tinjau dokumentasi siklus hidup platform. Uji logika dengan kemungkinan environment digunakan ulang, tanpa mengasumsikan permintaan mana dijadwalkan ke instans tertentu.