[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-HARDENINGSumber 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/PABService / Repository:
sipt-backendPIC 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:- Menerapkan autentikasi inbound API Key berbasis header
insw-keydengan komparasi waktu konstan (timing-safe) untuk memblokir akses ilegal dan mencegah serangan timing analysis. - Memasang batas ukuran berkas kiriman (payload limit) maksimum 500 KB guna melindungi memori server dari kelumpuhan akibat kiriman data raksasa (Denial of Service).
- 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.
- Menerapkan autentikasi inbound API Key berbasis header
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) menggunakancrypto.timingSafeEqualdan headerinsw-key - [x] Pembuatan semantic error class
PayloadTooLargeError(HTTP 413) padaapp/error/response-error.js - [x] Pemasangan payload limit guard (
pelindoPayloadLimitGuard) maksimal 500 KB padaapp/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
- [x] Implementasi middleware autentikasi inbound API Key (
2. Deployment Lifecycle
| # | Environment | Tanggal & Waktu | PIC | Status | Catatan |
|---|---|---|---|---|---|
| 1 | Development | 2026-10-16 11:31 | Hermansyah Efendi | DEPLOYED | Branch: hotfix/v1.6.5 (Commit: bada0a5 .. b6c565f) |
| 2 | Staging (UAT) | — | — | BYPASSED | Direct Hotfix Deployment (Bypass Staging ke Production) |
| 3 | Production | — | — | PENDING | Target Release Tag: v1.6.5 |
Riwayat Update Log
| Tanggal | Environment | Perubahan | PIC |
|---|---|---|---|
| 2026-10-16 | Development | Inisialisasi 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 headerinsw-key. Header ini divalidasi terhadap nilai environment variablePELINDO_INBOUND_API_KEY. Pemeriksaan string menggunakan fungsisafeCompareyang membungkuscrypto.timingSafeEqual. Jika panjang buffer tidak identik atau isinya berbeda, akses langsung ditolak denganUnauthorizedError(HTTP 401). Mekanisme ini mencegah attacker mengeksploitasi perbedaan waktu eksekusi string comparison (side-channel timing attack). Selain itu, jika konfigurasiPELINDO_INBOUND_API_KEYbelum 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 middlewarepelindoPayloadLimitGuarddi level router sebelum Express body-parser membedah payload JSON ke memory heap. Middleware memeriksa headercontent-lengthdengan ambang batasMAX_PELINDO_BODY_BYTES = 500 * 1024(512.000 byte). Request yang melampaui batas seketika di-abort dengan semantic errorPayloadTooLargeError(HTTP 413). - Sanitasi String Anti-Malicious (
safeStringRefinement): 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:- Penolakan awalan URL protocol (
http://,https://,ftp://) untuk mencegah SSRF probe atau remote link injection. - Penolakan skema Data URI (
data:) untuk mengeliminasi upaya penyelundupan berkas biner/eksekusi berkedok string. - Penolakan karakter tag HTML (
<dan>) guna meniadakan risiko Stored/Reflected XSS jika data logistik ditampilkan kembali di dashboard internal.
- Penolakan awalan URL protocol (
- 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.
- Timing-Safe API Key Authentication (
Daftar Environment Variable Baru / Konfigurasi DevOps:
Variabel Environment Sifat Deskripsi / Format Nilai PELINDO_INBOUND_API_KEYRequired Secret key otentikasi inbound machine-to-machine Pelindo (dikirim via header insw-key).TRUST_PROXYOptional / Recommended Konfigurasi 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 (pelindoPayloadLimitGuardmax 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 (pelindoPayloadLimitGuardmax 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 formatYYYY-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 ID | Branch | Author | Tanggal & Jam | Ringkasan Perubahan |
|---|---|---|---|---|
bada0a5ef1aea9f04485085037e7d5bc123f9263 | hotfix/v1.6.5 | Hermansyah Efendi | 2026-10-16 11:01 | feat(pelindo): implement strict zod validation on integration routes |
50ed6f17165388e611750a1fc1c4e54a43ea89b7 | hotfix/v1.6.5 | Hermansyah Efendi | 2026-10-16 11:20 | feat(pelindo): add inbound api key authentication with timing-safe comparison |
b6c565fd875e9d06a3c40f17ec9f914b9e7e4015 | hotfix/v1.6.5 | Hermansyah Efendi | 2026-10-16 11:31 | feat(pelindo): add payload limit guard and anti-malicious input validation |
Cuplikan Kode / Implementasi Inti
// 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();
};
};// 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);// 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.jstests/routers/pelindo_router.test.jstests/validations/pelindo.validation.test.js
- Status Eksekusi CLI:
PASSED (66 tests passed)
Cuplikan Kode Test Kunci
// 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
> 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 environmentPELINDO_INBOUND_API_KEY. - Postman Collection File: TASK-PELINDO-SECURITY-HARDENING_postman_collection.json
- Hasil Pengujian UAT:
PENDING - Catatan QA: —
Skenario Pengujian QA (Test Scenarios)
Skenario 1 — Happy Path: Pengambilan Data PAB dengan API Key Valid:
- Pre-kondisi: Environment variable
PELINDO_INBOUND_API_KEYterkonfigurasi di server. - Trigger / Request:
GET/pelindo/getData?nomor_pab=000001/PAB/TEST/2026- Header:
insw-key: <PELINDO_INBOUND_API_KEY>
- Header:
- Ekspektasi Response: HTTP
200 OK, body memuat metadata responsecode: 200dan arraydata. - Verifikasi Log: Log pino mencatat request masuk dengan response status 200 tanpa error warning.
- Pre-kondisi: Environment variable
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.
- Header:
- Ekspektasi Response: HTTP
200 OK,results.successbertambah sesuai jumlah data terkirim. - Verifikasi Database / Log: Tabel relasi gate SPMK ter-insert.
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 sembaranginsw-key: invalid-token-123).
- Header: Tanpa menyertakan header
- Ekspektasi Response: HTTP
401 Unauthorizedjson{ "code": 401, "message": "Akses ditolak: API Key tidak valid atau tidak disertakan" } - Verifikasi Log: Logger pino merekam log level
WARNbertuliskan"Unauthorized access attempt on GET /pelindo/getData".
Skenario 4 — Negative Case: Penolakan Payload Melebihi Batas (Payload Too Large):
- Pre-kondisi: Header
insw-keyvalid. - Trigger / Request:
POST/pelindo/spmkatauPOST/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).
- Header:
- Ekspektasi Response: HTTP
413 Payload Too Largejson{ "code": 413, "message": "Payload terlalu besar. Batas maksimal adalah 500 KB" } - Verifikasi Log: Request langsung ditolak di layer router sebelum membebani memory heap controller.
- Pre-kondisi: Header
Skenario 5 — Negative Case: Penolakan Injeksi URL dan File Terselubung (Base64/Data URI):
- Pre-kondisi: Header
insw-keyvalid. - 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" } ]
- Body:
- Ekspektasi Response: HTTP
422 Unprocessable Entityjson{ "code": 422, "message": "NO_PAB tidak boleh berupa URL, NO_SPMK tidak boleh berupa Data URI / Base64 payload" }
- Pre-kondisi: Header
Skenario 6 — Negative Case: Penolakan Parameter Tak Dikenal (Strict Contract Enforcement):
- Pre-kondisi: Header
insw-keyvalid. - Trigger / Request:
POST/pelindo/gateInfo- Body:json
{ "GATE_INFO": [ { "NO_PAB": "000001/PAB", "INJECTED_FIELD": "attacker_probe" } ] }
- Body:
- Ekspektasi Response: HTTP
422 Unprocessable Entitydengan rincian pesan bahwa payload mengandung field yang tidak dikenal:INJECTED_FIELD.
- Pre-kondisi: Header
Bukti Response Verifikasi Staging (Diisi saat UAT)
{
"status": "PENDING",
"note": "Menunggu deployment ke environment Staging untuk eksekusi skenario uji QA dan verifikasi integrasi inbound B2B Pelindo."
}