Skip to content

Security

Encryption model

Semua secret di-encrypt sebelum masuk database:

  • Algorithm: AES-256-GCM (authenticated encryption)
  • Key: 32-byte (256-bit), stored di ENCRYPTION_KEY env var — tidak di database
  • Nonce: 96-bit random, unik per-secret, regenerate tiap update
  • Authentication tag: GCM built-in (16-byte), deteksi tampering

Code reference: internal/crypto/crypto.go (Go stdlib crypto/aes + crypto/cipher).

Password hashing

  • Algorithm: bcrypt
  • Cost: 12 (configurable via BCRYPT_COST)
  • Cost 12 = ~250ms per hash di modern CPU → brute force resistant

Token hashing

Personal Access Token stored sebagai SHA-256 hash — tidak bisa di-decrypt:

Input: cvsm_abc123...
DB: hash(cvsm_abc123...) = "e3b0c442..."

Artinya: kalau DB bocor, attacker tidak punya token plain. Tapi kalau memori server/logs kena dump, ada risiko.

2FA / TOTP

  • Standard: RFC 6238 (TOTP)
  • Library: github.com/pquerna/otp (Go)
  • Period: 30 detik, 6 digit
  • Secret storage: encrypted dengan ENCRYPTION_KEY sama seperti secret
  • Backup codes: planned v0.3

Transport

  • HTTPS wajib di production (CVSM sendiri gak punya cert — gunakan nginx + Let’s Encrypt)
  • HSTS header dikirim default
  • Cookie Secure + HttpOnly + SameSite=Strict

Threat model

Yang kami lindungi

Database breach — secret tetap encrypted, attacker butuh ENCRYPTION_KEY juga
Network eavesdrop — TLS end-to-end
Brute force password — bcrypt + rate limit 10 req/min
Session hijack — JWT short-lived (24 jam) + sessions_invalidated_at
Token leak di log — token hashed, scanning pattern cvsm_ di GitHub
CSRF — SameSite cookie + OAuth state nonce

Yang tidak kami lindungi

Compromised server — kalau root akses ke VPS, ENCRYPTION_KEY + DB sama-sama accessible
Malicious insider — owner/admin bisa read semua secret team-nya (audit log cuma deteksi, bukan prevent)
Supply chain (NPM/PyPI) — dependency kompromise akan bypass SDK auth
Client-side leak — kalau aplikasi kamu log process.env ke external service, bukan tanggung jawab CVSM
End-to-end encryption ke user — value dikirim plaintext ke server, server decrypt dengan key-nya sendiri sebelum return. E2EE true (kunci di client) coming v0.3+

Secret lifecycle

  1. Create: POST /secrets dengan value plaintext → server encrypt → store ciphertext
  2. Read: SDK call GET /secrets/export → server decrypt → return plaintext via TLS
  3. Update: value baru encrypted; versi lama disimpan di secret_versions (juga encrypted)
  4. Delete: soft delete (deleted_at set), data tidak ter-erase — butuh admin DB operation
  5. Export: sama dengan read tapi multi-secret, semua ter-audit log

Disclosure policy

Bug kecil (UI, non-sensitive)

Security vulnerability

  • Jangan buka public issue
  • Email: [email protected] (GPG key planned)
  • Setiap laporan valid akan kami respons serius, investigasi, dan patch secepat mungkin — lihat SLA di bawah

Response timeline (SLA indie)

  • Ack: 48 jam
  • Triage: 7 hari
  • Patch deploy (critical): 14 hari
  • Public disclosure: 90 hari setelah patch (atau segera kalau sudah di-exploit in the wild)

Audit & compliance (planned)

  • GDPR — data di Singapore region, DPA tersedia (v0.2)
  • SOC 2 Type 1 — planned 2026 Q3, via Vanta
  • ISO 27001 — planned 2027
  • OJK compliance — planned via on-premise tier

Transparency report

Published quarterly di calvery.xyz/security:

  • Jumlah government data request (IND + asing)
  • Ringkasan laporan keamanan yang sudah di-patch (anonymous / consent reporter)
  • Incident postmortems (kalau ada)

Responsible disclosure hall of fame

Researchers yang disclose bug valid dapat:

  • Mention di hall of fame (consent dulu)
  • Credit di CHANGELOG & security advisory
  • Komunikasi terbuka selama investigasi + perbaikan

Calvery saat ini belum punya program bounty berbayar. Fokus kami membangun produk dulu — program apresiasi finansial akan hadir setelah layanan stabil dan menghasilkan revenue. Sampai saat itu, kontribusi kamu murni semangat bikin software lebih aman, dan itu yang paling kami hargai.

(List kosong untuk sekarang — jadilah yang pertama!)