Skip to content

운영 감사 후속: 보안 패치, call 무결성, 검증된 백업, cron typecheck - #38

Merged
mikim merged 1 commit into
mainfrom
claude/alpha-operations-review-da534e
Aug 21, 2026
Merged

운영 감사 후속: 보안 패치, call 무결성, 검증된 백업, cron typecheck#38
mikim merged 1 commit into
mainfrom
claude/alpha-operations-review-da534e

Conversation

@mikim

@mikim mikim commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

2026-08-20 운영 리포트 7건을 코드에 대조해 실제 결함만 고쳤습니다.

리포트가 하나 틀렸습니다: "persona-post 가 stance enum 을 검증하지 않는다" 는 사실이 아닙니다 — agree|disagree|observe 화이트리스트가 이미 있었습니다. 진짜 결함은 순서였습니다.

보안 (P1)

next 16.2.4 → 16.2.12. 공개 권고 22건(App Router Server Component DoS 포함)의 패치 하한이 16.2.11 이었습니다. 미사용 nanoid 직접 의존 제거.

pnpm audit 30건(high 17) → 7건. 남은 7건은 전부 next 가 스스로 핀한 postcss 8.4.31 · nanoid 3 · sharp 0.34.5 이고 이 앱에서 도달 불가입니다 — next/image 를 어디서도 import 하지 않고 images.remotePatterns 도 없어 /_next/image 는 동일 출처만 받으며, postcss 가 파싱하는 CSS 는 우리 Tailwind 입력뿐입니다. 이유와 함께 pnpm-workspace.yamlauditConfig 에 적고 pnpm audit --audit-level=high 를 CI 게이트로 넣었습니다 — next 를 올릴 때마다 그 목록을 지우고 다시 봐야 합니다.

--prod 가 아닌 이유: 여기선 devDependencies 가 운영에서 돕니다(모든 cron 앱이 ./node_modules/.bin/tsx).

Trackable call 무결성 (P1)

글을 먼저 쓰고 → 가격을 부르고 → call 삽입을 catch {} 로 삼키던 순서를 뒤집었습니다.

  • reference price 를 모델 호출 전에 확보. 가격 출처가 죽으면 토큰도 쓰지 않습니다.
  • 글과 call 을 한 transaction(createPostWithCall)으로. 둘 다 남거나 둘 다 없습니다.
  • callDirectionForagree|disagree 만 방향으로 인정합니다. 이전엔 예상 밖 stance 문자열이 조용히 down call 이 됐습니다 (잠복 버그).
  • persona-tick 의 priceable quota 를 "후보 2개 예약" → "하루 발행 2건". 예약된 둘이 30일 쿨다운이나 CoinGecko 429 에 걸리면 그날 call 이 0 이었습니다. 이제 SKIP 은 다음 후보만 소모합니다.

배포 (P1 / P2)

  • pm2 save 실패를 경고로 넘기지 않습니다. 재시도 → CRITICAL 알림 → 릴리스 prune 중단. stale dump.pm2 가 가리키는 디렉터리를 지우면 재부팅 때 아무것도 안 올라옵니다. 배포 자체는 실패로 기록하지 않습니다 — 새 릴리스는 살아서 health 를 통과했으니까요.
  • swap 직전 quiet-hour 재검사. 05:5x 에 시작한 tick 이 install+build+smoke 를 마치고 06시 cron 구간에서 swap 하면 pm2 재등록이 그 cron 을 두 번 태웁니다.
  • 릴리스에서 pnpm test 를 돌립니다(--force·CI 무판정 경로 대비).

관측 (P2)

  • audit-auto에러 결과를 완료로 세지 않습니다 → 다음 실행에서 재시도. 실패한 질의만 골라 재시도하므로 성공한 답변은 유지됩니다.
  • audit cron 의 실행 여부 를 health subsystem(audit_cron)으로. 인용률은 여전히 roll-up 밖입니다 — 0% 는 재시작으로 안 고쳐지고 ?strict=1 은 모니터에 물려 있으니까요. 94일 정체가 아무에게도 안 알려지던 이유가 "audit 이 통째로 빠져 있어서" 였습니다.
  • 일일 요약에 citation rate 한 줄.

재해복구 (P1)

scripts/backup-db.ts — 매일 03:00 KST: 스냅샷(WAL 이라 cp 아님) → PRAGMA integrity_checkBACKUP_REMOTE 로 off-host rsync → /healthdb_backup. 지금까지 백업은 배포 직전에만 찍혔고(= RPO 가 배포 주기) 원본과 같은 디스크에 있었습니다.

검증 (P1)

  • tests/ 24건 — KST cron 산술, call 적격성, 글+call 원자성. transaction 을 빼는 mutation 으로 실제로 실패하는 걸 확인했습니다.
  • tsconfig 가 scripts/ 를 제외하고 있었습니다. 20개 스크립트가 각자 하던 process.env.NODE_ENV = 대입이 next 의 readonly 선언과 충돌해서입니다. 즉 cron 스크립트 24개가 CI typecheck 를 한 번도 안 거쳤습니다 — 이 레포의 사고가 거의 다 거기서 났는데도. lib/script-env.ts 하나로 모으고 제외를 풀었습니다(중복 300줄 제거).

문서

README 의 "승인 1명·자기승인 금지" 는 실제 ruleset 과 달랐습니다(required_approving_review_count: 0, require_last_push_approval: false). 현재 상태를 사실대로 적고, 닫으려면 쓸 read-modify-write gh 명령을 함께 뒀습니다. 설정은 바꾸지 않았습니다.

머지 후 할 일

배포 직후 ?strict=1 이 한 번 503 이 됩니다. db_backup 은 03:00 KST 첫 실행 전까지 heartbeat 이 없어 fail(정확한 판정 — 백업이 아직 없음), audit_cron 은 94일 정체라 fail. 전자는 서버에서 한 번 돌리면 사라집니다:

pnpm tsx scripts/backup-db.ts

🤖 Generated with Claude Code

2026-08-20 운영 리포트 7건을 코드에 대조해 실제 결함만 고쳤다.
리포트의 "stance enum 미검증" 은 사실이 아니었다 — 화이트리스트가 이미
있었고, 진짜 결함은 순서였다.

보안
- Next 16.2.4 → 16.2.12. 공개 권고 22건(App Router Server Component DoS
  포함)의 패치 하한이 16.2.11 이었다.
- 미사용 nanoid 직접 의존 제거.
- 30건(high 17) → 7건. 남은 7건은 전부 next 가 스스로 핀한 postcss·
  nanoid3·sharp 이고 이 앱에서 도달 불가(next/image 미사용, remotePatterns
  없음, 자체 CSS 만) — 이유와 함께 pnpm-workspace.yaml 에 기록하고
  `pnpm audit --audit-level=high` 를 CI 게이트로. --prod 가 아닌 이유:
  여기선 devDependencies 가 운영에서 돈다(모든 cron 이 .bin/tsx).

Trackable call 무결성
- 글을 먼저 쓰고 가격을 부른 뒤 call 삽입을 catch{} 로 삼키던 순서를
  뒤집었다. reference price 를 모델 호출 *전에* 확보하고, 글과 call 을
  한 transaction(createPostWithCall)으로 쓴다. 둘 다 남거나 둘 다 없다.
  가격 출처가 죽으면 토큰도 쓰지 않는다.
- callDirectionFor 가 agree|disagree 만 방향으로 인정한다. 이전엔 예상 밖
  stance 문자열이 조용히 down call 이 됐다.
- persona-tick 의 priceable quota 를 "후보 2개 예약" 에서 "하루 발행 2건"
  으로. 예약된 둘이 쿨다운·429 에 걸리면 그날 call 이 0 이었다.

배포
- pm2 save 실패를 경고로 넘기지 않는다. 재시도 → CRITICAL 알림 → 릴리스
  prune 중단(stale dump 가 가리키는 디렉터리를 지우면 재부팅이 안 온다).
  배포 자체는 실패로 기록하지 않는다 — 새 릴리스는 살아 있다.
- swap 직전 quiet-hour 재검사. 05:5x 에 시작한 tick 이 06 시 cron 구간에서
  swap 하면 pm2 재등록이 그 cron 을 두 번 태운다.
- 릴리스에서 pnpm test 를 돌린다(--force·CI 무판정 경로 대비).

관측
- audit-auto 가 에러 결과를 완료로 세지 않는다 → 다음 실행에서 재시도.
  실패한 질의만 골라 재시도하므로 성공한 답변은 유지된다.
- audit cron 의 *실행 여부* 를 health subsystem 으로(인용률은 여전히 제외 —
  0% 는 재시작으로 안 고쳐지고 strict 는 모니터에 물려 있다). 94일 정체가
  아무에게도 안 알려지던 이유가 이것이었다.
- 일일 요약에 citation rate 한 줄.

재해복구
- scripts/backup-db.ts: 매일 03:00 KST 스냅샷 → PRAGMA integrity_check →
  BACKUP_REMOTE 로 off-host rsync → /health 의 db_backup. 지금까지 백업은
  배포 직전에만 찍혔고(=RPO 가 배포 주기) 원본과 같은 디스크에 있었다.

검증
- tests/ 24건(KST cron 산술, call 적격성, 글+call 원자성). CI·deploy.sh 에
  pnpm test.
- tsconfig 가 scripts/ 를 제외하고 있었다 — 20개 스크립트가 각자 하던
  process.env.NODE_ENV 대입이 next 의 readonly 선언과 충돌해서. 즉 cron
  스크립트 24개가 CI typecheck 를 한 번도 안 거쳤다. lib/script-env.ts 로
  모으고 제외를 풀었다(중복 300줄 제거).

문서
- README 의 "승인 1명·자기승인 금지" 는 실제 ruleset 과 달랐다. 현재 상태를
  사실대로 적고, 닫으려면 쓸 명령을 함께 뒀다. 설정은 바꾸지 않았다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@mikim
mikim merged commit 2a9becc into main Aug 21, 2026
1 check passed
@mossland
mossland deleted the claude/alpha-operations-review-da534e branch August 26, 2026 01:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant