20 Commits

Author SHA1 Message Date
0a7f91ba8c deploy: 멘토 AI 초안용 ollama-proxy 추가 + backend LLM 설정
Some checks failed
CI / backend-test (push) Failing after 2m22s
CI / frontend-build (push) Successful in 1m5s
CI / backend-dep-scan (push) Successful in 29s
edu(EC2)가 워크스테이션 tailnet의 Ollama를 쓰도록 프록시를 둔다.
Ollama는 DNS-rebinding 방어로 Host를 검증해 tailnet 이름 요청을 403으로
막는데, 이 프록시가 Host를 localhost로 재작성해 통과시킨다.
backend는 JAVA_TOOL_OPTIONS(-Dllm.base-url)로 프록시를 가리킨다.
2026-07-23 12:23:50 +09:00
1b559f5da2 fix: 전면 결함 스윕 — 4관점 감사 확정 결함 일괄 수정
All checks were successful
CI / backend-test (push) Successful in 1m10s
CI / frontend-build (push) Successful in 40s
CI / backend-dep-scan (push) Successful in 28s
4개 전문 감사관(프론트·백엔드·일관성·UX) 병렬 검증 결과 반영.

버그/깨짐(HIGH·MED):
- HtmlCssPage: 존재하지 않는 /learn/computer → /learn/hardware(깨진 링크, 대시보드로 튕김)
- MentorAnalyticsService: 체크리스트 활동(Progress.checkedAt)이 '마지막 활동'에 누락돼
  체크리스트만 한 학생이 '관심 필요'로 오분류되던 버그 수정(+회귀 테스트)
- 퀴즈 핫스팟 평균 정수절삭 → Math.round로 통일

접근성/UX(MED):
- CodeEditor: 코드 입력창에 aria-label 추가(WCAG 4.1.2, 스크린리더 이름 부여)
- 멘토 제출물 표: tr role=button → 셀 안 button으로(표 의미구조 보존 + 키보드)
- 대시보드 체크박스 토글 실패 시 무피드백 → try/catch + 에러 안내

콘텐츠/문서 드리프트:
- 초급 미니프로젝트 JS 예제 var→let(let/const 가르치며 var 쓰던 자기모순)
- README 강좌 수(71→83+48), BACKUP.md 복원검증 카운트(퀴즈655·문서19·코딩25)
- 브랜드/코스수 주석 정리(수습 플랫폼→학습 플랫폼, 72개→비수치화), 퀴즈 결과 '점'→'%'

테스트 백엔드 51→52, 프론트 30 유지. 운영 배포·스모크 11/11.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 14:01:05 +09:00
1661591d8b test(e2e): Playwright E2E 도입 — 로그인→강좌→퀴즈 + 멘토/감사로그
Some checks failed
CI / backend-test (push) Successful in 1m3s
CI / frontend-build (push) Failing after 27s
CI / backend-dep-scan (push) Successful in 6s
실제 브라우저로 '다 조립된 앱'(프론트+백엔드+DB)을 태우는 통합 테스트 6개:
- 인증 가드(비로그인→로그인), 로그인 실패 안내, 학생 로그인→대시보드
- 학습 센터→강좌 열기, 퀴즈 풀기→제출→서버 채점 결과
- 멘토 로그인→멘토 화면+개인정보 접근 기록(SEC-02 감사로그까지 검증)

운영 무오염 설계:
- deploy/docker-compose.e2e.yml — 별도 프로젝트(mirime2e)·네트워크·휘발성 DB(tmpfs)
- deploy/Caddyfile.e2e — 평문 HTTP로 SPA+/api 프록시(운영과 동일 구조)
- deploy/e2e.sh — 스택 기동→테스트 학생 API 생성→퀴즈 slug 자동조회→
  Playwright(브라우저 내장 이미지) 실행→down -v(정리)
- 백엔드 힙 256MB 제한(작은 인스턴스에서 운영과 공존)

EC2에서 6/6 통과 확인. 러너가 운영과 같은 인스턴스라 push마다가 아닌
온디맨드(릴리스 전)로 운영 — 전용 러너 확보 시 CI 게이트로 승격.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 08:27:42 +09:00
0e3ab14b14 fix(security): Spring Boot 3.4.2 → 3.5.16 (OSV 발견 CVE 전부 해결)
All checks were successful
CI / backend-test (push) Successful in 1m2s
CI / frontend-build (push) Successful in 23s
CI / backend-dep-scan (push) Successful in 27s
OSV-Scanner가 찾은 누적 CVE를 관리 의존성 버전 상향으로 일괄 해결:
- tomcat-embed-core 10.1.34 → 10.1.55 (CVSS 9.8 포함 다수)
- spring-security-* 6.4.2 → 6.5.11 (CVSS 9.1 포함)
- spring-web/webmvc/core/context 6.2.2 → 6.2.19
- assertj-core 3.26.3 → 3.27.7
취약 의존성 전부 OSV 수정버전 이상으로 확인(dependency:tree 대조).

현재 GA 라인(3.5.x) 최신 패치라 지원 수명도 길다. 표준 스타터만 써서
마이너 상향에도 회귀 없음: 전체 47개 테스트 통과.
운영 검증: 세션 로그인 200·멘토 권한 200·감사쓰기(실 IP)·비로그인 401·스모크 11/11.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 07:27:35 +09:00
a5deecf519 docs: SEC-02·03·04 구현 완료 표기 + CI 의존성 스캔 문서화
All checks were successful
CI / backend-test (push) Successful in 1m16s
CI / frontend-build (push) Successful in 23s
CI / backend-dep-scan (push) Successful in 5s
- 티켓 풀: 감사로그(SEC-02)·레이트리밋(SEC-03)·접근로그(SEC-04) '구현됨' 표기
- CI.md: npm audit·OSV-Scanner(비차단) 구성과 Spring Boot 3.4.2 누적 CVE 발견 기록

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 19:01:46 +09:00
daf3e9ac09 docs(CI): 품질 게이트·브랜치 보호 PR 워크플로 문서화
All checks were successful
CI / backend-test (push) Successful in 1m28s
CI / frontend-build (push) Successful in 21s
Spotless/JaCoCo/ESLint/커버리지 게이트와 로컬 교정법, main 보호 규칙과
학생 PR 실습 흐름을 CI.md에 정리.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 18:06:03 +09:00
c723cba92c test: 배포 스모크 테스트 (deploy/smoke-test.sh) — 인프라·설정 회귀 감지
All checks were successful
CI / backend-test (push) Successful in 51s
CI / frontend-build (push) Successful in 17s
CI가 못 잡는 '설정 회귀'(예: 보안헤더로 문서 iframe 깨짐)를 배포 후 잡는 그물.
살아있는 사이트를 실제 사용자처럼 두드려 11개 확인:
- 홈 200, HTTP→HTTPS 리다이렉트, 보안헤더 3종
- 학습 문서 200 + X-Frame-Options=SAMEORIGIN + no-cache + 한국어 본문
  (그 회귀를 정확히 재현·감지)
- 백엔드 API 401(Caddy→백엔드→DB 경로), SPA 라우트 폴백
하나라도 실패 시 exit 1. CI.md에 사용법·권장 배포 흐름 문서화.

운영 검증: 11/11 통과, exit 0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 17:39:27 +09:00
92e65760bc ci: 러너를 compose로 관리 + CI.md 문서 + OP-02 티켓 '구현됨'으로 갱신
All checks were successful
CI / backend-test (push) Successful in 2m6s
CI / frontend-build (push) Successful in 1m4s
- act_runner를 docker-compose.prod.yml에 편입(등록 정보는 /opt/mirim-runner에 영속)
  → compose down/up에도 관리되고, 재등록 없이 이어짐. 재연결 확인
- deploy/CI.md: 구성요소·동작흐름·재구축 절차·함정 메모(Maven archive·clone add-host·checkout node)
- 티켓 풀 OP-02를 ' 구현됨 — 확장(프론트 테스트·캐시·브랜치 보호)은 학생 몫'으로 갱신

CI 상태: run 2 백엔드·프론트 둘 다 초록. run 1 빨강→run 2 초록으로 게이트 동작 실증.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 17:04:36 +09:00
bf4b6955e3 ci: Gitea Actions로 push 시 자동 테스트 게이트 추가 (OP-02)
Some checks failed
CI / backend-test (push) Failing after 2m9s
CI / frontend-build (push) Successful in 1m6s
- .gitea/workflows/ci.yml: push/PR마다 백엔드 mvn test + 프론트 npm build 자동 실행
- Gitea Actions 활성화(compose GITEA__actions__ENABLED) + act_runner 등록(별도 컨테이너,
  deploy_default 네트워크, host-gateway로 job이 Gitea clone 가능)
- 방금 같은 회귀(보안헤더로 문서 깨짐 등)를 배포 전 자동으로 잡는 안전망.
  학생 티켓 OP-02를 실물로 구현 — 'CI가 왜 필요한가'의 살아있는 예제

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 16:54:12 +09:00
9ae3cf2709 fix(cache): 문서 iframe ?v= 캐시버스터 + SPA 표준 캐시 정책
옛 X-Frame-Options:DENY 응답이 브라우저 캐시에 박혀 서버 수정 후에도 계속
'연결 거부'가 나던 문제의 근본 대응:
- DocViewerPage iframe src에 ?v=2 — 캐시 키를 바꿔 새 응답을 받게(캐시 우회)
- Caddy 캐시 정책 정리: /assets/*(해시파일) 영구 immutable, 그 외 HTML·문서 no-cache
  → 앞으로 헤더/내용 변경이 캐시에 눌어붙지 않고 즉시 반영

검증: 문서=no-cache+SAMEORIGIN, index=no-cache, assets=immutable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 16:38:15 +09:00
91a6c2a9a9 fix(cache): 학습 문서에 Cache-Control:no-cache — 옛 보안헤더가 캐시에 눌어붙는 문제 방지
X-Frame-Options를 SAMEORIGIN으로 고쳤는데도, 예전에 DENY로 받은 문서가
브라우저 캐시에 남아 일반 새로고침으로는 계속 '연결 거부'가 났다.
/docs/* 응답에 no-cache를 걸어 브라우저가 매번 서버에 재검증하게 함 —
이후로는 헤더가 바뀌면 즉시 반영된다. Caddy 재기동만으로 적용.

검증: 문서 응답에 Cache-Control:no-cache + X-Frame-Options:SAMEORIGIN 확인.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 16:33:15 +09:00
0cbfe666d7 fix(security): X-Frame-Options DENY→SAMEORIGIN — 학습 문서 iframe 로딩 복구
보안 헤더 추가(0a4c769) 때 X-Frame-Options를 DENY로 걸었는데,
DENY는 '어떤 사이트에서도 iframe 금지'라 우리 자신의 iframe(DocViewerPage가
학습 문서를 표시하는 방식)까지 막아 '연결 거부'로 문서가 안 떴다.
SAMEORIGIN(같은 출처끼리는 허용)으로 변경 — 클릭재킹 방어는 그대로 유지.

원인·교훈을 Caddyfile 주석에 기록. Caddy 재기동만으로 반영(재빌드 불필요).
검증: 문서 헤더 SAMEORIGIN, SPA도 헤더 유지, intern-guide 문서 iframe 정상 로딩.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 16:23:22 +09:00
0a4c769d00 feat: 고도화 배치 7건 — 보안헤더·다운감시·로그회전·개인정보파기·문서정합성
[보안]
- Caddy 보안 응답헤더(HSTS·X-Frame-Options:DENY·X-Content-Type-Options·Referrer-Policy) +
  Server 헤더 제거. :80 catch-all 제거로 HTTP→HTTPS 자동 리다이렉트 복원(평문 표면 제거)
[운영 안정성]
- 헬스체크 스크립트 + systemd 타이머(2분): 공개 URL 두드려 UP/DOWN 변화 시에만 텔레그램 알림
- Docker 로그 회전(json-file 10m×3, YAML 앵커 공통적용) — 디스크 풀로 인한 조용한 다운 방지
[개인정보]
- 학생 파기: DELETE /api/mentor/students/{id} (멘토 권한) → 자식6테이블 먼저·계정 나중,
  @Transactional. 동의화면이 약속한 '파기' 이행. 멘토 화면에 이름입력 확인 파기 버튼
  StudentPurgeService + 테스트3(삭제순서·멘토거부·404)
[문서 정합성 — 지금 쓰는 학생 대상]
- STUDY.md: 없는 파일 가리키던 코드지도 정정(controller/→web/, ChecklistPage→DashboardPage,
  DocumentsPage→DocsPage, SubmissionsPage→AssignmentsPage)
- README: '6주'→8주, 학습센터/퀴즈/코딩 소개 추가, 폴더구조·초기계정(멘토만 시드·랜덤비번) 현행화
- 퀴즈 만점 안내: 없는 '코스 완료' 버튼 대신 '[← 코스로] 돌아가 완료' 로 정정

검증: 테스트29 통과, 운영에서 헤더4종·308리다이렉트·헬스체크·파기 E2E(무인증401·멘토200·
자식전체0건) 확인. 나머지 13건은 티켓 풀 확장감으로 남김.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 14:58:00 +09:00
84144a1608 fix(security+a11y): 종합 감사(PMD+5관점, 29에이전트) 확정 12건 수정
[HIGH]
- 로그인 잠금 XFF 스푸핑 우회: X-Forwarded-For leftmost(위조 가능) → rightmost(Caddy가 붙인 실제 IP) 사용
- 멘토 기본 비밀번호 하드코딩 제거: env 미설정 시 SecureRandom 생성 후 로그 1회 출력.
  compose에 MENTOR_INITIAL_PASSWORD 추가
- 클릭 카드 키보드 접근 불가(학습문서 전체): Card에 role=button·tabIndex·Enter/Space·aria-label

[MEDIUM]
- CSV 수식 인젝션: =+-@ 로 시작하는 학생 자유입력 앞에 ' 붙여 엑셀 수식화 차단 (+회귀 테스트)
- 학습문서 17개 <html lang=ko> 골격 주입 (스크린리더 한국어 발음)
- 과제 제출 폼 label htmlFor/id 연결
- 상단 아이콘 링크(👤·🔑) aria-label + 이모지 aria-hidden
- 멘토 제출물 표 행 키보드 접근(role/tabIndex/Enter·Space)
- 배지 amber/teal 글자 대비 4.5:1 확보(테마별 --amber-text/--teal-text)

[LOW]
- 코드에디터 포커스 표시(:focus-within), 문제번호 색 대비(--muted), 카드 :focus-visible
- 폼 오류 메시지 role=alert (스크린리더 즉시 안내)

검증: 테스트 26개 통과, 운영에서 XFF 우회 차단(6회째 429)·카드 role/aria·문서 lang=ko 확인.
PMD 29건은 전부 저심각 스멜로 실질 결함 없음. 각 수정에 원리 학습 주석.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 13:38:30 +09:00
a52999d81d feat: 텔레그램 알림 봇 연동 — 학생 가입·과제·코딩 제출을 멘토에게 즉시 알림
- TelegramNotifier: 봇 API sendMessage 호출. 두 가지 원칙 —
  · 알림 실패가 본 기능(가입·제출)을 절대 막지 않는다: 예외 전부 삼킴 + @Async 비동기
  · 시크릿은 env로만: TELEGRAM_BOT_TOKEN/CHAT_ID 비어 있으면 기능이 조용히 꺼짐(로컬 무설정 OK)
  · 에러 로그에 예외 종류만 남김 — HTTP 에러 메시지의 URL에 토큰이 포함되는 유출 경로 차단
- AsyncConfig: @EnableAsync (없으면 @Async가 조용히 무시되는 함정 문서화)
- 연결 지점 3곳: AuthService.signup(가입) / CodingService.submit(코딩) / SubmissionService(과제)
- 테스트: 생성자에 mock 주입 + 가입 시 알림 발송 검증 1건 추가 → 전체 17개 통과

운영 검증: 봇(@awesomedevmirim_bot)→'2026 현장실습 사전 과제' 그룹 발송 확인,
실가입 E2E로 알림 발송 확인. 토큰은 서버 .env에만 존재(git 미포함).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 10:09:52 +09:00
387180dbfc feat(ops): Gitea 자동 백업 추가 — 저장소·이슈·PR·계정 보호
- backup-gitea.sh: gitea dump(저장소+SQLite+설정을 일관된 시점으로) | S3 업로드
  볼륨 tar 대신 gitea dump를 쓴 이유를 주석에 명시 (실행 중 tar는 시점이 어긋날 수 있음)
- zip 무결성(unzip -t)·크기 검증 후에만 업로드
- systemd timer 매일 03:10 KST (DB 백업 10분 뒤 — 겹치지 않게)
- IAM 정책에 gitea/ 경로 추가 + 복원용 GetObject (여전히 해당 버킷 두 경로로만 제한)
- S3 수명주기에 gitea/ 30일 규칙 추가
- BACKUP.md: Gitea 복원 절차·검증법·'무엇이 이 서버에만 있는가' 관점 정리

검증: 백업 2.8MB, zip 무결성 OK, 백업 속 저장소에서 clone → HEAD·커밋20·파일269 운영과 일치

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 08:26:29 +09:00
44487d5746 feat(ops): DB 자동 백업 — 매일 S3 업로드, 30일 보관
- backup-db.sh: pg_dump | gzip | S3 업로드 + 로컬 7일 보관, 덤프 크기 검증
- restore-db.sh: 목록 조회·복원(확인 프롬프트·복원 전 자동 스냅샷)
- systemd timer로 매일 03:00 KST 실행 (AL2023에 cron 없음, 이력이 journalctl에 남음)
- S3 버킷: 퍼블릭 차단 + AES256 암호화 + 30일 수명주기
- IAM 역할(mirim-backup-role): 해당 버킷 postgres/ 에 PutObject만 — 최소 권한, 액세스 키 없음
- BACKUP.md: 구성·설계 이유·복원 리허설 절차

검증: 백업 성공(8.5MB→102KB), S3 업로드 확인, 임시 DB 복원 후 운영본과 건수 완전 일치

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 08:13:37 +09:00
8486c7e656 fix(security): 세션 쿠키 보안 플래그 + 운영 로그 레벨
- 세션 쿠키에 HttpOnly(XSS로 탈취 방어)·Secure(HTTPS 전용)·SameSite=Strict(CSRF 방어) 적용
- Secure는 COOKIE_SECURE 환경변수로 분리 (로컬 http 개발에서도 로그인되도록 기본 false)
- 운영 로그 레벨을 LOG_LEVEL 환경변수로 분리, 운영은 INFO
- 학습 포인트 주석: 세션 인증의 3대 쿠키 플래그가 왜 필요한가

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 07:19:21 +09:00
c6d723824b feat: 설치 가이드 개편 — IntelliJ 설치·Git 연결 단계 추가, 가이드 디자인 시스템 도입
- STEP 6 IntelliJ 설치(Community 선택, JDK 지정, 첫 실행까지)
- STEP 7 IntelliJ에서 Git 연결(클론→브랜치→커밋→푸시→PR, 클릭 경로 명시)
- 히어로·단계 필 내비·번호 리스트·팁/주의 박스 등 가이드 전용 스타일 추가 (global.css)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 08:32:51 +09:00
111590104e feat: 수습 학습 플랫폼 최초 구축 (React + Spring Boot + PostgreSQL)
- 로그인/회원가입/아이디찾기/비밀번호재설정 (세션 기반)
- 대시보드(주차별 진도 체크리스트), 학습 문서 뷰어(17종), 과제 제출/멘토 피드백
- 코딩 기초·개발 환경 설치 가이드 학습 페이지
- 전 소스 한국어 학습 주석 — 수습생 교육용 저장소
- Docker 배포 구성 (Caddy + Spring Boot + PG + Gitea)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 08:18:12 +09:00