Aplikasi Go modern jarang berdiri sendiri: sebagian besar kodenya berasal dari dependensi pihak ketiga yang diunduh dari internet dan berjalan dengan hak akses yang sama seperti kode kita. Inilah supply chain perangkat lunak — dan serangan pada rantai ini menyerang semua pemakai sekaligus: satu dependensi yang diretas menyebar ke ribuan aplikasi turunannya. Go membawa dua pertahanan bawaan: go.sum yang menjaga integritas setiap unduhan, dan govulncheck yang memeriksa celah keamanan pada dependensi yang benar-benar dipakai kode kita. Chapter ini mempraktikkan keduanya, lalu menutup dengan integrasi CI dan prinsip dependensi minimal.
Supply Chain: Risiko dari Dependensi #
Setiap baris require di go.mod adalah keputusan memercayai kode orang lain. Penyerang yang menguasai satu versi dependensi bisa menyisipkan backdoor yang mencuri environment variable — termasuk secret yang kita simpan dengan aman pada chapter sebelumnya. Dua pertanyaan kunci audit dependensi: apakah unduhan yang kita terima utuh, dan apakah versi yang kita pakai mengandung celah yang sudah diketahui publik.
go.sum Menjaga Integritas Unduhan #
Saat dependensi pertama kali diunduh, Go menghitung checksum (sidik jari kriptografis) isinya dan mencatatnya ke file go.sum. Setiap unduhan berikutnya — oleh rekan satu tim atau server CI — diverifikasi terhadap catatan tersebut; isinya yang berbeda membuat build berhenti. Barisnya terlihat seperti ini, diambil dari module sungguhan yang memakai golang.org/x/net versi lama:
golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=Mekanisme ini diperkuat checksum database pusat. Dua pengaturan berikut aktif secara default:
go env GOPROXY GOSUMDBOutput yang Dihasilkan:
https://proxy.golang.org,direct
sum.golang.orgProgram berikut menyimulasikan cara checksum mendeteksi dependensi yang dimodifikasi — semua berjalan di memori tanpa jaringan:
package main
import (
"crypto/sha256"
"encoding/hex"
"fmt"
)
// checksum menghitung sidik jari sha256 dari isi module,
// meniru format baris go.sum (prefiks h1:).
func checksum(konten string) string {
sum := sha256.Sum256([]byte(konten))
return "h1:" + hex.EncodeToString(sum[:])
}
func main() {
// Checksum dicatat ke go.sum saat dependensi pertama kali diunduh.
tercatat := checksum("package util v1.2.0")
// Skenario 1: dependensi terunduh utuh, isinya tidak berubah.
utuh := checksum("package util v1.2.0")
// Skenario 2: dependensi dimodifikasi penyerang di tengah jalan.
dimanipulasi := checksum("package util v1.2.0 backdoor")
fmt.Println("go.sum :", tercatat)
fmt.Println("unduhan utuh :", utuh)
fmt.Println("dimodifikasi :", dimanipulasi)
fmt.Println()
if utuh == tercatat {
fmt.Println("utuh : checksum cocok, dependensi diterima")
}
if dimanipulasi != tercatat {
fmt.Println("dimodifikasi : checksum berbeda, build dihentikan")
}
}Output yang Dihasilkan:
go.sum : h1:53998008639c067e6508ee1dd0c187fcc5f69b57187b709643aab8bfe94ab9cf
unduhan utuh : h1:53998008639c067e6508ee1dd0c187fcc5f69b57187b709643aab8bfe94ab9cf
dimodifikasi : h1:51b3ad6c63a153c09095f7f246211b655a42ea0628cd6aa918a78899b4cd9e1f
utuh : checksum cocok, dependensi diterima
dimodifikasi : checksum berbeda, build dihentikanMari kita telaah baris demi baris program di atas:
- Baris 9-14 (
checksum): menghitungsha256dari isi module lalu memformatnya dengan prefiksh1:, meniru gaya barisgo.sum. - Baris 18, 21, dan 24: tiga checksum dihitung — checksum tercatat di
go.sum, checksum unduhan utuh, dan checksum unduhan yang disisipi katabackdoor. - Baris 26-28: ketiga nilai dicetak berdampingan — satu kata tambahan pada konten mengubah seluruh sidik jari sha256; properti inilah yang membuat checksum sulit dipalsukan.
- Baris 30-32: unduhan utuh lolos karena checksum cocok dengan catatan
go.sum. - Baris 33-35: unduhan yang dimodifikasi ditolak — checksum berbeda, build dihentikan sebelum kode penyerang terpakai.
govulncheck: Audit Berbasis Call Graph #
Integritas terjaga bukan berarti versi yang kita pakai bebas celah. govulncheck membandingkan dependensi proyek terhadap database kerentanan resmi Go (vuln.go.dev) dan — ini keistimewaannya — menganalisis call graph kode kita: hanya kerentanan yang benar-benar dipanggil jalur kode kita yang dilaporkan sebagai temuan serius. Pasang alatnya sekali per mesin:
go install golang.org/x/vuln/cmd/govulncheck@latestOutput yang Dihasilkan (unduhan pertama):
go: downloading golang.org/x/vuln v1.9.0
go: downloading golang.org/x/telemetry v0.0.0-20261010193011-f71dc92da45e
go: downloading golang.org/x/mod v0.42.0
go: downloading golang.org/x/tools v0.52.0
go: downloading golang.org/x/sync v0.24.0Untuk melihat bentuk laporannya, kita siapkan module kecil yang sengaja memakai golang.org/x/net versi lama (v0.6.0) dan memanggil html.Parse:
doc, err := html.Parse(strings.NewReader("<html><body><p>Halo</p></body></html>"))Lalu jalankan audit dari direktori module:
govulncheck ./...Output yang Dihasilkan (cuplikan: 2 dari 9 temuan, plus ringkasan akhir):
=== Symbol Results ===
Vulnerability #1: GO-2026-5030
Invoking duplicate attributes can cause XSS in golang.org/x/net/html
More info: https://pkg.go.dev/vuln/GO-2026-5030
Module: golang.org/x/net
Found in: golang.org/x/[email protected]
Fixed in: golang.org/x/[email protected]
Example traces found:
#1: main.go:11:24: vulndemo.main calls html.Parse
Vulnerability #2: GO-2026-5029
Invoking incorrect handling of character references in DOCTYPE nodes in
golang.org/x/net/html
More info: https://pkg.go.dev/vuln/GO-2026-5029
Module: golang.org/x/net
Found in: golang.org/x/[email protected]
Fixed in: golang.org/x/[email protected]
Example traces found:
#1: main.go:11:24: vulndemo.main calls html.Parse
... (tujuh temuan lain dengan pola yang sama) ...
Your code is affected by 9 vulnerabilities from 1 module.
This scan also found 1 vulnerability in packages you import and 12
vulnerabilities in modules you require, but your code doesn't appear to call
these vulnerabilities.
Use '-show verbose' for more details.Setiap temuan memuat pengenal resmi (GO-2026-5030), versi dependensi yang terindikasi (v0.6.0), versi tempat celah diperbaiki (v0.55.0), sampai baris kode kita yang memanggil fungsi rentan (main.go:11:24). Perhatikan kalimat terakhir: database menandai 13 kerentanan lain pada dependensi tersebut, tetapi karena kode kita tidak memanggilnya, govulncheck tidak menjadikannya temuan serius — audit tetap fokus, bukan alarm palsu.
Integrasi ke CI dan Otomatisasi #
Jalankan audit pada setiap pipeline build agar celah tertangkap sebelum masuk ke main:
- name: Audit kerentanan dependensi
run: |
go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./...Pipeline gagal otomatis saat govulncheck menemukan kerentanan yang terpakai. Lengkapi dengan Dependabot atau Renovate — keduanya membuka pull request pembaruan dependensi secara berkala — dan tetap pertahankan prinsip minimal dependency: setiap dependensi baru adalah permukaan serangan baru, jauhi package kecil yang fungsinya bisa ditulis dalam satu function.
Referensi #
- Blog Go: govulncheck — pengumuman resmi dan filosofi call graph.
- Dokumentasi package vuln — database kerentanan dan pengidentifikasi GO-xxxx.
- Go Security — halaman pusat praktik keamanan Go.
Dependensi kini terjaga integritasnya dan diaudit secara rutin. Satu lapisan terakhir yang sering menjadi titik bocor diam-diam adalah aktivitas pencatatan: log yang menyimpan password, token, dan data pribadi pengguna.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
vuln-demo.go
Unduh berkaspackage main
import (
"crypto/sha256"
"encoding/hex"
"fmt"
)
// checksum menghitung sidik jari sha256 dari isi module,
// meniru format baris go.sum (prefiks h1:).
func checksum(konten string) string {
sum := sha256.Sum256([]byte(konten))
return "h1:" + hex.EncodeToString(sum[:])
}
func main() {
// Checksum dicatat ke go.sum saat dependensi pertama kali diunduh.
tercatat := checksum("package util v1.2.0")
// Skenario 1: dependensi terunduh utuh, isinya tidak berubah.
utuh := checksum("package util v1.2.0")
// Skenario 2: dependensi dimodifikasi penyerang di tengah jalan.
dimanipulasi := checksum("package util v1.2.0 backdoor")
fmt.Println("go.sum :", tercatat)
fmt.Println("unduhan utuh :", utuh)
fmt.Println("dimodifikasi :", dimanipulasi)
fmt.Println()
if utuh == tercatat {
fmt.Println("utuh : checksum cocok, dependensi diterima")
}
if dimanipulasi != tercatat {
fmt.Println("dimodifikasi : checksum berbeda, build dihentikan")
}
}