본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 05. 29. 03:24

Cerberus.js 구축하기: 클라이언트 사이드 AI를 활용한 브라우저 네이티브 온라인 감독 시스템

요약

Cerberus.js는 서버로 비디오나 오디오를 전송하지 않고 브라우저 내에서 모든 처리를 수행하는 클라이언트 사이드 AI 기반 온라인 감독 시스템입니다. MediaPipe와 Web Audio API를 활용하여 개인정보를 보호하면서도 실시간 얼굴 감지 및 소음 분석 기능을 제공합니다.

핵심 포인트

  • 서버 전송 없는 로컬 클라이언트 사이드 AI 처리
  • MediaPipe와 WebGL을 이용한 GPU 가속 얼굴 감지
  • Web Audio API를 활용한 실시간 오디오 분석
  • 점진적 기능 저하(Graceful Degradation) 전략 적용
  • 중앙 스코어링 엔진을 통한 이벤트 관리 및 차단

네이티브 앱도, 서버 사이드 비디오 처리도 없이 브라우저에서 완전히 실행되는 프로덕션급 감독 시스템을 구축한 방법 —

온라인 감독(Online proctoring)은 전통적으로 시스템 수준의 후크(hooks)를 설치하고, 화면 콘텐츠를 캡처하며, 분석을 위해 비디오를 클라우드 서버로 스트리밍하는 무거운 네이티브 데스크톱 애플리케이션을 필요로 했습니다. 이러한 솔루션은 침해적이고, 운영 비용이 많이 들며, 개인정보 보호 문제를 야기합니다.

저는 현대적인 브라우저 API가 클라이언트 사이드 머신러닝 (Client-side machine learning)과 결합될 때, 단 하나의 네이티브 의존성 없이도 대등한 감독 기능을 제공할 수 있음을 증명하기 위해 Cerberus.js를 구축했습니다.

핵심 제약 사항: 모든 처리는 장치에서 로컬로 수행됩니다. 비디오나 오디오는 절대 서버로 전송되지 않습니다.

아키텍처 개요 (Architecture Overview)

Cerberus.js는 React 19 프론트엔드를 갖춘 Next.js 16 애플리케이션입니다. 모니터링 시스템은 중앙 집중식 스코어링 엔진 (Scoring engine)에 이벤트를 전달하는 5개의 독립적인 훅 (Hooks)으로 구성됩니다:

useInfractionMatrix() ── 중앙 스코어링 엔진
  ├── useHardwareModule()  ── 화면 공유, 모니터, 장치
  ├── useFocusLockdown()   ── 전체 화면, 키보드, 가시성
...

각 훅은 특정 공격 벡터 (Attack vector)를 모니터링하며, 의심스러운 동작이 감지되면 addEvent()를 호출합니다. 스코어링 엔진은 이벤트의 중복을 제거하고 (유형별 쿨다운 타이머를 통해), 누적 점수를 계산하며, 중단 임계값(100점)에 도달하면 이벤트를 차단합니다.

MediaPipe를 이용한 얼굴 감지 (Face Detection)

기술적으로 가장 흥미로운 구성 요소는 얼굴 감지 엔진입니다. 저희는 WebGL을 통해 GPU에서 실시간으로 실행되는 경량 얼굴 감지기인 BlazeFace 모델과 @mediapipe/tasks-vision을 사용합니다.

점진적 기능 저하 전략 (Graceful Degradation Strategy)

const modelSources = [FACE_MODEL_LOCAL, FACE_MODEL_URL];
const delegates = ["GPU", "CPU"] as const;

...

이는 다음 순서대로 네 가지 조합을 시도합니다:

  1. GPU 상의 로컬 모델 파일 (public/models/)
  2. CPU 상의 로컬 모델 파일
  3. GPU 상의 CDN 호스팅 모델
  4. CPU 상의 CDN 호스팅 모델

만약 어떤 방식도 성공하지 못하면, vision_init_fail 이벤트를 발생시키고 (10pts) 기능이 저하된 상태로 모니터링을 계속합니다.

Web Audio API를 활용한 오디오 분석

오디오를 녹음하여 서버로 전송하는 대신, Web Audio API의 AnalyserNode를 사용하여 클라이언트에서 완전히 실시간으로 RMS (Root Mean Square, 제곱 평균 제곱근) 값을 계산합니다:

const ctx = new AudioContext();
const src = ctx.createMediaStreamSource(stream);
const ana = ctx.createAnalyser();
...

누적 윈도우 (accumulation window)를 통해 짧은 소음으로 인한 오탐 (false positives)을 방지하는 동시에, 지속적인 오디오 이벤트(예: 누군가 정답을 크게 읽는 경우)를 포착합니다.

스코어링 엔진 (The Scoring Engine)

useInfractionMatrix 훅은 중추 신경계 역할을 합니다. 주요 설계 결정 사항은 다음과 같습니다:

Ref를 통한 쿨다운 중복 제거 (Cooldown Dedup via Refs)

각 이벤트 유형은 독립적인 쿨다운 타이머를 가집니다 (상태(state)가 아닌 ref 맵에 저장됨). 이는 실제로 구별되는 이벤트를 놓치지 않으면서도 이벤트 폭풍 (event storms)을 방지합니다:

const isCoolingDown = (type: InfractionEventType): boolean => {
  const cooldown = EVENT_COOLDOWN_MS[type];
  if (!cooldown) return false;
...

Ref를 통한 정지 게이트 (Suspension Gate via Ref)

점수가 100점에 도달하면, ref가 향후 발생하는 모든 이벤트를 영구적으로 차단합니다. 이는 여러 콜백(RAF 루프, 이벤트 리스너)이 동일한 렌더링 사이클 내에서 실행될 수 있기 때문에 매우 중요합니다. 상태(state)만으로는 경합 조건 (race conditions)을 방지할 수 없습니다:

if (suspendedRef.current) return;

setTotalScore((prev) => {
...

세션 보안 (Session Security)

세션 토큰은 Web Crypto API를 사용하여 생성된 HMAC-SHA256 서명 JWT이며, 외부 의존성이 전혀 없습니다:

const key = await crypto.subtle.importKey(
  "raw", secret, { name: "HMAC", hash: "SHA-256" },
  false, ["sign", "verify"]
...

API 계층에서는 다음을 강제합니다:

  • IP 기반 속도 제한 (IP-based rate limiting): 시간당 세션 생성 10회, 시간당 제출 100회
  • 세션 기반 속도 제한 (Session-based rate limiting): 세션당 분당 제출 30회
  • CSRF 보호 (CSRF protection): 모든 POST 엔드포인트에 대한 Origin/referer 검증
  • 본문 크기 제한 (Body size limits): 최대 페이로드 100KB

제출 신뢰성 (Submission Reliability)

제출 시 네트워크를 사용할 수 없는 경우, 클라이언트는 이벤트 로그를 localStorage에 캐싱하고 지수 백오프 (Exponential backoff) 방식을 사용하여 재시도합니다:

const RETRY_BACKOFF_MS = [2_000, 5_000, 15_000, 30_000, 60_000];

// 페이지 로드 시, 대기 중인 제출 사항을 모두 전송(flush)
...

내가 배운 점 (What I Learned)

  1. React refs vs state: 고빈도 모니터링 루프 (High-frequency monitoring loops)를 위해서는 refs가 필수적입니다. state 업데이트는 리렌더링 (Re-renders)을 트리거하지만, refs는 그렇지 않습니다. 하지만 refs는 값이 변경될 때 알림을 주지 않으므로, 컴포넌트 생명주기 (Component lifecycle) 내에서 이를 고려해야 합니다.

  2. 브라우저 내 MediaPipe: tasks-vision 패키지는 놀라울 정도로 효율적입니다. 얼굴 감지 (Face detection)는 GPU 델리게이트 (GPU delegate)를 사용하는 현대적인 하드웨어에서 30fps 이상으로 작동합니다. WASM 파일의 크기는 크지만 (~총 5MB), 한 번 번들링되면 캐싱됩니다.

  3. 브라우저 API의 한계: 실행 중인 애플리케이션을 열거하거나, 공유 선택기 (Share picker)에서 "전체 화면"을 미리 선택하거나, 모든 형태의 부정행위를 방지할 수는 없습니다. 목표는 부정행위의 비용을 높이는 것이지, 이를 완전히 제거하는 것이 아닙니다.

  4. 우아한 성능 저하 (Graceful degradation)의 필수성: 모델 로드에 실패할 수 있습니다 (네트워크 문제, 호환되지 않는 하드웨어, 브라우저 제한 등). 모든 모니터링 모듈은 평가를 중단시키지 않고 실패를 처리할 수 있어야 합니다.

직접 시도해보기 (Try It Yourself)

git clone https://github.com/harishkotra/cerberus-js.git
cd cerberus-js
cp .env.example .env.local
...

http://localhost:3000을 열고, "Begin Assessment"를 클릭한 뒤 요청된 권한을 허용하세요. 감독 대시보드 (Proctoring dashboard)에 카메라 피드, 오디오 레벨, 하드웨어 상태 및 실시간 이벤트 타임라인이 표시됩니다.

스크린샷 (Screenshots)

cerberus-js-1

cerberus-js-2

전체 소스 코드는 GitHub에서 확인할 수 있습니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0