Kebocoran referensi objek mutable antar-modul merupakan celah struktural yang sering memicu privilege escalation dan korupsi state tanpa terdeteksi layer otorisasi. Ketika modul backend mengembalikan referensi internal langsung atau mengandalkan ambient authority (seperti service locator global atau token lingkungan yang bisa diakses siapa saja), boundary integritas modul tersebut runtuh.

Prinsip OOP yang dirumuskan Alan Kay menekankan bahwa objek semestinya mempertahankan retensi state lokal secara absolut dan hanya berinteraksi via pengiriman pesan (pure message passing). Model Object Capability (OCap) memformalkan prinsip ini: suatu komponen hanya boleh mengeksekusi mutasi jika memegang referensi kapabilitas eksplisit yang tidak dapat dipalsukan (unforgeable), bukan karena identitas global pemanggil.

Bahaya Ambient Authority dan Kebocoran Referensi

Pada arsitektur konvensional, komponen sering kali memiliki dependensi implisit terhadap resource global:

// Anti-pattern: Ambient Authority & Leaked Reference
class WalletService {
  private state = { balance: 1000, frozen: false };

  // Mengembalikan referensi mutable internal
  getState() {
    return this.state;
  }
}

// Kode eksternal dapat memanipulasi state tanpa validasi modul:
const wallet = new WalletService();
const leakedState = wallet.getState();
leakedState.balance = 999999; // Bypass invariant bisnis

Dalam model OCap, pemegang referensi tidak boleh memiliki wewenang otomatis atas seluruh kapabilitas sistem. Otoritas didelegasikan secara eksplisit dalam bentuk token yang terikat pada sub-aksi tertentu, dan seluruh input pesan diperlakukan sebagai batas ketidakpercayaan (untrusted boundary).

Arsitektur Dispatcher Boundary Berbasis OCap

Untuk mengamankan boundary modul, ada tiga mekanisme wajib:

  1. Deep Freezing / Defensive Cloning: Mencegah pemanggil memodifikasi payload pesan setelah dikirim (payload tampering / TOCTOU).
  2. Unforgeable Capability Token: Menggunakan instance unik berbasis closure atau WeakSet internal yang tidak dapat direkonstruksi oleh pihak luar.
  3. Caretaker Pattern (Revokasi): Membungkus kapabilitas dengan proxy kontrol yang dapat diputus aksesnya seketika tanpa perlu merestart proses.

Implementasi Kode: TypeScript OCap Boundary

Berikut adalah implementasi modul akun finansial dengan dispatcher ketat, token kapabilitas tidak terpalsukan, pencegahan mutasi langsung, mekanisme revokasi, dan validasi runtime:

// ledger-boundary.ts

// 1. Tipe Pesan dan Token
export type Command = 
  | { type: 'DEPOSIT'; amount: number }
  | { type: 'WITHDRAW'; amount: number };

export interface CapabilityToken {
  readonly __brand: unique symbol;
}

export interface Caretaker {
  token: CapabilityToken;
  revoke: () => void;
}

// 2. Modul Boundary Terisolasi
export function createSecureLedger(initialBalance: number) {
  // State internal: Tidak dapat diakses langsung dari scope luar
  let balance = initialBalance;
  const validTokens = new WeakSet<CapabilityToken>();

  // Validasi skema pesan runtime murni tanpa dependensi eksternal
  function validateCommand(msg: unknown): asserts msg is Command {
    if (typeof msg !== 'object' || msg === null) {
      throw new Error('INVALID_PAYLOAD: Payload harus berupa object');
    }
    const candidate = msg as Record<string, unknown>;
    if (candidate.type !== 'DEPOSIT' && candidate.type !== 'WITHDRAW') {
      throw new Error(`INVALID_COMMAND_TYPE: ${String(candidate.type)}`);
    }
    if (typeof candidate.amount !== 'number' || Number.isNaN(candidate.amount) || candidate.amount <= 0) {
      throw new Error('INVALID_AMOUNT: Nominal harus angka positif');
    }
  }

  // Factory unforgeable capability token menggunakan closure identity
  function issueCapability(): Caretaker {
    let isRevoked = false;
    const token: CapabilityToken = Object.freeze(Object.create(null));
    validTokens.add(token);

    return {
      token,
      revoke: () => {
        isRevoked = true;
        validTokens.delete(token);
      }
    };
  }

  // Dispatcher tunggal: Komunikasi murni via message passing
  function dispatch(token: CapabilityToken, rawMessage: unknown): { balance: number } {
    // Proteksi OCap: Cek validitas kapabilitas
    if (!validTokens.has(token)) {
      throw new Error('UNAUTHORIZED_CAPABILITY: Token tidak valid atau telah direvokasi');
    }

    // Proteksi Payload Tampering: Deep freeze copy untuk mencegah mutasi asinkron
    const message = Object.freeze(structuredClone(rawMessage));
    validateCommand(message);

    // Mutasi state lokal terisolasi
    switch (message.type) {
      case 'DEPOSIT':
        balance += message.amount;
        break;
      case 'WITHDRAW':
        if (balance < message.amount) {
          throw new Error('INSUFFICIENT_FUNDS');
        }
        balance -= message.amount;
        break;
    }

    // Kembalikan immutable snapshot, bukan state pointer internal
    // ponytail: snapshot shallow-freeze cukup untuk primitive record. Pakai deepClone jika state kompleks.
    return Object.freeze({ balance });
  }

  return {
    issueCapability,
    dispatch
  };
}

Uji Verifikasi Integritas State (Runnable Assert)

Kode uji mandiri di bawah memvalidasi empat vektor pelanggaran: pemalsuan token, manipulasi payload via referensi bersama, revokasi akses runtime, dan bypass invariant.

// test-runner.ts
import assert from 'node:assert/strict';
import { createSecureLedger, CapabilityToken } from './ledger-boundary.js';

function runSecurityAssertions() {
  const ledger = createSecureLedger(500);
  const caretaker = ledger.issueCapability();

  // 1. Eksekusi Sah via Capability Token
  const res1 = ledger.dispatch(caretaker.token, { type: 'DEPOSIT', amount: 200 });
  assert.equal(res1.balance, 700, 'Deposit sah harus berhasil mengubah state');

  // 2. Uji Mutasi State Ilegal via Snapshot Output
  const snapshot = res1 as { balance: number };
  assert.throws(() => {
    // @ts-expect-error Pengujian runtime freeze
    snapshot.balance = 99999;
  }, /Cannot assign to read only property/, 'Output snapshot tidak boleh mutable');

  // 3. Uji Payload Tampering via Referensi Luar
  const mutableCommand = { type: 'WITHDRAW' as const, amount: 100 };
  // Jalankan dispatch sambil mencoba memutasi objek secara paralel
  ledger.dispatch(caretaker.token, mutableCommand);
  mutableCommand.amount = 50000; // Tidak boleh berdampak pada balance yang sudah terisolasi
  assert.equal(ledger.dispatch(caretaker.token, { type: 'DEPOSIT', amount: 0.01 }).balance, 600.01);

  // 4. Uji Penolakan Token Palsu (Forged / Ambient Authority Token)
  const forgedToken = Object.freeze(Object.create(null)) as CapabilityToken;
  assert.throws(() => {
    ledger.dispatch(forgedToken, { type: 'DEPOSIT', amount: 100 });
  }, /UNAUTHORIZED_CAPABILITY/, 'Token palsu harus ditolak');

  // 5. Uji Mekanisme Revokasi Kapabilitas
  caretaker.revoke();
  assert.throws(() => {
    ledger.dispatch(caretaker.token, { type: 'DEPOSIT', amount: 100 });
  }, /UNAUTHORIZED_CAPABILITY/, 'Token yang direvokasi harus ditolak');

  console.log('Semua pengujian OCap boundary berhasil: Integritas modul terverifikasi.');
}

runSecurityAssertions();

Trade-Offs dan Batasan Praktis

  • Overhead Kloning: Penggunaan structuredClone pada boundary menjamin isolasi payload bebas efek samping, tetapi memicu alokasi memori tambahan jika payload berukuran megabytes. Terapkan validasi ukuran (payload size budgeting) sebelum kloning.
  • Manajemen Siklus Hidup Token: Penggunaan WeakSet memastikan token tidak bocor di memori jika modul pemanggil terhapus oleh garbage collection. Namun, status revokasi eksplisit tetap memerlukan referensi handle via caretaker.
  • Disiplin Boundary Antar-Modul: Arsitektur OCap menuntut disiplin penuh untuk tidak pernah mengekspor instance runtime mutable ke dependency injection container secara terbuka.