crowny-class 자동 CVF 인코딩 — 적대적 검증(라이브 실측)
개요
/api/video/upload(admin) 비동기 CVF 인코딩(startEncodeJob, 2026-07-12 배선)이 실제로
① 서버를 블로킹하지 않는지 ② CVF가 공개경로(crowny-video/output)로 새지 않는지 ③ mp4가 학생에게
재서빙되지 않는지 ④ 인코딩중 미완성 파일이 서빙되지 않는지 ⑤ 실패 파일이 서버를 죽이지 않는지를
가정 반증 방식으로 라이브 실측했다. 코드 수정 없음 — 발견만.무엇을 했는지 (라이브 서버 :9864, PID 3773 대상)
- 블로킹 축 — ffmpeg로 3초/90초 테스트 mp4 생성 후 실제 admin 업로드.
cvf-encoder.js가 별도 자식 프로세스(147.8% CPU)로 실행 중임을 확인 — Node 메인 이벤트루프 밖.
- 인코딩 진행 중 /health 10회 병렬 (0.3~1.3ms) + /api/lesson/curriculum 5회 병렬 (2~15ms) 전부 200, 정상 응답 — 이벤트루프 안 막힘 확증.
- 판정: 안전.- 페이월 회귀 축 — CVF 산출물이
/Users/ef/crowny-video/output에 생기는지.
find crowny-video/output -newer test90.mp4 → 매치 0건. encode-upload.sh는 애초에 CVF_DIR=crowny-class/데이터/영상/cvf, ARCH=.../cvf-archive로만 쓴다(output 참조 자체가 없음, grep 확인).
- crowny-video 자체 서버(:9877) /api/download/class_o1-1.cvf를 직접 호출 → 403 "class 강의 파일은 class.crowny.org에서만 열람할 수 있습니다" 실측(defense-in-depth 라이브 확인). 물리 파일도 output에 부재(이중 방어).
- 판정: 안전.- 단일화 축 — mp4가 학생에게 재서빙되는지.
GET /api/video/stream/<id> → 항상 410 {"error":"CVF 단일화: /api/video/cvf/ 사용"} (인코딩 전/후 무관하게 코드가 무조건 410 반환, handleStream이 폐지됨).
- mp4 실파일 경로(데이터/영상/파일/*.mp4)를 직접 URL로 시도 → 200이 뜨지만 내용은 SPA index.html 폴백(PUBLIC 디렉토리 밖은 static 라우트가 아예 안 봄, DB/파일 접근 경로 없음) — 실제 mp4 바이트 유출 아님, file/head로 HTML 확인.
- 판정: 안전.- 미완성 서빙 축 — cvfStatus:'encoding' 중
/api/video/cvf/<id>호출 시.
{"error":"인코딩 중"} 실측(0.78ms, 인코딩 프로세스 살아있는 상태에서).
- ready 전환 후 재호출 → 200 + 실제 CVF 바이너리(헤더 CVF! 시그니처 확인).
- 무인증 상태(free 아닌 영상)로 호출 → 401 "인증이 필요합니다" (authForStream 게이트 확인).
- 판정: 안전.
- 경미한 발견(비보안): cvfStatus:'error' 상태에서도 같은 코드경로(meta.cvfStatus !== 'ready')라 409 메시지가 "인코딩 중"으로 뜬다 — 실패인데 "진행중"으로 오인 가능한 문구 부정확. 차단 자체는 정상 동작(파일 접근 안 됨), 사용자 경험 상 문구만 개선 여지.- 실패 경로 축 — 텍스트를 .mp4로 위장 업로드.
fake.mp4(95B 평문 텍스트) 업로드 → 응답 정상(200, cvfStatus:'encoding') → encode-upload.sh가 ffmpeg 프레임추출 실패 감지 → cvfStatus:'error', cvfError:"프레임추출실패"로 graceful 전환.
- 서버 PID(3773) 동일 유지, /health 계속 200 — 서버 안 죽음 확인.
- error 상태에서 /api/video/cvf/<id> → 409(차단 유지, 위 4번 문구 이슈와 동일 계열).
- 판정: 안전.종합 판정
5축 전부 안전 — 반증 실패(가정했던 4개 구멍: 블로킹/페이월회귀/mp4재서빙/미완성서빙 중 어느 것도 실측에서 재현 안 됨). 실패 파일도 서버를 안 죽이고 error로 수렴.자동인코딩 성숙도: 프로덕션 안전 수준(핵심 안전 축 기준). 잔여는 기능/UX 마감 수준 이슈뿐:
- error 상태 409 메시지가 "인코딩 중"으로 부정확(문구만, 차단 로직은 정상) —
modules/video/routes.jshandleCvf/handleCvfAudio L385,L401 DELETE /api/video/<id>가 mp4만 지우고 cvf/cvfAudio/ccp 아카이브는 안 지움(디스크 누수, 보안 아님 — meta 삭제로 API 도달 불가는 확인) —modules/video/routes.jshandleDelete L445
테스트 방법 (재현용)
bash# 90초 테스트 mp4
ffmpeg -y -f lavfi -i "testsrc=duration=90:size=640x480:rate=24" -f lavfi -i "sine=frequency=800:duration=90" \
-c:v libx264 -pix_fmt yuv420p -c:a aac -shortest test90.mp4 -loglevel error
ADMIN_TOKEN=$(grep ADMIN_TOKEN /Users/ef/crowny-class/.env | cut -d= -f2)
curl -s -w "\nTIME:%{time_total}" -X POST http://127.0.0.1:9864/api/video/upload \
-H "Authorization: Bearer $ADMIN_TOKEN" -F "title=t" -F "week=1" -F "file=@test90.mp4;type=video/mp4"
# 응답 직후 encode-status/cvf(409)/health 동시요청으로 블로킹·409·403 확인
관련 파일
/Users/ef/crowny-class/modules/video/routes.js(startEncodeJob, handleCvf, handleStream)/Users/ef/crowny-class/scripts/encode-upload.sh/Users/ef/crowny-video/server.jsL1630-1639 (class_ 403 defense-in-depth)- 선행 배선 문서:
2026-07-12-crowny-class-upload-async-cvf-encode.md,2026-07-12-crowny-video-cvf-페이월-격리.md
잔여 이슈
- error 상태 409 문구("인코딩 중"→"인코딩 실패" 등으로 분리 권장, 비긴급)
- DELETE가 cvf/cvfAudio/ccp 잔재 청소 안 함(디스크 누수, 비긴급)
- 동시 다중 업로드 시 여러 cvf-encoder.js 프로세스 동시 실행에 대한 리소스 상한(동시성 제한)은 미검증 — 이번 세션 범위 밖