@ojokesusu/lintasai 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish-npm.yml +40 -0
- package/.github/workflows/validate.yml +93 -0
- package/AUDIT_POST_SETUP_PROMPT_v1.md +280 -0
- package/BOOTSTRAP_PROJECT_DOCS_PROMPT_v1.md +3 -0
- package/CHANGELOG.md +313 -0
- package/CLAUDE_universal_v1.md +1021 -0
- package/CONTRIBUTING.md +101 -0
- package/FIRST_SESSION_PROMPT_v1.md +7 -0
- package/JALANKAN_KIT.md +188 -0
- package/LICENSE +21 -0
- package/MULAI_DI_SINI.md +145 -0
- package/PROJECT_KICKOFF_PROMPT_v1.md +3 -0
- package/PROJECT_LIFECYCLE_PROMPT_v1.md +536 -0
- package/PROJECT_MIGRATION_PROMPT_v1.md +3 -0
- package/README.md +505 -0
- package/SETUP_POLA_B_PROMPT_v1.md +5 -0
- package/SPLIT_REPO_MIGRATION_PROMPT_v1.md +485 -0
- package/TEAM_ROLLOUT_GUIDE_v1.md +172 -0
- package/UPDATE_DOCS_PROMPT_v1.md +3 -0
- package/UPDATE_KIT_PROMPT_v1.md +213 -0
- package/bin/lintasai.js +81 -0
- package/docs/SIGNED_RELEASE.md +162 -0
- package/install-windows.ps1 +225 -0
- package/kit.ps1 +508 -0
- package/lib/agents-md.ps1 +174 -0
- package/lib/git-helpers.ps1 +104 -0
- package/lib/kit-files.psd1 +133 -0
- package/lib/manifest-signing.ps1 +65 -0
- package/lib/manifest.ps1 +267 -0
- package/lib/rollback.ps1 +241 -0
- package/lib/safety.ps1 +193 -0
- package/lib/template-deploy.ps1 +242 -0
- package/lib/version-detect.ps1 +161 -0
- package/package.json +36 -0
- package/setup-pola-b.ps1 +687 -0
- package/templates/ANALOGI_LIBRARY.md +7 -0
- package/templates/CLAUDE_TEAM_GUIDE.md +505 -0
- package/templates/CROSS_REPO_TYPES_PIPELINE.md +473 -0
- package/templates/DB_SCHEMA_SCAN_PROMPT.md +194 -0
- package/templates/DISCORD_BOT_INTEGRATION.md +187 -0
- package/templates/GLOSSARY_NON_PROGRAMMER.md +361 -0
- package/templates/INDEX.md +157 -0
- package/templates/MCP_SETUP.md +1145 -0
- package/templates/MIGRATE_TO_SUBFOLDER_PROMPT_v1.md +220 -0
- package/templates/ONBOARDING.md +172 -0
- package/templates/PROJECT_STARTER_TEMPLATES.md +264 -0
- package/templates/PROMPT_LIBRARY.md +790 -0
- package/templates/RLS_SETUP_PROMPT.md +167 -0
- package/templates/SECURITY_INCIDENT_PLAYBOOK.md +191 -0
- package/templates/SPLIT_REPO_AGENTS_TEMPLATES.md +32 -0
- package/templates/SPLIT_REPO_NON_PROGRAMMER_PROMPTS.md +604 -0
- package/templates/SPLIT_REPO_TOOLS_SETUP.md +388 -0
- package/templates/STACK_DETECTION_PATTERN.md +261 -0
- package/templates/STACK_GUIDE.md +564 -0
- package/templates/STACK_MIGRATION_GUIDE.md +154 -0
- package/templates/STACK_VERSIONS.md +31 -0
- package/templates/UPDATE_GUIDE.md +246 -0
- package/templates/_EXAMPLE.md +110 -0
- package/templates/_PATTERNS.md +173 -0
- package/templates/architecture.md +180 -0
- package/templates/architecture_auto.md +61 -0
- package/templates/decisions/README.md +108 -0
- package/templates/decisions/_TEMPLATE.md +84 -0
- package/templates/feature-flags-advanced.md +171 -0
- package/templates/github/CODEOWNERS.template +61 -0
- package/templates/github/GENERATE_TYPES_SCRIPT.md +77 -0
- package/templates/github/PUBLISH_SHARED_WORKFLOW.yml +52 -0
- package/templates/github/RECEIVE_BACKEND_UPDATE.yml +106 -0
- package/templates/github/RENOVATE_FRONTEND.json +28 -0
- package/templates/github/TRIGGER_FRONTEND_UPDATE.yml +29 -0
- package/templates/github/pull_request_template.md +44 -0
- package/templates/github/scripts/ai-review.js +153 -0
- package/templates/github/workflows/ai-review.yml +61 -0
- package/templates/github/workflows/backup-schemas.yml +169 -0
- package/templates/glossary.md +110 -0
- package/templates/split-agents/BACKEND.md +149 -0
- package/templates/split-agents/FRONTEND.md +141 -0
- package/templates/split-agents/SHARED.md +82 -0
- package/templates/split-agents/TOOLS.md +77 -0
- package/tests/Run-Tests.ps1 +19 -0
- package/tests/lib-safety.Tests.ps1 +66 -0
- package/tests/rollback.Tests.ps1 +66 -0
- package/tests/uninstall.Tests.ps1 +265 -0
- package/tests/update-kit.Tests.ps1 +78 -0
- package/uninstall.ps1 +794 -0
- package/update-kit.ps1 +907 -0
|
@@ -0,0 +1,1145 @@
|
|
|
1
|
+
# templates/MCP_SETUP.md — Setup MCP Servers untuk Tim AI-first
|
|
2
|
+
|
|
3
|
+
> Versi 1 · 2026-06-01
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Daftar Isi
|
|
8
|
+
|
|
9
|
+
- [0. Decision Tree — Pilih MCP yang Mana?](#0-decision-tree--pilih-mcp-yang-mana)
|
|
10
|
+
- [1. Pengantar](#1-pengantar)
|
|
11
|
+
- [2. PostgreSQL MCP dengan Schema Isolation](#2-postgresql-mcp-dengan-schema-isolation)
|
|
12
|
+
- [2.1 Phase-Based DB Environment Strategy](#21-phase-based-db-environment-strategy-pilih-berdasarkan-stage-project)
|
|
13
|
+
- [2.2 Seed Data Strategy](#22-seed-data-strategy--penting-untuk-ai-context-quality)
|
|
14
|
+
- [2.3 Multi-Layer Backup Strategy](#23-multi-layer-backup-strategy-defense-in-depth)
|
|
15
|
+
- [2.4 Pilih Multi-Schema Strategy](#24-pilih-multi-schema-strategy--sesuai-stage-project)
|
|
16
|
+
- [2.5 Setup PostgreSQL Role per-User — Option A](#25-setup-postgresql-role-per-user--option-a-owner-only-step)
|
|
17
|
+
- [2.6 Setup Per-Staff Isolated Schema — Option B](#26-setup-per-staff-isolated-schema--option-b-untuk-development)
|
|
18
|
+
- [2.7 Setup Hybrid Strategy — Option C](#27-setup-hybrid-strategy--option-c-sandbox--read-prod)
|
|
19
|
+
- [2.8 Backup Plan untuk Option B & C](#28-backup-plan-untuk-option-b--c-wajib)
|
|
20
|
+
- [2.9 Restore Flow](#29-restore-flow-kalau-staff-accidentally-drop-table)
|
|
21
|
+
- [2.10 Promote Sandbox Schema → Production Schema](#210-promote-sandbox-schema--production-schema-saat-fitur-ready)
|
|
22
|
+
- [2.11 Verifikasi Schema Isolation](#211-verifikasi-schema-isolation)
|
|
23
|
+
- [2.12 Connection String per-Dev](#212-connection-string-per-dev-format-benar--supabase-pooler)
|
|
24
|
+
- [2.13 RLS Setup Workflow](#213-rls-setup-workflow-wajib-sebelum-data-produksi-masuk)
|
|
25
|
+
- [2.14 MCP Config di Claude Code](#214-mcp-config-di-claude-code)
|
|
26
|
+
- [2.15 Audit Trail](#215-audit-trail)
|
|
27
|
+
- [3. Supabase MCP (Owner Only — WARNING)](#3-supabase-mcp-owner-only--warning)
|
|
28
|
+
- [4. GitHub MCP](#4-github-mcp)
|
|
29
|
+
- [5. Filesystem MCP (Default Claude Code)](#5-filesystem-mcp-default-claude-code)
|
|
30
|
+
- [6. Anti-Pattern (Jangan Lakukan)](#6-anti-pattern-jangan-lakukan)
|
|
31
|
+
- [7. Audit + Rotation](#7-audit--rotation)
|
|
32
|
+
- [8. Troubleshooting](#8-troubleshooting)
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 0. Decision Tree — Pilih MCP yang Mana?
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
Kamu siapa?
|
|
40
|
+
├─ Staff IT / Developer schema-scoped
|
|
41
|
+
│ → Pakai PostgreSQL MCP (Section 2) ⭐ DEFAULT
|
|
42
|
+
│ → Username: creative_<dev>.<project-ref> via Supabase pooler
|
|
43
|
+
│ → Akses HANYA schema kamu (terisolasi dari tenant lain)
|
|
44
|
+
│ → WAJIB setup RLS di schema kamu sebelum data masuk (Section 2.13)
|
|
45
|
+
│
|
|
46
|
+
└─ Owner / Admin database
|
|
47
|
+
→ Boleh PostgreSQL MCP (Section 2) untuk operasi sehari-hari
|
|
48
|
+
→ Boleh Supabase MCP (Section 3) untuk debug owner-level
|
|
49
|
+
⚠️ Supabase MCP pakai service_role_key = BYPASS RLS = SUPERUSER
|
|
50
|
+
⚠️ JANGAN share key ini ke staff IT
|
|
51
|
+
⚠️ Hanya pakai saat butuh akses lintas-schema (audit, migrasi global)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Aturan ketat untuk Staff IT**:
|
|
55
|
+
- ❌ JANGAN pakai username `postgres.<ref>` (= superuser, akses SEMUA tenant)
|
|
56
|
+
- ❌ JANGAN pakai `service_role_key` (= bypass RLS, akses data user lain)
|
|
57
|
+
- ✅ PAKAI username `creative_<dev>.<project-ref>` dengan password kamu sendiri
|
|
58
|
+
- ✅ PAKAI Supabase pooler URL (port 6543 untuk app, 5432 untuk DDL/migrasi)
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## 1. Pengantar
|
|
63
|
+
|
|
64
|
+
### Apa itu MCP?
|
|
65
|
+
|
|
66
|
+
**MCP (Model Context Protocol)** = standar protokol yang dibikin Anthropic untuk hubungkan AI (Claude Code) ke **tool eksternal**: database, GitHub, filesystem, API custom, dll.
|
|
67
|
+
|
|
68
|
+
Analogi: kalau Claude Code = otak, MCP server = tangan & mata. Tanpa MCP, AI cuma bisa baca yang user paste manual. Dengan MCP, AI bisa query DB sendiri, baca issue GitHub, edit file di workspace.
|
|
69
|
+
|
|
70
|
+
### Kenapa standardize?
|
|
71
|
+
|
|
72
|
+
Kalau tiap dev setup MCP beda-beda → AI assistant tiap orang punya "power" beda → hasil kerja inconsistent + risiko keamanan beda.
|
|
73
|
+
|
|
74
|
+
Standar tim AI-first:
|
|
75
|
+
|
|
76
|
+
- **Semua dev** pakai MCP yang sama (PostgreSQL + GitHub + Filesystem).
|
|
77
|
+
- **Tiap dev** punya kredensial sendiri (PostgreSQL role per-user, GitHub PAT per-user).
|
|
78
|
+
- **Tidak ada** akses superuser via MCP (cegah privilege escalation).
|
|
79
|
+
- **Audit trail** aktif (siapa query apa, kapan).
|
|
80
|
+
|
|
81
|
+
> *Privilege escalation* = naik level akses tanpa izin. Mis. user biasa bisa jadi admin lewat bug → cegah dengan least-privilege di awal.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## 2. PostgreSQL MCP dengan Schema Isolation
|
|
86
|
+
|
|
87
|
+
### Latar belakang security
|
|
88
|
+
|
|
89
|
+
**Jangan pakai Supabase MCP** (yang official) untuk staff IT. Alasan:
|
|
90
|
+
|
|
91
|
+
- Supabase MCP butuh **service_role_key** → bypass RLS (Row-Level Security) → AI bisa baca/edit data user lain.
|
|
92
|
+
- Service_role_key = full superuser → tidak bisa di-scope per-orang.
|
|
93
|
+
- Audit trail terbatas (semua query muncul atas nama service_role).
|
|
94
|
+
|
|
95
|
+
**Pakai `@modelcontextprotocol/server-postgres`** dengan **PostgreSQL role per-user** + **schema isolation**. Tiap dev cuma bisa akses schema yang relevan dengan kerjaannya.
|
|
96
|
+
|
|
97
|
+
### 2.1. Phase-Based DB Environment Strategy (PILIH BERDASARKAN STAGE PROJECT)
|
|
98
|
+
|
|
99
|
+
**SEBELUM milih multi-schema option**, tentukan dulu **DB environment strategy** sesuai stage project kamu. Ini KRITIKAL — salah pilih = staff bisa accidentally rusak data prod.
|
|
100
|
+
|
|
101
|
+
#### 🚦 Aturan Emas (Conditional, BUKAN Absolut)
|
|
102
|
+
|
|
103
|
+
**TIDAK ADA staff non-programmer yang boleh punya WRITE access ke schema PROD — KALAU ada user real / payment masuk / PII customer.**
|
|
104
|
+
|
|
105
|
+
Aturan ini BUKAN absolut. Untuk **early-stage project tanpa user real**, aturan ini TIDAK berlaku karena:
|
|
106
|
+
- Belum ada PII customer yang harus dilindungi (compliance N/A)
|
|
107
|
+
- Belum ada revenue stream yang loss kalau down
|
|
108
|
+
- Belum ada reputation risk (no users tahu kalau ada incident)
|
|
109
|
+
- Backup berlapis cukup mitigasi risk DROP TABLE (recoverable <1 hari)
|
|
110
|
+
- Velocity early-stage > Safety overhead
|
|
111
|
+
|
|
112
|
+
Backup mengurangi DAMPAK kalau bencana terjadi. Access control CEGAH bencana terjadi. **Kedua-duanya perlu saat ada user real**. Sebelum itu — backup berlapis sudah cukup.
|
|
113
|
+
|
|
114
|
+
#### Phase 0 — Pre-Launch (Project Belum Punya User Real) ⭐ KAMU SEKARANG
|
|
115
|
+
|
|
116
|
+
**Setup**: **1 Supabase project = single environment** (technically called "DEV" tapi sebenarnya satu-satunya DB yang ada)
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
Supabase Project: akses (single environment)
|
|
120
|
+
├── Schema creative_a (Bagus owner — full CREATE/SELECT/INSERT/UPDATE/DELETE)
|
|
121
|
+
├── Schema creative_b (Andi owner — full akses)
|
|
122
|
+
├── Schema creative_c (Citra owner — full akses)
|
|
123
|
+
├── Schema bigseo (shared "main" schema — semua staff FULL akses sementara)
|
|
124
|
+
└── Schema public (default PostgreSQL)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**Staff IT dapat full WRITE access ke schema bigseo**, plus schema mereka sendiri. Aturan emas "no write prod" **TIDAK berlaku** di phase ini.
|
|
128
|
+
|
|
129
|
+
**Strategy**:
|
|
130
|
+
- Belum ada user real = belum ada PROD secara konsep
|
|
131
|
+
- Staff full akses Option B atau C (per-staff schema isolated) + shared bigseo
|
|
132
|
+
- Faker library untuk seed data realistic (penting untuk AI context, lihat 2.2 di bawah)
|
|
133
|
+
- Backup 3-5 layer aktif (Supabase Daily + per-schema pg_dump + Cloudflare R2 + BackBlaze B2)
|
|
134
|
+
- Owner masih kontrol Prisma migration via Git PR (gatekeeper untuk DDL structure)
|
|
135
|
+
|
|
136
|
+
**Kapan TRANSISI ke Phase 1 (split DEV + PROD)?**
|
|
137
|
+
|
|
138
|
+
Trigger transisi (ANY of these → IMMEDIATELY split):
|
|
139
|
+
- 🔴 **First real user signup** (bukan owner/staff IT)
|
|
140
|
+
- 🔴 **First payment / transaction masuk** (revenue stream aktif)
|
|
141
|
+
- 🔴 **First PII customer data masuk** (email/NPWP/alamat real)
|
|
142
|
+
- 🔴 **Compliance requirement aktif** (GDPR, UU PDP, SOC2, klien minta audit)
|
|
143
|
+
- 🔴 **SLA dengan klien eksternal** (uptime/response time contract)
|
|
144
|
+
|
|
145
|
+
Saat salah satu trigger di atas terjadi → IMMEDIATELY setup Phase 1 (split DEV + PROD).
|
|
146
|
+
|
|
147
|
+
**Cocok untuk akses sekarang (5% progress)**. Pakai Phase 0 sampai salah satu trigger di atas.
|
|
148
|
+
|
|
149
|
+
#### Phase 1 — Soon Launch / Early Users (Salah Satu Trigger Phase 0 Terpenuhi)
|
|
150
|
+
|
|
151
|
+
**Setup**: **2 Supabase project terpisah** (free tier cukup, max 2 project)
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
Supabase Project: akses-dev (existing, downgrade dari "akses" yang dulu)
|
|
155
|
+
└── Staff full access (creative_a/b/c + bigseo)
|
|
156
|
+
|
|
157
|
+
Supabase Project: akses-prod (BARU, dispawn saat trigger Phase 0 hit)
|
|
158
|
+
└── Owner only — staff TIDAK punya akses
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**Transisi Phase 0 → Phase 1 step-by-step**:
|
|
162
|
+
1. Spin up Supabase project baru `akses-prod`
|
|
163
|
+
2. Export schema dari `akses` (current) via `pg_dump --schema-only`
|
|
164
|
+
3. Import schema-only ke `akses-prod` (no data, fresh start)
|
|
165
|
+
4. Setup user real signup di app pakai env var DATABASE_URL → `akses-prod`
|
|
166
|
+
5. Restrict staff: revoke akses ke `akses-prod`, mereka stay di `akses-dev` (rename `akses` → `akses-dev`)
|
|
167
|
+
6. Vercel preview deploy → connect `akses-dev` DB
|
|
168
|
+
Vercel production deploy → connect `akses-prod` DB (env var per-environment)
|
|
169
|
+
7. Setup migration workflow: owner approve PR yang ubah schema → Vercel build run `prisma migrate deploy` ke `akses-prod`
|
|
170
|
+
|
|
171
|
+
**Aturan emas WAJIB berlaku** mulai Phase 1: zero staff write access ke `akses-prod`.
|
|
172
|
+
|
|
173
|
+
#### Phase 2 — Post-Launch Scale (>1000 Users atau Multiple Domain)
|
|
174
|
+
|
|
175
|
+
**Setup**: 3 environment (DEV + STAGING + PROD) **ATAU** Supabase Pro Branching
|
|
176
|
+
|
|
177
|
+
```
|
|
178
|
+
Option A: 3 environment manual
|
|
179
|
+
- DEV: staff develop
|
|
180
|
+
- STAGING: owner test merged code dengan data near-prod (sanitized snapshot)
|
|
181
|
+
- PROD: live users
|
|
182
|
+
|
|
183
|
+
Option B: Supabase Pro ($25/bulan) + Branching
|
|
184
|
+
- Per-PR auto-create DB branch (ephemeral)
|
|
185
|
+
- Staff develop di branch DB-nya sendiri
|
|
186
|
+
- Merge to main = auto-apply migration ke prod
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**Untuk akses spesifik**: belum perlu Phase 2 sampai user 1000+ atau monthly active users tinggi.
|
|
190
|
+
|
|
191
|
+
### 2.2. Seed Data Strategy — Penting untuk AI Context Quality
|
|
192
|
+
|
|
193
|
+
**Masalah**: Kalau DEV environment cuma punya "Test User 1-5", AI tidak dapat context realistic → bisa miss edge cases (nama dengan apostrophe, panjang nama, format NPWP, distribusi performa real).
|
|
194
|
+
|
|
195
|
+
**Solusi**: Seed data realistic via Faker library (Indonesian locale).
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
// prisma/seed.ts (jalankan: npx prisma db seed)
|
|
199
|
+
import { PrismaClient } from '@prisma/client'
|
|
200
|
+
import { faker } from '@faker-js/faker/locale/id_ID'
|
|
201
|
+
|
|
202
|
+
const prisma = new PrismaClient()
|
|
203
|
+
|
|
204
|
+
async function main() {
|
|
205
|
+
// 1000 user Indonesia dengan distribusi realistic
|
|
206
|
+
for (let i = 0; i < 1000; i++) {
|
|
207
|
+
await prisma.user.create({
|
|
208
|
+
data: {
|
|
209
|
+
nama: faker.person.fullName(), // "Bagus Setiawan", "Sri Wahyuni"
|
|
210
|
+
email: faker.internet.email(),
|
|
211
|
+
npwp: faker.numerics({ length: 16 }),
|
|
212
|
+
alamat: faker.location.streetAddress(),
|
|
213
|
+
created_at: faker.date.past({ years: 2 }),
|
|
214
|
+
}
|
|
215
|
+
})
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// 10000 transaksi dengan range tanggal & amount realistic
|
|
219
|
+
// dst untuk relasi yang penting
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
main()
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
**Setup `package.json`**:
|
|
226
|
+
|
|
227
|
+
```json
|
|
228
|
+
{
|
|
229
|
+
"prisma": {
|
|
230
|
+
"seed": "tsx prisma/seed.ts"
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
**Jalankan**: `npx prisma db seed` setiap kali setup DEV environment baru atau reset data.
|
|
236
|
+
|
|
237
|
+
**Hasil**: AI dapat context realistic → catch edge cases yang mirip prod → fitur lebih robust saat naik ke PROD.
|
|
238
|
+
|
|
239
|
+
**Phase 2+ Alternative**: Sanitized snapshot prod → DEV (monthly basis). Owner export prod, jalankan script masking (mask email, hash NPWP, ganti nama Faker), restore ke DEV.
|
|
240
|
+
|
|
241
|
+
### 2.3. Multi-Layer Backup Strategy (Defense in Depth)
|
|
242
|
+
|
|
243
|
+
Backup berlapis untuk redundancy + selective restore:
|
|
244
|
+
|
|
245
|
+
| Layer | Tool | Frekuensi | Retention | Selective Restore? | Cost |
|
|
246
|
+
|---|---|---|---|---|---|
|
|
247
|
+
| **L1: Supabase Daily Backup** | Built-in (free tier) | Daily otomatis | 7 hari (free) / 30 hari (Pro) | ❌ Full project only | Gratis |
|
|
248
|
+
| **L2: Manual Snapshot** | Owner via Supabase Dashboard | Pre-migration / risky op | Sampai owner delete | ❌ Full project only | Gratis |
|
|
249
|
+
| **L3: Per-Schema pg_dump** ⭐ | GitHub Action cron 03:00 WIB | Daily otomatis | 30 hari | ✅ **Per-schema selective** | ~$1/bulan storage |
|
|
250
|
+
| **L4: Off-site Backup R2** | Cloudflare R2 cron sync | Daily mirror dari L3 | 90 hari | ✅ Per-schema | ~$0.5/bulan |
|
|
251
|
+
| **L5: BackBlaze B2 Archive** | Monthly cold storage | Monthly | 1 tahun | ✅ Per-schema | ~$0.2/bulan |
|
|
252
|
+
|
|
253
|
+
**Total backup cost**: ~$2/bulan untuk redundancy berlapis. Insurance terbaik.
|
|
254
|
+
|
|
255
|
+
**Konfigurasi**: Lihat `.github/workflows/backup-schemas.yml` (L3) + setup manual untuk L4 + L5.
|
|
256
|
+
|
|
257
|
+
### 2.4. Pilih Multi-Schema Strategy — Sesuai Stage Project
|
|
258
|
+
|
|
259
|
+
**Sebelum lanjut setup**, pilih strategi yang cocok dengan stage project + level trust tim:
|
|
260
|
+
|
|
261
|
+
#### Option A — Shared Schema, Restricted CREATE (Production / Mature Project)
|
|
262
|
+
|
|
263
|
+
**Setup**: Semua staff pakai 1 schema bersama (mis. `pbn` atau `bigseo`). Owner kontrol DDL via Prisma migration di Git.
|
|
264
|
+
|
|
265
|
+
| Aspek | Detail |
|
|
266
|
+
|---|---|
|
|
267
|
+
| Schema | `pbn` (1 schema shared) |
|
|
268
|
+
| Staff permission | SELECT, INSERT, UPDATE, DELETE (CRUD data) — **TIDAK** punya CREATE TABLE |
|
|
269
|
+
| Schema change | Lewat Git PR + Prisma migrate, owner approve |
|
|
270
|
+
| Risk | Low — staff tidak bisa rusak struktur DB accidentally |
|
|
271
|
+
| Bottleneck owner | Code review PR (sama dengan code change biasa) |
|
|
272
|
+
| **Cocok untuk** | Project mature, data sudah live, butuh stabilitas |
|
|
273
|
+
|
|
274
|
+
→ Setup detail di section 2.5 di bawah.
|
|
275
|
+
|
|
276
|
+
#### Option B — Per-Staff Isolated Schema (Development / Early Stage) ⭐ RECOMMENDED untuk 5-30% progress
|
|
277
|
+
|
|
278
|
+
**Setup**: Tiap staff dapat **schema terisolasi sendiri**, full owner di schema-nya (CREATE TABLE bebas, eksperimen bebas).
|
|
279
|
+
|
|
280
|
+
| Aspek | Detail |
|
|
281
|
+
|---|---|
|
|
282
|
+
| Schema | `creative_a` (untuk creative_a), `creative_b`, `creative_c`, dst. |
|
|
283
|
+
| Staff permission | **OWNER schema sendiri** → full CREATE/ALTER/DROP TABLE |
|
|
284
|
+
| Cross-schema access | **BLOCKED** — creative_a tidak bisa lihat data creative_b |
|
|
285
|
+
| Schema change | Staff lakukan langsung di schema-nya. Tidak perlu owner. |
|
|
286
|
+
| Risk | Medium — staff bisa drop table own schema (mitigasi via backup) |
|
|
287
|
+
| Bottleneck owner | **Minimal** — cuma untuk promote ke schema main saat ready |
|
|
288
|
+
| **Cocok untuk** | Eksplorasi domain (mis. A ngerjain namecheap API, B setup security login), early-stage development |
|
|
289
|
+
|
|
290
|
+
→ Setup detail di section 2.6 di bawah.
|
|
291
|
+
|
|
292
|
+
#### Option C — Hybrid (Best of Both)
|
|
293
|
+
|
|
294
|
+
**Setup**: Staff punya sandbox schema sendiri (`creative_a` dst.) + read-only access ke schema shared (`bigseo` prod) + sandbox-only write.
|
|
295
|
+
|
|
296
|
+
→ Setup detail di section 2.7.
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
### 2.5. Setup PostgreSQL Role per-User — Option A (Owner-only step)
|
|
301
|
+
|
|
302
|
+
**Catatan**: Hanya OWNER yang jalankan SQL berikut di Supabase SQL Editor (login sebagai superuser `postgres`). Staff IT cukup terima username + password dari owner — tidak perlu jalankan ini.
|
|
303
|
+
|
|
304
|
+
**Asumsi**: ada schema multi-tenant di Supabase project (`<project-ref>` = ID project Supabase, mis. `vhsridhwjbqypwummrtp`):
|
|
305
|
+
|
|
306
|
+
- `public` — schema umum (semua bisa baca metadata)
|
|
307
|
+
- `pbn` — schema project PBN (untuk tim creative)
|
|
308
|
+
- `internal_data` — schema sensitif (cuma admin)
|
|
309
|
+
- `analytics` — schema laporan (read-only untuk semua)
|
|
310
|
+
|
|
311
|
+
```sql
|
|
312
|
+
-- ============================================
|
|
313
|
+
-- 1. Bikin role per-dev (sesuaikan nama)
|
|
314
|
+
-- Nama role di PostgreSQL = `creative_<dev>` (TANPA project-ref)
|
|
315
|
+
-- Project-ref disisipkan otomatis Supabase pooler saat koneksi.
|
|
316
|
+
-- ============================================
|
|
317
|
+
CREATE ROLE creative_a LOGIN PASSWORD 'GANTI_PASSWORD_KUAT_1';
|
|
318
|
+
CREATE ROLE creative_b LOGIN PASSWORD 'GANTI_PASSWORD_KUAT_2';
|
|
319
|
+
CREATE ROLE creative_c LOGIN PASSWORD 'GANTI_PASSWORD_KUAT_3';
|
|
320
|
+
CREATE ROLE creative_f LOGIN PASSWORD 'GANTI_PASSWORD_KUAT_4';
|
|
321
|
+
CREATE ROLE admin_dev LOGIN PASSWORD 'GANTI_PASSWORD_KUAT_5';
|
|
322
|
+
|
|
323
|
+
-- ============================================
|
|
324
|
+
-- 2. Default: REVOKE semua dari semua schema
|
|
325
|
+
-- (clean slate, baru kasih akses yang perlu)
|
|
326
|
+
-- ============================================
|
|
327
|
+
REVOKE ALL ON SCHEMA public FROM creative_a, creative_b, creative_c, creative_f;
|
|
328
|
+
REVOKE ALL ON SCHEMA pbn FROM creative_a, creative_b, creative_c, creative_f;
|
|
329
|
+
REVOKE ALL ON SCHEMA internal_data FROM creative_a, creative_b, creative_c, creative_f;
|
|
330
|
+
REVOKE ALL ON SCHEMA analytics FROM creative_a, creative_b, creative_c, creative_f;
|
|
331
|
+
|
|
332
|
+
-- ============================================
|
|
333
|
+
-- 3. Beri akses schema pbn ke tim creative
|
|
334
|
+
-- ============================================
|
|
335
|
+
GRANT USAGE ON SCHEMA pbn TO creative_a, creative_b, creative_c, creative_f;
|
|
336
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA pbn
|
|
337
|
+
TO creative_a, creative_b, creative_c, creative_f;
|
|
338
|
+
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA pbn
|
|
339
|
+
TO creative_a, creative_b, creative_c, creative_f;
|
|
340
|
+
|
|
341
|
+
-- Default privilege untuk tabel baru yang dibuat di pbn nanti:
|
|
342
|
+
ALTER DEFAULT PRIVILEGES IN SCHEMA pbn
|
|
343
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES
|
|
344
|
+
TO creative_a, creative_b, creative_c, creative_f;
|
|
345
|
+
|
|
346
|
+
-- ============================================
|
|
347
|
+
-- 4. Beri akses analytics READ-ONLY ke semua dev
|
|
348
|
+
-- ============================================
|
|
349
|
+
GRANT USAGE ON SCHEMA analytics TO creative_a, creative_b, creative_c, creative_f;
|
|
350
|
+
GRANT SELECT ON ALL TABLES IN SCHEMA analytics
|
|
351
|
+
TO creative_a, creative_b, creative_c, creative_f;
|
|
352
|
+
|
|
353
|
+
ALTER DEFAULT PRIVILEGES IN SCHEMA analytics
|
|
354
|
+
GRANT SELECT ON TABLES
|
|
355
|
+
TO creative_a, creative_b, creative_c, creative_f;
|
|
356
|
+
|
|
357
|
+
-- ============================================
|
|
358
|
+
-- 5. PASTIKAN internal_data TIDAK ter-akses
|
|
359
|
+
-- ============================================
|
|
360
|
+
-- (sudah di-REVOKE di step 2, ini cuma verifikasi)
|
|
361
|
+
-- Test: login sebagai creative_a → SELECT * FROM internal_data.users
|
|
362
|
+
-- Harus error "permission denied for schema internal_data"
|
|
363
|
+
|
|
364
|
+
-- ============================================
|
|
365
|
+
-- 6. Cegah privilege escalation
|
|
366
|
+
-- ============================================
|
|
367
|
+
-- Cegah dev bikin tabel sendiri di schema yang bukan miliknya
|
|
368
|
+
REVOKE CREATE ON SCHEMA public FROM creative_a, creative_b, creative_c, creative_f;
|
|
369
|
+
REVOKE CREATE ON SCHEMA pbn FROM creative_a, creative_b, creative_c, creative_f;
|
|
370
|
+
|
|
371
|
+
-- Cegah dev kasih grant ke orang lain (chain privilege)
|
|
372
|
+
-- Default: GRANT tanpa WITH GRANT OPTION (sudah aman), tapi verify:
|
|
373
|
+
-- Jangan PERNAH jalankan: GRANT ... WITH GRANT OPTION
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### 2.6. Setup Per-Staff Isolated Schema — Option B (Untuk Development)
|
|
377
|
+
|
|
378
|
+
**Tujuan**: Tiap staff punya schema isolated, full CREATE permission di schema-nya sendiri. Owner TIDAK jadi bottleneck untuk DDL.
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
## ⚠️ WARNING — JANGAN TULIS PASSWORD DI FILE INI
|
|
383
|
+
|
|
384
|
+
> SQL di bawah adalah **TEMPLATE**. `GANTI_PASSWORD_KUAT_N` = placeholder text, **BUKAN password real**.
|
|
385
|
+
>
|
|
386
|
+
> **OWNER WAJIB**:
|
|
387
|
+
> 1. **JANGAN PERNAH** tulis password real di file `MCP_SETUP.md` (atau file `.md` apapun di repo).
|
|
388
|
+
> 2. Run SQL **langsung di Supabase SQL Editor** (browser), ganti placeholder INLINE di editor — bukan di file repo.
|
|
389
|
+
> 3. **Share password via secure DM** (Signal/Telegram encrypted), bukan email atau channel chat publik.
|
|
390
|
+
> 4. **Hapus pesan DM** setelah staff konfirm sudah simpan ke password manager.
|
|
391
|
+
>
|
|
392
|
+
> **STAFF WAJIB**:
|
|
393
|
+
> 1. Simpan password ke **password manager** (1Password, Bitwarden) — bukan Notepad atau file teks biasa.
|
|
394
|
+
> 2. Paste connection string ke `.env.local` di laptop sendiri — file ini sudah ter-gitignore default.
|
|
395
|
+
> 3. **JANGAN paste password** di chat publik / Slack channel / email / commit message.
|
|
396
|
+
> 4. Kalau owner kirim password via email (BUKAN best practice), pakai password manager untuk auto-fill, jangan copy-paste manual.
|
|
397
|
+
|
|
398
|
+
### Owner Workflow Secure Password Sharing (Step-by-Step)
|
|
399
|
+
|
|
400
|
+
```
|
|
401
|
+
Step 1: Owner generate password kuat di password manager
|
|
402
|
+
(1Password "Generate Password" → 24 char, mixed case + symbol)
|
|
403
|
+
|
|
404
|
+
Step 2: Owner login Supabase Dashboard → SQL Editor (sebagai postgres superuser)
|
|
405
|
+
|
|
406
|
+
Step 3: Paste SQL template di bawah, EDIT INLINE di SQL Editor browser:
|
|
407
|
+
CREATE ROLE creative_a LOGIN PASSWORD 'aB9$xY2!mN7@kQ4...';
|
|
408
|
+
↑ EDIT INI DI BROWSER, BUKAN DI FILE REPO
|
|
409
|
+
|
|
410
|
+
Step 4: Run SQL → role terbentuk
|
|
411
|
+
|
|
412
|
+
Step 5: Owner buat connection string lengkap:
|
|
413
|
+
DATABASE_URL=postgresql://creative_a.<project-ref>:aB9$xY2!mN7@kQ4...@aws-1-ap-southeast-1.pooler.supabase.com:6543/postgres
|
|
414
|
+
|
|
415
|
+
Step 6: Owner DM ke staff via Signal/Telegram (encrypted):
|
|
416
|
+
"Connection string DB kamu (jangan share ke siapapun):
|
|
417
|
+
DATABASE_URL=postgresql://creative_a.<ref>:aB9$xY2!mN7@kQ4...@aws-1-ap-southeast-1.pooler.supabase.com:6543/postgres
|
|
418
|
+
|
|
419
|
+
Simpan di password manager + paste ke .env.local kamu.
|
|
420
|
+
Konfirm kalau sudah."
|
|
421
|
+
|
|
422
|
+
Step 7: Tunggu staff konfirm "OK, sudah simpan"
|
|
423
|
+
|
|
424
|
+
Step 8: Owner HAPUS pesan DM dari Signal/Telegram (paranoia level)
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
### Staff Workflow Secure Password Storage
|
|
428
|
+
|
|
429
|
+
```
|
|
430
|
+
Step 1: Buka password manager (1Password/Bitwarden)
|
|
431
|
+
Step 2: Tambah entry baru:
|
|
432
|
+
- Title: "bigseo-project1 DB - creative_a"
|
|
433
|
+
- Username: creative_a.<ref>
|
|
434
|
+
- Password: aB9$xY2!mN7@kQ4...
|
|
435
|
+
- URL: aws-1-ap-southeast-1.pooler.supabase.com:6543
|
|
436
|
+
- Notes: "Project akses - dev DB. Jangan share."
|
|
437
|
+
Step 3: Konfirm ke owner via DM: "OK sudah simpan ke 1Password"
|
|
438
|
+
Step 4: Buka project di laptop:
|
|
439
|
+
cd /path/to/project
|
|
440
|
+
cp .env.example .env.local
|
|
441
|
+
notepad .env.local (atau VS Code)
|
|
442
|
+
Step 5: Paste connection string DARI password manager (bukan dari DM lagi)
|
|
443
|
+
Step 6: Save .env.local
|
|
444
|
+
Step 7: Verifikasi .gitignore:
|
|
445
|
+
cat .gitignore | grep "env.local"
|
|
446
|
+
Harus ada: ".env.local" atau ".env*"
|
|
447
|
+
Step 8: Test:
|
|
448
|
+
npx prisma generate
|
|
449
|
+
pnpm dev → http://localhost:3000 jalan tanpa error "database unreachable"
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### Pre-commit Hook untuk Mencegah Accidental Password Commit (Optional)
|
|
453
|
+
|
|
454
|
+
Owner setup `.husky/pre-commit` di project untuk block commit kalau detect .env file atau pattern password:
|
|
455
|
+
|
|
456
|
+
```bash
|
|
457
|
+
#!/bin/sh
|
|
458
|
+
# .husky/pre-commit (require husky installed: pnpm add -D husky && pnpm husky init)
|
|
459
|
+
|
|
460
|
+
# Block kalau .env* file ke-stage
|
|
461
|
+
if git diff --cached --name-only | grep -E '^\.env'; then
|
|
462
|
+
echo "🚨 BLOCKED: jangan commit .env files. Pakai .env.example untuk template saja."
|
|
463
|
+
exit 1
|
|
464
|
+
fi
|
|
465
|
+
|
|
466
|
+
# Block kalau ada pattern password hardcoded di file yang ter-stage
|
|
467
|
+
if git diff --cached | grep -E "PASSWORD\s*=\s*['\"][^'\"]{6,}['\"]"; then
|
|
468
|
+
echo "🚨 BLOCKED: terdeteksi hardcoded password di staged files."
|
|
469
|
+
echo "Pakai env var dari .env.local sebagai gantinya."
|
|
470
|
+
exit 1
|
|
471
|
+
fi
|
|
472
|
+
|
|
473
|
+
# Block kalau ada pattern token sensitif (sk-ant-, eyJ, dst.)
|
|
474
|
+
if git diff --cached | grep -E "sk-ant-|eyJ[A-Za-z0-9_-]{20,}|ghp_[A-Za-z0-9]{36}"; then
|
|
475
|
+
echo "🚨 BLOCKED: terdeteksi token sensitif (Anthropic API key / JWT / GitHub PAT)."
|
|
476
|
+
echo "Pindahkan ke .env.local. Lihat SECURITY_INCIDENT_PLAYBOOK.md."
|
|
477
|
+
exit 1
|
|
478
|
+
fi
|
|
479
|
+
|
|
480
|
+
exit 0
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
Setup once per project, semua dev otomatis dapat protection ini saat clone + `pnpm install`.
|
|
484
|
+
|
|
485
|
+
---
|
|
486
|
+
|
|
487
|
+
### SQL Template (RUN DI SUPABASE SQL EDITOR, ISI PASSWORD INLINE DI BROWSER)
|
|
488
|
+
|
|
489
|
+
```sql
|
|
490
|
+
-- ============================================
|
|
491
|
+
-- 1. Bikin role per-staff (sama seperti Option A)
|
|
492
|
+
-- ============================================
|
|
493
|
+
CREATE ROLE creative_a LOGIN PASSWORD 'GANTI_PASSWORD_KUAT_1';
|
|
494
|
+
CREATE ROLE creative_b LOGIN PASSWORD 'GANTI_PASSWORD_KUAT_2';
|
|
495
|
+
CREATE ROLE creative_c LOGIN PASSWORD 'GANTI_PASSWORD_KUAT_3';
|
|
496
|
+
|
|
497
|
+
-- ============================================
|
|
498
|
+
-- 2. Bikin schema per-staff, staff = OWNER schema
|
|
499
|
+
-- ============================================
|
|
500
|
+
CREATE SCHEMA creative_a AUTHORIZATION creative_a;
|
|
501
|
+
CREATE SCHEMA creative_b AUTHORIZATION creative_b;
|
|
502
|
+
CREATE SCHEMA creative_c AUTHORIZATION creative_c;
|
|
503
|
+
|
|
504
|
+
-- AUTHORIZATION = staff jadi OWNER schema → full DDL (CREATE/ALTER/DROP).
|
|
505
|
+
-- Tidak perlu GRANT manual untuk DDL — sudah include di OWNER privilege.
|
|
506
|
+
|
|
507
|
+
-- ============================================
|
|
508
|
+
-- 3. BLOCK cross-schema access (creative_a tidak bisa lihat creative_b)
|
|
509
|
+
-- ============================================
|
|
510
|
+
REVOKE ALL ON SCHEMA creative_b FROM creative_a, creative_c;
|
|
511
|
+
REVOKE ALL ON SCHEMA creative_c FROM creative_a, creative_b;
|
|
512
|
+
REVOKE ALL ON SCHEMA creative_a FROM creative_b, creative_c;
|
|
513
|
+
|
|
514
|
+
-- ============================================
|
|
515
|
+
-- 4. BLOCK staff akses ke schema 'bigseo' atau schema prod lainnya
|
|
516
|
+
-- (kalau ada, sesuaikan nama)
|
|
517
|
+
-- ============================================
|
|
518
|
+
REVOKE ALL ON SCHEMA public FROM creative_a, creative_b, creative_c;
|
|
519
|
+
-- REVOKE ALL ON SCHEMA bigseo FROM creative_a, creative_b, creative_c; -- uncomment kalau ada
|
|
520
|
+
|
|
521
|
+
-- ============================================
|
|
522
|
+
-- 5. Set search_path default ke schema sendiri
|
|
523
|
+
-- Tujuan: tiap staff query `SELECT * FROM users` otomatis cari di schema-nya
|
|
524
|
+
-- ============================================
|
|
525
|
+
ALTER USER creative_a SET search_path TO creative_a;
|
|
526
|
+
ALTER USER creative_b SET search_path TO creative_b;
|
|
527
|
+
ALTER USER creative_c SET search_path TO creative_c;
|
|
528
|
+
|
|
529
|
+
-- ============================================
|
|
530
|
+
-- 6. Test isolation (login sebagai creative_a)
|
|
531
|
+
-- ============================================
|
|
532
|
+
-- SELECT * FROM creative_b.users; -- harus error "permission denied for schema creative_b"
|
|
533
|
+
-- CREATE TABLE creative_a.test_table (id int); -- harus sukses
|
|
534
|
+
-- DROP TABLE creative_a.test_table; -- harus sukses
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
### 2.7. Setup Hybrid Strategy — Option C (Sandbox + Read Prod)
|
|
538
|
+
|
|
539
|
+
Kombinasi Option A + B:
|
|
540
|
+
- Staff punya schema sandbox sendiri (full CREATE)
|
|
541
|
+
- Staff bisa READ schema prod shared (mis. `bigseo`) untuk reference
|
|
542
|
+
- Staff TIDAK bisa WRITE ke schema prod
|
|
543
|
+
|
|
544
|
+
```sql
|
|
545
|
+
-- Step 1-3 sama seperti Option B (per-staff schema dengan OWNER)
|
|
546
|
+
-- (lihat 2.6 di atas)
|
|
547
|
+
|
|
548
|
+
-- Plus: kasih READ access ke schema prod
|
|
549
|
+
GRANT USAGE ON SCHEMA bigseo TO creative_a, creative_b, creative_c;
|
|
550
|
+
GRANT SELECT ON ALL TABLES IN SCHEMA bigseo TO creative_a, creative_b, creative_c;
|
|
551
|
+
|
|
552
|
+
-- Default privilege: tabel baru di bigseo otomatis read-only untuk staff
|
|
553
|
+
ALTER DEFAULT PRIVILEGES IN SCHEMA bigseo
|
|
554
|
+
GRANT SELECT ON TABLES TO creative_a, creative_b, creative_c;
|
|
555
|
+
|
|
556
|
+
-- BLOCK write ke bigseo
|
|
557
|
+
REVOKE INSERT, UPDATE, DELETE, TRUNCATE ON ALL TABLES IN SCHEMA bigseo FROM creative_a, creative_b, creative_c;
|
|
558
|
+
REVOKE CREATE ON SCHEMA bigseo FROM creative_a, creative_b, creative_c;
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
### 2.8. Backup Plan untuk Option B & C (WAJIB)
|
|
562
|
+
|
|
563
|
+
Karena staff punya full CREATE/DROP di schema mereka = risiko accidentally drop table. **3 layer backup**:
|
|
564
|
+
|
|
565
|
+
#### Layer 1: Supabase Daily Backup (Default, Gratis)
|
|
566
|
+
- Aktif by default di Supabase free tier
|
|
567
|
+
- Retention: 7 hari (free tier) / 30 hari (Pro tier)
|
|
568
|
+
- Restore: Dashboard → Database → Backups → Restore
|
|
569
|
+
- ⚠️ **Caveat**: full project restore = wipe semua schema. **Tidak selective per-schema**.
|
|
570
|
+
|
|
571
|
+
#### Layer 2: Manual Snapshot Sebelum Risky Operation
|
|
572
|
+
- Owner trigger via Supabase Dashboard → Database → Backups → "Create Manual Backup"
|
|
573
|
+
- Kapan: sebelum staff lakukan migrasi besar, sebelum approve PR yang touch schema
|
|
574
|
+
- Retention: sampai owner delete manual
|
|
575
|
+
- Cost: gratis
|
|
576
|
+
|
|
577
|
+
#### Layer 3: Per-Schema pg_dump Daily (Otomatis via GitHub Action)
|
|
578
|
+
- Cronjob daily ~03:00 WIB
|
|
579
|
+
- Dump tiap schema staff terpisah: `pg_dump --schema=creative_a --no-owner --clean`
|
|
580
|
+
- Upload ke Supabase Storage / S3 / Google Drive
|
|
581
|
+
- Retention: 30 hari (configurable)
|
|
582
|
+
- Restore selective per-schema: `psql < creative_a-2026-06-15.sql`
|
|
583
|
+
|
|
584
|
+
GitHub Action template: `.github/workflows/backup-schemas.yml` (auto-copy oleh setup script).
|
|
585
|
+
|
|
586
|
+
### 2.9. Restore Flow (Kalau Staff Accidentally Drop Table)
|
|
587
|
+
|
|
588
|
+
```
|
|
589
|
+
Skenario: creative_a tidak sengaja jalankan `DROP TABLE creative_a.namecheap_logs;`
|
|
590
|
+
|
|
591
|
+
1. Staff lapor owner via DM (per SECURITY_INCIDENT_PLAYBOOK)
|
|
592
|
+
2. Owner cek backup terbaru sebelum incident:
|
|
593
|
+
- Layer 3 (pg_dump): biasanya 03:00 WIB tadi malam
|
|
594
|
+
- Layer 2 (manual): kalau ada snapshot belakangan
|
|
595
|
+
3. Owner restore selective:
|
|
596
|
+
- Login Supabase SQL Editor sebagai postgres superuser
|
|
597
|
+
- Run: psql < backup-creative_a-2026-06-15.sql
|
|
598
|
+
- Verify: SELECT count(*) FROM creative_a.namecheap_logs;
|
|
599
|
+
4. Owner DM staff: "Restored, lanjut kerja"
|
|
600
|
+
5. Post-mortem dalam 24 jam (per SECURITY_INCIDENT_PLAYBOOK)
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
⚠️ **JANGAN pakai Layer 1 (full project restore) untuk restore selective per-staff** — bisa wipe progress staff lain. Pakai Layer 3 dengan `pg_dump` per-schema.
|
|
604
|
+
|
|
605
|
+
### 2.10. Promote Sandbox Schema → Production Schema (Saat Fitur Ready)
|
|
606
|
+
|
|
607
|
+
Saat staff selesai develop di sandbox dan fitur ready untuk prod:
|
|
608
|
+
|
|
609
|
+
```
|
|
610
|
+
1. Staff lapor owner: "Fitur namecheap API ready di creative_a, mau promote ke bigseo"
|
|
611
|
+
2. Owner review schema staff (DDL + sample data)
|
|
612
|
+
3. Owner buat Prisma migration di branch:
|
|
613
|
+
- Edit prisma/schema.prisma → tambah model yang sudah di-test di creative_a
|
|
614
|
+
- npx prisma migrate dev --name add_namecheap_models
|
|
615
|
+
4. PR review + AI Reviewer check
|
|
616
|
+
5. Merge → Prisma migrate deploy ke bigseo (prod)
|
|
617
|
+
6. Owner optional: pindahkan data dari creative_a.namecheap_logs ke bigseo.namecheap_logs
|
|
618
|
+
(INSERT INTO bigseo.namecheap_logs SELECT * FROM creative_a.namecheap_logs;)
|
|
619
|
+
7. Staff lanjut develop fitur baru di sandbox-nya
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
---
|
|
623
|
+
|
|
624
|
+
### 2.11. Verifikasi Schema Isolation
|
|
625
|
+
|
|
626
|
+
Login pakai role baru, test akses:
|
|
627
|
+
|
|
628
|
+
```sql
|
|
629
|
+
-- Login sebagai creative_a (pakai psql atau Supabase SQL Editor dengan SET ROLE)
|
|
630
|
+
SET ROLE creative_a;
|
|
631
|
+
|
|
632
|
+
-- Harus BERHASIL:
|
|
633
|
+
SELECT * FROM pbn.projects LIMIT 5;
|
|
634
|
+
SELECT * FROM analytics.daily_stats LIMIT 5;
|
|
635
|
+
|
|
636
|
+
-- Harus GAGAL ("permission denied"):
|
|
637
|
+
SELECT * FROM internal_data.users LIMIT 5;
|
|
638
|
+
CREATE TABLE pbn.test_table (id int);
|
|
639
|
+
CREATE TABLE public.test_table (id int);
|
|
640
|
+
|
|
641
|
+
-- Reset role:
|
|
642
|
+
RESET ROLE;
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
### 2.12. Connection String per-Dev (Format BENAR — Supabase Pooler)
|
|
646
|
+
|
|
647
|
+
**Format Supabase Pooler** (yang dipakai produksi): username harus disuffix dengan project-ref `creative_<dev>.<project-ref>`. Project-ref bisa dilihat di Supabase Dashboard → Project Settings → General → Reference ID (mis. `vhsridhwjbqypwummrtp`).
|
|
648
|
+
|
|
649
|
+
```text
|
|
650
|
+
# App/runtime (port 6543, transaction mode pooler) — DEFAULT untuk MCP + Next.js app
|
|
651
|
+
postgresql://creative_a.vhsridhwjbqypwummrtp:GANTI_PASSWORD_KUAT_1@aws-1-ap-southeast-1.pooler.supabase.com:6543/postgres
|
|
652
|
+
|
|
653
|
+
# DDL/migrasi (port 5432, session mode pooler) — Prisma migrate / psql command
|
|
654
|
+
postgresql://creative_a.vhsridhwjbqypwummrtp:GANTI_PASSWORD_KUAT_1@aws-1-ap-southeast-1.pooler.supabase.com:5432/postgres?sslmode=require
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
**Kapan pakai port mana?**
|
|
658
|
+
|
|
659
|
+
| Port | Mode pooler | Pakai untuk |
|
|
660
|
+
|-------|--------------|----------------------------------------------------------|
|
|
661
|
+
| 6543 | Transaction | App runtime (Next.js Prisma client, MCP server-postgres) — short-lived queries |
|
|
662
|
+
| 5432 | Session | DDL (`CREATE TABLE`, `ALTER`), `prisma migrate deploy`, psql interactive, full session |
|
|
663
|
+
|
|
664
|
+
**Region pooler**: sesuaikan dengan project Supabase. Contoh region:
|
|
665
|
+
- `aws-1-ap-southeast-1.pooler.supabase.com` (Singapore)
|
|
666
|
+
- `aws-0-us-east-1.pooler.supabase.com` (US East)
|
|
667
|
+
|
|
668
|
+
Cek di Supabase Dashboard → Connect (pojok kanan atas) untuk URL persis project kamu.
|
|
669
|
+
|
|
670
|
+
**LARANGAN KERAS** (sesuai update keamanan tim):
|
|
671
|
+
|
|
672
|
+
- ❌ JANGAN pakai username `postgres.<ref>` (= superuser, akses SEMUA tenant) — **DITUTUP** demi keamanan.
|
|
673
|
+
- ❌ JANGAN pakai `service_role_key` Supabase (REST API bypass RLS, full superuser).
|
|
674
|
+
- ❌ JANGAN share connection string di chat / Slack / Discord — kirim via password manager (1Password, Bitwarden) atau encrypted DM auto-delete.
|
|
675
|
+
- ✅ PAKAI `creative_<dev>.<project-ref>` dengan password kamu sendiri.
|
|
676
|
+
- ✅ Kalau password lupa, minta owner generate ulang (owner: `ALTER USER creative_x PASSWORD 'NEW';`).
|
|
677
|
+
|
|
678
|
+
### 2.13. RLS Setup Workflow (WAJIB sebelum data produksi masuk)
|
|
679
|
+
|
|
680
|
+
**Konteks**: setelah dapat akses schema sendiri, staff IT WAJIB setup Row-Level Security (RLS) untuk mencegah pemegang `anon` / `publishable_key` (yang PUBLIK) baca/tulis data atau melewati cek role app.
|
|
681
|
+
|
|
682
|
+
**ATURAN RLS WAJIB DIPATUHI**:
|
|
683
|
+
|
|
684
|
+
1. **JANGAN ENABLE RLS sebelum bikin policy dulu** — RLS tanpa policy = app error semua query gagal.
|
|
685
|
+
2. **Tes lewat APP asli** (login tiap role di UI), BUKAN psql / SQL Editor — koneksi langsung dari owner tabel **bypass RLS** karena PostgreSQL role rule.
|
|
686
|
+
3. **JANGAN pakai `USING(true)` untuk operasi TULIS** — policy tulis HARUS cek role app yang authoritative.
|
|
687
|
+
4. **HANYA sentuh schema kamu sendiri** — jangan touch tabel di schema lain.
|
|
688
|
+
|
|
689
|
+
**Workflow per-tabel** (ulangi tiap tabel di schema kamu):
|
|
690
|
+
|
|
691
|
+
```sql
|
|
692
|
+
-- ========== Langkah 1: Bikin policy DULU (sebelum enable RLS) ==========
|
|
693
|
+
-- Contoh tabel `posts` di schema `pbn`, app punya role `admin_access`.
|
|
694
|
+
|
|
695
|
+
-- Policy READ: cuma admin_access yang boleh baca
|
|
696
|
+
CREATE POLICY posts_read_policy ON pbn.posts
|
|
697
|
+
FOR SELECT
|
|
698
|
+
USING (current_setting('request.jwt.claims', true)::json->>'role' = 'admin_access');
|
|
699
|
+
|
|
700
|
+
-- Policy INSERT: cuma admin_access boleh insert
|
|
701
|
+
CREATE POLICY posts_insert_policy ON pbn.posts
|
|
702
|
+
FOR INSERT
|
|
703
|
+
WITH CHECK (current_setting('request.jwt.claims', true)::json->>'role' = 'admin_access');
|
|
704
|
+
|
|
705
|
+
-- Policy UPDATE: cuma admin_access boleh update
|
|
706
|
+
CREATE POLICY posts_update_policy ON pbn.posts
|
|
707
|
+
FOR UPDATE
|
|
708
|
+
USING (current_setting('request.jwt.claims', true)::json->>'role' = 'admin_access')
|
|
709
|
+
WITH CHECK (current_setting('request.jwt.claims', true)::json->>'role' = 'admin_access');
|
|
710
|
+
|
|
711
|
+
-- Policy DELETE: cuma admin_access boleh delete
|
|
712
|
+
CREATE POLICY posts_delete_policy ON pbn.posts
|
|
713
|
+
FOR DELETE
|
|
714
|
+
USING (current_setting('request.jwt.claims', true)::json->>'role' = 'admin_access');
|
|
715
|
+
|
|
716
|
+
-- ========== Langkah 2: ENABLE RLS setelah policy lengkap ==========
|
|
717
|
+
ALTER TABLE pbn.posts ENABLE ROW LEVEL SECURITY;
|
|
718
|
+
|
|
719
|
+
-- ========== Langkah 3: REVOKE anon (kalau app wajib login) ==========
|
|
720
|
+
REVOKE ALL ON pbn.posts FROM anon;
|
|
721
|
+
-- Catatan: kalau app punya endpoint public (login page, marketing), JANGAN revoke anon — gunakan policy yang lebih spesifik.
|
|
722
|
+
|
|
723
|
+
-- ========== Langkah 4: TEST via APP asli ==========
|
|
724
|
+
-- Login UI sebagai admin → buka halaman yang baca/tulis pbn.posts → harus berhasil.
|
|
725
|
+
-- Logout → coba akses tanpa login → harus error 401/403 (bukan 500).
|
|
726
|
+
-- Login sebagai role lain (kalau ada) → harus block sesuai matrix RBAC.
|
|
727
|
+
|
|
728
|
+
-- ========== Langkah 5: VERIFIKASI anon terblokir ==========
|
|
729
|
+
-- Pakai Supabase Dashboard "API" tab atau curl dengan publishable_key:
|
|
730
|
+
-- curl -H "apikey: <publishable_key>" <project-url>/rest/v1/posts
|
|
731
|
+
-- Harus return 401 / 403 / empty array (depending on policy).
|
|
732
|
+
```
|
|
733
|
+
|
|
734
|
+
**Prompt siap-paste untuk staff IT**: lihat `RLS_SETUP_PROMPT.md` di kit (`./.claude-kit/templates/RLS_SETUP_PROMPT.md`).
|
|
735
|
+
|
|
736
|
+
### 2.14. MCP Config di Claude Code
|
|
737
|
+
|
|
738
|
+
File: `%APPDATA%\Claude\claude_desktop_config.json` (Windows) atau `~/Library/Application Support/Claude/claude_desktop_config.json` (Mac).
|
|
739
|
+
|
|
740
|
+
Untuk Claude Code CLI: `~/.claude/settings.json` atau `.claude/settings.local.json` per-proyek.
|
|
741
|
+
|
|
742
|
+
```json
|
|
743
|
+
{
|
|
744
|
+
"mcpServers": {
|
|
745
|
+
"postgres-pbn": {
|
|
746
|
+
"command": "npx",
|
|
747
|
+
"args": [
|
|
748
|
+
"-y",
|
|
749
|
+
"@modelcontextprotocol/server-postgres",
|
|
750
|
+
"postgresql://creative_a.vhsridhwjbqypwummrtp:GANTI_PASSWORD_KUAT_1@aws-1-ap-southeast-1.pooler.supabase.com:6543/postgres"
|
|
751
|
+
]
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
```
|
|
756
|
+
|
|
757
|
+
**Penting**:
|
|
758
|
+
- Username pakai format `creative_<dev>.<project-ref>` (BUKAN `creative_a` saja).
|
|
759
|
+
- Port `6543` (transaction mode) — cocok untuk MCP read query + sebagian besar Prisma query.
|
|
760
|
+
- Untuk MCP yang butuh DDL (mis. `ALTER TABLE` via AI), pakai port `5432?sslmode=require`.
|
|
761
|
+
- Region pooler match dengan project kamu (cek Dashboard → Connect).
|
|
762
|
+
|
|
763
|
+
Untuk Windows + WSL, atau kalau npx lambat, install global:
|
|
764
|
+
|
|
765
|
+
```bash
|
|
766
|
+
npm install -g @modelcontextprotocol/server-postgres
|
|
767
|
+
```
|
|
768
|
+
|
|
769
|
+
Lalu config:
|
|
770
|
+
|
|
771
|
+
```json
|
|
772
|
+
{
|
|
773
|
+
"mcpServers": {
|
|
774
|
+
"postgres-pbn": {
|
|
775
|
+
"command": "mcp-server-postgres",
|
|
776
|
+
"args": ["postgresql://creative_a.vhsridhwjbqypwummrtp:GANTI_PASSWORD_KUAT_1@aws-1-ap-southeast-1.pooler.supabase.com:6543/postgres"]
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
### 2.15. Audit Trail
|
|
783
|
+
|
|
784
|
+
#### Cek aktif siapa sekarang
|
|
785
|
+
|
|
786
|
+
```sql
|
|
787
|
+
SELECT pid, usename, application_name, client_addr, state, query_start, query
|
|
788
|
+
FROM pg_stat_activity
|
|
789
|
+
WHERE usename LIKE 'creative_%' OR usename = 'admin_dev'
|
|
790
|
+
ORDER BY query_start DESC;
|
|
791
|
+
```
|
|
792
|
+
|
|
793
|
+
#### Aktifkan pgaudit (lanjutan, optional)
|
|
794
|
+
|
|
795
|
+
Extension `pgaudit` log semua DDL + DML per role. Di Supabase:
|
|
796
|
+
|
|
797
|
+
```sql
|
|
798
|
+
-- Cek apakah pgaudit tersedia
|
|
799
|
+
SELECT * FROM pg_available_extensions WHERE name = 'pgaudit';
|
|
800
|
+
|
|
801
|
+
-- Kalau tersedia:
|
|
802
|
+
CREATE EXTENSION IF NOT EXISTS pgaudit;
|
|
803
|
+
|
|
804
|
+
-- Config (perlu superuser):
|
|
805
|
+
ALTER SYSTEM SET pgaudit.log = 'write, ddl';
|
|
806
|
+
ALTER SYSTEM SET pgaudit.log_relation = on;
|
|
807
|
+
SELECT pg_reload_conf();
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
Log muncul di Supabase Dashboard → Logs → Postgres Logs.
|
|
811
|
+
|
|
812
|
+
#### Audit GRANT/REVOKE (manual quarterly)
|
|
813
|
+
|
|
814
|
+
```sql
|
|
815
|
+
-- Lihat semua privilege per role
|
|
816
|
+
SELECT grantee, table_schema, table_name, privilege_type
|
|
817
|
+
FROM information_schema.role_table_grants
|
|
818
|
+
WHERE grantee LIKE 'creative_%'
|
|
819
|
+
ORDER BY grantee, table_schema, table_name;
|
|
820
|
+
|
|
821
|
+
-- Lihat siapa punya akses ke schema sensitif
|
|
822
|
+
SELECT nspname AS schema, r.rolname, has_schema_privilege(r.rolname, n.nspname, 'USAGE') AS has_usage
|
|
823
|
+
FROM pg_namespace n
|
|
824
|
+
CROSS JOIN pg_roles r
|
|
825
|
+
WHERE n.nspname IN ('internal_data', 'pbn', 'analytics')
|
|
826
|
+
AND r.rolname NOT LIKE 'pg_%'
|
|
827
|
+
AND r.rolcanlogin = true
|
|
828
|
+
ORDER BY schema, rolname;
|
|
829
|
+
```
|
|
830
|
+
|
|
831
|
+
---
|
|
832
|
+
|
|
833
|
+
## 3. Supabase MCP (Owner Only — WARNING)
|
|
834
|
+
|
|
835
|
+
> ⚠️ **PERINGATAN KERAS**: Section ini **HANYA untuk OWNER database** (orang yang punya `service_role_key`). Staff IT / developer schema-scoped **TIDAK BOLEH** pakai konfigurasi ini. Pakai PostgreSQL MCP (Section 2) sebagai gantinya.
|
|
836
|
+
|
|
837
|
+
### 3.1. Kenapa Supabase MCP berisiko untuk staff IT?
|
|
838
|
+
|
|
839
|
+
Supabase MCP official pakai `service_role_key` — yang artinya:
|
|
840
|
+
|
|
841
|
+
- **BYPASS RLS** (Row-Level Security) — AI bisa baca/edit data SEMUA user di SEMUA schema.
|
|
842
|
+
- **FULL SUPERUSER** — bisa `DROP TABLE`, `REVOKE` siapapun, ubah policy.
|
|
843
|
+
- **TIDAK ADA isolation per-tenant** — semua orang yang pegang key punya akses identik.
|
|
844
|
+
- **Audit trail buruk** — semua query muncul "atas nama service_role", tidak bisa trace per-dev.
|
|
845
|
+
|
|
846
|
+
Konsekuensi kalau key bocor: data SEMUA tenant kompromi sekaligus.
|
|
847
|
+
|
|
848
|
+
### 3.2. Kapan OWNER boleh pakai Supabase MCP?
|
|
849
|
+
|
|
850
|
+
Hanya untuk kasus spesifik yang PostgreSQL MCP tidak bisa cover:
|
|
851
|
+
|
|
852
|
+
| Use case | Justifikasi |
|
|
853
|
+
|---|---|
|
|
854
|
+
| **Migrasi schema lintas-tenant** | Butuh ALTER SCHEMA di banyak schema sekaligus (mis. tambah audit column ke semua tenant) |
|
|
855
|
+
| **Investigasi keamanan** | Audit "siapa baca apa" lintas tenant saat suspected breach |
|
|
856
|
+
| **Setup awal Supabase** | Bikin role + policy template untuk tenant baru |
|
|
857
|
+
| **Backup / restore** | Snapshot full database (`pg_dump` superuser) |
|
|
858
|
+
|
|
859
|
+
Untuk operasi OWNER sehari-hari (cek data sendiri, debug Prisma), tetap pakai PostgreSQL MCP dengan role `admin_dev` (Section 2.5) — bukan Supabase MCP.
|
|
860
|
+
|
|
861
|
+
### 3.3. Setup Supabase MCP (kalau benar-benar perlu)
|
|
862
|
+
|
|
863
|
+
Install Supabase MCP server:
|
|
864
|
+
|
|
865
|
+
```bash
|
|
866
|
+
npm install -g @supabase/mcp-server-supabase
|
|
867
|
+
```
|
|
868
|
+
|
|
869
|
+
Atau cek paket resmi terbaru di https://github.com/supabase/mcp-server-supabase.
|
|
870
|
+
|
|
871
|
+
Config (`~/.claude/settings.local.json` — **JANGAN** commit):
|
|
872
|
+
|
|
873
|
+
```json
|
|
874
|
+
{
|
|
875
|
+
"mcpServers": {
|
|
876
|
+
"supabase-owner": {
|
|
877
|
+
"command": "npx",
|
|
878
|
+
"args": ["-y", "@supabase/mcp-server-supabase"],
|
|
879
|
+
"env": {
|
|
880
|
+
"SUPABASE_URL": "https://<project-ref>.supabase.co",
|
|
881
|
+
"SUPABASE_SERVICE_ROLE_KEY": "${SUPABASE_SERVICE_ROLE_KEY}"
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
```
|
|
887
|
+
|
|
888
|
+
`SUPABASE_SERVICE_ROLE_KEY` ambil dari OS env var (Windows: System Properties → Environment Variables), JANGAN tulis literal di JSON.
|
|
889
|
+
|
|
890
|
+
### 3.4. Aturan saat pakai Supabase MCP
|
|
891
|
+
|
|
892
|
+
- ⚠️ **JANGAN pernah share `service_role_key`** ke siapapun (termasuk staff IT, ditanya pun jangan).
|
|
893
|
+
- ⚠️ **JANGAN commit `service_role_key`** ke repo / Slack / Discord / email / WhatsApp.
|
|
894
|
+
- ⚠️ **JANGAN aktifkan Supabase MCP saat sesi screen-share** — AI bisa execute query yang ter-display.
|
|
895
|
+
- ✅ **Rotation tiap 3 bulan minimum** — generate key baru di Supabase Dashboard → Settings → API → Reset service_role.
|
|
896
|
+
- ✅ **Disable Supabase MCP saat tidak butuh** — buka `settings.local.json`, comment out section, restart Claude Code.
|
|
897
|
+
- ✅ **Audit setelah pakai** — cek `pg_stat_activity` 24 jam terakhir, lihat query apa yang dijalankan.
|
|
898
|
+
|
|
899
|
+
### 3.5. Kalau staff IT minta Supabase MCP
|
|
900
|
+
|
|
901
|
+
Tolak. Kasih PostgreSQL MCP (Section 2) + schema-scoped role mereka. Kalau mereka argue butuh akses lintas-schema, eskalasi ke owner — **owner yang jalankan query**, BUKAN delegate key.
|
|
902
|
+
|
|
903
|
+
---
|
|
904
|
+
|
|
905
|
+
## 4. GitHub MCP
|
|
906
|
+
|
|
907
|
+
### 4.1. Setup PAT (Personal Access Token)
|
|
908
|
+
|
|
909
|
+
1. Buka https://github.com/settings/tokens → **Generate new token (classic)** atau **fine-grained**.
|
|
910
|
+
2. **Fine-grained** (recommended):
|
|
911
|
+
- Repository access: pilih repo yang relevan (jangan "All repositories").
|
|
912
|
+
- Permissions:
|
|
913
|
+
- **Contents**: Read & write (untuk commit + read code)
|
|
914
|
+
- **Issues**: Read & write
|
|
915
|
+
- **Pull requests**: Read & write
|
|
916
|
+
- **Metadata**: Read (auto)
|
|
917
|
+
- **Workflows**: Read (untuk lihat CI status)
|
|
918
|
+
3. Expiration: **90 hari** (paksa rotation tiap quarter).
|
|
919
|
+
4. Copy token, simpan di password manager. **JANGAN** commit ke repo.
|
|
920
|
+
|
|
921
|
+
### 4.2. MCP Config
|
|
922
|
+
|
|
923
|
+
```json
|
|
924
|
+
{
|
|
925
|
+
"mcpServers": {
|
|
926
|
+
"github": {
|
|
927
|
+
"command": "npx",
|
|
928
|
+
"args": ["-y", "@modelcontextprotocol/server-github"],
|
|
929
|
+
"env": {
|
|
930
|
+
"GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_xxxxxxxxxxxxxxxxxxxx"
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
```
|
|
936
|
+
|
|
937
|
+
### 4.3. Use Case
|
|
938
|
+
|
|
939
|
+
Dengan GitHub MCP aktif, AI bisa:
|
|
940
|
+
|
|
941
|
+
- Buka issue: "Buka issue baru di repo `<your-project>` dengan judul X dan body Y."
|
|
942
|
+
- Review PR: "Lihat PR #42, ringkas perubahan, kasih saran."
|
|
943
|
+
- Cek CI: "Cek status workflow di branch feat/login."
|
|
944
|
+
- Search code: "Cari semua occurrence `service_role_key` di organisasi."
|
|
945
|
+
|
|
946
|
+
---
|
|
947
|
+
|
|
948
|
+
## 5. Filesystem MCP (Default Claude Code)
|
|
949
|
+
|
|
950
|
+
Claude Code sudah bundled filesystem access ke working directory. Tidak perlu config tambahan untuk kasus normal.
|
|
951
|
+
|
|
952
|
+
### Limit Access ke Workspace Folder
|
|
953
|
+
|
|
954
|
+
Kalau pakai Claude Desktop (bukan CLI), perlu config eksplisit:
|
|
955
|
+
|
|
956
|
+
```json
|
|
957
|
+
{
|
|
958
|
+
"mcpServers": {
|
|
959
|
+
"filesystem": {
|
|
960
|
+
"command": "npx",
|
|
961
|
+
"args": [
|
|
962
|
+
"-y",
|
|
963
|
+
"@modelcontextprotocol/server-filesystem",
|
|
964
|
+
"C:\\workspace\\<project-folder>"
|
|
965
|
+
]
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
```
|
|
970
|
+
|
|
971
|
+
**JANGAN** kasih access ke root `C:\` atau `/` — AI bisa baca file sistem / kredensial OS lain.
|
|
972
|
+
|
|
973
|
+
**JANGAN** kasih access ke folder yang berisi `.env`, password manager export, atau key SSH.
|
|
974
|
+
|
|
975
|
+
---
|
|
976
|
+
|
|
977
|
+
## 6. Anti-Pattern (Jangan Lakukan)
|
|
978
|
+
|
|
979
|
+
### 6.1. Pakai `service_role_key` Supabase untuk staff IT
|
|
980
|
+
|
|
981
|
+
```json
|
|
982
|
+
// SALAH — bypass RLS, full superuser, audit trail buruk
|
|
983
|
+
{
|
|
984
|
+
"mcpServers": {
|
|
985
|
+
"supabase": {
|
|
986
|
+
"env": {
|
|
987
|
+
"SUPABASE_SERVICE_ROLE_KEY": "eyJxxx..."
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
```
|
|
993
|
+
|
|
994
|
+
**Kenapa salah**: service_role_key bypass RLS → AI bisa edit data user mana saja → kalau token bocor, semua data kompromi. Plus tidak ada audit per-dev (semua query "atas nama service_role").
|
|
995
|
+
|
|
996
|
+
**Yang benar**: PostgreSQL role per-dev (section 2.5).
|
|
997
|
+
|
|
998
|
+
### 6.2. Share connection string di chat
|
|
999
|
+
|
|
1000
|
+
```text
|
|
1001
|
+
// SALAH — di Slack channel #dev:
|
|
1002
|
+
"Tolong test pake connection ini ya: postgresql://creative_a:Pass123@..."
|
|
1003
|
+
```
|
|
1004
|
+
|
|
1005
|
+
**Kenapa salah**: Slack di-index, history nyimpen. Kalau Slack workspace di-breach atau ada eks-member, kredensial bocor.
|
|
1006
|
+
|
|
1007
|
+
**Yang benar**: kirim via password manager shared vault (1Password, Bitwarden Organizations), atau encrypted DM yang auto-delete.
|
|
1008
|
+
|
|
1009
|
+
### 6.3. Pakai role `postgres` (superuser) sebagai default
|
|
1010
|
+
|
|
1011
|
+
```json
|
|
1012
|
+
// SALAH — superuser, bisa DROP TABLE, REVOKE, dll
|
|
1013
|
+
{
|
|
1014
|
+
"command": "mcp-server-postgres",
|
|
1015
|
+
"args": ["postgresql://postgres:xxxxx@db.xxx.supabase.co:5432/postgres"]
|
|
1016
|
+
}
|
|
1017
|
+
```
|
|
1018
|
+
|
|
1019
|
+
**Kenapa salah**: AI bisa jalankan `DROP TABLE users;` tanpa konfirmasi. Sekali typo command, data hilang.
|
|
1020
|
+
|
|
1021
|
+
**Yang benar**: role per-dev dengan privilege terbatas (no CREATE, no DROP di schema sensitif).
|
|
1022
|
+
|
|
1023
|
+
### 6.4. Commit MCP config ke repo
|
|
1024
|
+
|
|
1025
|
+
```bash
|
|
1026
|
+
# SALAH — config dengan token ter-commit
|
|
1027
|
+
git add .claude/settings.json
|
|
1028
|
+
git commit -m "add mcp config"
|
|
1029
|
+
git push
|
|
1030
|
+
```
|
|
1031
|
+
|
|
1032
|
+
**Kenapa salah**: token di history Git permanent. Walaupun di-revert, history tetap ada (kecuali rewrite force-push).
|
|
1033
|
+
|
|
1034
|
+
**Yang benar**: pakai `.claude/settings.local.json` (sudah di `.gitignore` default Claude Code) untuk config yang ada secret. Atau pakai env var dari OS:
|
|
1035
|
+
|
|
1036
|
+
```json
|
|
1037
|
+
{
|
|
1038
|
+
"mcpServers": {
|
|
1039
|
+
"github": {
|
|
1040
|
+
"command": "npx",
|
|
1041
|
+
"args": ["-y", "@modelcontextprotocol/server-github"],
|
|
1042
|
+
"env": {
|
|
1043
|
+
"GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_PAT}"
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
```
|
|
1049
|
+
|
|
1050
|
+
Lalu set `GITHUB_PAT` di env OS user (Windows: System Properties → Environment Variables).
|
|
1051
|
+
|
|
1052
|
+
---
|
|
1053
|
+
|
|
1054
|
+
## 7. Audit + Rotation
|
|
1055
|
+
|
|
1056
|
+
### 7.1. Jadwal Rotation
|
|
1057
|
+
|
|
1058
|
+
| Kredensial | Frekuensi rotation | Triggered by |
|
|
1059
|
+
|-----------------------------|--------------------|----------------------------------------|
|
|
1060
|
+
| GitHub PAT | Tiap 3 bulan | Calendar reminder + token expiry |
|
|
1061
|
+
| PostgreSQL password role | Tiap 3 bulan | Calendar reminder |
|
|
1062
|
+
| Semua kredensial dev keluar | Immediate | Hari H+0 staff keluar / di-PHK |
|
|
1063
|
+
| Suspected leak | Immediate | Saat dicurigai bocor (push accident) |
|
|
1064
|
+
|
|
1065
|
+
### 7.2. Quarterly Audit Checklist
|
|
1066
|
+
|
|
1067
|
+
Tiap Q1 (Maret), Q2 (Juni), Q3 (September), Q4 (Desember):
|
|
1068
|
+
|
|
1069
|
+
- [ ] Cek `pg_stat_activity` 7 hari terakhir — siapa query schema sensitif?
|
|
1070
|
+
- [ ] Cek `information_schema.role_table_grants` — ada privilege escalation tidak terdeteksi?
|
|
1071
|
+
- [ ] Cek GitHub audit log — ada PAT dengan scope berlebih?
|
|
1072
|
+
- [ ] Rotate semua password role PostgreSQL.
|
|
1073
|
+
- [ ] Generate ulang GitHub PAT yang akan expire.
|
|
1074
|
+
- [ ] Review MCP config tiap dev — apakah masih relevan dengan kerjaan sekarang?
|
|
1075
|
+
- [ ] Hapus role PostgreSQL untuk dev yang sudah keluar (sebelumnya: pastikan tidak ada object yang owned by role tsb).
|
|
1076
|
+
|
|
1077
|
+
### 7.3. Cara Hapus Role Dev yang Keluar
|
|
1078
|
+
|
|
1079
|
+
```sql
|
|
1080
|
+
-- 1. Cek apakah role punya object
|
|
1081
|
+
SELECT n.nspname AS schema, c.relname AS object, c.relkind
|
|
1082
|
+
FROM pg_class c
|
|
1083
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
1084
|
+
JOIN pg_roles r ON r.oid = c.relowner
|
|
1085
|
+
WHERE r.rolname = 'creative_x';
|
|
1086
|
+
|
|
1087
|
+
-- 2. Kalau ada, transfer ownership dulu
|
|
1088
|
+
REASSIGN OWNED BY creative_x TO admin_dev;
|
|
1089
|
+
|
|
1090
|
+
-- 3. Drop semua privilege
|
|
1091
|
+
DROP OWNED BY creative_x;
|
|
1092
|
+
|
|
1093
|
+
-- 4. Drop role
|
|
1094
|
+
DROP ROLE creative_x;
|
|
1095
|
+
```
|
|
1096
|
+
|
|
1097
|
+
### 7.4. Incident Response: Token Bocor
|
|
1098
|
+
|
|
1099
|
+
Kalau PAT GitHub atau password DB ke-commit / ke-post di publik:
|
|
1100
|
+
|
|
1101
|
+
1. **Immediate (5 menit)**:
|
|
1102
|
+
- GitHub: revoke token di https://github.com/settings/tokens (klik token → Delete).
|
|
1103
|
+
- PostgreSQL: `ALTER USER creative_a PASSWORD 'NEW_STRONG_PASSWORD';`
|
|
1104
|
+
2. **Short-term (1 jam)**:
|
|
1105
|
+
- Cek audit log (`pg_stat_activity`, GitHub audit log) — ada akses suspicious 7 hari terakhir?
|
|
1106
|
+
- Cek apakah ada commit / push yang tidak diakui di repo.
|
|
1107
|
+
3. **Cleanup (24 jam)**:
|
|
1108
|
+
- Update MCP config semua dev yang affected.
|
|
1109
|
+
- Notify tim via channel resmi (jangan via channel yang sama dengan leak source).
|
|
1110
|
+
- Post-mortem: kenapa bisa bocor? Update process supaya tidak terulang.
|
|
1111
|
+
|
|
1112
|
+
---
|
|
1113
|
+
|
|
1114
|
+
## 8. Troubleshooting
|
|
1115
|
+
|
|
1116
|
+
### MCP server tidak nyala
|
|
1117
|
+
|
|
1118
|
+
- Cek log Claude Code: `~/.claude/logs/` (Linux/Mac) atau `%APPDATA%\Claude\logs\` (Windows).
|
|
1119
|
+
- Test connection string manual: `psql "postgresql://creative_a:...@host:5432/postgres"`.
|
|
1120
|
+
- Kalau error "FATAL: password authentication failed" → password salah / role belum dibikin.
|
|
1121
|
+
|
|
1122
|
+
### "permission denied for schema X"
|
|
1123
|
+
|
|
1124
|
+
- Role belum di-GRANT USAGE ke schema tsb. Cek section 2.5 step 3.
|
|
1125
|
+
- Atau memang sengaja (mis. `internal_data` untuk creative_a) — switch ke role yang punya akses.
|
|
1126
|
+
|
|
1127
|
+
### MCP terlalu lambat
|
|
1128
|
+
|
|
1129
|
+
- Pakai pooled connection (port 6543) untuk Supabase, bukan direct (5432) — overhead lebih rendah.
|
|
1130
|
+
- Atau install MCP server global (`npm i -g @modelcontextprotocol/server-postgres`) supaya tidak perlu npx download tiap kali.
|
|
1131
|
+
|
|
1132
|
+
---
|
|
1133
|
+
|
|
1134
|
+
## Referensi
|
|
1135
|
+
|
|
1136
|
+
- MCP spec: https://modelcontextprotocol.io
|
|
1137
|
+
- PostgreSQL MCP server: https://github.com/modelcontextprotocol/servers/tree/main/src/postgres
|
|
1138
|
+
- GitHub MCP server: https://github.com/modelcontextprotocol/servers/tree/main/src/github
|
|
1139
|
+
- Filesystem MCP server: https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem
|
|
1140
|
+
- PostgreSQL GRANT/REVOKE docs: https://www.postgresql.org/docs/current/sql-grant.html
|
|
1141
|
+
- pgaudit extension: https://www.pgaudit.org
|
|
1142
|
+
|
|
1143
|
+
---
|
|
1144
|
+
|
|
1145
|
+
> **Update file ini** tiap kali ada MCP server baru yang di-standardize, atau ada perubahan policy security (mis. rotation period berubah). Catat di `CHANGELOG.md` kit + bump versi.
|