Kamu menjalankan npm ci, menunggu test hijau, lalu merasa pipeline aman karena aplikasi belum start. Masalahnya, attacker tidak perlu menunggu aplikasimu berjalan. Mereka cukup menaruh script di preinstall, install, atau postinstall, lalu CI kamu sendiri yang mengeksekusinya.

Di sinilah npm lifecycle scripts CI security jadi penting. Dengan mematikan lifecycle scripts yang tidak perlu saat build dan test, kamu memangkas salah satu jalur RCE paling licin di supply chain JavaScript.

⚡ Jawaban Singkat / Key Takeaways

Matikan npm lifecycle scripts di CI default dengan npm ci --ignore-scripts, lalu jalankan script yang benar-benar kamu percaya secara eksplisit. Strategi ini mengurangi risiko postinstall RCE, dependency beracun, dan pencurian secret saat proses install.

npm lifecycle scripts CI security untuk mencegah postinstall RCE

Kenapa Lifecycle Script Bisa Jadi Jalur RCE?

npm mendukung script otomatis seperti preinstall, install, postinstall, prepare, dan beberapa hook lain. Fitur ini berguna untuk compile native module, generate file, atau setup package.

Namun, fitur yang sama juga memberi dependency kemampuan untuk menjalankan command saat install. Kalau package berbahaya masuk ke dependency graph, CI bisa menjalankan kode asing sebelum lint, test, SAST, atau review artifact mulai bekerja.

Risikonya naik tajam karena CI sering punya akses ke:

  • NPM token untuk publish package.
  • GitHub token untuk release automation.
  • Cloud credentials untuk deploy.
  • Environment variables berisi API key internal.
  • Cache build yang bisa dipakai untuk persistence.

Default Aman: Install Dulu, Eksekusi Belakangan

Banyak tim masih menganggap npm ci sudah cukup aman karena memakai lockfile. Itu benar untuk reproducibility, tetapi belum cukup untuk eksekusi script. Lockfile mengunci versi, bukan niat maintainer.

Ubah baseline CI kamu jadi seperti ini:

npm ci --ignore-scripts
npm run lint
npm test

Atau, permanenkan di konfigurasi npm untuk job tertentu:

npm config set ignore-scripts true
npm ci

Dengan begitu, dependency tetap terpasang, tetapi hook otomatis tidak jalan. Setelah itu, kamu bisa menjalankan hanya script yang kamu kontrol sendiri.

Framework “Deny by Default, Bless by Need”

Jangan mulai dari pertanyaan, “script mana yang berbahaya?” Mulailah dari pertanyaan yang lebih tajam: script mana yang benar-benar harus jalan agar build valid?

Pakai framework sederhana ini:

  • Deny: semua lifecycle scripts mati secara default di CI.
  • Detect: audit package yang punya install hook.
  • Decide: izinkan hanya dependency yang punya alasan teknis jelas.
  • Delegate: jalankan build step eksplisit dari repo kamu sendiri.

Ini terasa lebih ribet di awal. Namun, hasilnya lebih bersih karena tim jadi tahu dependency mana yang benar-benar butuh hak eksekusi.

CI pipeline dengan npm ignore scripts untuk mengurangi risiko RCE

Contoh GitHub Actions yang Lebih Aman

Untuk frontend app biasa, pattern ini sering cukup:

name: test

on:
  pull_request:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm
      - run: npm ci --ignore-scripts
      - run: npm run lint
      - run: npm test
      - run: npm run build

Perhatikan dua hal. Pertama, permission token dibuat minimal. Kedua, install dependency tidak boleh menjalankan lifecycle scripts otomatis.

Kalau Dependency Memang Butuh Postinstall?

Beberapa package sah memang butuh script. Contohnya native module, browser binary downloader, atau tool yang melakukan code generation.

Namun, jangan langsung menghidupkan semua script. Lebih aman pakai allowlist operasional:

npm ci --ignore-scripts
npm rebuild esbuild
npm rebuild sharp
npm run build

Dengan pendekatan ini, kamu memberi izin ke package spesifik, bukan membuka pintu untuk seluruh dependency tree.

Tip Penting untuk Monorepo

Di monorepo, jangan samakan semua package. Package dokumentasi, UI library, worker, dan app production punya risiko berbeda. Karena itu, pisahkan job CI berdasarkan trust level.

  • PR dari fork: --ignore-scripts, tanpa secret, permission read-only.
  • Branch internal: script terbatas, secret tetap minimal.
  • Release job: script eksplisit, approval manual, provenance aktif.

Strategi ini nyambung dengan praktik memisahkan secret CI dari pull request berisiko. Kalau lifecycle script menyala di PR asing, isolasi secret jadi jauh lebih penting.

Cara Mendeteksi Package yang Punya Install Script

Sebelum mematikan script secara total, kamu bisa memetakan dulu package yang bergantung pada hook install.

npm query ':attr(scripts, [postinstall])'
npm query ':attr(scripts, [preinstall])'
npm query ':attr(scripts, [install])'

Kamu juga bisa cek perubahan lockfile saat dependency update. Kalau package baru tiba-tiba membawa postinstall, treat sebagai sinyal review, bukan sekadar perubahan teknis.

Untuk konteks lebih luas, baca juga pembahasan tentang install-time script abuse dan kenapa CI sebaiknya tidak memakai install biasa.

Review keamanan dependency install sebelum menjalankan script build

pnpm dan Yarn: Prinsipnya Sama

Kalau kamu memakai pnpm, gunakan:

pnpm install --frozen-lockfile --ignore-scripts

Untuk Yarn, cek versi yang kamu pakai karena flag dan perilakunya bisa berbeda. Namun, prinsipnya tetap sama: install dependency tanpa auto-execution, lalu jalankan build step eksplisit.

Rujukan resmi yang layak kamu simpan:

Checklist Cepat untuk Pipeline Kamu

  • Gunakan npm ci --ignore-scripts untuk job lint, test, dan build standar.
  • Jalankan package build step secara eksplisit dari repo sendiri.
  • Audit package yang punya preinstall, install, atau postinstall.
  • Pisahkan PR dari fork dari job yang punya secret.
  • Batasi token CI dengan permission minimum.
  • Review lockfile saat dependency baru membawa lifecycle script.
  • Cache dependency, tetapi jangan cache hasil eksekusi script yang tidak kamu pahami.

FAQ

Apakah npm ci --ignore-scripts akan merusak build?

Bisa, kalau dependency kamu bergantung pada install script. Namun, itu justru memberi sinyal bagus. Kamu jadi tahu package mana yang butuh izin khusus, lalu bisa menjalankannya secara eksplisit.

Apakah lockfile cukup untuk mencegah postinstall RCE?

Belum. Lockfile mengunci versi dan integrity package, tetapi tidak mencegah package tersebut menjalankan lifecycle script. Karena itu, lockfile perlu dipadukan dengan --ignore-scripts, review dependency, dan pembatasan secret.

Kapan lifecycle scripts boleh diaktifkan?

Aktifkan hanya di job yang benar-benar membutuhkan, misalnya release atau build native dependency tertentu. Bahkan saat itu, gunakan allowlist, permission minimum, dan approval bila job punya akses ke secret penting.

Kesimpulan: CI Aman Bukan CI yang Percaya Semua Package

Supply chain attack modern sering menang bukan karena exploit rumit, tetapi karena pipeline terlalu ramah. Saat CI mengeksekusi semua lifecycle script secara otomatis, setiap dependency mendapat peluang menjalankan kode.

Jadikan npm lifecycle scripts CI security sebagai baseline: matikan script default, izinkan seperlunya, lalu pisahkan job berisiko dari secret. Kalau kamu mengelola frontend, DevOps, atau security engineering, perubahan kecil ini bisa menutup jalur RCE besar sebelum test pertama mulai.

Mau checklist keamanan CI dan supply chain yang lebih praktis? Subscribe newsletter Google kami di bawah ini.

About the Author

Dzul Qurnain

Suka nonton Anime, ngoding dan bagi-bagi tips kalau tahu.. Oh iya, suka baca ( tapi yang menarik menurutku aja)... Praktisi WordPress, web development, SEO, dan server administration yang membagikan tutorial teknis dan catatan implementasi nyata.

View All Articles