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:

text
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:

bash
go env GOPROXY GOSUMDB

Output yang Dihasilkan:

text
https://proxy.golang.org,direct
sum.golang.org

Program berikut menyimulasikan cara checksum mendeteksi dependensi yang dimodifikasi — semua berjalan di memori tanpa jaringan:

go
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:

text
go.sum       : h1:53998008639c067e6508ee1dd0c187fcc5f69b57187b709643aab8bfe94ab9cf
unduhan utuh : h1:53998008639c067e6508ee1dd0c187fcc5f69b57187b709643aab8bfe94ab9cf
dimodifikasi : h1:51b3ad6c63a153c09095f7f246211b655a42ea0628cd6aa918a78899b4cd9e1f

utuh         : checksum cocok, dependensi diterima
dimodifikasi : checksum berbeda, build dihentikan

Mari kita telaah baris demi baris program di atas:

  1. Baris 9-14 (checksum): menghitung sha256 dari isi module lalu memformatnya dengan prefiks h1:, meniru gaya baris go.sum.
  2. Baris 18, 21, dan 24: tiga checksum dihitung — checksum tercatat di go.sum, checksum unduhan utuh, dan checksum unduhan yang disisipi kata backdoor.
  3. Baris 26-28: ketiga nilai dicetak berdampingan — satu kata tambahan pada konten mengubah seluruh sidik jari sha256; properti inilah yang membuat checksum sulit dipalsukan.
  4. Baris 30-32: unduhan utuh lolos karena checksum cocok dengan catatan go.sum.
  5. 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:

bash
go install golang.org/x/vuln/cmd/govulncheck@latest

Output yang Dihasilkan (unduhan pertama):

text
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.0

Untuk melihat bentuk laporannya, kita siapkan module kecil yang sengaja memakai golang.org/x/net versi lama (v0.6.0) dan memanggil html.Parse:

go
doc, err := html.Parse(strings.NewReader("<html><body><p>Halo</p></body></html>"))

Lalu jalankan audit dari direktori module:

bash
govulncheck ./...

Output yang Dihasilkan (cuplikan: 2 dari 9 temuan, plus ringkasan akhir):

text
=== 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:

yaml
- 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 #

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 berkas
go
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")
	}
}