Files
open-isp/docs/AUDIT_SECURITY_2026.md
T
oxpoe eaf05f2811 OPEN-ISP v0.5 (pickle-lime) — rilis open-source perdana
Aplikasi billing & manajemen ISP (fiber optic) — PWA pelanggan, portal teknisi & kolektor,
admin panel lengkap, multi-vendor OLT, RADIUS, WhatsApp/Telegram, pembayaran QRIS.

Kredit & lisensi (ISC):
- Base kode & repo: alijayanet/billing-rtrw oleh Alijaya Net — terima kasih banyak
  atas base kode dan repo yang bermanfaat.
- Add-on: GoWA, LibreSpeed/OpenSpeedTest, Uptime Kuma, notification-forwarder.
- Dikembangkan menjadi OPEN-ISP oleh oxpoe — https://github.com/oxpoe/open-isp
2026-10-09 05:51:21 +07:00

21 KiB

AUDIT KEAMANAN & KUALITAS APLIKASI BILLING RTRWNET

Status: ✅ AUDIT LENGKAP
Tanggal Audit: 14 Agustus 2026
Auditor: Security & Code Quality Review
Aplikasi Status: 🟠 PRODUCTION WITH CRITICAL ISSUES


📋 RINGKASAN EKSEKUTIF

Aplikasi RTRWNET Billing System adalah sistem yang comprehensive dan feature-rich untuk manajemen ISP. Namun, audit menemukan beberapa kerentanan keamanan KRITIS yang harus segera diperbaiki sebelum production deployment atau jika sudah di production harus segera ditindaklanjuti.

Skor Keamanan: 5.5/10 🔴

  • ✅ Aspek Positif: Struktur kode baik, error handling solid, logging terimplementasi
  • ⚠️ Aspek Kritis: Keamanan password, configuration management, input validation
  • ❌ Aspek Negatif: Plaintext credentials, default passwords, weak hashing

🔐 KEAMANAN KRITIS - SEGERA PERBAIKI

1. PASSWORD DISIMPAN DALAM PLAINTEXT ❌ CRITICAL

Lokasi:

Masalah:

// ❌ TIDAK AMAN - Line 4 di techService.js
const tech = db.prepare('SELECT * FROM technicians WHERE username = ? AND password = ? AND is_active = 1').get(username, password);
// Password dibandingkan langsung tanpa hashing

// ❌ TIDAK AMAN - settings.json
"admin_username": "admin",
"admin_password": "admin123",  // Password plaintext!

Risiko:

  • Jika database bocor, semua password terekspos
  • Jika settings.json terlihat, credentials admin langsung ketahuan
  • Tidak compliance dengan standar keamanan (OWASP, ISO27001)
  • Database breach = total kompromis sistem

Rekomendasi - HARUS DILAKUKAN:

  1. Implementasi bcrypt untuk hashing password:
// Install bcrypt terlebih dahulu
// npm install bcrypt

// Saat register/create user:
const bcrypt = require('bcrypt');
const hashedPassword = await bcrypt.hash(plainPassword, 10);
// Simpan hashedPassword ke database

// Saat authenticate:
const isValid = await bcrypt.compare(plainPassword, hashedPassword);
if (isValid) {
  // Login berhasil
}
  1. Update database schema:
-- Tambahkan kolom password_hash
ALTER TABLE technicians ADD COLUMN password_hash TEXT;
ALTER TABLE administrators ADD COLUMN password_hash TEXT;

-- Update existing passwords dengan hash
-- (Lakukan migration script terpisah)
  1. Jangan simpan password di settings.json:
// ❌ SEBELUM
{
  "admin_password": "admin123"
}

// ✅ SESUDAH - Gunakan database atau environment variable
{
  "admin_user_id": 1  // Reference ke database saja
}

// Atau gunakan .env file:
ADMIN_PASSWORD_HASH="$2b$10$..."

Timeline: IMMEDIATE - Sebelum production


2. DEFAULT CREDENTIALS (admin/admin123) ❌ CRITICAL

Lokasi: settings.json

Masalah:

  • Default username/password yang sangat lemah
  • Siapapun yang tahu dokumentasi aplikasi bisa login
  • OWASP Top 10: A01:2021 - Broken Access Control

Rekomendasi:

# Script untuk force password change saat first-time setup
node scripts/setup-admin-password.js

# Atau implementasikan setup wizard:
- Saat pertama kali aplikasi dijalankan
- Wajib setup admin username & password baru
- Simpan di database dengan hash bcrypt
- Tandai setup selesai di flag di database

Timeline: IMMEDIATE


3. API KEY & CREDENTIALS DALAM PLAINTEXT ❌ CRITICAL

Lokasi: settings.json

"genieacs_password": "admin",
"midtrans_server_key": "",
"xendit_api_key": "",
"tripay_private_key": "",
"digiflazz_api_key": "",
"radius_secret": "secret123"

Masalah:

  • Semua credentials eksternal dalam plaintext di file JSON
  • File bisa terlihat di source code management
  • Risiko exposure jika server dikompromis
  • Tidak praktis untuk berbagai environment (dev/staging/production)

Rekomendasi - HARUS DILAKUKAN:

  1. Gunakan environment variables (.env):
# File: .env (JANGAN commit ke git!)
GENIEACS_URL=http://192.168.8.189:7557
GENIEACS_USERNAME=admin
GENIEACS_PASSWORD=SecurePassword123!
MIDTRANS_SERVER_KEY=Mid-xxxxx-xxx
XENDIT_API_KEY=xnd_development_xxxxx
TRIPAY_API_KEY=xxxxx
DIGIFLAZZ_API_KEY=xxxxx
RADIUS_SECRET=RadiusSecret123!
SESSION_SECRET=RandomSecure123!SecretHere
ADMIN_API_KEY=AdminApiKey123!Secure
  1. Implementasi di code:
// ✅ AMAN
const settings = {
  genieacs_password: process.env.GENIEACS_PASSWORD || getSetting('genieacs_password'),
  midtrans_server_key: process.env.MIDTRANS_SERVER_KEY,
  xendit_api_key: process.env.XENDIT_API_KEY,
  // ... dll
};
  1. Update .gitignore:
# Jangan commit file ini!
.env
.env.local
.env.*.local
settings.json  # Alternatif: commit template saja
  1. Implementasi encryption untuk settings.json (jika diperlukan):
// Gunakan settingsEncryption.js yang sudah ada
const { encryptValue, decryptValue } = require('./config/settingsEncryption');

// Encrypt credentials:
const encrypted = encryptValue(credentialValue);

// Decrypt saat digunakan:
const plainValue = decryptValue(encryptedValue);

Timeline: IMMEDIATE


4. WEAK PASSWORD HASHING ⚠️ HIGH

Lokasi Lainnya:

Masalah:

// ❌ TIDAK AMAN - SHA256 bukan untuk password hashing
const hash = crypto.createHash('sha256').update(input).digest('hex');

// ❌ TIDAK AMAN - MD5 sudah deprecated
const sign = crypto.createHash('md5').update(username + apiKey).digest('hex');
  • SHA256/MD5 = algoritma hashing cepat (bisa di-brute force)
  • Tidak ada salt random
  • Tidak ada rate limiting pada hashing
  • Bisa di-crack dengan GPU dalam hitungan jam

Rekomendasi:

// ✅ AMAN - Gunakan bcrypt atau Argon2
const bcrypt = require('bcrypt');
const hash = await bcrypt.hash(password, 10);  // 10 = cost factor
const isValid = await bcrypt.compare(plainPassword, hash);

// Atau Argon2 (lebih kuat):
const argon2 = require('argon2');
const hash = await argon2.hash(password);
const isValid = await argon2.verify(hash, password);

Timeline: HIGH - Dalam 1 minggu


🔒 KEAMANAN SEDANG - TINGKATKAN

5. SESSION MANAGEMENT ⚠️ MEDIUM

Lokasi: app-customer.js

Status Saat Ini: ✅ Baik

app.use(session({
  secret: getSetting('session_secret', 'default-secret'),
  resave: false,
  saveUninitialized: false,
  cookie: {
    secure: Boolean(cookieSecure),    // ✅ HTTPS only (jika production)
    httpOnly: true,                    // ✅ JavaScript tidak bisa akses
    sameSite: 'lax',                   // ✅ CSRF protection
    maxAge: 24 * 60 * 60 * 1000       // ✅ Timeout 24 jam
  },
  name: 'customer.sid'
}));

Rekomendasi Improvement:

  1. Gunakan session store eksternal:
// Jangan gunakan default memory store di production
const SessionStore = require('better-sqlite3-session-store')(session);
const store = new SessionStore({
  client: db,
  expired: {
    clear: true,
    intervalMs: 15 * 60 * 1000  // Clear expired sessions tiap 15 menit
  }
});

app.use(session({
  store: store,
  // ... config lainnya
}));
  1. Regenerate session ID saat login:
// Saat login berhasil
req.session.regenerate((err) => {
  if (err) return res.status(500).json({ error: 'Session error' });
  req.session.userId = user.id;
  req.session.userRole = user.role;
  // ... simpan data user lainnya
  res.redirect('/dashboard');
});
  1. Validasi session pada setiap request:
app.use((req, res, next) => {
  // Cek timeout session
  const lastActivity = req.session.lastActivity || Date.now();
  const inactivityTimeout = 30 * 60 * 1000; // 30 menit
  
  if (Date.now() - lastActivity > inactivityTimeout) {
    req.session.destroy();
    return res.status(401).json({ error: 'Session expired' });
  }
  
  req.session.lastActivity = Date.now();
  next();
});

Timeline: MEDIUM - 2 minggu


6. CSRF PROTECTION ⚠️ MEDIUM

Status Saat Ini: ⚠️ Terbatas

// Hanya check Referer/Origin, tidak ada CSRF token explicit
if (origin) {
  const originHost = new URL(origin).host;
  if (originHost !== host) {
    return res.status(403).json({ error: 'Forbidden - Invalid Origin' });
  }
}

Masalah:

  • Referer bisa di-spoof dalam beberapa kasus
  • Browser lama atau proxy bisa hilangkan Referer header
  • Tidak ada explicit CSRF token di form

Rekomendasi:

  1. Implementasi CSRF token library:
const csrf = require('csurf');
const csrfProtection = csrf({ cookie: false });  // Gunakan session, bukan cookie

app.post('/admin/action', csrfProtection, (req, res) => {
  // CSRF token sudah tervalidasi otomatis
  // Proses request
});
  1. Di EJS templates:
<form method="POST" action="/admin/billing/generate">
  <input type="hidden" name="_csrf" value="<%= csrfToken %>">
  <button type="submit">Generate Invoice</button>
</form>

Timeline: MEDIUM - 2 minggu


7. INPUT VALIDATION & SANITIZATION ⚠️ MEDIUM

Issue: Beberapa endpoint tidak melakukan validasi input yang ketat sebelum:

  • Memasukkan ke database
  • Menampilkan ke user (XSS risk)
  • Digunakan dalam query

Contoh lokasi yang perlu improvement:

Rekomendasi:

  1. Gunakan input validation library:
npm install joi express-validator
  1. Implementasi validation:
const { body, validationResult } = require('express-validator');

router.post('/admin/customers/add', [
  body('name').trim().notEmpty().isLength({ max: 100 }),
  body('phone').trim().isMobilePhone('id-ID'),
  body('email').isEmail().normalizeEmail(),
  body('address').trim().isLength({ max: 255 }),
  body('package_id').isInt({ gt: 0 }),
], (req, res) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    return res.status(400).json({ errors: errors.array() });
  }
  // Lanjutkan proses
});
  1. Sanitize output di EJS:
<!-- ❌ TIDAK AMAN - Bisa XSS -->
<%= customer.name %>

<!-- ✅ AMAN - Sanitized -->
<%- cleanHtml(customer.name) %>

<!-- atau gunakan escape by default di EJS config -->

Timeline: MEDIUM - 2 minggu


⚠️ KEAMANAN RENDAH - PERHATIAN

8. FILE UPLOAD SECURITY ⚠️ LOW

Status Saat Ini: ⚠️ Cukup

// ✅ Ada validasi MIME type
const allowedTypes = /jpeg|jpg|png|gif|webp/;
const mimetype = allowedTypes.test(file.mimetype);

// ✅ Ada file size limit (5MB)
limits: { fileSize: 5 * 1024 * 1024 }

Rekomendasi Improvement:

  1. Validasi file lebih ketat:
const FileType = require('file-type');

// Validasi magic bytes (bukan hanya extension)
const fileType = await FileType.fromBuffer(file.buffer);
if (!['image/jpeg', 'image/png', 'image/webp'].includes(fileType?.mime)) {
  throw new Error('Invalid file type');
}
  1. Scan file dengan antivirus:
const ClamScan = require('clamscan');
const clamscan = await new ClamScan().init({
  clamdscan: { host: 'localhost', port: 3310 }
});

const { isInfected } = await clamscan.scanFile(filePath);
if (isInfected) {
  fs.unlinkSync(filePath);
  throw new Error('Malicious file detected');
}

Timeline: LOW - Dalam 1 bulan


9. LOGGING & MONITORING ⚠️ LOW

Status Saat Ini: ✅ Baik

  • Winston logger terimplementasi dengan baik
  • Terpisah antara error.log, combined.log, exceptions.log
  • Console output di development

Rekomendasi:

  1. Audit trail untuk sensitive operations:
const auditTrailSvc = require('./services/auditTrailService');

// Log setiap action sensitive
auditTrailSvc.logAction({
  user_id: req.session.adminId,
  action: 'invoice_generated',
  resource_type: 'invoice',
  resource_id: invoiceId,
  changes: { status: 'generated' },
  ip_address: req.ip,
  timestamp: new Date()
});
  1. Centralized log aggregation:
// Contoh: Kirim logs ke ELK Stack atau Datadog
const winston = require('winston');
const WinstonCloudWatch = require('winston-cloudwatch');

logger.add(new WinstonCloudWatch({
  logGroupName: 'billing-rtrwnet',
  logStreamName: 'production',
  awsRegion: 'ap-southeast-1'
}));

Timeline: LOW - Dalam 1 bulan


🎯 KUALITAS KODE

10. ERROR HANDLING ✅ BAIK

Status: ✅ Baik

  • Middleware error handler sudah terimplementasi
  • Custom error classes (ValidationError, UnauthorizedError, etc)
  • Proper HTTP status codes

Minor Improvements:

// Pastikan semua error di-catch dan di-log
router.get('/api/data', async (req, res, next) => {
  try {
    const data = await fetchData();
    res.json(data);
  } catch (err) {
    logger.error(`API Error: ${err.message}`, { 
      stack: err.stack,
      endpoint: req.path,
      method: req.method,
      user_id: req.session?.userId 
    });
    next(err);  // Pass ke error handler middleware
  }
});

11. DATABASE QUERIES ✅ BAIK

Status: ✅ Baik

  • Menggunakan parameterized queries (prepared statements)
  • Tidak ada string concatenation pada SQL queries
  • Foreign key constraints aktif

Contoh aman:

// ✅ AMAN - Parameterized query
const customer = db.prepare('SELECT * FROM customers WHERE id = ?').get(customerId);

// ✅ AMAN - Multiple parameters
const result = db.prepare('SELECT * FROM customers WHERE router_id = ? AND status = ?').all(routerId, 'active');

Minor Issue - Dynamic SQL:

// ⚠️ PERLU REVIEW - Jika search input tidak di-sanitize
const base = `SELECT * FROM customers WHERE ...${userInput}...`;

// ✅ SOLUSI - Gunakan parameterized dengan conditions array
const whereClauses = [];
const params = [];
if (search) {
  whereClauses.push('name LIKE ?');
  params.push(`%${search}%`);
}

12. ASYNC/AWAIT HANDLING ⚠️ MEDIUM

Issue: Beberapa function masih menggunakan callback style yang bisa error-prone.

Contoh yang perlu improvement:

// ❌ CALLBACK STYLE
fs.readFile(path, (err, data) => {
  if (err) handleError(err);
  // process data
});

// ✅ ASYNC/AWAIT STYLE
try {
  const data = await fs.promises.readFile(path);
  // process data
} catch (err) {
  handleError(err);
}

Timeline: LOW - Refactoring gradual


🗄️ DATABASE INTEGRITY

13. Database Schema ✅ SOLID

Status: ✅ Sudah baik

  • Foreign key constraints aktif
  • Proper data types
  • Indexes pada frequently queried fields
  • WAL mode untuk reliability

Rekomendasi:

-- Pastikan ada indexes pada frequently queried columns
CREATE INDEX IF NOT EXISTS idx_customers_router_id ON customers(router_id);
CREATE INDEX IF NOT EXISTS idx_invoices_customer_id ON invoices(customer_id);
CREATE INDEX IF NOT EXISTS idx_invoices_status ON invoices(status);
CREATE INDEX IF NOT EXISTS idx_pppoe_users_username ON pppoe_users(username);

14. Backup & Recovery ⚠️ MEDIUM

Status Saat Ini: ⚠️ Ada backup service tapi belum clear

  • backupService.js sudah ada
  • auto_backup_enabled: false di settings.json (disabled!)

Rekomendasi:

// Enable automatic backup
const settings = {
  "auto_backup_enabled": true,
  "auto_backup_interval": "0 2 * * *",  // Jam 2 pagi setiap hari
  "auto_backup_retention_days": 30,
  "backup_encryption_enabled": true
};

// Implementasi di cronService.js
const backup = require('./services/backupService');
schedule.scheduleJob('0 2 * * *', async () => {
  try {
    const result = await backup.createBackup();
    logger.info(`Backup created: ${result.filename}`);
  } catch (err) {
    logger.error(`Backup failed: ${err.message}`);
  }
});

Timeline: MEDIUM - 2 minggu


📋 ACTION ITEMS & TIMELINE

IMMEDIATE (Hari ini - 48 jam) 🔴

  • Implementasi bcrypt untuk password hashing
  • Hapus default credentials dari settings.json
  • Setup .env file untuk credentials
  • Update .gitignore untuk .env dan settings.json
  • Create setup wizard untuk first-time admin password

HIGH PRIORITY (Minggu 1) 🟠

  • Migrate semua password di database dengan bcrypt
  • Implementasi CSRF token protection
  • Setup session store eksternal (SQLite session)
  • Enable auto backup dalam cron jobs
  • Update API key management

MEDIUM PRIORITY (Minggu 2-3) 🟡

  • Input validation dengan joi/express-validator
  • Enhanced file upload scanning
  • Session regeneration saat login
  • Centralized logging setup
  • Database query audit

LOW PRIORITY (Bulan 1) 🟢

  • Antivirus integration untuk file upload
  • Log aggregation (ELK/Datadog)
  • Async/await refactoring
  • Performance optimization
  • Documentation update

📊 SECURITY CHECKLIST - PRODUCTION DEPLOYMENT

Sebelum go live ke production, pastikan:

  • Authentication

    • Password menggunakan bcrypt (cost ≥ 10)
    • Admin password bukan default
    • Session timeout implemented
    • Session regeneration saat login
  • Authorization

    • Role-based access control diterapkan
    • API endpoints check permission
    • Admin endpoints protected
  • Data Protection

    • Sensitive data encrypted at rest
    • HTTPS enforced (secure cookie flag)
    • CSRF protection aktif
    • XSS protection aktif
  • Configuration

    • Semua credentials di .env
    • .env tidak di-commit
    • settings.json di-review untuk sensitive data
    • API keys di-rotate
  • Logging & Monitoring

    • Audit trail untuk sensitive operations
    • Error logging configured
    • Monitoring alerts setup
  • Backup & Recovery

    • Automated backup enabled
    • Backup encryption enabled
    • Recovery procedure tested
    • Backup storage off-site
  • Network

    • Firewall configured
    • Only needed ports open
    • Rate limiting implemented
    • DDoS protection (jika applicable)
  • Testing

    • Security testing completed
    • Penetration testing done (jika budget ada)
    • Load testing completed
    • Recovery testing done

📞 REKOMENDASI LANJUTAN

1. Security Code Review

Jadwalkan code review berkala (monthly) oleh developer berpengalaman untuk:

  • Review pull requests sebelum merge
  • Check untuk security vulnerabilities
  • Update dependencies

2. Dependency Management

# Regular security audit
npm audit

# Update dependencies (carefully)
npm update

# Check for outdated packages
npm outdated

3. Environment Management

# Development
NODE_ENV=development npm start

# Staging
NODE_ENV=staging npm start

# Production
NODE_ENV=production npm start

4. Rate Limiting

const rateLimit = require('express-rate-limit');

const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,  // 15 menit
  max: 5,                     // 5 attempts
  message: 'Terlalu banyak login attempts, coba lagi nanti'
});

router.post('/login', loginLimiter, authController.login);

5. Security Headers

const helmet = require('helmet');
app.use(helmet());  // Adds various HTTP headers for security

📈 PERFORMANCE OPTIMIZATION

Database Performance

// Add connection pooling
const DatabasePool = require('pg-pool');

// Add query optimization
EXPLAIN ANALYZE SELECT ...;  // Check query plans

Caching Strategy

// Implement Redis caching untuk frequently accessed data
const redis = require('redis');
const client = redis.createClient();

// Cache customer data
const getCachedCustomer = async (id) => {
  const cached = await client.get(`customer:${id}`);
  if (cached) return JSON.parse(cached);
  
  const customer = db.prepare('SELECT * FROM customers WHERE id = ?').get(id);
  await client.setex(`customer:${id}`, 3600, JSON.stringify(customer));
  return customer;
};

API Response Compression

const compression = require('compression');
app.use(compression());  // Compress responses

🎓 DEVELOPER TRAINING

Recommended training topics untuk team:

  1. OWASP Top 10 vulnerabilities
  2. Secure coding practices
  3. Dependency vulnerability scanning
  4. Database security
  5. API security
  6. Authentication & Authorization best practices

📝 KESIMPULAN

Aplikasi RTRWNET Billing System memiliki foundation yang kuat dengan:

  • ✅ Arsitektur yang well-structured
  • ✅ Comprehensive feature set
  • ✅ Good error handling & logging
  • ✅ Proper database design

Namun ada beberapa kerentanan keamanan KRITIS yang harus diperbaiki:

  • 🔴 Password plaintext storage
  • 🔴 Default credentials
  • 🔴 Unencrypted credentials in config

Dengan mengikuti rekomendasi di audit ini, aplikasi dapat mencapai production-ready security level dalam waktu 2-3 minggu.


Status Audit: COMPLETE
Recommendation: DO NOT DEPLOY TO PRODUCTION sampai critical issues di-fix.
Next Review: 30 hari setelah fixes di-implementasi