CodeQL 2.26.0 dari GitHub menambahkan kueri bawaan yang mendeteksi pola prompt-injection AI, dan perubahan ini sudah mulai menyebabkan pipeline CI menandai risiko baru. Pembaruan saja tidak cukup—tim membutuhkan rangkaian uji regresi (regression test suite) yang menjamin aturan tersebut tetap efektif seiring berkembangnya kode.
Mengapa fixture regresi itu penting
Prompt injection memungkinkan penyerang menyisipkan instruksi berbahaya ke dalam prompt yang kemudian diikuti oleh model bahasa. Dengan kueri baru ini, analisis statis dapat melacak data dari sumber yang tidak tepercaya (untrusted source) ke sink pemanggilan model (model-calling sink). Jika aturan tersebut hanya diaktifkan tanpa pernah diverifikasi, refaktor di kemudian hari dapat memutus jalur aliran data (data-flow path) dan peringatan akan hilang secara diam-diam. Sebuah fixture regresi menangkap jalur tepat yang seharusnya memicu (atau tidak memicu) aturan tersebut, mengubah hasil analisis statis menjadi kontrak yang ditegakkan oleh build.
Tiga komponen fixture yang andal
- Untrusted source – fungsi apa pun yang membawa data dari luar basis kode yang tepercaya (misalnya, isi GitHub issue, payload webhook).
- Prompt construction – kode yang menyusun permintaan model, biasanya berupa panggilan ke SDK klien.
- Model sink – metode SDK yang mengirimkan prompt ke model. Mesin data-flow CodeQL perlu melihat panggilan nyata dari stack produksi Anda untuk mengenali sink tersebut.
Kueri hanya akan berjalan jika ketiga komponen tersebut ada.
Mengatur file pengujian
Tata letak konvensional membuat rangkaian pengujian mudah diaudit:
security-fixtures/prompt-injection/
├─ positive/
│ ├─ direct-flow.ts
│ └─ helper-flow.ts
├─ negative/
│ └─ trusted-instruction.ts
└─ expected-alerts.json
File positive berisi kode yang harus ditandai; file negative berisi pola aman yang harus tetap diam (tidak memicu peringatan).
Menulis kasus positif
Contoh paling sederhana menunjukkan aliran langsung dari nilai yang tidak tepercaya ke panggilan model:
import { model } from "./supported-client";
declare function loadIssueBody(id: number): Promise<string>;
export async function summarize(id: number) {
const untrusted = await loadIssueBody(id);
return model.generate({
system: "Summarize the issue",
user: untrusted,
});
}
Di sini loadIssueBody adalah untrusted source, model.generate adalah sink, dan data mengalir tanpa langkah sanitasi apa pun—tepat seperti yang dirancang untuk ditangkap oleh kueri tersebut.
Kasus positif kedua harus mengarahkan data melalui fungsi pembantu (helper function), untuk membuktikan bahwa analisis mengikuti jalur tidak langsung:
function wrapUserInput(input: string) {
return { system: "Summarize the issue", user: input };
}
export async function summarizeViaHelper(id: number) {
const raw = await loadIssueBody(id);
return model.generate(wrapUserInput(raw));
}
Kedua file tersebut berada di bawah direktori positive/.
Menulis kasus negatif
Fixture negatif harus menunjukkan bahwa input pengguna tidak dapat mengubah instruksi model. Kesalahan umum adalah berasumsi bahwa fungsi bernama sanitize() menjamin keamanan. Analisis statis tidak menganggap nama fungsi sebagai bukti, jadi pengujian harus menghindari stub sanitasi yang menyesatkan:
export async function safeSummarize(id: number) {
const trusted = "Summarize the issue";
const user = await loadIssueBody(id); // not used in the system prompt
return model.generate({
system: trusted,
user: "Static placeholder",
});
}
Karena data yang tidak tepercaya tidak pernah mencapai field system, aturan tersebut harus tetap diam.
Mendeklarasikan ekspektasi dalam JSON
Kontrak rangkaian pengujian ini berada di expected-alerts.json. File ini mencantumkan peringatan yang diwajibkan dan jalur yang secara eksplisit dilarang:
{
"required": [
{
"ruleId": "USE_ACTUAL_RULE_ID",
"pathSuffix": "positive/direct-flow.ts"
},
{
"ruleId": "USE_ACTUAL_RULE_ID",
"pathSuffix": "positive/helper-flow.ts"
}
],
"forbiddenPathSuffixes": [
"negative/trusted-instruction.ts"
]
}
Ganti USE_ACTUAL_RULE_ID dengan pengenal (identifier) yang tertera dalam dokumentasi CodeQL atau output SARIF. Jangan menebak ID; string yang tepat sangat penting untuk pemeriksaan CI.
Menghubungkan fixture ke CI
- Kunci (pin) versi CodeQL CLI yang digunakan dalam pipeline ke versi 2.26.0 (atau yang lebih baru).
- Bangun database sekali pakai (disposable database) dari checkout saat ini sebelum menjalankan fixture.
- Jalankan kueri, tangkap peringatan, dan bandingkan dengan
expected-alerts.json. - Batalkan build (fail the build) jika ada peringatan wajib yang hilang atau jika jalur yang dilarang mulai memicu peringatan.
Jangan melakukan asersi terhadap total jumlah peringatan di seluruh repositori—perubahan yang tidak terkait dapat meningkatkan jumlah tersebut dan menyebabkan kegagalan palsu (false failures).
Apa yang perlu diperhatikan setelah pembaruan
Saat Anda memperbarui CodeQL ke versi yang lebih baru:
- Peringatan wajib masih muncul – lanjutkan dengan peninjauan normal.
- Peringatan wajib hilang – blokir build; selidiki apakah versi baru mengubah logika kueri atau apakah perubahan kode memutus aliran data.
- Lokasi positif baru muncul – tambahkan ke daftar
requiredsetelah mengonfirmasi bahwa itu adalah jalur injeksi yang nyata. - Kontrol negatif mulai memicu peringatan – tinjau kembali strategi mitigasi; aturan tersebut mungkin menjadi lebih ketat.
Analisis statis tidak dapat membuktikan bagaimana model akan bereaksi saat runtime. Lengkapi rangkaian regresi dengan pengujian adversarial yang benar-benar mengirimkan prompt buatan ke model dan memverifikasi responsnya.
Kesimpulan
CodeQL 2.26.0 memberi Anda kemampuan untuk menangkap bug prompt-injection sebelum dirilis, tetapi hanya jika Anda mengunci kemampuan tersebut dengan fixture regresi yang terfokus. Dengan mendefinisikan untrusted source, sink SDK yang nyata, dan ekspektasi yang jelas dalam kontrak JSON, Anda mengubah aturan analisis statis menjadi gerbang yang menghentikan regresi dan memaksa perhatian berkelanjutan pada permukaan serangan (attack surface) yang berkembang pesat.
