Hardening auth untuk monorepo multi-site dan pipeline publish berarti memastikan dua aplikasi site dan satu tool admin/publish yang hidup dalam satu repository tidak otomatis berbagi kepercayaan, session, secret, atau hak akses. Masalah utama di arsitektur seperti ini bukan hanya login, tetapi juga blast radius: satu kebocoran di tool publish atau satu kesalahan konfigurasi environment bisa membuka akses ke semua site.

Jika Anda mengelola monorepo yang berisi beberapa site dan alat sinkronisasi atau publish konten, pendekatan paling aman adalah memperlakukan setiap app sebagai security boundary terpisah. Artikel ini membahas langkah konkret: threat model, pemisahan kredensial per app dan per environment, session aman, CSRF, validasi input, upload media, rate limiting, audit log, rotasi secret, serta jebakan umum saat berbagi konfigurasi dalam monorepo.

Arsitektur dasar: dua site, satu publish tool, satu repo

Konteks yang sering muncul adalah satu monorepo berisi:

  • site-a: public-facing site untuk brand atau properti A
  • site-b: public-facing site untuk brand atau properti B
  • publisher: admin/tool internal untuk menulis, menyunting, sinkronisasi, dan publish konten ke kedua site

Secara operasional, monorepo memudahkan berbagi komponen, schema, utilitas, dan pipeline CI/CD. Namun dari sisi keamanan, kemudahan berbagi ini justru berbahaya bila auth, secret, dan environment ikut dibagi tanpa batas yang jelas.

repo/
  apps/
    site-a/
    site-b/
    publisher/
  packages/
    ui/
    content-schema/
    shared-utils/
  infra/
    ci/
    terraform/

Prinsip utamanya:

  • Kode boleh berbagi, identitas jangan otomatis berbagi.
  • Session satu app tidak boleh valid di app lain kecuali memang dirancang secara eksplisit dengan SSO yang benar.
  • Secret, token, dan kredensial harus dipisah per app dan per environment.
  • Publisher harus dianggap sebagai aset berisiko tinggi karena memiliki kemampuan mengubah konten, upload media, dan memicu publish.

Threat model yang realistis untuk monorepo multi-site

Sebelum memilih middleware, token format, atau provider auth, tentukan ancaman yang paling masuk akal.

Aset yang harus dilindungi

  • Akun admin dan editor
  • Session login admin
  • API token untuk publish ke site-a dan site-b
  • Secret environment dan credential database/storage
  • Konten draft dan konten terpublish
  • File media yang di-upload
  • Audit log dan histori perubahan

Permukaan serangan utama

  • Panel admin publisher
  • Endpoint login dan reset password
  • API publish internal
  • Webhook dari CI/CD atau CMS
  • Form upload media
  • Shared package di monorepo yang diam-diam membaca env global
  • Pipeline build yang menyuntikkan secret terlalu luas

Skenario serangan yang sering terjadi

  1. Credential reuse: password admin bocor dari satu sistem lain, lalu dipakai untuk login ke publisher.
  2. Session leakage: cookie diset pada domain terlalu luas, sehingga site lain bisa mengirim atau membaca session yang tidak semestinya.
  3. Cross-app secret exposure: semua app memakai file env yang sama; compromise di site-a membuka token publish site-b.
  4. CSRF pada aksi publish: admin sedang login, lalu dipancing membuka halaman berbahaya yang memicu publish, delete, atau upload.
  5. Upload abuse: file berbahaya, ekstensi palsu, SVG berisi script, atau file terlalu besar untuk DoS.
  6. Privilege escalation: editor untuk site-a dapat publish ke site-b karena scope token tidak dipisah.
  7. Pipeline impersonation: job CI menggunakan token panjang umur yang bisa dipakai ulang di luar konteks pipeline.

Threat model ini penting karena jawaban teknisnya tidak sama. Misalnya, masalah utama Anda mungkin bukan algoritma hash password, tetapi token publish yang dibagi ke semua app atau cookie yang berlaku untuk seluruh subdomain.

Prinsip hardening auth yang paling penting

1. Pisahkan identitas, session, dan kredensial per app

Jangan memakai satu SESSION_SECRET, satu cookie name, atau satu API token untuk seluruh repo. Meski app hidup di repo yang sama, runtime-nya harus dipisah.

Minimal, pisahkan:

  • Cookie name per app, misalnya publisher_session, site_a_session, site_b_session
  • Session secret per app dan per environment
  • OAuth client atau auth client terpisah untuk publisher vs public site
  • API token publish terpisah untuk site-a dan site-b
  • Storage credential terpisah untuk media public dan media internal

Trade-off-nya: konfigurasi jadi lebih banyak dan onboarding sedikit lebih rumit. Keuntungannya jauh lebih besar: kebocoran satu secret tidak langsung mengompromikan seluruh sistem.

2. Gunakan SSO hanya bila memang perlu

Jika publisher dan kedua site hanya membutuhkan admin internal, sering kali lebih aman dan sederhana bila publisher menjadi satu-satunya panel admin, sementara site-a dan site-b tidak memiliki area admin sendiri. Ini mengurangi duplikasi auth.

Jika Anda membutuhkan login yang sama di beberapa app admin, gunakan SSO dari identity provider yang jelas, bukan berbagi cookie lintas app secara manual. Berbagi cookie antar subdomain tanpa desain SSO yang tepat sering berujung pada:

  • domain cookie terlalu luas
  • logout tidak sinkron
  • kebingungan CSRF
  • session fixation
  • kesalahan validasi audience/issuer pada token

3. Terapkan MFA untuk publisher

Publisher adalah target paling bernilai karena bisa mengubah konten dan menyentuh pipeline publish. MFA bukan pengganti desain auth yang benar, tetapi lapisan penting untuk menahan credential stuffing dan password reuse.

Prioritas penerapan MFA:

  1. akun admin superuser
  2. akun dengan hak publish ke production
  3. akun yang dapat mengelola user, secret, atau webhook

Session management dan cookie yang aman

Cookie yang tepat untuk admin

Untuk panel publisher berbasis web, sesi berbasis cookie server-side biasanya lebih aman dan lebih mudah dicabut daripada token bearer panjang umur di browser.

Setidaknya terapkan:

  • HttpOnly: mencegah akses lewat JavaScript
  • Secure: hanya terkirim lewat HTTPS
  • SameSite=Lax atau Strict untuk admin, tergantung alur login dan integrasi
  • Domain sesempit mungkin: jangan set ke parent domain bila tidak perlu
  • TTL session masuk akal: pendek untuk panel admin, lebih pendek lagi untuk aksi sensitif

Kesalahan umum: mengatur cookie domain ke .example.com karena ingin “mudah” dipakai semua app. Ini memperbesar risiko cross-subdomain abuse. Jika publisher berada di publisher.example.com, lebih aman batasi cookie ke host tersebut saja kecuali ada kebutuhan SSO yang sah.

Rotasi session setelah login dan privilege change

Regenerasi session identifier setelah login berhasil, setelah MFA selesai, dan setelah perubahan hak akses. Ini mengurangi risiko session fixation.

Timeout dan re-auth untuk aksi sensitif

Jangan menganggap session valid sepanjang hari untuk semua aksi. Untuk operasi seperti publish ke production, hapus konten, ubah role user, atau rotasi token, minta step-up authentication:

  • masukkan password ulang, atau
  • verifikasi MFA ulang, atau
  • konfirmasi lewat challenge singkat dengan masa berlaku pendek

Contoh konfigurasi konseptual cookie/session

# apps/publisher/.env.production
SESSION_COOKIE_NAME=publisher_session
SESSION_SECRET=***unik untuk publisher-prod***
SESSION_TTL_MINUTES=60
COOKIE_SECURE=true
COOKIE_HTTP_ONLY=true
COOKIE_SAME_SITE=lax
COOKIE_DOMAIN=publisher.example.com

# apps/site-a/.env.production
SESSION_COOKIE_NAME=site_a_session
SESSION_SECRET=***unik untuk site-a-prod***
COOKIE_DOMAIN=site-a.example.com

# apps/site-b/.env.production
SESSION_COOKIE_NAME=site_b_session
SESSION_SECRET=***unik untuk site-b-prod***
COOKIE_DOMAIN=site-b.example.com

Nama key di atas sengaja generik. Sesuaikan dengan framework Anda, tetapi prinsip pemisahannya tetap sama.

Penyimpanan secret dan isolasi environment di monorepo

Jangan pakai satu file env untuk semua app

Kesalahan paling sering di monorepo adalah meletakkan semua secret dalam satu file root seperti .env, lalu setiap app bebas membacanya. Ini praktis saat development, tetapi berbahaya di CI maupun production.

Masalahnya:

  • site-a bisa secara tidak sengaja membaca token publish site-b
  • shared package dapat mengakses env yang tidak seharusnya
  • build log dapat membeberkan variabel yang tidak relevan
  • reviewer sulit tahu secret mana dipakai app mana

Pola yang lebih aman

  • Secret store per environment: dev, staging, prod dipisah
  • Namespace per app: publisher/prod, site-a/prod, site-b/prod
  • Inject hanya secret yang diperlukan saat build atau deploy app tersebut
  • Jangan expose secret ke client build kecuali memang public config
  • Batasi akses CI job agar job site-a tidak bisa membaca secret publisher

Rotasi secret

Rotasi secret perlu direncanakan sejak awal. Jika semua app berbagi satu secret, rotasi akan menyakitkan. Dengan pemisahan per app dan per env, Anda bisa merotasi bertahap.

Checklist rotasi yang praktis:

  1. inventaris semua secret dan pemiliknya
  2. bedakan secret manual vs machine credential
  3. sediakan masa transisi bila sistem mendukung dua credential aktif
  4. putuskan prosedur revocation untuk insiden
  5. uji rotasi di staging sebelum production
  6. catat rotasi di audit log/change log operasional

Catatan: token panjang umur untuk pipeline publish adalah sumber masalah umum. Jika platform Anda mendukung kredensial jangka pendek atau federasi identitas untuk CI, itu biasanya lebih aman daripada token statis yang disimpan lama.

Desain auth dan authorization untuk publisher

RBAC minimal yang masuk akal

Jangan semua admin menjadi superuser. Untuk dua site dan satu tool publish, struktur role sederhana berikut sudah sangat membantu:

  • viewer: lihat draft dan log
  • editor: edit konten untuk site tertentu
  • publisher: publish ke site tertentu
  • admin: kelola user, role, integrasi, secret non-produksi
  • superadmin: aksi darurat dan konfigurasi sensitif production

Lebih penting lagi, scope role harus terkait target:

  • editor:site-a
  • publisher:site-a
  • publisher:site-b

Ini mencegah user yang semestinya hanya mengelola site-a ikut menerbitkan ke site-b.

Gunakan service account untuk pipeline, bukan akun manusia

Pipeline publish atau sinkronisasi konten sebaiknya memakai identitas non-manusia yang terpisah dari akun admin biasa. Alasannya:

  • hak akses bisa dibuat sempit
  • rotasi lebih mudah
  • audit lebih jelas
  • tidak tergantung masa hidup akun individu

Contoh model izin yang lebih aman

Human users:
  - alice: editor:site-a
  - bob: publisher:site-a
  - carol: publisher:site-b
  - dave: admin

Service accounts:
  - publisher-to-site-a: publish:site-a only
  - publisher-to-site-b: publish:site-b only
  - media-processor: media:write only

Dengan pola ini, compromise pada token publish site-a tidak otomatis memberikan akses ke site-b.

CSRF, validasi input, dan pengamanan form admin

CSRF wajib untuk aksi berbasis cookie

Jika admin login dengan session cookie, maka endpoint yang mengubah state harus dilindungi CSRF. Jangan mengandalkan SameSite saja. SameSite membantu, tetapi bukan pengganti proteksi CSRF yang eksplisit.

Lindungi endpoint seperti:

  • publish/unpublish
  • delete konten
  • invite user
  • change password
  • upload media
  • rotate token

Pastikan token CSRF:

  • unik per session atau per request, sesuai pola framework Anda
  • divalidasi server-side
  • tidak dinonaktifkan hanya karena “internal tool”

Validasi input jangan berhenti di frontend

Monorepo sering berbagi schema validasi antara frontend dan backend. Ini bagus untuk konsistensi, tetapi backend tetap harus memvalidasi sendiri. Jangan percaya hasil validasi frontend atau shared package semata.

Validasi penting pada publisher:

  • slug dan path agar tidak menimpa route tak semestinya
  • HTML/Markdown content untuk mencegah XSS saat preview atau render
  • URL media dan embed agar tidak memicu SSRF atau open redirect
  • metadata publish seperti target site, locale, dan schedule

Sanitasi konten rich text

Jika editor menghasilkan HTML, sanitasi wajib dilakukan sebelum render. XSS di panel admin sangat berbahaya karena bisa mencuri aksi admin, memicu publish, atau membaca data sensitif yang tampil di browser.

Hati-hati khususnya pada:

  • <script> dan event handler inline
  • URL javascript:
  • SVG yang memuat script atau payload tak diinginkan
  • iframe dan embed pihak ketiga

Upload media: area yang sering diremehkan

Upload media bukan sekadar menerima file lalu menyimpannya ke object storage. Ini salah satu jalur abuse paling umum.

Checklist pengamanan upload

  • Batasi tipe file berdasarkan MIME dan inspeksi konten, bukan ekstensi saja
  • Batasi ukuran file dan jumlah upload per periode
  • Rename file di server, jangan pakai nama asli mentah
  • Simpan di bucket/path yang terpisah antara public asset dan internal upload
  • Jangan izinkan overwrite file kritis berdasarkan path yang dikirim client
  • Jika memungkinkan, scan malware sebelum file tersedia publik
  • Blok atau sangat batasi SVG jika belum ada sanitasi yang matang

Pre-signed upload: pilih dengan hati-hati

Pre-signed URL berguna untuk mengurangi beban server aplikasi, tetapi pembatasannya harus ketat:

  • masa berlaku pendek
  • scope hanya ke path tertentu
  • batas ukuran/tipe file bila storage mendukung kebijakan tersebut
  • objek hasil upload tidak langsung dianggap aman atau terpublish

Pola yang baik adalah: client upload ke lokasi karantina, lalu backend memvalidasi, scan, dan memindahkan ke lokasi final setelah lolos pemeriksaan.

Rate limiting dan pencegahan abuse

Rate limiting per endpoint, bukan satu angka global

Endpoint login, reset password, invite user, upload media, dan publish memiliki profil risiko berbeda. Jangan gunakan satu limit umum untuk semuanya.

Contoh kebijakan yang masuk akal secara konsep:

  • login: limit ketat per IP dan per identifier akun
  • password reset: limit ketat + respons generik agar tidak bocorkan user enumeration
  • publish: limit per user dan per site untuk mencegah spam atau loop otomatis
  • upload media: limit berdasarkan ukuran total dan frekuensi

Gabungkan rate limiting dengan sinyal lain

Rate limiting saja tidak cukup. Tambahkan:

  • lockout sementara atau challenge tambahan setelah banyak gagal login
  • deteksi anomali lokasi/perangkat untuk aksi sensitif
  • notifikasi saat login baru atau saat token baru dibuat
  • approval tambahan untuk publish skala besar atau bulk delete

Bedakan abuse publik dan abuse internal

Site publik dan publisher punya profil abuse berbeda. Pada site publik, fokus sering ke scraping, brute force, dan spam. Pada publisher, fokusnya ke account takeover, privilege misuse, dan aksi destruktif dari user yang sudah login. Karena itu kontrol untuk publisher biasanya lebih ketat dan lebih audit-heavy.

Audit log yang benar-benar berguna

Audit log bukan sekadar “user login berhasil”. Log yang berguna harus membantu investigasi insiden dan menjawab pertanyaan: siapa melakukan apa, terhadap resource mana, kapan, dari mana, dan hasilnya apa.

Event yang sebaiknya dicatat

  • login berhasil/gagal
  • MFA enable/disable
  • password reset request dan completion
  • perubahan role dan permission
  • pembuatan, penggunaan, dan rotasi API token
  • publish, unpublish, bulk update, delete
  • upload media dan hasil validasi/scan
  • perubahan konfigurasi environment atau webhook

Isi minimum audit log

  • timestamp
  • actor type: user atau service account
  • actor id
  • action
  • target resource id/type
  • site target atau environment target
  • source IP atau request context yang relevan
  • hasil: success, denied, failed

Hindari mencatat secret, token mentah, atau data sensitif penuh di log. Jika perlu referensi token, simpan fingerprint atau identifier yang aman.

Pipeline publish yang lebih aman

Jangan beri CI akses lebih luas dari yang dibutuhkan

Sering kali satu pipeline di monorepo bisa membangun semua app. Itu tidak berarti setiap job perlu secret untuk semua app. Job publish site-a cukup tahu token site-a, bukan token site-b atau credential publisher database.

Pisahkan jalur build dan jalur deploy

Build artefak sebaiknya tidak otomatis punya hak deploy. Untuk perubahan sensitif, Anda bisa menerapkan:

  • approval manual untuk production publish
  • protected branch/tag
  • signed commits atau provenance build bila tersedia di platform Anda
  • job deploy yang hanya berjalan dari context tertentu

Validasi payload publish

Jika publisher memanggil API internal site-a/site-b untuk publish konten, validasi berikut penting:

  • issuer identitas pemanggil
  • audience atau target app
  • scope tindakan, misalnya hanya publish:site-a
  • timestamp/expiry untuk mencegah replay
  • idempotency key agar retry tidak mem-publish ganda
Publisher UI
  - human login + MFA
  - creates publish request

Publisher backend
  - authorize user for target site
  - write audit log
  - call site-specific publish API using service credential

Site publish API
  - validate caller identity/scope
  - validate payload/schema
  - apply content change
  - return publish result

Pemisahan ini membuat keputusan otorisasi user dan tindakan mesin menjadi lebih jelas dan lebih mudah diaudit.

Kesalahan umum saat berbagi environment di monorepo

1. Shared package membaca env langsung

Package bersama yang memanggil process.env atau mekanisme env global lain sering menjadi kebocoran tak terlihat. Lebih aman bila package menerima konfigurasi secara eksplisit dari app pemanggil.

2. Prefix env client/server tercampur

Pada stack fullstack modern, sebagian env bisa terekspos ke browser jika salah prefix atau salah bundling. Jangan letakkan secret auth server dalam namespace config yang dapat ikut terbawa ke client.

3. Satu secret untuk dev, staging, prod

Ini mempermudah local setup, tetapi merusak isolasi. Insiden di environment non-production bisa berdampak ke production jika token atau secret ternyata sama.

4. Akun admin dipakai pipeline

Menggunakan akun manusia untuk automation menyulitkan audit dan rotasi, serta meningkatkan risiko saat orang keluar dari tim atau hak akses berubah.

5. CSRF dimatikan karena “API internal”

Jika endpoint dipanggil dari browser dan memakai cookie, anggap tetap perlu CSRF. Label “internal” tidak mengubah model ancaman browser.

6. Cookie scope terlalu luas

Sering terjadi ketika semua app diletakkan di subdomain yang mirip lalu dipaksa berbagi auth tanpa desain SSO yang benar.

Checklist implementasi hardening auth

  1. Definisikan boundary: site-a, site-b, dan publisher sebagai app terpisah.
  2. Pisahkan cookie name, session secret, dan domain cookie per app.
  3. Terapkan MFA minimal untuk user yang dapat publish ke production.
  4. Gunakan RBAC dengan scope per site, bukan role global saja.
  5. Buat service account terpisah untuk publish site-a dan site-b.
  6. Simpan secret per app dan per environment di secret manager, bukan satu env root.
  7. Batasi CI job agar hanya menerima secret yang relevan.
  8. Regenerasi session setelah login/MFA dan untuk privilege change.
  9. Terapkan CSRF pada semua endpoint state-changing berbasis cookie.
  10. Validasi input di backend, termasuk slug, HTML, URL, target site, dan metadata publish.
  11. Sanitasi rich text dan hati-hati pada SVG/embed.
  12. Keraskan upload media: MIME check, size limit, rename, scan, storage isolation.
  13. Terapkan rate limiting spesifik per endpoint dan per identitas.
  14. Tambahkan audit log untuk login, role change, token, upload, dan publish.
  15. Rencanakan rotasi secret dan prosedur revocation insiden.
  16. Uji cross-app isolation: cookie site-a tidak valid di publisher, token site-b tidak bisa publish ke site-a, dan seterusnya.

Debugging dan verifikasi yang perlu dilakukan

Uji isolasi cookie

Periksa response header dan browser storage:

  • apakah cookie publisher muncul di host yang benar?
  • apakah domain terlalu luas?
  • apakah Secure, HttpOnly, dan SameSite terset sesuai harapan?

Uji authz lintas site

Buat akun dengan hak hanya untuk site-a, lalu pastikan seluruh aksi ke site-b ditolak, termasuk publish API, upload yang ditautkan ke site-b, dan preview konten site-b bila seharusnya terlarang.

Uji rotasi secret

Lakukan simulasi pergantian token publish tanpa downtime besar. Jika rotasi memerlukan deploy seluruh monorepo sekaligus, itu tanda coupling terlalu tinggi.

Uji replay dan idempotency

Kirim ulang request publish yang sama. Pastikan sistem tidak menghasilkan dua publish atau state yang membingungkan.

Penutup

Pada monorepo multi-site, tantangan keamanan terbesar biasanya bukan “bagaimana cara login”, melainkan bagaimana mencegah satu app, satu token, atau satu kesalahan konfigurasi menyebar ke seluruh sistem. Hardening auth untuk monorepo multi-site dan pipeline publish berarti membatasi kepercayaan secara sengaja: secret dipisah, session diisolasi, role diberi scope, pipeline memakai service account, dan semua aksi penting diaudit.

Jika Anda hanya mengambil beberapa langkah dari artikel ini, mulai dari tiga hal berikut: pisahkan kredensial per app/env, keraskan session/cookie dan CSRF pada publisher, serta batasi token publish per site dengan audit log yang jelas. Tiga langkah ini biasanya memberi pengurangan risiko paling nyata tanpa harus mengubah seluruh arsitektur sekaligus.