Skip to content

v1.11.9

Choose a tag to compare

@github-actions github-actions released this 02 Sep 13:32
· 45 commits to main since this release

Relio v1.11.9 — 인증 없는 CSP 보고로 나던 500 제거, OpenAPI 문서를 실제 라우터와 일치

이번 릴리스는 두 가지 계약 문제를 바로잡습니다. 브라우저가 보낼 수 있는 형태의 CSP 위반 보고 하나가 서버를 패닉시키던 문제와, /api/openapi.json이 서버가 실제로 제공하지 않는 API를 설명하던 문제입니다.

1. 지시자 없는 CSP 보고가 일으키던 패닉

/api/v1/csp-report는 브라우저가 보내는 위반 보고를 받는 엔드포인트로, 로그인 이전 화면도 정책을 위반할 수 있으므로 인증이 없습니다. 이 핸들러가 구형 보고 본문에서 지시자 이름을 꺼내는 코드가 다음과 같았습니다.

directive = strings.Fields(legacy.Report.Violated + " ")[0]

뒤에 붙인 공백은 가드처럼 보이지만 아무 역할도 하지 않습니다. strings.Fields는 공백뿐인 문자열에 빈 슬라이스를 돌려주므로, 첫 원소를 읽는 순간 index out of range로 패닉합니다.

effective-directiveviolated-directive는 둘 다 선택 항목입니다. blocked-uri만 담고 두 지시자가 모두 없는 보고 — 브라우저가 실제로 보낼 수 있는 형태입니다 — 가 도착하면 요청은 500으로 끝나고 로그에는 스택트레이스가 남았습니다. 인증이 없으므로 누구든 이 응답과 로그를 반복해서 유발할 수 있었습니다.

보고 본문 수정 전 수정 후
effective-directive: "script-src" script-src script-src
violated-directive: "script-src 'self'" script-src script-src
blocked-uri만 있고 두 지시자 모두 없음 패닉 → 500 204
두 지시자가 모두 공백 문자열 패닉 → 500 204

지시자 이름을 꺼내는 일을 reportedDirective 헬퍼로 분리했습니다. effective-directive를 먼저 쓰고, 없으면 정책 전체를 담는 violated-directive의 첫 토큰을 쓰며, 어느 쪽도 이름을 대지 못하면 빈 문자열을 돌려줍니다. 빈 값은 RecordViolation이 이미 버리므로 이름 없는 위반이 통계에 섞이지 않습니다.

2. OpenAPI 문서와 라우터의 불일치

/api/openapi.json은 REST·MCP 클라이언트가 가진 유일한 계약인데, 이 문서를 라우터와 대조하는 장치가 없어 서로 어긋나 있었습니다.

  • PUT /opportunities/{id}/playbook을 약속했지만 서버는 이 경로에 405로 답합니다. 실제 라우트는 항목을 받는 PUT /opportunities/{id}/playbook/{itemId}입니다
  • 로그인 흐름 전체(/auth/status, /auth/login, /auth/logout, /auth/me, OIDC 시작·Callback)와 /me/password, /dashboard 등 8개 엔드포인트가 문서에서 빠져 있었습니다. 문서만 읽는 클라이언트는 인증하는 방법조차 알 수 없었습니다

빠진 경로를 채우고 Playbook 경로를 실제 라우트로 바로잡았습니다.

3. 재발 방지

문서가 다시 조용히 어긋나지 않도록, 패키지 원본에서 파싱한 라우트 표와 OpenAPI 문서를 양방향으로 대조하는 Test 2개를 두었습니다. 새 /api/v1 라우트는 문서에 적히기 전까지, 문서에 적힌 경로는 라우팅되기 전까지 빌드를 실패시킵니다.

검증

  • CSP 보고 단위 Test 4개(두 지시자 조합)와 지시자 없는 보고가 204로 끝나는 핸들러 Test 추가
  • OpenAPI 문서와 라우터를 양방향 대조하는 계약 Test 2개 추가
  • Go 전체 Test/Vet (go test ./..., go vet ./...), gofmt
  • TypeScript Typecheck와 React Production Build
  • 환경변수 계약(check-env-contract.sh)과 외부 정적 Asset 차단(check-static-assets.sh)

업그레이드

DB Migration은 없습니다. 저장된 데이터와 화면 동작에는 영향이 없습니다.

/api/openapi.json을 읽어 클라이언트를 생성해 두었다면 다시 생성하세요. Playbook 항목 저장 경로가 문서상 PUT /opportunities/{id}/playbook에서 PUT /opportunities/{id}/playbook/{itemId}로 바뀌지만, 서버가 답하던 경로는 처음부터 후자였으므로 실제 동작이 달라지는 것은 아닙니다.

오프라인 이미지

항목
Asset relio-v1.11.9.tar.gz
Docker Image relio:v1.11.9
SHA-256 b9ff330078b0b65a02c93f8b63eeb3ca12a2658ddbfdac5f88deb7a9524dbfec
압축 크기 36M
Architecture linux/amd64

이 Release가 직접 제공하는 Asset은 위 Docker Image tar.gz 하나뿐입니다.

오프라인 서버에서 로드

gunzip -c relio-v1.11.9.tar.gz | docker load
docker image inspect relio:v1.11.9

실행

# 최초 1회만 생성하고 비밀번호 관리 도구에 보관하세요.
ENCRYPTION_KEY="$(openssl rand -hex 32)"

docker run -d \
  --name relio \
  -p 8080:8080 \
  -e POSTGRES_DSN="postgres://relio:password@postgres:5432/relio" \
  -e BOOTSTRAP_ADMIN="admin" \
  -e BOOTSTRAP_ADMIN_PASSWORD="ChangeMe-Immediately" \
  -e ENCRYPTION_KEY="$ENCRYPTION_KEY" \
  -v relio-data:/var/lib/relio \
  relio:v1.11.9

Relio Application이 받는 환경변수는 필수 3개(POSTGRES_DSN, BOOTSTRAP_ADMIN, BOOTSTRAP_ADMIN_PASSWORD)와 선택 1개(ENCRYPTION_KEY)뿐입니다.

ENCRYPTION_KEY를 설정하면 Personal Key와 SSO Client Secret이 재기동, 이미지 교체, relio-data Volume 재생성 후에도 유지됩니다. 설정하지 않으면 자격증명을 여는 Key가 Volume 안에만 존재하므로 /var/lib/relio를 PostgreSQL과 같은 복구 시점으로 반드시 보존해야 합니다.

검증

Release 전에 Source Test, TypeScript Typecheck, Frontend Build, Go Vet, 환경변수 계약, 외부 정적 Asset 차단, 비 Root 실행, 완전 격리 Network 신규 설치 Smoke Test, 이전 Release 업그레이드 Test, Docker Save/Load 재검증을 통과합니다.