Laporan Remediasi Audit CBT — Batch 4: Kesiapan SEB/Kamera, LMS Sync, Validasi Numerik & Keamanan Ganti Email
Tanggal: 29 September 2026
Status Batch: SELESAI & TERVERIFIKASI (188 Integration Tests Passed)
Baseline Commit: 8abc4dac0bbc610ea8a38ea32550b45299d4c63d
Lingkungan Pengujian: PostgreSQL 16 disposable (scola_assessment_test) via Alembic e2f3a4b5c6d7
1. Ringkasan Temuan & Solusi
B12 — Pemeriksaan Kesiapan Klien (Readiness Check) Bersifat Simulasi Kosmetik & Leak MediaStream (P2)
- Akar Masalah:
ReadinessCheck.vuemensimulasikan seluruh pemeriksaan perangkat dengan timer buatan dan menghasilkan kode dukungan acak di sisi peramban (SC-xxxxxx), tanpa memanggil endpoint backend yang sudah tersedia (POST /api/v1/secure/readiness-check).- Kamera peserta tidak diuji keasliannya dan stream media di
ExamRunner.vuetidak selalu dihentikan (track.stop()) saat komponen di-unmount, meninggalkan indikator kamera aktif di OS. - Nilai minimum SEB di
seb_service.pyadalah"3.4.0", tidak konsisten denganAGENTS.mddanSEBConfiguration("3.7.0"). - Perbaikan:
- Di
seb_service.py: MemperbaruiMINIMUM_SEB_VERSION = "3.7.0". - Di
ReadinessCheck.vue:- Menguji kamera nyata via
navigator.mediaDevices.getUserMedia({ video: true })dan langsung mematikan tracks setelah status terverifikasi. - Mendeteksi jenis OS klien dan memeriksa
screenCount. - Memanggil
POST /api/v1/secure/readiness-checkuntuk menyimpan telemetri diagnostik ke basis data dan menampilkansupport_coderesmi dari server (SUP-YYYYMMDD-XXXXXX).
- Menguji kamera nyata via
- Di
ExamRunner.vue: Menyimpan referensiactiveCameraStreamdan memastikan seluruh track ditutup (track.stop()) pada hookonUnmounted.
B13 — Sinkronisasi Nilai LMS Membaca Properti Kosong & Menghitung Persentase Statis (P2)
- Akar Masalah:
lms_integration_service.py:sync_session_grades_to_lmsmembacaatt.participant.participant_number(padahal relasi berada diatt.enrollment.participant),att.score(kolom ini tidak ada di tabelattempts), danatt.max_score(tidak ada diattempts), menghasilkan nilai 0.0 atauAttributeError.- Upaya ujian berstatus
in_progressikut disinkronkan ke LMS sebagai peserta selesai. - Perbaikan:
- Hanya memfilter sesi ujian yang telah selesai:
Attempt.status.in_(["submitted", "graded", "finalized"]). - Membaca identitas peserta secara aman:
att.enrollment.participant. - Menghitung nilai peserta secara riil dengan menjumlahkan revisi nilai terbaru (
GradeRevision.score) per butir soal, konsisten denganranking_service.py. - Mengambil batas nilai maksimal riil dari
ExamForm.max_score(dengan fallback 100.0). - Menghitung persentase akurat:
percentage = round((final_score / max_score) * 100, 2).
B14 — Penulis Soal & Impor CSV Dapat Memasukkan Nilai Numerik Non-Finite (P2)
- Akar Masalah:
- Impor CSV (
import_questions) dan validasi form (validate_exam_form_pre_admission) menerima string"nan","inf","-inf"atau toleransi negatif karena Pythonfloat("nan")tidak memicu exception danfloat("nan") < 0bernilaiFalse. - Hal ini menyebabkan evaluasi jawaban otomatis peserta (
abs(student_ans - target) <= tol) menghasilkanFalsepermanen atau kalkulasi nilai tidak stabil. - Perbaikan:
- Di
question_service.py: Menambahkan validasimath.isfinite(num_target)danmath.isfinite(num_tol) and num_tol >= 0saat memproses baris impor CSV tipe numerik. Baris yang melanggar ditolak denganQuestionRowErroryang deskriptif. - Di
form_service.py:validate_exam_form_pre_admission: Memvalidasi bahwa setiap butir soal bertipe numerik memilikinumeric_targetyang finite dannumeric_tolerancefinite non-negatif.
B15 — Permintaan Perubahan Email Menulis OTP ke Log/Response Tanpa Outbox & TTL Bypass (P2)
- Akar Masalah:
auth_service.py:request_email_changemengembalikandev_otpsecara terbuka di response JSON bahkan di lingkungan staging/production, dan tidak menyalurkan pengiriman email via transactional outbox.- Fallback memori pada
confirm_email_change(_in_memory_email_otp) tidak memeriksaexpires_at, sehingga OTP kadaluwarsa dapat diterima jika Redis sedang offline. - Tidak ada rate limiting pada percobaan verifikasi OTP ganti email.
- Perbaikan:
- Di
auth_service.py:request_email_change:- Menyisipkan pesan outbox
auth.email_change_requestedviaenqueue_outbox_eventuntuk audit dan pengiriman asinkron. - Menyembunyikan
dev_otpdi lingkungan non-development (dev_otp = Nonepadastagingdanproduction).
- Menyisipkan pesan outbox
- Di
auth_service.py:confirm_email_change:- Memasang progressive rate limiting (
check_rate_limit(f"email_change:{user_id}")danrecord_failure). - Memeriksa batas kedaluwarsa pada fallback memori:
mem.get("expires_at", 0) > time.time(). - Menyisipkan pesan outbox
auth.email_changedsaat perubahan email berhasil dikonfirmasi.
- Memasang progressive rate limiting (
2. File yang Diubah & Dibuat
| File | Status | Keterangan Perubahan |
|---|---|---|
backend/app/services/seb_service.py |
Dimodifikasi | Menaikkan MINIMUM_SEB_VERSION ke "3.7.0". |
backend/app/services/lms_integration_service.py |
Dimodifikasi | Perhitungan nilai riil via GradeRevision, pembacaan ExamForm.max_score, dan filter status attempt selesai. |
backend/app/services/question_service.py |
Dimodifikasi | Validasi math.isfinite pada target dan toleransi numerik saat impor CSV. |
backend/app/services/form_service.py |
Dimodifikasi | Validasi math.isfinite pra-admisi paket ujian untuk butir numerik. |
backend/app/services/auth_service.py |
Dimodifikasi | Transactional outbox event email change, penyembunyian OTP staging/prod, rate limiting 5 kali gagal, dan kepatuhan TTL fallback memori. |
frontend/src/views/runner/ReadinessCheck.vue |
Dimodifikasi | Probe kamera nyata dengan penutupan track seketika, integrasi API POST /api/v1/secure/readiness-check, dan pengikatan server support code. |
frontend/src/views/runner/ExamRunner.vue |
Dimodifikasi | Penutupan track kamera aktif (track.stop()) saat unmount. |
tests/integration/test_batch4_regression.py |
Baru | 8 pengujian regresi komprehensif untuk B12, B13, B14, dan B15 (100% lulus). |
3. Bukti Verifikasi Pengujian
A. Test Suite Regresi Batch 4 (tests/integration/test_batch4_regression.py)
==================== test session starts =====================
collected 8 items
tests/integration/test_batch4_regression.py::test_b12_readiness_check_persists_to_backend PASSED [ 12%]
tests/integration/test_batch4_regression.py::test_b12_minimum_seb_version_enforced PASSED [ 25%]
tests/integration/test_batch4_regression.py::test_b13_lms_grade_sync_uses_real_grade_revisions_and_form_max_score PASSED [ 37%]
tests/integration/test_batch4_regression.py::test_b14_csv_import_rejects_nan_and_infinity_numeric PASSED [ 50%]
tests/integration/test_batch4_regression.py::test_b14_form_validation_rejects_nonfinite_numeric PASSED [ 62%]
tests/integration/test_batch4_regression.py::test_b15_email_change_queues_outbox_message_and_hides_otp_in_staging PASSED [ 75%]
tests/integration/test_batch4_regression.py::test_b15_email_change_memory_fallback_respects_expiry PASSED [ 87%]
tests/integration/test_batch4_regression.py::test_b15_email_change_rate_limiting PASSED [100%]
=============== 8 passed, 5 warnings in 3.20s ================
B. Frontend Typecheck & Build
npm run build (vue-tsc && vite build)
✓ 389 modules transformed.
✓ built in 1.17s with 0 errors
C. Full Backend Integration Test Suite
tests/integration/ -q --tb=short
.................................................... [ 27%]
.................................................... [ 55%]
.................................................... [ 82%]
................................ [100%]
188 passed, 6 warnings in 90.07s (0:01:30)