tRPC 12의 createCallerFactory로 RSC에서 procedure를 직접 호출하는 패턴을 도입했습니다. 그런데 일부 procedure가 권한 체크 미들웨어를 건너뛰는 걸 발견했습니다. 보안팀이 발견하기 전에 패치하긴 했는데 어떻게 정착시켜야 할지 모르겠습니다.
// server/trpc.ts
export const createCaller = createCallerFactory(appRouter)
// app/dashboard/page.tsx
import { createCaller } from '@/server/trpc'
export default async function Page() {
const caller = createCaller({}) // ← ctx 빈 객체!
const data = await caller.user.profile() // 권한 체크 통과
return <Profile data={data} />
}
실수로 createCaller({}) 빈 컨텍스트로 호출하니 procedure 내부의 ctx.user가 undefined인데도 권한 체크 통과. 모든 사용자 데이터에 접근 가능한 상태였습니다.
이런 실수를 빌드 단계에서 막을 방법이 있을까요?
댓글 2
아주 흔한 함정이고 보안 사고로 이어집니다. 사용하시던 패턴을 두 단계로 안전하게 잠가야 합니다.
1단계 — ctx를 반드시 검증하는 미들웨어
procedure 자체에서 ctx.user를 항상 검증하는 baseProcedure를 만들고, 모든 procedure는 거기서 파생.
2단계 — createCaller 헬퍼로 ctx 강제
raw createCallerFactory를 직접 export하지 말고, ctx를 자동 구성하는 헬퍼로 감싸세요.
RSC에서는 항상
getCaller()를 사용.createCallerFactory를 직접 import 하면 ESLint 룰로 차단.3단계 — ESLint 룰
이렇게 하면 실수로 빈 ctx로 caller 만드는 게 빌드 단계에서 차단됩니다. 저희도 같은 사고 한 번 겪고 이 패턴으로 굳혔습니다.
보안 관점에서 한 가지 추가 — protectedProcedure의 미들웨어 안에서 IP rate limit도 같이 걸어두면 사고 방지에 한 겹 더 안전합니다. 누가 실수로 ctx 빈 상태로 caller 만들어도 분당 N회 이상 호출 시 자동 차단.