Skip to content

[TASK-PELINDO-SECURITY-HARDENING] Penguatan Keamanan Integrasi Inbound Pelindo B2B: Autentikasi Timing-Safe API Key, Guard Batas Ukuran Payload, dan Strict Schema Sanitation ​

Modul: SIPT/PAB | Tipe: Hotfix | Stage Saat Ini: HOTFIX | Tanggal Dokumen: 2026-10-16

HOTFIX STAGE (DIRECT-TO-PROD)

Dokumen ini sedang berada dalam tahap HOTFIX (DIRECT-TO-PROD). Penguatan keamanan rute B2B Pelindo (autentikasi API Key, pembatasan ukuran payload, dan validasi Zod strict) telah diimplementasikan pada branch hotfix/v1.6.5 dan siap dirilis langsung ke Production dengan mem-bypass Staging.


1. Informasi Tugas & Referensi Bisnis ​

  • Task ID / Nomor Referensi: TASK-PELINDO-SECURITY-HARDENING

  • Sumber Instruksi: Integrasi B2B Inbound Pelindo (Security Hardening & Input Sanitation)

  • Dokumen Referensi / Lampiran: TASK-PELINDO-SECURITY-HARDENING_postman_collection.json

  • Task / Dependensi Terkait (Cross-Service): —

  • Modul Terkait: SIPT/PAB

  • Service / Repository: sipt-backend

  • PIC Pengembang: Hermansyah Efendi

  • Ringkasan Non-Teknis (Executive Summary): Endpoint integrasi B2B /pelindo/* pada sipt-backend sebelumnya belum memiliki pengamanan autentikasi khusus, pembatasan ukuran payload, serta skema validasi yang ketat. Dilakukan penguatan keamanan menyeluruh pada rute pertukaran data Pelindo dengan:

    1. Menerapkan autentikasi inbound API Key berbasis header insw-key dengan komparasi waktu konstan (timing-safe) untuk memblokir akses ilegal dan mencegah serangan timing analysis.
    2. Memasang batas ukuran berkas kiriman (payload limit) maksimum 500 KB guna melindungi memori server dari kelumpuhan akibat kiriman data raksasa (Denial of Service).
    3. Mengunci validasi skema data secara ketat (strict) yang otomatis menolak parameter tak dikenal, serta menyaring input dari muatan berbahaya seperti URL eksternal, format Base64 tersembunyi, dan tag HTML manipulatif.
  • Latar Belakang & Ringkasan Kebutuhan: Endpoint integrasi B2B /pelindo/* pada sipt-backend sebelumnya belum memiliki pengamanan autentikasi khusus, pembatasan ukuran payload, serta skema validasi yang ketat. Kondisi ini membuka potensi risiko manipulasi parameter tak berizin, penyerangan memory exhaustion (DoS) akibat kiriman batch berukuran raksasa, serta penyelundupan payload berbahaya (seperti URL scheme, Data URI Base64, dan tag HTML). Diterapkan arsitektur multi-layer defense berupa autentikasi inbound API Key berbasis timing-safe comparison, guard batas payload maksimal 500 KB, serta penguatan validasi Zod strict pada seluruh endpoint operasional Pelindo.

  • Ruang Lingkup & Rincian Sub-Pekerjaan (Technical Scope & Milestones):

    • [x] Implementasi middleware autentikasi inbound API Key (pelindoApiKeyAuth) menggunakan crypto.timingSafeEqual dan header insw-key
    • [x] Pembuatan semantic error class PayloadTooLargeError (HTTP 413) pada app/error/response-error.js
    • [x] Pemasangan payload limit guard (pelindoPayloadLimitGuard) maksimal 500 KB pada app/routers/pelindo_router.js
    • [x] Penguatan Zod Validation Schema dengan .strict() dan penolakan pola URL (http://, https://), Data URI (data:), serta tag HTML (<, >)
    • [x] Standardisasi format kalender dan waktu (YYYY-MM-DD / YYYY-MM-DD HH:mm:ss) pada field tanggal dokumen
    • [x] Penyusunan automated unit & integration test suites komprehensif (66 test assertions) dengan status 100% lulus

2. Deployment Lifecycle ​

#EnvironmentTanggal & WaktuPICStatusCatatan
1Development2026-10-16 11:31Hermansyah EfendiDEPLOYEDBranch: hotfix/v1.6.5 (Commit: bada0a5 .. b6c565f)
2Staging (UAT)——BYPASSEDDirect Hotfix Deployment (Bypass Staging ke Production)
3Production——PENDINGTarget Release Tag: v1.6.5

Riwayat Update Log ​

TanggalEnvironmentPerubahanPIC
2026-10-16DevelopmentInisialisasi technical audit log: security hardening rute Pelindo B2B (API key auth, payload limit guard 500 KB, dan strict Zod validation)Hermansyah Efendi

3. Analisis Teknis & Rincian Perubahan ​

  • Alur Logika & Bisnis:

    • Timing-Safe API Key Authentication (app/middlewares/inbound-api-key.js): Setiap request masuk ke rute /pelindo/* wajib menyertakan header insw-key. Header ini divalidasi terhadap nilai environment variable PELINDO_INBOUND_API_KEY. Pemeriksaan string menggunakan fungsi safeCompare yang membungkus crypto.timingSafeEqual. Jika panjang buffer tidak identik atau isinya berbeda, akses langsung ditolak dengan UnauthorizedError (HTTP 401). Mekanisme ini mencegah attacker mengeksploitasi perbedaan waktu eksekusi string comparison (side-channel timing attack). Selain itu, jika konfigurasi PELINDO_INBOUND_API_KEY belum diset pada environment, server mengadopsi prinsip fail-closed dengan menolak request dan menghasilkan error configuration missing.
    • Payload Size Guard 500 KB Limit (pelindoPayloadLimitGuard): Rute Pelindo memproses kiriman batch kontainer dan dokumen SPMK. Untuk mengantisipasi eksploitasi memory exhaustion (DoS) akibat pengiriman array JSON berukuran gigantik, dipasang middleware pelindoPayloadLimitGuard di level router sebelum Express body-parser membedah payload JSON ke memory heap. Middleware memeriksa header content-length dengan ambang batas MAX_PELINDO_BODY_BYTES = 500 * 1024 (512.000 byte). Request yang melampaui batas seketika di-abort dengan semantic error PayloadTooLargeError (HTTP 413).
    • Sanitasi String Anti-Malicious (safeString Refinement): Semua identifier dokumen logistik (NO_PAB, NO_SPMK, ID_PELABUHAN, TERMINAL, CONTAINER_NO, TRUCK_NO, NO_TRX_TOS) disaring melalui chaining helper .refine() pada skema Zod:
      1. Penolakan awalan URL protocol (http://, https://, ftp://) untuk mencegah SSRF probe atau remote link injection.
      2. Penolakan skema Data URI (data:) untuk mengeliminasi upaya penyelundupan berkas biner/eksekusi berkedok string.
      3. Penolakan karakter tag HTML (< dan >) guna meniadakan risiko Stored/Reflected XSS jika data logistik ditampilkan kembali di dashboard internal.
    • Strict Schema Enforcement: Seluruh skema validasi objek Zod pada request body maupun query params dikunci menggunakan method .strict(). Jika klien mengirimkan atribut liar atau field di luar kontrak integrasi, request langsung di-reject dengan status HTTP 422 (Unprocessable Entity), mencegah mass assignment vulnerability atau pencemaran field database.
  • Daftar Environment Variable Baru / Konfigurasi DevOps:

    Variabel EnvironmentSifatDeskripsi / Format Nilai
    PELINDO_INBOUND_API_KEYRequiredSecret key otentikasi inbound machine-to-machine Pelindo (dikirim via header insw-key).
    TRUST_PROXYOptional / RecommendedKonfigurasi reverse proxy trust untuk pembacaan real client IP melalui gateway / reverse proxy server.
  • Perubahan Database (DDL / DML):

    sql
    -- Tidak ada perubahan skema database (DDL/DML) untuk task ini.
    -- Perubahan murni pada layer middleware autentikasi, router guard, dan skema validasi Zod.
  • Kontrak API & Response Payload:

    GET /pelindo/getData ​

    • Deskripsi: Mengambil data PAB berdasarkan nomor PAB atau rentang waktu pencarian (maksimal 24 jam).
    • Autentikasi / Guard: Inbound API Key (pelindoApiKeyAuth), Guard Payload Limit (pelindoPayloadLimitGuard), Zod Query Validation (zodMiddleware).
    • Request Header / Query:
      • Header: insw-key: <PELINDO_INBOUND_API_KEY>
      • Query (Opsi 1): nomor_pab=000001/PAB/TEST/2026 (maks 100 karakter, bebas URL/Base64/HTML)
      • Query (Opsi 2): start_time=YYYY-MM-DD HH:mm:ss&end_time=YYYY-MM-DD HH:mm:ss (rentang maksimal 24 jam / 86.400 detik)
    • Response Payload (200 OK):
      json
      {
        "code": 200,
        "message": "Berhasil Mengambil Data",
        "data": []
      }
    • Response Error (401 Unauthorized / 422 Unprocessable Entity):
      json
      {
        "code": 401,
        "message": "Akses ditolak: API Key tidak valid atau tidak disertakan"
      }

    POST /pelindo/spmk ​

    • Deskripsi: Menerima pengiriman batch data SPMK (Surat Perintah Muat Kapal) dari Pelindo (1 s.d. 100 data per request).
    • Autentikasi / Guard: Inbound API Key (pelindoApiKeyAuth), Guard Payload Limit (pelindoPayloadLimitGuard max 500 KB), Zod Body Validation (zodMiddleware).
    • Request Header:
      • insw-key: <PELINDO_INBOUND_API_KEY>
      • Content-Type: application/json
    • Request Body (Array of Objects, max 100 items):
      json
      [
        {
          "NO_PAB": ["000001/PAB/TEST/2026"],
          "ID_PELABUHAN": "IDJKT",
          "TERMINAL": "TER3",
          "NO_SPMK": "SPMK-12345",
          "TANGGAL_TERBIT": "2026-10-16 10:00:00",
          "VESSEL_CLOSE": "2026-10-17 12:00:00"
        }
      ]
    • Response Payload (200 OK):
      json
      {
        "code": 200,
        "message": "Data SPMK berhasil diproses",
        "results": {
          "success": 1,
          "failed": 0
        }
      }
    • Response Error (413 Payload Too Large / 422 Unprocessable Entity):
      json
      {
        "code": 413,
        "message": "Payload terlalu besar. Batas maksimal adalah 500 KB"
      }

    POST /pelindo/gateInfo ​

    • Deskripsi: Menerima pengiriman data Gate In / Gate Out kontainer dari TOS Pelindo (1 s.d. 200 item).
    • Autentikasi / Guard: Inbound API Key (pelindoApiKeyAuth), Guard Payload Limit (pelindoPayloadLimitGuard max 500 KB), Zod Body Validation (zodMiddleware).
    • Request Header:
      • insw-key: <PELINDO_INBOUND_API_KEY>
      • Content-Type: application/json
    • Request Body:
      json
      {
        "GATE_INFO": [
          {
            "NO_PAB": ["000001/PAB/TEST/2026"],
            "REQUEST_DATE": "2026-10-16 13:00:00",
            "GATEIN_DATE": "2026-10-16 13:17:00",
            "GATEOUT_DATE": "2026-10-16 15:30:00",
            "STATUS": "GATE_IN",
            "TRUCK_NO": "B1234XYZ",
            "NO_TRX_TOS": "TRX-998877",
            "CONTAINER_NO": "TEMU1234567",
            "ID_PELABUHAN": "IDJKT",
            "TERMINAL": "TER3"
          }
        ]
      }
    • Response Payload (200 OK):
      json
      {
        "code": 200,
        "message": "Data Gate Info berhasil diproses",
        "data": {
          "total": 1,
          "processed": 1
        }
      }

    GET /pelindo/getDataKonsolidasi ​

    • Deskripsi: Mengambil data konsolidasi kargo berdasarkan nomor layanan, nomor PAB konsolidasi, atau rentang waktu (maksimal 7 hari).
    • Autentikasi / Guard: Inbound API Key (pelindoApiKeyAuth), Guard Payload Limit (pelindoPayloadLimitGuard), Zod Query Validation (zodMiddleware).
    • Request Header:
      • insw-key: <PELINDO_INBOUND_API_KEY>
    • Request Query:
      • nomor_layanan: string opsional (maks 100 karakter)
      • no_pab_konsolidasi: string opsional (maks 100 karakter)
      • start_time & end_time: string format YYYY-MM-DD HH:mm:ss (rentang maksimal 7 hari / 604.800 detik)
    • Response Payload (200 OK):
      json
      {
        "code": 200,
        "message": "Berhasil Mengambil Data Konsolidasi",
        "data": []
      }

4. Bukti Teknis (Technical Proof & Audit Trail) ​

  • Repository: sipt-backend
  • Branch Utama: hotfix/v1.6.5
  • Merge Request / PR: -

Daftar Commit Terkait (Multi-Commit Trail) ​

Commit IDBranchAuthorTanggal & JamRingkasan Perubahan
bada0a5ef1aea9f04485085037e7d5bc123f9263hotfix/v1.6.5Hermansyah Efendi2026-10-16 11:01feat(pelindo): implement strict zod validation on integration routes
50ed6f17165388e611750a1fc1c4e54a43ea89b7hotfix/v1.6.5Hermansyah Efendi2026-10-16 11:20feat(pelindo): add inbound api key authentication with timing-safe comparison
b6c565fd875e9d06a3c40f17ec9f914b9e7e4015hotfix/v1.6.5Hermansyah Efendi2026-10-16 11:31feat(pelindo): add payload limit guard and anti-malicious input validation

Cuplikan Kode / Implementasi Inti ​

javascript
// File: app/middlewares/inbound-api-key.js
function safeCompare(a, b) {
  if (typeof a !== "string" || typeof b !== "string") return false;
  const bufA = Buffer.from(a);
  const bufB = Buffer.from(b);
  if (bufA.length !== bufB.length) return false;
  return crypto.timingSafeEqual(bufA, bufB);
}

export const validateInboundApiKey = (envKeyName, headerName = "insw-key") => {
  return (req, res, next) => {
    const expectedKey = process.env[envKeyName];
    if (!expectedKey) {
      return next(new UnauthorizedError("Akses ditolak: Konfigurasi autentikasi server belum diset"));
    }
    const providedKey = req.headers[headerName.toLowerCase()];
    if (!providedKey || !safeCompare(providedKey, expectedKey)) {
      return next(new UnauthorizedError("Akses ditolak: API Key tidak valid atau tidak disertakan"));
    }
    next();
  };
};
javascript
// File: app/routers/pelindo_router.js
const MAX_PELINDO_BODY_BYTES = 500 * 1024; // 500 KB

export const pelindoPayloadLimitGuard = (req, res, next) => {
  const contentLength = req.headers["content-length"];
  if (contentLength && parseInt(contentLength, 10) > MAX_PELINDO_BODY_BYTES) {
    return next(new PayloadTooLargeError("Payload terlalu besar. Batas maksimal adalah 500 KB"));
  }
  next();
};

router.use(pelindoApiKeyAuth);
router.use(pelindoPayloadLimitGuard);
javascript
// File: app/validations/pelindo.validation.js
const URL_PATTERN = /^(?:https?|ftp):\/\//i;
const DATA_URI_PATTERN = /^data:/i;
const HTML_TAG_PATTERN = /[<>]/;
const DATE_TIME_REGEX = /^\d{4}-\d{2}-\d{2}(?:[ T]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?)?$/;

const safeString = (schema, fieldName = "Field") =>
  schema
    .refine((val) => !URL_PATTERN.test(val), { message: `${fieldName} tidak boleh berupa URL` })
    .refine((val) => !DATA_URI_PATTERN.test(val), { message: `${fieldName} tidak boleh berupa Data URI / Base64 payload` })
    .refine((val) => !HTML_TAG_PATTERN.test(val), { message: `${fieldName} tidak boleh mengandung karakter tag HTML (< atau >)` });

5. Bukti Verifikasi Pengujian (QA / Unit Test / API Verification) ​

A. Verifikasi Unit Test (Development Stage) ​

  • Test Suite Files:
    • tests/middlewares/inbound-api-key.test.js
    • tests/routers/pelindo_router.test.js
    • tests/validations/pelindo.validation.test.js
  • Status Eksekusi CLI: PASSED (66 tests passed)
Cuplikan Kode Test Kunci ​
javascript
// File: tests/routers/pelindo_router.test.js
describe("pelindoPayloadLimitGuard (500 KB limit)", () => {
  it("rejects request when content-length exceeds 500 KB with PayloadTooLargeError", () => {
    const oversizedBytes = 500 * 1024 + 1; // 512,001 bytes
    const req = { headers: { "content-length": oversizedBytes.toString() } };
    const res = {};
    const next = vi.fn();

    pelindoPayloadLimitGuard(req, res, next);

    expect(next).toHaveBeenCalledWith(expect.any(PayloadTooLargeError));
    expect(next.mock.calls[0][0].code).toBe(413);
    expect(next.mock.calls[0][0].message).toContain("Payload terlalu besar. Batas maksimal adalah 500 KB");
  });
});
Output Raw Eksekusi CLI Test Runner ​
text
> npx vitest run tests/routers/pelindo_router.test.js tests/validations/pelindo.validation.test.js tests/middlewares/inbound-api-key.test.js

 RUN  v4.1.12 D:/WORK/LNSW/sipt-backend

 ✓ tests/middlewares/inbound-api-key.test.js (7 tests) 16ms
 ✓ tests/validations/pelindo.validation.test.js (48 tests) 53ms
 ✓ tests/routers/pelindo_router.test.js (11 tests) 394ms

 Test Files  3 passed (3)
      Tests  66 passed (66)
   Start at  16:35:23
   Duration  1.11s (transform 221ms, setup 0ms, import 760ms, tests 483ms, environment 0ms)

B. Panduan Pengujian QA & Verifikasi Staging (QA Playbook) ​

  • Cakupan / Surface Pengujian: API / Postman Only (Inbound Machine-to-Machine Integration)
  • Kebutuhan Akses / Role Akun: Secret Key inbound Pelindo (header insw-key), terkonfigurasi pada environment PELINDO_INBOUND_API_KEY.
  • Postman Collection File: TASK-PELINDO-SECURITY-HARDENING_postman_collection.json
  • Hasil Pengujian UAT: PENDING
  • Catatan QA: —
Skenario Pengujian QA (Test Scenarios) ​
  1. Skenario 1 — Happy Path: Pengambilan Data PAB dengan API Key Valid:

    • Pre-kondisi: Environment variable PELINDO_INBOUND_API_KEY terkonfigurasi di server.
    • Trigger / Request: GET /pelindo/getData?nomor_pab=000001/PAB/TEST/2026
      • Header: insw-key: <PELINDO_INBOUND_API_KEY>
    • Ekspektasi Response: HTTP 200 OK, body memuat metadata response code: 200 dan array data.
    • Verifikasi Log: Log pino mencatat request masuk dengan response status 200 tanpa error warning.
  2. Skenario 2 — Happy Path: Pengiriman Batch Data SPMK:

    • Pre-kondisi: Data nomor PAB yang diajukan terdaftar di database.
    • Trigger / Request: POST /pelindo/spmk
      • Header: insw-key: <PELINDO_INBOUND_API_KEY>, Content-Type: application/json
      • Body: Array berisi 1–10 data SPMK dengan field dan tanggal valid.
    • Ekspektasi Response: HTTP 200 OK, results.success bertambah sesuai jumlah data terkirim.
    • Verifikasi Database / Log: Tabel relasi gate SPMK ter-insert.
  3. Skenario 3 — Negative Case: Penolakan Akses Tanpa API Key (Unauthorized):

    • Pre-kondisi: Service backend aktif.
    • Trigger / Request: GET /pelindo/getData?nomor_pab=000001/PAB/TEST/2026
      • Header: Tanpa menyertakan header insw-key (atau diisi nilai sembarang insw-key: invalid-token-123).
    • Ekspektasi Response: HTTP 401 Unauthorized
      json
      {
        "code": 401,
        "message": "Akses ditolak: API Key tidak valid atau tidak disertakan"
      }
    • Verifikasi Log: Logger pino merekam log level WARN bertuliskan "Unauthorized access attempt on GET /pelindo/getData".
  4. Skenario 4 — Negative Case: Penolakan Payload Melebihi Batas (Payload Too Large):

    • Pre-kondisi: Header insw-key valid.
    • Trigger / Request: POST /pelindo/spmk atau POST /pelindo/gateInfo
      • Header: insw-key: <PELINDO_INBOUND_API_KEY>, Content-Type: application/json
      • Body: Payload dummy berukuran lebih dari 500 KB (misal array 300 data atau string padding > 512.000 byte).
    • Ekspektasi Response: HTTP 413 Payload Too Large
      json
      {
        "code": 413,
        "message": "Payload terlalu besar. Batas maksimal adalah 500 KB"
      }
    • Verifikasi Log: Request langsung ditolak di layer router sebelum membebani memory heap controller.
  5. Skenario 5 — Negative Case: Penolakan Injeksi URL dan File Terselubung (Base64/Data URI):

    • Pre-kondisi: Header insw-key valid.
    • Trigger / Request: POST /pelindo/spmk
      • Body:
        json
        [
          {
            "NO_PAB": ["https://malicious-site.com/exploit"],
            "ID_PELABUHAN": "IDJKT",
            "TERMINAL": "TER3",
            "NO_SPMK": "data:image/png;base64,iVBORw0KGgo...",
            "TANGGAL_TERBIT": "2026-10-16 10:00:00",
            "VESSEL_CLOSE": "2026-10-17 12:00:00"
          }
        ]
    • Ekspektasi Response: HTTP 422 Unprocessable Entity
      json
      {
        "code": 422,
        "message": "NO_PAB tidak boleh berupa URL, NO_SPMK tidak boleh berupa Data URI / Base64 payload"
      }
  6. Skenario 6 — Negative Case: Penolakan Parameter Tak Dikenal (Strict Contract Enforcement):

    • Pre-kondisi: Header insw-key valid.
    • Trigger / Request: POST /pelindo/gateInfo
      • Body:
        json
        {
          "GATE_INFO": [
            {
              "NO_PAB": "000001/PAB",
              "INJECTED_FIELD": "attacker_probe"
            }
          ]
        }
    • Ekspektasi Response: HTTP 422 Unprocessable Entity dengan rincian pesan bahwa payload mengandung field yang tidak dikenal: INJECTED_FIELD.
Bukti Response Verifikasi Staging (Diisi saat UAT) ​
json
{
  "status": "PENDING",
  "note": "Menunggu deployment ke environment Staging untuk eksekusi skenario uji QA dan verifikasi integrasi inbound B2B Pelindo."
}

Single Source of Truth (SSOT) Arsitektur & Perubahan Teknis Backend