Kerapuhan ekosistem open-source berakar pada masalah insentif ekonomi: ribuan sistem produksi bergantung pada library krusial yang dirawat oleh satu orang sukarelawan tanpa kompensasi finansial langsung. Saat pengembang tersebut kelelahan (burnout) atau meninggalkan proyeknya, paket tersebut berubah menjadi abandonware. Kerentanan keamanan yang ditemukan kemudian hari tidak lagi ditambal, dependensi transitive menjadi usang, dan upgrade framework utama terhambat.

Mendeteksi paket bermasalah setelah muncul insiden CVE di produksi menghasilkan biaya perbaikan yang mahal. Solusi preventif yang efektif adalah menjalankan audit dependensi abandonware di CI dengan mengukur metrik health package secara objektif sebelum dependensi tersebut terkunci permanen di basis kode.

Metrik Riil Kesehatan Dependensi

Pemeriksaan metadata statis versi rilis saja tidak memadai. Paket yang tidak merilis versi baru selama 12 bulan bisa jadi memang sudah berstatus feature-complete, namun bisa juga ditinggalkan. Untuk menentukan status dependensi secara akurat, gunakan kombinasi metrik riil berikut:

  • Aktivitas Komit Terakhir (Commit Recency): Memeriksa rentang waktu antara komit terakhir di branch default dengan tanggal saat ini. Proyek tanpa komit sama sekali selama lebih dari 365 hari membutuhkan evaluasi manual.
  • Tingkat Respons Masalah (Issue & PR Responsiveness): Mengukur rasio issue/pull request terbuka yang tidak direspons oleh maintainer dalam 90 hari terakhir.
  • Bus Factor: Menghitung konsentrasi kontribusi. Jika lebih dari 90% komit dalam setahun terakhir hanya dilakukan oleh satu orang tanpa maintainer sekunder, risiko proyek menjadi abandonware sangat tinggi.
  • Status Security Sign-off: Memeriksa adopsi praktik keamanan modern, seperti branch protection, review dependensi otomatis (Dependabot/Renovate), dan penandatanganan rilis (release cryptographic signing).

Implementasi Audit Menggunakan OpenSSF Scorecard CLI

OpenSSF Scorecard mengotomatisasi evaluasi metrik keamanan dan keberlanjutan repositori. CLI ini mengevaluasi berbagai faktor kesehatan dan mengembalikan skor dari skala 0 hingga 10.

Gunakan script shell berikut untuk mengekstrak dependensi eksternal dari repositori GitHub, memanggil scorecard, dan menerapkan threshold minimum:

#!/usr/bin/env bash
set -euo pipefail

# Threshold skor minimum (0-10)
SCORE_THRESHOLD=5.0
FAILED=0

# Daftar repositori target dalam format 'owner/repo'
# Pada praktiknya, daftar ini dapat di-generate otomatis dari lockfile
REPOS=(
  "expressjs/express"
  "request/request"
)

for REPO in "${REPOS[@]}"; do
  echo "=== Mengecek: ${REPO} ==="
  
  # Jalankan OpenSSF Scorecard CLI dalam format JSON
  RESULT=$(scorecard --repo="github.com/${REPO}" --format=json 2>/dev/null || true)
  
  if [ -z "$RESULT" ]; then
    echo "Error: Gagal mengambil data untuk ${REPO}"
    FAILED=1
    continue
  fi

  # Ambil skor total dan status Maintained
  TOTAL_SCORE=$(echo "$RESULT" | jq -r '.score')
  MAINTAINED_SCORE=$(echo "$RESULT" | jq -r '.checks[] | select(.name=="Maintained") | .score')

  echo "Skor Total: ${TOTAL_SCORE} | Skor Maintained: ${MAINTAINED_SCORE}"

  # Evaluasi threshold
  IS_FAIL=$(echo "${TOTAL_SCORE} < ${SCORE_THRESHOLD}" | bc -l)
  if [ "$IS_FAIL" -eq 1 ] || [ "$MAINTAINED_SCORE" -le 0 ]; then
    echo "[FAIL] Repositori ${REPO} berada di bawah threshold keamanan/keaktifan."
    FAILED=1
  else
    echo "[PASS] Repositori ${REPO} memenuhi standar."
  fi
  echo ""
done

if [ "$FAILED" -ne 0 ]; then
  echo "Audit dependensi gagal: ditemukan paket yang tidak terawat atau berisiko tinggi."
  exit 1
fi

echo "Semua dependensi lolos audit."
exit 0

Otomasi Custom Checker via GitHub GraphQL API

Jika ingin mengevaluasi metrik kustom seperti jarak komit terakhir dan bus factor tanpa dependency eksternal berat, gunakan GitHub GraphQL API via script Node.js berikut:

import { Octokit } from "@octokit/core";

const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });

const QUERY = `
query($owner: String!, $name: String!) {
  repository(owner: $owner, name: $name) {
    isArchived
    defaultBranchRef {
      target {
        ... on Commit {
          committedDate
          history(first: 50) {
            nodes {
              author {
                user {
                  login
                }
              }
            }
          }
        }
      }
    }
  }
}`;

async function auditRepository(owner, name) {
  const { repository } = await octokit.graphql(QUERY, { owner, name });

  if (repository.isArchived) {
    throw new Error(`${owner}/${name} telah di-archive (abandoned).`);
  }

  const lastCommitDate = new Date(repository.defaultBranchRef.target.committedDate);
  const diffDays = (Date.now() - lastCommitDate.getTime()) / (1000 * 3600 * 24);

  if (diffDays > 365) {
    throw new Error(`${owner}/${name} tidak memiliki komit selama ${Math.floor(diffDays)} hari.`);
  }

  // Hitung Bus Factor dari 50 komit terakhir
  const authors = {};
  const commits = repository.defaultBranchRef.target.history.nodes;
  commits.forEach(c => {
    const login = c.author.user?.login || "unknown";
    authors[login] = (authors[login] || 0) + 1;
  });

  const topAuthorCount = Math.max(...Object.values(authors));
  const busFactorRatio = topAuthorCount / commits.length;

  if (busFactorRatio > 0.9) {
    console.warn(`[WARNING] ${owner}/${name} memiliki bus factor tinggi: ${(busFactorRatio * 100).toFixed(1)}% komit dari 1 maintainer.`);
  }

  return true;
}

auditRepository("expressjs", "express")
  .then(() => console.log("Audit berhasil."))
  .catch(err => {
    console.error(`[AUDIT FAILED] ${err.message}`);
    process.exit(1);
  });

Rule Enforcement pada Pipeline CI/CD

Pemeriksaan kesehatan dependensi harus memblokir pull request yang menambahkan dependensi berisiko. Pasang script audit sebagai pipeline job di GitHub Actions yang mengembalikan exit code non-zero saat pelanggaran terdeteksi.

name: Dependency Health Audit

on:
  pull_request:
    paths:
      - 'package.json'
      - 'package-lock.json'
      - 'go.mod'
      - 'Cargo.lock'

jobs:
  audit-abandonware:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Go (untuk Scorecard CLI)
        uses: actions/setup-go@v5
        with:
          go-version: 'stable'

      - name: Install OpenSSF Scorecard
        run: go install github.com/ossf/scorecard/v4@latest

      - name: Execute Audit Script
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          chmod +x ./scripts/audit-dependencies.sh
          ./scripts/audit-dependencies.sh

Langkah Mitigasi Dependensi Berstatus Abandonware

Jika proyek Anda terlanjur bergantung pada dependensi abandonware yang belum memiliki pengganti langsung, terapkan langkah mitigasi pragmatis berikut sesuai tingkat kebutuhan sistem:

  1. Pin Lockfile (Jangka Pendek): Kunci versi paket secara absolut pada commit hash atau cryptographic hash di lockfile (misal: package-lock.json atau go.sum). Cara ini memastikan build tetap deterministik dan mencegah malicious takeover akun registri maintainer lama.
  2. Vendoring Lokal (Jangka Menengah): Salin source code library langsung ke dalam repositori aplikasi (misalnya di folder vendor/ atau via Go vendor). Tim internal mengambil alih kontrol patching kode dan mengeliminasi ketergantungan pada registri eksternal.
  3. Internal Fork (Jangka Panjang): Fork repositori ke organization internal perusahaan, perbaiki kompatibilitas atau lubang keamanan yang ada, lalu arahkan manifest dependensi ke internal artifact repository (seperti private npm registry atau Artifactory).

Catatan: Hindari melakukan fork bila tim internal tidak memiliki kapasitas untuk menambal celah keamanan di masa mendatang. Jika library terlalu besar untuk di-maintain mandiri, rencanakan refactoring untuk bermigrasi ke alternatif yang didukung komunitas aktif.