Revizto Authentication failed: URI must be ascii only "https://api.singapore.revizto.com/v5/oauth2?c
RCA: Revizto Authentication failed — URI must be ascii only
Overview#
What Happened#
2026-06-11 11:05 KST 무렵 약 14초 동안 production us-west-2 의 cupixworks-api에서 Revizto OAuth 토큰 교환 호출이 24건 연속 실패했다. 사용자가 입력한 OAuth code 파라미터에 Korean Hangul Jamo (한글 자모 ㅌㅌㅊㅌㅊㅍ, \u314C, \u314A, \u314D) 가 포함되어 있어 RestClient.get 내부의 URI.parse가 URI must be ascii only 예외를 던졌다. ReviztoOperation.get_token에서 비ASCII 문자를 sanitize/encode 하지 않고 그대로 URL에 보간(interpolate)한 것이 원인이다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | URI::InvalidURIError (logged as StandardError in rescue StandardError => e) |
| exception.message | URI must be ascii only "https://api.singapore.revizto.com/v5/oauth2?code=\u314C\u314A" |
| top_frame | app/operations/revizto_operation.rb:34 (Cupix::HttpClient.get(url)) |
| logger source | app/operations/revizto_operation.rb:78 (Cupix::Logger.error) |
| deploy | production-us-west-2-20260611T0142Z0-9443a6d8-cupixworks |
| env | production, us-west-2 |
| tenant | cupix |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| Integrations / Revizto OAuth | 24 | Revizto 통합 연결(integration creation/auth) 시 사용자에게 BadGateway (BG10001) 응답이 반환됨. 동일 si_trace 패턴으로 보아 소수의 사용자가 짧은 시간에 반복 시도한 것으로 추정. |
Timeline#
- 2026-06-11 11:05:30 KST — 첫 에러 발생 (
code=\u314C). - 2026-06-11 11:05:32–42 KST —
code길이가 한 글자씩 늘어나며 (\u314C→\u314C\u314A→\u314C\u314A\u314D) 동일 에러가 반복 발생. 클라이언트가 한글 IME 상태에서 매 키입력마다 OAuth code 교환을 시도하는 패턴으로 보임. - 2026-06-11 11:05:44 KST — 마지막 에러 발생, 14초 윈도우 종료.
Error Log#
Revizto Authentication failed: URI must be ascii only "https://api.singapore.revizto.com/v5/oauth2?code=\u314C\u314A"
Impact#
- Service:
cupixworks-api - 발생 횟수: 24
- 최초 발생: 2026-06-11 11:05 KST
- 최근 발생: 2026-06-11 11:05 KST
- 사용자 영향: Revizto integration 생성/인증 단계에서 502/
BG10001(Cupix::Errors::BadGateway) 가 사용자에게 반환되어 통합 설정이 차단됨. 다른 통합이나 다른 사용자에게는 영향 없음 (region: singapore Revizto 만 해당).
Root Cause Summary#
ReviztoOperation.get_token 가 사용자 입력 code를 그대로 URL string interpolation 으로 합쳐 Cupix::HttpClient.get 에 전달한다 (revizto_operation.rb:33-34). 내부적으로 RestClient.get → URI.parse 가 호출되는데, Ruby 의 URI.parse 는 비ASCII 문자가 들어오면 URI::InvalidURIError: URI must be ascii only 를 던진다. 사용자가 한글 IME 로 인해 비ASCII Hangul Jamo (\u314C 등) 가 섞인 값을 OAuth code 로 제출했고, 코드는 percent-encoding(URI.encode_www_form_component 또는 Addressable::URI) 없이 raw string 으로 URL 을 구성했기 때문에 URI.parse 단계에서 즉시 실패한다. 본질적으로 user-controlled input 을 URL path/query 로 보간할 때의 인코딩 누락이다.
Technical Analysis#
Code Path#
- Entry point:
app/concerns/parameter/integration.rb:53-54—params[:code]값을 그대로operation_class.get_token으로 전달. - URL 조립:
app/operations/revizto_operation.rb:33— string interpolation 으로code를 query 에 직접 삽입. - Failure point:
app/operations/revizto_operation.rb:34에서 호출되는Cupix::HttpClient.get(url)→RestClient.get(url, headers)내부의URI.parse(url)가 비ASCII 문자에 대해URI::InvalidURIError를 raise. - Rescue 위치:
app/operations/revizto_operation.rb:77-83—URI::InvalidURIError는StandardError하위이므로 두 번째 rescue 절에 잡혀Cupix::Logger.error("Revizto Authentication failed: #{e.message}", ...)로 기록되고Cupix::Errors::BadGateway (BG10001)가 raise.
elsif params[:code].present?
token = operation_class.get_token(params[:code], params[:region])
def self.get_token(code, region)
begin
raise RestClient::Exception, { 'message' => 'Region is required' } if region.nil?
url = "https://api.#{region}.revizto.com#{$OAUTH[:revizto][:token_url]}#{code}"
response = Cupix::HttpClient.get(url)
# ...
rescue RestClient::Exception => e
if e.response.nil?
response = { 'message' => "Revizto Authentication failed #{e}" }
else
response = JSON.parse(e.response) rescue { 'message' => "Revizto Authentication failed #{e.response['message']}" }
end
# ... downstream classification, raises Cupix::Errors::Parameter
rescue StandardError => e
Cupix::Logger.error("Revizto Authentication failed: #{e.message}", class: self.name, function: __method__)
raise Cupix::Errors::BadGateway.new(
code: 'BG10001',
reason: "Revizto Authentication failed: #{e.message}",
message: e.message
)
end
token_url: '/v5/oauth2?code=',
기대 동작: code 가 어떤 문자열이든 percent-encoding 후 안전한 ASCII URL 로 만들어 Revizto 에 전달하고, Revizto 가 invalid code 라고 응답하면 ARG10080/ARG10082/ARG10083 등으로 분류해 사용자에게 4xx 를 반환해야 함. 또한 비ASCII code 자체는 URI.parse 까지 가기 전에 controller 검증 단계에서 거부하거나 sanitize 되어야 함.
실제 동작: code 를 raw string 으로 URL 에 보간 → URI.parse 가 비ASCII 에서 즉시 raise → RestClient::Exception 이 아닌 URI::InvalidURIError 라서 1차 rescue 절을 건너뛰고 rescue StandardError 에 잡혀 BG10001 BadGateway (502) 가 반환됨. 이는 사용자 입력 오류임에도 5xx 로 분류되어 error metric 을 오염시키고 알람 노이즈를 유발한다.
Log Evidence#
Datadog query:
service:cupixworks-api status:error "Revizto Authentication failed"
대표 로그 (raw):
{
"timestamp": "2026-06-11T02:05:44.527Z",
"service": "cupixworks-api",
"class": "ReviztoOperation",
"function": "get_token",
"level": "error",
"environment": "production",
"region": "us-west-2",
"request_id": "c6c97f23-3360-4b2c-9b2b-2f8ad28bdec3",
"tenant": "cupix",
"version": "production-us-west-2-20260611T0142Z0-9443a6d8-cupixworks",
"message": "Revizto Authentication failed: URI must be ascii only \"https://api.singapore.revizto.com/v5/oauth2?code=\\u314C\\u314A\\u314D\""
}
code 값이 시간이 지나며 한 글자씩 누적되는 패턴 (한글 IME 로 매 키입력마다 OAuth flow 가 트리거된 것으로 보임):
11:05:30 KST code=\u314C (ㅌ)
11:05:42 KST code=\u314C\u314A (ㅌㅊ)
11:05:44 KST code=\u314C\u314A\u314D (ㅌㅊㅍ)
\u314C 는 Hangul Compatibility Jamo "ㅌ", \u314A 는 "ㅊ", \u314D 는 "ㅍ". OAuth code 값이 정상 토큰이 아닌, 키보드에서 한글 자모만 입력된 부분 문자열임.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | 사용자 입력 code 에 비ASCII 문자가 포함되어 URI.parse 가 실패. URL 조립 시 percent-encoding 누락이 root cause |
revizto_operation.rb:33 에서 raw interpolation, error message 가 URI must be ascii only 로 일치, 로그의 code= 값이 모두 Korean Hangul Jamo unicode escape (\u314C 등) |
— | Confirmed |
| H2 | Revizto Singapore 리전 엔드포인트(api.singapore.revizto.com) 자체의 장애/DNS 문제 |
동일 시간대 동일 호스트 호출이 24건 발생 | 에러 메시지가 네트워크 단계가 아닌 Ruby URI.parse 단계 문자열이며, 같은 사용자가 code 길이만 늘려가며 동일 패턴으로 실패 — Revizto 서버에는 요청이 도달하지도 않음 |
Rejected |
| H3 | OAuth 설정 ($OAUTH[:revizto][:token_url]) 변경으로 invalid URL 생성 |
— | config/environments/production.rb:173 의 token_url 은 /v5/oauth2?code= 로 정상이며, ASCII 부분은 그대로 valid. 사용자 code 만 비ASCII |
Rejected |
| H4 | Revizto 가 비ASCII redirect callback 을 보냄 (서버 측 버그) | — | OAuth code 는 우리 시스템 사용자가 Revizto 로부터 받아 다시 우리 API 로 제출하는 값. Revizto 가 정상 발급한 code 는 ASCII-safe random string 임. 입력값이 한글 자모 한 두 글자라는 점은 사용자가 빈 입력 또는 IME 입력을 그대로 제출했음을 시사 |
Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
app/operations/revizto_operation.rb:33:code를 URL 에 보간하기 전에 percent-encoding 을 적용. 방향 —URI::DEFAULT_PARSER.escape(code)또는URI.encode_www_form_component(code)사용.token_url이?code=형태로 query string 마지막에 붙는 형태이므로encode_www_form_component가 적합.app/operations/revizto_operation.rb:77-83:URI::InvalidURIError를StandardError보다 먼저 rescue 해서 사용자 입력 오류로 분류 —Cupix::Errors::Parameter (ARG10080/신규ARG10086"Authorization code is not valid")로 4xx 반환하고Cupix::Logger.warn으로 다운그레이드. 5xxBadGateway` 는 외부 시스템 장애에만 사용. 메모리에 기록된 패턴(AUTH20022/23 사례)과 일치하는 log level 정책.- Controller 입력 검증 (
app/concerns/parameter/integration.rb:53근처):params[:code]가 ASCII 가능한 OAuth code format 인지 strong parameter 또는 별도 validator 에서 검증. 비ASCII 또는 비정상 길이일 경우 즉시ARG10080반환.
단기 개선 (1주 이내)#
- 동일 패턴(URL interpolation with user input)이 다른 OAuth provider operation(
OauthOperation하위 클래스들 — Procore, BIM360 등)에도 있는지 audit. 동일하게 percent-encoding 적용. ReviztoOperation.get_token의 5xx vs 4xx 분류를 정리: Revizto 로 요청이 가기 전(URI/네트워크 DNS) 에러 중 사용자 입력 기인은 4xx, Revizto 응답이 5xx 인 경우만BadGateway.- 프론트엔드 OAuth callback 처리에서
code가 비어있거나 IME 상태 partial input 인 경우 폼 submit 을 막도록 검토. Datadog 패턴(매 키스트로크마다 호출)이 프론트의 debounce 누락을 시사.
장기 개선 (재발 방지)#
- 모든 외부 HTTP 호출 시 user-controlled segment 는 percent-encoding 을 강제하는
Cupix::HttpClient.build_url(host:, path:, query:)helper 도입. raw string interpolation 으로 URL 을 만드는 패턴을 lint 차원에서 막기. URI::InvalidURIError,URI::Error등 URL parsing 예외를 controller layer 에서 자동으로 4xx 로 매핑하는 공통 rescue middleware 추가.
Monitoring#
Datadog 쿼리 (release dashboard 의 timeseries widget 에 직접 사용 가능):
service:cupixworks-api status:error @class:ReviztoOperation "URI must be ascii only"
service:cupixworks-api status:error @class:ReviztoOperation @function:get_token
Revizto OAuth 4xx vs 5xx 분류 모니터링 (수정 후 5xx 가 0 에 가까워야 함):
service:cupixworks-api @class:ReviztoOperation @function:get_token status:(error OR warn)
Risk Assessment#
- Risk level: low — 영향 범위가 Revizto integration 인증 단계로 한정, Korean IME 입력이라는 좁은 트리거 조건. 다만 5xx 로 분류돼 error budget 과 알람을 노이즈로 오염시키는 부작용 있음.
- 예상 복잡도: trivial —
code인자 percent-encoding + rescue 절 분리. 약 5–10 줄 변경, 단위 테스트 한두 개 추가로 충분.