ES /docs

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.parseURI 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#

  1. 2026-06-11 11:05:30 KST — 첫 에러 발생 (code=\u314C).
  2. 2026-06-11 11:05:32–42 KSTcode 길이가 한 글자씩 늘어나며 (\u314C\u314C\u314A\u314C\u314A\u314D) 동일 에러가 반복 발생. 클라이언트가 한글 IME 상태에서 매 키입력마다 OAuth code 교환을 시도하는 패턴으로 보임.
  3. 2026-06-11 11:05:44 KST — 마지막 에러 발생, 14초 윈도우 종료.

Error Log#

Datadog Logs

text
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.getURI.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-54params[: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-83URI::InvalidURIErrorStandardError 하위이므로 두 번째 rescue 절에 잡혀 Cupix::Logger.error("Revizto Authentication failed: #{e.message}", ...) 로 기록되고 Cupix::Errors::BadGateway (BG10001) 가 raise.
app/concerns/parameter/integration.rb:53-54ruby
elsif params[:code].present?
  token = operation_class.get_token(params[:code], params[:region])
app/operations/revizto_operation.rb:29-46ruby
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
app/operations/revizto_operation.rb:77-83ruby
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
config/environments/production.rb:173ruby
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:

text
service:cupixworks-api status:error "Revizto Authentication failed"

대표 로그 (raw):

json
{
  "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 가 트리거된 것으로 보임):

text
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:173token_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::InvalidURIErrorStandardError 보다 먼저 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 에 직접 사용 가능):

text
service:cupixworks-api status:error @class:ReviztoOperation "URI must be ascii only"
text
service:cupixworks-api status:error @class:ReviztoOperation @function:get_token

Revizto OAuth 4xx vs 5xx 분류 모니터링 (수정 후 5xx 가 0 에 가까워야 함):

text
service:cupixworks-api @class:ReviztoOperation @function:get_token status:(error OR warn)

Risk Assessment#

  • Risk level: low — 영향 범위가 Revizto integration 인증 단계로 한정, Korean IME 입력이라는 좁은 트리거 조건. 다만 5xx 로 분류돼 error budget 과 알람을 노이즈로 오염시키는 부작용 있음.
  • 예상 복잡도: trivialcode 인자 percent-encoding + rescue 절 분리. 약 5–10 줄 변경, 단위 테스트 한두 개 추가로 충분.