ES /docs

Cupix::Errors::PermissionDenied: Permission denied to publish

RCA: Cupix::Errors::PermissionDenied: Permission denied to publish

Overview#

What Happened#

cupixworks-api (tesla) 에서 편집 권한이 없는 사용자가 리소스 publish 엔드포인트(PUT /api/v1/captures/{id}/publish 등)를 호출하자 Pundit 정책 검사가 실패하여 Cupix::Errors::PermissionDenied (PERM10000) 가 발생했다. 이 예외는 client_error_controller.rb 에서 HTTP 403 으로 정상 매핑되며, 서버 결함이 아니라 예상된 인가 실패다. retention 창(now-14d) 내 26건 중 대부분이 dev/qa 테스트 트래픽(tg-not-editing 팀)이었다.

Quick Facts#

Field Value
exception.class Cupix::Errors::PermissionDenied
exception.message Permission denied to publish
error.code PERM10000
top_frame app/repositories/concerns/publishable_repository.rb:5
http.status 403
runtime Ruby on Rails (tesla)
env dev / qa / production, region us-west-2

Affected Teams#

로그 team.domain 기준 분포 (now-14d, 26건):

Team / Domain Error Count Impact
tg-not-editing 16 편집 권한 없는 QA 테스트 팀 — 인가 거부 테스트로 추정
hani 6 비편집 사용자 publish 시도 → 403
ikukbo 2 비편집 사용자 publish 시도 → 403
cupix 1 비편집 사용자 publish 시도 → 403
qatest 1 QA 테스트 트래픽

환경 분포: dev 18, qa 6, production 2.

Timeline#

  1. 2025-02-24 14:21 KST — 최초 발생 (first_seen, ET 집계 기준)
  2. 2026-07-22 12:48 KST — retention 창 내 첫 로그 확인 (captures/78908/publish, team tg-not-editing)
  3. 2026-07-27 10:09 KST — 클러스터 last_seen
  4. 2026-08-04 16:06 KST — retention 창 내 최신 발생 (여전히 진행 중)

Error Log#

Datadog Logs

text
Permission denied to publish

실제 request 로그 (status:info, 403):

text
[403] PUT /api/v1/captures/78908/publish (Api::V1::CapturesController#publish)

Impact#

  • Service: cupixworks-api (tesla)
  • 발생 횟수: 12 (클러스터), retention 창 내 실측 26건
  • 최초 발생: 2025-02-24 14:21 KST
  • 최근 발생: 2026-07-27 10:09 KST (last_seen); 실측 최신 2026-08-04 16:06 KST

Root Cause Summary#

편집 권한이 없는 사용자가 capture/review/floorplan 의 publish 엔드포인트를 호출하면, PublishableRepository#publish! 첫 줄에서 Pundit.policy(current_user, model).publish?false 를 반환하여 Cupix::Errors::PermissionDenied (PERM10000, reason Permission denied to publish) 를 raise 한다. publish? 정책은 editor? (admin-team editor) 또는 delete? 권한을 요구하는데 (publishable_policy.rb:5-9), 두 조건을 모두 만족하지 못하는 viewer/비편집자가 publish 를 시도한 것이 원인이다. 이 예외는 client_error_controller.rb:26rescue_from Cupix::Errors::PermissionDenied → permission_denied_403_errorHTTP 403 에 정확히 매핑된다. 즉 클라이언트 인가 실패가 올바르게 처리된 정상 동작이며, 서버 코드 결함이 아니다.

Technical Analysis#

Code Path#

  • Entry point: PublishableController#publish (app/controllers/concerns/publishable_controller.rb:4-9)
app/controllers/concerns/publishable_controller.rb:4-9ruby
def publish
  repository_instance.publish!
  render_api Renderable.new({
    contents: @model
  })
end
  • Failure point: PublishableRepository#publish! (app/repositories/concerns/publishable_repository.rb:4-11)
app/repositories/concerns/publishable_repository.rb:4-11ruby
def publish!
  raise Cupix::Errors::PermissionDenied.new(code: 'PERM10000', reason: 'Permission denied to publish') unless Pundit.policy(self.current_user, self.model).publish?
  return if self.model.published?

  self.model.check_publishable

  _publish
end
  • 정책 검사: PublishablePolicy#publish? (app/policies/concerns/publishable_policy.rb:4-9)
app/policies/concerns/publishable_policy.rb:4-9ruby
def publish?
  return true if editor?

  delete?
end
  • editor? 는 admin 팀의 editor 만 true (application_policy.rb:255-261):
app/policies/application_policy.rb:255-261ruby
def editor?
  return false unless user.team&.domain == 'admin'
  return false unless record.respond_to?(:editor)
  record.editor_id == user.id || record.try(:editing).try(:editor_id) == user.id
end
  • HTTP 매핑: ClientErrorController (app/controllers/concerns/client_error_controller.rb:26,69-71)
app/controllers/concerns/client_error_controller.rb:26,69-71ruby
rescue_from Cupix::Errors::PermissionDenied, with: :permission_denied_403_error
# ...
def permission_denied_403_error(exception)
  raise_error(403, exception)
end

기대 동작 vs 실제 동작: 편집 권한 없는 사용자의 publish 요청은 인가 거부(403)로 반환되어야 하고, 실제로도 그렇게 처리된다. publish! (bang) 은 컨트롤러에서 별도 rescue 하지 않으므로 예외가 controller rescue chain 까지 전파되어 403 으로 렌더된다. 동일 reason 문자열은 siteinsights_publishable_repository.rb:5publishable_repository/review.rb:50 에도 존재하지만, retention 창 로그는 모두 일반 captures/reviews/floorplans publish 경로였다.

Log Evidence#

Datadog 쿼리 (재현용):

text
service:cupixworks-api "Permission denied to publish"
  • LOG 26건 (now-14d). 모두 status:info [403] request 로그. status:error / span 검색은 0건 (403 은 error 로 로깅되지 않음).

대표 로그 원문 (attributes 발췌):

json
{
  "timestamp": "2026-07-22T03:48:24.918Z",
  "status": "info",
  "message": "[403] PUT /api/v1/captures/78908/publish (Api::V1::CapturesController#publish)",
  "attributes": {
    "error": {
      "reason": "Permission denied to publish",
      "code": "PERM10000",
      "class": "Cupix::Errors::PermissionDenied"
    },
    "action": "publish",
    "controller": "Api::V1::CapturesController",
    "team": { "domain": "tg-not-editing", "id": 377 },
    "environment": "dev"
  }
}

엔드포인트 분포 (message 기준):

text
19  PUT /api/v1/captures/{id}/publish   (Api::V1::CapturesController#publish)
 2  PUT /api/v1/reviews/{key}/publish   (Api::V1::ReviewsController#publish)
 1  PUT /api/v1/floorplans/{id}/publish (Api::V1::FloorplansController#publish)

환경/팀 분포:

text
env:   dev 18, qa 6, production 2
team:  tg-not-editing 16, hani 6, ikukbo 2, cupix 1, qatest 1
status: info 26 (전부 [403], error 0)

tg-not-editing (팀명이 문자 그대로 "편집 불가") + qatest 가 22/26 을 차지 → 인가 거부 동작을 검증하는 dev/qa 자동화 테스트가 대부분. distinct capture ID 가 매번 다르고(45942, 78908, 79106, 80701 …) 특정 리소스 재진입 패턴이 아니라 저빈도 산발 → 클라이언트 인가 실패의 자연스러운 분포.

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 편집 권한 없는 사용자의 publish 시도가 403 으로 정상 매핑되는 예상된 인가 실패 (noise) 로그 26건 전부 status:info [403]; error.code=PERM10000; client_error_controller.rb:26 가 403 으로 매핑; 팀 tg-not-editing/qatest 가 대부분; publish? 정책은 editor/delete 권한 요구 Confirmed
H2 publish! 예외가 rescue 되지 않아 500 으로 새는 매핑 버그 (bug) publish! 은 bang 메서드로 controller 에서 개별 rescue 없음 PermissionDenied < BaseErrorclient_error_controller.rb:26 rescue_from 에 등록되어 확정적으로 403; 로그 status 도 전부 info/403, 500 로그 0 Rejected
H3 정책 로직 회귀로 정당한 editor 도 거부되는 결함 (bug) 발생이 16개월 지속 editor?/delete? 정책은 정상; 트래픽이 비편집 테스트 팀(tg-not-editing)에 집중, editor 팀의 거부 로그 없음; production 은 2건뿐 Rejected

Fix Recommendation#

즉시 조치 (Critical)#

  • 코드 변경 불필요. Cupix::Errors::PermissionDenied → HTTP 403 매핑은 이미 정상(client_error_controller.rb:26,69-71)이며, publish 정책(publishable_policy.rb)도 의도대로 동작한다. 예상된 클라이언트 인가 실패이므로 Error Tracking 에서 IGNORE 권장.

단기 개선 (1주 이내)#

  • 알람 노이즈 감소가 목적이라면, 프런트엔드에서 비편집(viewer) 사용자에게 publish 버튼을 비활성화/숨김 처리하여 사전 차단하는 UX 개선을 검토 (백엔드 변경 아님, 프런트엔드 담당자 조율 필요).

장기 개선 (재발 방지)#

  • QA 자동화(tg-not-editing/qatest)가 생성하는 인가 거부 트래픽은 정상 테스트 산출물이므로, ET 필터에서 dev/qa env 또는 PERM10000 계열 4xx 를 노이즈로 분류하는 규칙을 유지한다.

Monitoring#

Publish 인가 거부 추이 (403 request 로그):

text
service:cupixworks-api "Permission denied to publish"

production 환경만 별도 추적 (테스트 트래픽 제외):

text
service:cupixworks-api "Permission denied to publish" env:production

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: trivial (코드 변경 없음 — ET IGNORE)

Noise Verdict#

noise — 편집 권한 없는 사용자의 publish 시도가 PERM10000 로 HTTP 403 에 이미 정상 매핑되는 예상된 클라이언트 인가 실패이며, 트래픽 대부분이 dev/qa 테스트 팀(tg-not-editing/qatest)이라 서버 코드 결함이 아니다.