Cupix::Errors::PermissionDenied: Permission denied
RCA: Cupix::Errors::PermissionDenied: Permission denied
Overview#
What Happened#
cupixworks-api에서 Cupix::Errors::PermissionDenied (code PERM10000) 예외가 반복 발생하며 Error Tracking 이슈로 묶였다. 최근 로그를 확인하면 거의 전부가 GET /api/v1/admin/teams/{id} (Api::V1::Admin::TeamsController#show) 요청에서 발생하며, API가 권한이 없는 요청자에게 HTTP 403을 정상 반환한 케이스다. 로그 레벨은 모두 info이고 status:error 로그는 존재하지 않는다. 이는 인가(authorization) 정책이 의도대로 동작한 결과이며 코드 결함이 아니다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | Cupix::Errors::PermissionDenied |
| exception.message | Permission denied |
| error.code | PERM10000 |
| top_frame | app/repositories/base_repository.rb:359-360 |
| http_status | 403 |
| log_level | info (error 로그 없음) |
| env | production |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
admin (/api/v1/admin/teams/*) |
대다수 (샘플 50건 중 45+건) | 권한 없는 team 상세 조회 요청이 403으로 정상 거부됨. 사용자 데이터/상태 영향 없음 |
| facilities / captures publish | 각 1건 | trash/publish/unpublish 시 권한 없는 요청 403 거부 |
Timeline#
- 2025-01-21 12:16 KST — 최초 발생 (
first_seen). Representative Error 는 이 시점의 오래된 샘플. - 2026-07-29 13:07 KST — 클러스터 기준 최근 발생 (
last_seen). - 2026-08-04 (RCA 시점) — 최근 14일 로그에서도 동일 패턴 지속 확인 (
GET /api/v1/admin/teams/{id}403 반복).
Error Log#
Permission denied
Impact#
- Service:
cupixworks-api - 발생 횟수: 20 (클러스터 기준)
- 최초 발생: 2025-01-21 12:16 KST
- 최근 발생: 2026-07-29 13:07 KST
Root Cause Summary#
이 예외는 코드 버그가 아니라 정상적인 인가 거부다. Api::V1::Admin::TeamsController#show 의 set_team before_action 이 Admin::TeamRepository#show(params[:id]) 를 호출하고, 이는 BaseRepository.show (base_repository.rb:359-360) 에서 Pundit.policy(current_user, model).read? 가 false 이면 Cupix::Errors::PermissionDenied (PERM10000) 를 raise 한다. 이 예외는 ClientErrorController (client_error_controller.rb:26,69-71) 에서 permission_denied_403_error 로 rescue 되어 HTTP 403 으로 렌더링되고 info 레벨로 기록된다. 즉 권한 없는 사용자가 자신이 접근할 수 없는 team(예: team 469, 830, 691)을 조회하려 할 때 API가 접근을 올바르게 차단한 것이다. Datadog Error Tracking 은 응답에 exception class 가 실려 있어 이를 이슈로 묶었을 뿐, 서버 오류(5xx)나 nil reference 같은 결함은 없다.
Technical Analysis#
Code Path#
- Entry point:
app/controllers/api/v1/admin/teams_controller.rb:8—before_action :set_team set_team이 repositoryshow를 호출:
def set_team
@model = repository_instance.show(params[:id])
end
- Repository 위임 후 클래스 메서드
BaseRepository.show로 진입하여 Punditread?정책을 검사:
if !skip_permission && current_user.present? && !Pundit.policy(current_user, model).read?
raise Cupix::Errors::PermissionDenied.new(code: 'PERM10000', reason: 'Permission denied')
end
- Failure point (의도된 raise):
app/repositories/base_repository.rb:360 - 이 예외는 4xx client-error 핸들러에서 rescue 되어 403 으로 변환:
rescue_from Cupix::Errors::PermissionDenied, with: :permission_denied_403_error
# ...
def permission_denied_403_error(exception)
raise_error(403, exception)
end
- 기대 동작 vs 실제 동작: 기대 동작 = 권한 없는 요청은 403 으로 거부. 실제 동작 = 정확히 403 으로 거부됨. 둘이 일치하므로 결함 없음. 유일한 부작용은 exception class 가 응답 메타에 실려 Error Tracking 이 이를 "error" 처럼 묶는다는 점.
Log Evidence#
사용한 Datadog 쿼리:
service:cupixworks-api "Permission denied"
service:cupixworks-api "Permission denied" status:error
첫 번째 쿼리는 최근 14일 내 다수 결과 반환, 두 번째 쿼리(status:error)는 0건 — 즉 error 레벨 로그가 전혀 없다.
최근(last_seen 이후에도 지속) 발생 로그 원문 — 모두 status:info, HTTP 403:
{
"timestamp": "2026-08-04 16:42:18",
"status": "info",
"message": "[403] GET /api/v1/admin/teams/469 (Api::V1::Admin::TeamsController#show)",
"error": {
"reason": "Permission denied",
"code": "PERM10000",
"message": "Permission denied",
"class": "Cupix::Errors::PermissionDenied"
}
}
엔드포인트 분포 (샘플 50건):
8 [403] GET /api/v1/admin/teams/469 (Api::V1::Admin::TeamsController#show)
7 [403] GET /api/v1/admin/teams/830 (Api::V1::Admin::TeamsController#show)
7 [403] GET /api/v1/admin/teams/691 (Api::V1::Admin::TeamsController#show)
6 [403] GET /api/v1/admin/teams/32 (Api::V1::Admin::TeamsController#show)
...
1 [403] PUT /api/v1/facilities/po00en/trash (Api::V1::FacilitiesController#trash)
1 [403] PUT /api/v1/captures/82636/publish (Api::V1::CapturesController#publish)
1 [403] DELETE /api/v1/captures/82636/publish (Api::V1::CapturesController#unpublish)
압도적 다수가 admin/teams/{id} 조회이며 소수의 facilities/captures 권한 거부가 섞여 있다. 모두 read?/권한 정책에 따른 정상적 접근 차단이다.
Representative Error 불일치 참고: 클러스터의 Representative Error 는 Permission denied 문자열뿐이며, 이는 first_seen(2025-01-21) 시점의 오래된 샘플이다. last_seen 이후의 실제 최근 발생 메시지도 동일한 Permission denied / PERM10000 이므로 근본 성격(정상 403 인가 거부)에는 변화가 없다.
Status Board#
bun run cli/incident-board.ts for-cluster 결과 scope 는 svc:cupixworks-api::unknown, active: null. 최근 resolved 인시던트(2026-07-29, 2026-07-30 "service degraded")는 이 클러스터를 포함하지 않으며 별개 사건이다. 외부 의존성 outage 아님.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | 정상적인 인가 거부(권한 없는 요청에 대한 403) — 코드 결함 아님 | 모든 발생이 status:info/HTTP 403; base_repository.rb:359-360 Pundit read? 실패 시 의도적 raise; client_error_controller.rb:26 이 4xx client error 로 분류 |
status:error 로그 0건, 5xx 없음, nil/logic 오류 흔적 없음 |
Confirmed |
| H2 | 서버 측 결함(nil reference, 잘못된 정책 로직)으로 인해 잘못 403 반환 | — | error 레벨 로그 없음; 요청이 특정 team id(469, 830, 691)에 반복 집중되어 요청자별 권한 부재 패턴에 부합; show 는 model 을 정상 조회한 뒤 정책 검사에서만 거부 |
Rejected |
| H3 | 외부 의존성 outage 로 인한 대량 발생 | — | status-board active: null, dep scope 아님; 인증/DB 오류가 아니라 인가 로직 |
Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
- 코드 변경 불필요. 이 예외는 의도된 인가 거부이며 API 동작은 정확하다.
단기 개선 (1주 이내)#
- Error Tracking 노이즈 억제:
Cupix::Errors::PermissionDenied(및 유사 4xx client error 인Cupix::Errors::NotFound403) 는 이미info레벨로 기록되고 있으므로, Datadog Error Tracking 에서 이 exception class 를 error 로 수집하지 않도록 이슈를 Ignore/Archive 처리하거나 error-tracking exclusion filter(예:@error.code:PERM10000제외)를 추가하는 것을 권장한다. - 프런트엔드/클라이언트가 권한 없는 team id 로 반복 조회하는 패턴(team 469/830/691 등)이 UX 버그인지 별도 확인은 선택 사항이나, 이는 API 결함이 아닌 클라이언트 조회 로직 이슈다.
장기 개선 (재발 방지)#
- Error Tracking 수집 파이프라인에서 HTTP 4xx (특히 403 인가 거부) 응답에 실린 exception class 를 자동으로 error 클러스터에서 제외하는 규칙을 추가하면 동일 유형의 노이즈 이슈 생성을 방지할 수 있다.
Monitoring#
정상 동작이므로 알림은 불필요하나, 403 비율이 비정상적으로 급증하면(정책 회귀 또는 클라이언트 버그 신호) 파악할 수 있도록 추이 위젯을 둘 수 있다.
sum:trace.rack.request.hits{service:cupixworks-api,http.status_code:403}.as_count()
sum:trace.rack.request.hits{service:cupixworks-api,resource_name:api/v1/admin/teams/show}.as_count()
Risk Assessment#
- Risk level: low (정상 인가 동작, 사용자 영향 없음)
- 예상 복잡도: trivial (코드 수정 없음, Error Tracking 노이즈 정리만)
Noise Verdict#
noise — 권한 없는 요청에 대해 API가 의도대로 HTTP 403(PERM10000, info 레벨)을 반환한 정상적인 인가 거부이므로 코드 수정이 필요 없는 노이즈다.