개선 리포트 실행: 0~4단계 보안 하드닝·기능 정합·품질 인프라 + 보류 3건 - #74
Merged
Conversation
…시지 방언 중립화 - admin users/[id] load: 14개 순차 쿼리를 Promise.all 로 병렬화 (워터폴 제거) - skin sanitizer: FORBIDDEN_TAGS 에 <style> 추가 — CSS 리드레싱/피싱 차단 - OIDC discovery: scopes_supported(+phone)·claims_supported 를 실제 발급 클레임과 일치 - guards.ts 503 메시지를 D1 전용 문구에서 방언 중립 문구로 - 전체 개선 리포트 문서 추가 (docs/improvement-report-2026-07-02.md) Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
- SAML LogoutRequest 파서에 DOCTYPE/ENTITY 차단 + onErrorStopParsing + IssueInstant skew(±5분), SLO 라우트에 Destination 대조 + 서명 검증 후 request-ID 1회용 소비, xmlEscape 제어문자 제거 (C1/C2/N-8) - TOTP /verify·enroll/confirm 에 rate-limit + counter 기반 코드 재사용 방지 (C3) - rate-limit IP 키를 IPv6 /64 로 정규화(normalizeIpForRateLimit), 12개 호출부 ipKey 전환, webauthn verify 2곳의 x-forwarded-for fallback 제거 (C6/N-12) - admin oidc-clients 액션에 double-submit CSRF 토큰(auth/csrf.ts 신설) 적용 (H-OIDC-5) - find-id 메일 발송을 응답 경로에서 분리해 타이밍 계정 열거 차단 (C5) - reset-password 제출 action rate-limit 추가 (C8) - LDAP 레거시 평문 bindPassword 를 로그인 시 자동 암호화 마이그레이션 (M-D) Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
D1 을 선택적 방언으로 유지(D1 옵션 유지 결정)하기 위해, 모든 wrangler.jsonc 가 DB 바인딩을 선언하지는 않아도 코드가 타입상 성립하도록 app.d.ts 의 Platform.env 에 DB?: D1Database 를 추가. HYPERDRIVE/SKIN_CACHE 와 동일하게 optional 로 두어 방언·바인딩 조합 변경에도 타입체크가 그린으로 유지된다. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
- oidc/refresh.ts 신설: issueRefreshToken/rotateRefreshToken(회전+재사용 감지)/
revokeRefreshTokenFamily/revokeRefreshTokensForSession/revokeAllUserRefreshTokens.
raw 토큰은 SHA-256 해시로만 저장, 회전은 revokedAt IS NULL 가드로 원자적 claim.
- token 엔드포인트: grant_type 분기 + 공통 토큰 빌더 추출.
- authorization_code: offline_access scope + 클라이언트 refresh_token grant 허용 시 발급.
- refresh_token: 회전 후 새 access/id/refresh token 발급, scope 축소 지원,
재사용 감지 시 family 폐기(RFC 6819), 로그아웃된 세션 거부.
- 전역 무효화: 로그아웃(즉시/SAML SLO 체인)·비밀번호 재설정·관리자 role 변경 시 refresh token 폐기.
- discovery: grant_types_supported 에 refresh_token, scopes_supported 에 offline_access 광고.
svelte-check 0 errors(1192 files), eslint 통과.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
- /oidc/introspect: access token(HMAC 검증) 및 refresh token(해시 조회) introspection.
요청 클라이언트 소유 토큰만 active, 그 외 { active: false }.
- /oidc/revoke: refresh token 폐기(멱등). access token 은 stateless 라 no-op, 항상 200.
- oidc/client.ts: token/revoke/introspect 공통 authenticateOidcClient 헬퍼 추출.
- oidc/refresh.ts: findActiveRefreshToken / revokeRefreshTokenByValue 추가.
- discovery: introspection_endpoint / revocation_endpoint + auth methods 광고.
- 두 엔드포인트에 IP rate-limit(60/분).
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
- users load: createdAt 기준 커서 페이지네이션(PAGE_SIZE=50, +1 조회로 다음 페이지 판단). - 방언 무관 lower() LIKE 검색(email/username/displayName), LIKE 와일드카드 이스케이프. - UI: 검색창(GET) + 다음 페이지 링크(q 파라미터 보존), i18n 키(common.search/next_page, users.search_*). - 대규모 테넌트에서 전 사용자 무제한 로드 문제(A3) 해소. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
…C Core 3.1.2) - prompt: none(무상호작용, 필요 시 login_required 를 redirect_uri 로), login(강제 재인증). none 과 타 값 동시 지정은 invalid_request. - max_age: 세션 auth_time(createdAt) 초과 시 재인증(forceAuthn). - id_token_hint: 서명 검증(만료 무시) 후 sub 가 현 세션 사용자와 다르면 재인증. - login_hint: 로그인 페이지 아이디 입력란 프리필. - verifyIdToken 에 ignoreExpiry 옵션 추가(id_token_hint 는 만료가 정상). svelte-check 0 errors(1196 files), eslint 통과. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
- saml/encrypt.ts: WebCrypto 로 AES-256-CBC(assertion) + RSA-OAEP-mgf1p SHA-1(세션키) EncryptedAssertion 생성. 외부 XML-Enc 라이브러리 없이 구현. - response.ts: 서명 이후·Response 서명 이전에 암호화. exc-c14n 서명이라 네임스페이스 명시 선언해도 Assertion 서명 유효. importNode 로 EncryptedAssertion 치환. - sso 라우트: sp.encryptAssertion + sp.cert 를 buildSignedSamlResponse 에 전달. - admin saml-sps: create/update 에 encryptAssertion 처리(cert 없으면 활성 거부) + UI 토글. - scripts/verify-saml-encryption.ts: 암호화→독립 복호화 라운드트립 검증(통과). package.json 에 verify:saml-encryption 스크립트 추가. 주의: 자체 라운드트립은 통과. 실제 SP XML-Enc 복호화기 상호운용은 별도 테스트 필요. svelte-check 0 errors(1197 files), eslint 통과. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
- vitest.config.ts: SvelteKit 플러그인 없이 서버 라이브러리 유닛 테스트 (alias/env 스텁). - test/unit: 26건 6파일 — IPv6 rate-limit 정규화, PKCE(S256/plain 거부), TOTP 재사용 방지, SAML 암호화 라운드트립, OIDC scope/redirect_uri 검증, 3방언 스키마 parity(E1). - CI: Test 단계 추가 + test/**·vitest.config.* path filter. package.json test/test:watch 스크립트. - E3: 런타임 서버 패키지 7종(@simplewebauthn/server, @peculiar/x509, @xmldom/xmldom, @yrneh_jang/ldapjs, reflect-metadata, xmldsigjs, xpath)을 dependencies 로 이동. vitest 26 passed, svelte-check 0 errors, eslint/prettier 통과. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
- /api/health: liveness + 얕은 DB readiness (hooks 가 baseline 조회 skip 하는 경로). - src/routes/+error.svelte: 404/403/503 등 상태별 정돈된 에러 화면(기존 SvelteKit 기본 화면 대체). - wrangler.example.jsonc: observability.enabled 추가 (Workers Logs). - admin users 페이지네이션 링크를 resolve() 사용으로 수정(svelte/no-navigation-without-resolve). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
- audit_events 에 hash 컬럼 추가(3방언). recordAuditEvent 가 안정 필드를 순서 고정
직렬화해 IDP_SIGNING_KEY_SECRET 으로 HMAC-SHA256 계산·저장.
DB write 권한만으로는 필드 변조/행 위조 불가. prev-hash 체인이 아닌 행 단위 MAC 이라
동시 쓰기 fork 문제 없음(행 삭제 탐지는 Logpush 외부 미러 권장 — 주석 명시).
- computeAuditHash export + 유닛 테스트 3건(결정론성/변조탐지/키격리).
- find-password: SMTP 발송을 waitUntil 로 응답 경로에서 분리(find-id 와 동일, 타이밍 열거 차단).
- 마이그레이션 생성(적용 안 함): drizzle/0018, drizzle/{pg,mysql,sqlite}/0001.
vitest 29 passed, svelte-check 0 errors, eslint 통과.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
- src/lib/server/validation.ts 신설(isLoopbackHost). oidc-clients/saml-sps/skins 3곳에 중복되던 loopback 판정을 공통 함수로 통합. svelte-check 0 errors, vitest 29 passed, eslint 통과. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
- D2/E8/E5/find-password 완료 기록. - E4 CRUD 팩토리+zod, D3 argon2, E10 i18n, TOTP TOCTOU 는 품질·리스크 사유로 보류(근거 명시). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
- credentials 3방언 스키마에 nullable totp_owner_id + 일반 uniqueIndex(credentials_totp_owner_uidx) 추가. type='totp' 행만 userId 채우고 나머지는 NULL → NULL 은 unique 검사 제외(4방언 표준)라 webauthn/backup_code 다중 보유 무영향, totp 만 사용자당 1개 DB 강제. mysql 은 TEXT unique 불가라 varchar(64). → partial unique index(mysql 미지원)를 회피해 signing_keys 류 drift 재생산 없음. - enroll/confirm: TOTP+백업코드 INSERT 를 dialect 분기(d1/sqlite=batch, pg/mysql=transaction)로 원자화, totpOwnerId=userId 설정, unique 위반 try/catch → 409. 기존 rate-limit·counter 재사용방지 보존. 백업코드는 TOTP INSERT 성공 시에만 커밋(고아 방지). - 마이그레이션 생성만(d1 0019, pg/mysql/sqlite 0002) — 적용은 사용자(CLAUDE.md 준수). 독립 검증 통과. vitest 54 passed, svelte-check 0 errors, build 성공. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
- 엔진(i18n.svelte.ts): Locale="ko"|"en", t() 가 현재로케일→ko→원본key 순 폴백(누락 키는 한국어로, 원본 key 노출 0). ko 회귀 없음. - SSR 로케일: hooks.server.ts 가 쿠키(idp_locale)→Accept-Language(q-value)→기본 ko 로 결정, transformPageChunk 로 <html lang> 치환. +layout.server.ts 가 data.locale 전달, +layout.svelte 가 렌더 전 setLocale → 하이드레이션 미스매치 없음. app.html lang 동적화, app.d.ts 에 Locals.locale. - en.json: 엔드유저 인증 플로우(common/app/nav/login/mfa_login/signup/find_id/find_password/reset_password) 영어 번역. admin/내부 섹션은 ko 폴백(부분영어 UX 회피). ※ 원어민 검수 권장. - auth 3 화면(login/signup/reset) 잔존 하드코딩 한국어 → t(). LocaleToggle(쿠키+reload) 배치. - 서버 +page.server.ts 에러 메시지는 범위 밖(미수정). 독립 검증 통과. app.d.ts 의 DB optional 보존(platform.env.DB 에러 재발 없음). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
- zod 도입(dependencies). src/lib/server/admin/{crud-factory,schemas}.ts 신설.
- createAdminCrudRoute 팩토리: requireAdminContext → zod safeParse → tenant 스코프
insert/update/delete(and(eq(id),eq(tenantId))) → recordAuditEvent(kind=`${prefix}_created|updated|deleted`)
→ 성공 반환. 에러 계약 보존(create=fail(400,{create:true,error}), update/delete=fail(400,{error})).
- teams/parts/positions/departments 4라우트를 팩토리로 통합. 훅 주입:
departments 계층검증(self/순환/깊이8/참조무결성) 정확 이식, positions level zod 흡수,
teams/parts 의 departmentId/teamId 동일 tenant FK 검증 신규 추가(기존 latent 결함 차단).
- zod 스키마: status z.enum, level/displayOrder coerce. UI(+page.svelte) 무변경.
- 유닛 테스트 2건(crud-factory, admin-zod).
독립 검증 통과. vitest 54 passed(9 files), svelte-check 0 errors, build 성공.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
TOTP TOCTOU / i18n(en) / CRUD 팩토리+zod 의 게이트형 기획서·실행 투두. 완료·검증 상태 기록. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
개요
전체 코드베이스 분석 리포트(
docs/improvement-report-2026-07-02.md)를 기반으로 0단계~4단계 + 보류 3건을 단계별로 처리했습니다. 각 단계는 타입체크·테스트·빌드·린트로 검증했고, 대형 작업(Refresh Token, SAML 암호화, i18n, CRUD 팩토리)은 병렬 에이전트 구현 후 작성과 분리된 독립 검증을 거쳤습니다.검증(최종):
vitest54 passed(9 files) ·svelte-check0 errors(1349 files) ·eslint·prettier통과 ·bun run build(adapter-cloudflare) 성공.변경 요약 (17 커밋)
0단계 — 정리·정합성
<style>차단(CSS 인젝션), OIDC discovery 정합, 503 메시지 방언 중립화, wrangler 잡파일·평문 시크릿 삭제.1단계 — 보안 하드닝
Platform.envoptional 타입으로 선언(adapter-node/방언 조합 대응).2단계 — 기능 정합
3단계 — 품질·운영
/api/health, 루트+error.svelte, Workers observability.4단계 + 보류 3건
totp_owner_idnullable + 일반 unique index(4방언), enroll 원자화 + unique 위반 409.DB 마이그레이션은 생성만 했고 적용하지 않았습니다. (CLAUDE.md 규칙 준수)
drizzle/0019_*(d1),drizzle/{pg,mysql,sqlite}/0002_*—credentials.totp_owner_id컬럼 +credentials_totp_owner_uidx추가. 그리고 이전 단계의audit_events.hash마이그레이션(0018 / 0001).SAML Assertion 암호화 interop: 자체 라운드트립은 통과했으나 실제 SP(Shibboleth/ADFS/SimpleSAMLphp) XML-Enc 복호화기와의 상호운용은 별도 테스트 필요. 기본 off라 켜기 전 검증 권장.
en.json 원어민 검수 권장: 엔드유저 인증 플로우만 영어, admin 콘솔은 ko 폴백으로 한국어 유지.
스킨
<style>차단: 인라인<style>에 의존하는 배포된 커스텀 스킨이 있으면 외관이 달라질 수 있음.남은 항목(리포트에 기록)
argon2 WASM 전환 평가(현행 유지 권장), OIDC 스코프 확장(groups/address), 서버 에러 메시지 i18n, admin 콘솔 영어화 — 별도 작업으로 문서화.
🤖 Generated with Claude Code