Files
webgis/audit_and_deployment_plan.md
T
2026-06-11 17:43:01 +07:00

22 KiB

Audit & Rencana Deployment: SIG UAS WebGIS

Dokumen ini berisi hasil audit teknis, analisis keamanan, dan rencana implementasi deployment untuk tugas akhir Sistem Informasi Geografis (SIG) yang terdiri dari dua project: Poverty Mapping (tugas utama) dan Tugas SPBU (tugas penunjang).


1. Analisis Struktur Project

Kedua project memiliki karakteristik teknologi yang hampir sama tetapi memiliki tingkat kematangan arsitektur yang berbeda.

A. Folder poverty-mapping (Project Utama)

  • Teknologi & Framework: PHP Native untuk backend, HTML/CSS/JS native dengan Leaflet.js dan Leaflet Draw untuk frontend.
  • Dependencies:
    • phpmailer/phpmailer (pengiriman email verifikasi).
    • vlucas/phpdotenv (manajemen environment variables).
  • Struktur Folder:
    poverty-mapping/
    ├── src/
    │   ├── api/          # Rest API (tambah_lokasi, get_lokasi, dll.)
    │   │   ├── admin/    # Manajemen persetujuan manager oleh admin
    │   │   └── auth/     # Register, login, email verification
    │   └── config/       # auth.php, config.php, koneksi.php
    ├── sql/migrations/   # 001_init_schema.sql (skema DB)
    ├── uploads/          # Folder penyimpanan foto lokasi
    ├── index.html        # Frontend SPA utama
    └── composer.json     # Deklarasi dependencies composer
    
  • Konfigurasi Database: Menggunakan file src/config/koneksi.php yang membaca getenv() dari environment system dengan fallback ke kredensial default (localhost, root, ilham, webgis).
  • Konfigurasi Environment: Membaca .env dari root directory project menggunakan parser kustom di src/config/config.php serta autoloading library dotenv.
  • Potensi Masalah: Nama database default adalah webgis yang berpotensi konflik dengan project kedua.

B. Folder tugas-spbu (Tugas Penunjang)

  • Teknologi & Framework: PHP Native untuk backend, HTML/CSS/JS native dengan Leaflet.js, Leaflet Draw, dan Leaflet GeometryUtil.
  • Dependencies: Tidak menggunakan Composer (pure PHP + JavaScript CDN).
  • Struktur Folder:
    tugas-spbu/
    ├── src/
    │   ├── api/          # Rest API (get_lokasi, tambah_jalan, dll.)
    │   └── config/       # auth.php, config.php, koneksi.php
    ├── sql/              # webgis.sql (dump skema dan data awal)
    └── index.html        # Frontend SPA utama
    
  • Konfigurasi Database: KREDENSIAL HARDCODED di src/config/koneksi.php:
    $conn = new mysqli("localhost", "root", "ilham", "webgis2");
    
    Ini adalah masalah kritis karena server tujuan deployment pasti menggunakan host, user, password, atau nama database yang berbeda.
  • Konfigurasi Environment: Hanya memiliki variabel API_KEY di .env.example.
  • Potensi Masalah:
    1. Koneksi database yang di-hardcode.
    2. Frontend (index.html) tidak memiliki antarmuka atau logika untuk memuat/menyimpan API_KEY, sehingga seluruh operasi write (tambah, edit, hapus) di server akan menghasilkan 401 Unauthorized karena header X-API-KEY bernilai undefined.

2. Landing Page Utama

Untuk menyatukan pintu masuk ke kedua project, opsi terbaik adalah menggunakan Reverse Proxy (Nginx) yang melayani Landing Page di root path (/) dan membelokkan traffic subpath ke container masing-masing aplikasi.

Keuntungan Pendekatan Reverse Proxy:

  1. Isolasi Kode: Project poverty-mapping dan tugas-spbu tetap terpisah secara logika dan runtime di container-nya masing-masing.
  2. Kemudahan Jalur URL: Memungkinkan konfigurasi domain tunggal (misal: sig.domain.com) tanpa memerlukan subdomain terpisah untuk setiap aplikasi.
  3. Bebas Konflik Cookie/Session: Cookie PHP (PHPSESSID) dapat dipisah lingkup path-nya dengan aman.
  4. Tanpa Mengubah Path Relatif Code: Karena proxy Nginx melakukan rewrite prefix path, relative requests di browser (seperti src/api/...) tetap berjalan dengan aman.

Rekomendasi Struktur Folder Root (Monorepo):

sig-uas/                  # Workspace Root
├── docker-compose.yml    # Orkestrasi seluruh service (Nginx, App 1, App 2, MySQL)
├── init-db.sql           # Inisialisasi awal database
├── nginx.conf            # Konfigurasi reverse proxy dan landing page
├── landing-page/         # Folder Landing Page utama
│   ├── index.html        # File HTML Landing Page premium
│   └── style.css         # Styling Landing Page
├── poverty-mapping/      # Project 1
│   ├── Dockerfile
│   └── ...
└── tugas-spbu/          # Project 2
    ├── Dockerfile
    └── ...

3. Persiapan Deployment (Readiness Audit)

Berikut adalah daftar temuan kritis yang wajib diperbaiki sebelum melakukan deployment:

Temuan Masalah Lokasi File Tingkat Risiko Dampak & Rekomendasi Perbaikan
Kredensial Gmail Riil poverty-mapping/.env CRITICAL Terungkap SMTP_USER dan SMTP_PASS (App Password Gmail) secara tertulis di workspace. Hapus file .env dari upload repositori Git (masukkan ke .gitignore) dan masukkan password sebagai secrets di Coolify.
Database Kredensial Hardcoded tugas-spbu/src/config/koneksi.php HIGH Koneksi menggunakan parameter string statik. Ubah agar membaca dari environment variables (getenv()) dengan fallback ke konfigurasi lokal.
API Key Hilang di Frontend tugas-spbu/index.html HIGH JavaScript mencoba membaca variabel API_KEY global yang tidak pernah di-declare di script. Seluruh request mutasi akan ditolak API backend. Tambahkan pop-up input API Key di halaman untuk admin atau simpan di localStorage.
Penyimpanan Foto Gagal poverty-mapping/src/api/upload_photo.php MEDIUM Folder uploads/ di server produksi harus dapat ditulisi oleh user web server (www-data). Perlu dipastikan folder di-mount dengan volume Docker yang memiliki hak akses 775.
Expose File Dotfiles (.env, .git) Root / Project folders HIGH Jika server web salah konfigurasi, file .env dapat diunduh publik. Konfigurasikan Nginx/Apache untuk memblokir akses ke berkas tersembunyi (~ /\.(?!well-known)).

4. Analisis Keamanan (Security Audit)

Kami melakukan audit mendalam terhadap API dan frontend dari kedua aplikasi:

A. SQL Injection (Risiko: LOW)

  • Temuan: Baik poverty-mapping maupun tugas-spbu telah menggunakan prepared statement ($conn->prepare dan $stmt->bind_param) untuk semua endpoint yang memproses data masukan pengguna.
  • Rekomendasi: Pertahankan standar ini. Hindari interpolasi string langsung (seperti "$user_input") dalam query SQL.

B. Stored XSS - Cross-Site Scripting (Risiko: HIGH)

  • Temuan: Di tugas-spbu/index.html, data lokasi yang diambil dari database dirender langsung ke dalam popups Leaflet tanpa escaping:
    marker.bindPopup(`<b>${item.nama}</b><br>...<div ...>${alamatEsc}</div>`);
    
    Jika penyerang menyisipkan nama lokasi seperti <img src=x onerror=alert(document.cookie)>, script tersebut akan tereksekusi pada browser setiap pengunjung yang mengklik marker tersebut.
  • Rekomendasi: Buat fungsi pembantu escapeHtml() di tugas-spbu/index.html seperti yang sudah ada di poverty-mapping/index.html:
    function escapeHtml(str) {
        return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
    }
    
    Bungkus seluruh output variabel pengguna (item.nama, item.alamat) dengan fungsi tersebut saat melakukan bindPopup.

C. CSRF - Cross-Site Request Forgery (Risiko: MEDIUM)

  • Temuan: Endpoint API di poverty-mapping dapat membaca autentikasi dari $_SESSION['jwt'] (cookie-based). Karena tidak ada token anti-CSRF pada request mutasi, situs pihak ketiga yang dibuka di tab browser yang sama dapat memaksa browser mengirimkan request mutasi berotentikasi.
  • Rekomendasi: Ubah API untuk hanya menerima otentikasi melalui header HTTP Authorization: Bearer <token> (tidak membaca session cookie untuk API sensitif), karena header Authorization tidak akan ditransmisikan secara otomatis oleh browser pada request lintas situs (cross-site).

D. File Upload Security (Risiko: MEDIUM)

  • Temuan: Di poverty-mapping/src/api/upload_photo.php, tipe file diverifikasi menggunakan MIME detection (finfo_file). Namun, sanitation filename:
    $safeName = preg_replace('/[^a-zA-Z0-9_-]/','_',basename($file['name']));
    
    Akan mengganti karakter titik (.) dengan underscore (_), sehingga ekstensi file akan hilang (misal gambar.jpg menjadi gambar_jpg). Hal ini aman dari eksekusi script PHP liar, tetapi dapat mengganggu pembacaan file gambar oleh browser jika web server tidak mengirimkan header Content-Type yang tepat.
  • Rekomendasi: Pisahkan proses sanitasi nama berkas dengan tetap mempertahankan ekstensi aslinya secara terpisah di bagian akhir nama berkas:
    $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
    if (!in_array($ext, ['jpg', 'jpeg', 'png', 'gif', 'webp'])) { /* reject */ }
    $safeName = preg_replace('/[^a-zA-Z0-9_-]/', '_', pathinfo($file['name'], PATHINFO_FILENAME)) . '.' . $ext;
    

5. Analisis Database

Kedua project menggunakan database dengan nama default webgis pada repositori asal mereka. Namun, keduanya memiliki struktur tabel lokasi yang berbeda:

  • poverty-mapping.lokasi: Menggunakan primary key id INT AUTO_INCREMENT dan menyimpan data KK, status kemiskinan, jumlah anggota keluarga, pendapatan, serta status bantuan sosial.
  • tugas-spbu.lokasi: Menggunakan primary key id VARCHAR(8) (UUID pendek) dan dirancang untuk memetakan koordinat SPBU, Masjid, dan Rumah.

Rekomendasi Solusi:

  1. Pemisahan Database: Wajib dipisahkan menjadi dua database berbeda di server database MySQL yang sama:
    • Database 1: poverty_db (untuk Poverty Mapping)
    • Database 2: spbu_db (untuk Tugas SPBU)
  2. Struktur Environment:
    • Di server produksi, buat satu container MySQL yang menampung kedua database tersebut.
    • Konfigurasikan masing-masing aplikasi melalui environment variable DB_NAME untuk menunjuk ke database yang bersesuaian.

6. Analisis Docker

Sangat direkomendasikan untuk menggunakan Docker demi portabilitas dan kemudahan deployment di Coolify.

Kelebihan Menggunakan Docker:

  • Konsistensi: Menjamin PHP 8.x dan modul mysqli terinstal dengan tepat di server tanpa perlu konfigurasi OS manual.
  • Isolasi Dependensi: Folder uploads dan file konfigurasi terisolasi penuh.
  • Kemudahan Coolify: Coolify secara native mendukung deployment berbasis Docker Compose. Kita hanya perlu mengarahkan Coolify ke repositori Git kita, dan Coolify akan membangun lingkungan lengkap secara otomatis.

Rencana File Docker & Compose:

A. Dockerfile untuk poverty-mapping (poverty-mapping/Dockerfile)

FROM php:8.2-apache

# Install ekstensi mysqli untuk PHP
RUN docker-php-ext-install mysqli && docker-php-ext-enable mysqli

# Aktifkan mod_rewrite Apache untuk menangani routing .htaccess jika ada
RUN a2enmod rewrite

# Salin source code project ke dalam container
COPY . /var/www/html/

# Atur hak akses direktori uploads agar web server bisa menulis file foto
RUN chown -R www-data:www-data /var/www/html && chmod -R 775 /var/www/html/uploads

# Expose port 80
EXPOSE 80

B. Dockerfile untuk tugas-spbu (tugas-spbu/Dockerfile)

FROM php:8.2-apache

# Install ekstensi mysqli
RUN docker-php-ext-install mysqli && docker-php-ext-enable mysqli

# Salin source code
COPY . /var/www/html/

# Atur hak akses
RUN chown -R www-data:www-data /var/www/html

EXPOSE 80

C. Root docker-compose.yml

version: '3.8'

services:
  # 1. Reverse Proxy & Landing Page
  landing-page:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./landing-page:/usr/share/nginx/html:ro
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      - poverty-mapping
      - tugas-spbu
    restart: always

  # 2. Poverty Mapping Application
  poverty-mapping:
    build:
      context: ./poverty-mapping
      dockerfile: Dockerfile
    environment:
      - DB_HOST=db
      - DB_PORT=3306
      - DB_USER=webgis_user
      - DB_PASS=${DB_PASSWORD}
      - DB_NAME=poverty_db
      - API_KEY=${POVERTY_API_KEY}
      - JWT_SECRET=${JWT_SECRET}
      - INTERNAL_AUTH_KEY=${INTERNAL_AUTH_KEY}
      - BASIC_API_USER=webgis-api
      - BASIC_API_PASS=${BASIC_API_PASS}
      - SMTP_HOST=smtp.gmail.com
      - SMTP_PORT=587
      - SMTP_USER=${SMTP_USER}
      - SMTP_PASS=${SMTP_PASS}
      - SMTP_ENCRYPTION=tls
      - MAIL_FROM=${SMTP_USER}
      - MAIL_FROM_NAME=PovertyMapping
    volumes:
      - poverty_uploads:/var/www/html/uploads
    depends_on:
      - db
    restart: always

  # 3. Tugas SPBU Application
  tugas-spbu:
    build:
      context: ./tugas-spbu
      dockerfile: Dockerfile
    environment:
      - DB_HOST=db
      - DB_PORT=3306
      - DB_USER=webgis_user
      - DB_PASS=${DB_PASSWORD}
      - DB_NAME=spbu_db
      - API_KEY=${SPBU_API_KEY}
    depends_on:
      - db
    restart: always

  # 4. Database MySQL
  db:
    image: mysql:8.0
    command: --default-authentication-plugin=mysql_native_password
    environment:
      - MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD}
      - MYSQL_DATABASE=poverty_db
      - MYSQL_USER=webgis_user
      - MYSQL_PASSWORD=${DB_PASSWORD}
    volumes:
      - mysql_data:/var/lib/mysql
      - ./init-db.sql:/docker-entrypoint-initdb.d/init-db.sql
    restart: always

volumes:
  mysql_data:
  poverty_uploads:

D. Nginx Config (nginx.conf)

server {
    listen 80;
    server_name localhost;

    # 1. Landing Page Utama
    location / {
        root /usr/share/nginx/html;
        index index.html;
        try_files $uri $uri/ =404;
    }

    # 2. Route ke Poverty Mapping App
    location /poverty-mapping/ {
        proxy_pass http://poverty-mapping:80/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Scope cookies ke subpath agar tidak tabrakan session PHP-nya
        proxy_cookie_path / /poverty-mapping/;
    }

    # 3. Route ke Tugas SPBU App
    location /tugas-spbu/ {
        proxy_pass http://tugas-spbu:80/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_cookie_path / /tugas-spbu/;
    }

    # Blokir akses ke file .env / .git secara global di level proxy
    location ~ /\. {
        deny all;
        access_log off;
        log_not_found off;
    }
}

7. Identifikasi Role dan Akun Demo

Untuk memudahkan proses penilaian oleh Dosen, berikut adalah daftar peran (role) yang teridentifikasi beserta rancangan akun demonya.

Daftar Peran (Roles):

  1. Admin (Poverty Mapping): Memiliki akses penuh terhadap visualisasi peta kemiskinan, serta persetujuan pembuatan akun manager baru.
  2. Manager (Poverty Mapping): Memiliki akses untuk menambah lokasi keluarga miskin, mengubah data kemiskinan, serta mengunggah foto bukti penyerahan bantuan sosial.
  3. Guest / Publik (Poverty Mapping): Hanya bisa melihat data peta dengan pembatasan/masking pada kolom sensitif (seperti Nama Kepala Keluarga, No Telepon, Catatan internal bantuan).
  4. Admin SPBU (Tugas SPBU): Menggunakan token API_KEY untuk menambah, mengedit, atau menghapus data spasial jalan, tanah, dan lokasi SPBU.

Daftar Akun Demo (Password: password):

Aplikasi Role Email / Akun Password Catatan Kredensial
Poverty Mapping Admin admin@example.com password Akun administrator utama untuk persetujuan akun Manager.
Poverty Mapping Manager manager@example.com password Perwakilan role Manager untuk penambahan/pengeditan data kemiskinan.
Tugas SPBU Admin Token/Key 8f3b7e6a9c4d2f1a6b8c9d0e4f7a1b2c Berupa token API_KEY yang di-input ke panel admin SPBU.

Mekanisme Seed SQL (init-db.sql):

Ketika container MySQL dibangun pertama kali, script ini akan secara otomatis membuat kedua database, mengatur tabel awal, dan menaruh data akun demo dengan hash password yang valid untuk kata sandi "password" (yaitu $2y$10$7YbX4D/QUo1OFJw.ZxyDl.plX03wY/7POuPNx3/ZfyX0vWwaKPNR.):

-- Buat database jika belum ada
CREATE DATABASE IF NOT EXISTS poverty_db;
CREATE DATABASE IF NOT EXISTS spbu_db;

-- Pindah ke database poverty_db untuk inisialisasi tabel users dan admin default
USE poverty_db;

CREATE TABLE IF NOT EXISTS users (
  id INT UNSIGNED NOT NULL AUTO_INCREMENT,
  email VARCHAR(255) NOT NULL,
  password_hash VARCHAR(255) NOT NULL,
  name VARCHAR(255) DEFAULT NULL,
  organization VARCHAR(255) DEFAULT NULL,
  org_address TEXT DEFAULT NULL,
  org_phone VARCHAR(32) DEFAULT NULL,
  org_proof_path VARCHAR(255) DEFAULT NULL,
  role ENUM('manager','admin') NOT NULL DEFAULT 'manager',
  status ENUM('pending','active','rejected','pending_verification') NOT NULL DEFAULT 'pending',
  email_verified TINYINT(1) NOT NULL DEFAULT 0,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  UNIQUE KEY uq_users_email (email)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Insert akun demo Admin & Manager (Password: password)
INSERT INTO users (email, password_hash, name, organization, role, status, email_verified)
VALUES 
('admin@example.com', '$2y$10$7YbX4D/QUo1OFJw.ZxyDl.plX03wY/7POuPNx3/ZfyX0vWwaKPNR.', 'Admin Demo', 'SIG WebGIS', 'admin', 'active', 1),
('manager@example.com', '$2y$10$7YbX4D/QUo1OFJw.ZxyDl.plX03wY/7POuPNx3/ZfyX0vWwaKPNR.', 'Manager Demo', 'Pemberdayaan Masyarakat', 'manager', 'active', 1)
ON DUPLICATE KEY UPDATE password_hash = VALUES(password_hash), status = 'active';

8. Langkah Rencana Deployment Lengkap

Berikut adalah workflow deployment lengkap dari repositori lokal ke server Coolify:

Langkah 1: Persiapan Repositori Git & Gitea

  1. Inisialisasi Git Lokal: Di root folder /media/ilham/data/Code/sig-uas/, jalankan:
    git init
    
  2. Konfigurasi .gitignore: Buat file .gitignore di root folder untuk mencegah data rahasia masuk repositori:
    # Mengabaikan file environment lokal
    **/.env
    **/vendor/
    **/node_modules/
    .idea/
    .vscode/
    
  3. Commit Code:
    git add .
    git commit -m "Initial commit for SIG UAS unified project"
    
  4. Buat Repositori Baru di Gitea:
    • Login ke akun Gitea Anda.
    • Pilih New Repository, beri nama sig-uas.
    • Salin URL remote git (contoh: http://gitea.domain.com/user/sig-uas.git).
  5. Push ke Gitea:
    git remote add origin <URL_GITEA>
    git branch -M main
    git push -u origin main
    

Langkah 2: Setup Database & Environment Variables di Coolify

  1. Login ke dashboard Coolify Anda.
  2. Pilih / buat Project baru untuk SIG UAS.
  3. Sebelum meluncurkan container aplikasi, definisikan variabel environment (Secrets) yang dibutuhkan dalam menu konfigurasi environment di Coolify:
    • DB_ROOT_PASSWORD: Password root MySQL (bebas & aman).
    • DB_PASSWORD: Password user database MySQL.
    • POVERTY_API_KEY: API Key acak untuk Poverty Mapping.
    • SPBU_API_KEY: API Key acak untuk SPBU (8f3b7e6a9c4d2f1a6b8c9d0e4f7a1b2c jika ingin mencocokkan default frontend).
    • JWT_SECRET: Secret key JWT acak.
    • INTERNAL_AUTH_KEY: Key autentikasi internal acak.
    • SMTP_USER: Alamat Gmail pengirim notifikasi.
    • SMTP_PASS: App password akun Gmail pengirim.

Langkah 3: Setup Project di Coolify (Deployment)

  1. Di dalam Project Coolify, klik Add Resource -> Docker Compose.
  2. Masukkan detail Git Gitea Anda (Coolify akan menggunakan SSH key atau Credentials untuk membaca repositori).
  3. Masukkan path ke docker-compose.yml (secara default diisi ./docker-compose.yml).
  4. Coolify secara otomatis membaca file compose tersebut.
  5. Konfigurasikan Domains pada service landing-page di panel Coolify (misal: https://sig.domain.com). Coolify akan secara otomatis membuatkan sertifikat SSL gratis menggunakan Let's Encrypt dan merutekannya ke port 80 milik service landing-page.
  6. Klik Deploy. Coolify akan:
    • Melakukan pull source code dari Gitea.
    • Membangun gambar Docker untuk Poverty Mapping dan Tugas SPBU.
    • Menyalakan database MySQL dan menjalankan inisialisasi SQL dari init-db.sql.
    • Menghidupkan proxy Nginx dan Landing Page.

Langkah 4: Pengujian Pasca Deployment (Testing)

  1. Akses Domain: Buka https://sig.domain.com untuk memastikan landing page premium tampil.
  2. Navigasi Poverty Mapping:
    • Buka https://sig.domain.com/poverty-mapping/.
    • Klik Masuk dan login dengan akun demo admin@example.com (password: password).
    • Uji penambahan pin peta kemiskinan dan unggah foto.
  3. Navigasi SPBU:
    • Buka https://sig.domain.com/tugas-spbu/.
    • Uji kelancaran data spasial jalan, tanah, dan titik lokasi SPBU yang dimuat secara otomatis dari database.

Langkah 5: Skema Backup dan Rollback

  1. Backup Database & Data Volume:
    • Coolify menyediakan fitur backup database MySQL bawaan yang berjalan secara periodik. Pastikan fitur ini diaktifkan dengan tujuan ke S3 storage atau direktori lokal server.
    • Volume poverty_uploads yang menyimpan gambar-gambar bukti foto kemiskinan disimpan di /var/lib/docker/volumes/ pada host. Folder ini harus dimasukkan ke daftar rutin backup server.
  2. Rollback Cepat:
    • Jika terjadi masalah saat rilis pembaruan baru, buka tab Deployments di Coolify untuk service tersebut, lalu klik tombol Redeploy pada versi commit stabil sebelumnya.
    • Coolify akan langsung mematikan container yang bermasalah dan menghidupkan kembali container berbasis image stabil sebelumnya dalam waktu kurang dari 10 detik.