./discourse-setup가 SMTP 비밀번호 앞에 SMTP 사용자 이름을 붙입니다

LLM/AI -generated recommendations

아래는 다음과 같은 작업을 수행하는 간결한 패치입니다:

  • sed 사용 중단
  • percent-encoded SMTP_URL 구성
  • Ruby의 YAML(Psych)을 사용하여 containers/app.yml을 편집하여 YAML 인용부호/이스케이프가 실제 파서에서 처리되도록 함
  • 충돌을 피하기 위해 키별 SMTP 변수 삭제

discourse_docker 저장소에서 git apply -p0를 사용하여 적용하십시오.


패치 1 -

discourse-setup

(sed가 아닌 Ruby YAML로 SMTP 기록)

--- a/discourse-setup
+++ b/discourse-setup
@@ -867,6 +867,77 @@ write_smtp_settings() {
   local app_yml="containers/app.yml"
   [[ -f "$app_yml" ]] || die "Cannot find $app_yml. Did you run bootstrap?"

+  # Python 표준 라이브러리를 사용하여 URL-인코딩된 SMTP_URL 생성 (셸 이스케이프 게임 없음)
+  urlencode() {
+    python3 - <<'PY'
+import sys, urllib.parse
+print(urllib.parse.quote(sys.stdin.read().strip(), safe='._~-'))
+PY
+  }
+
+  # 중요: 역슬래시 변형 없이 변수 읽기
+  # (이것들은 이전 프롬프트에서 온 것입니다; 프롬프트 시점에 -r이 사용되었는지 확인하십시오)
+  local addr="$smtp_address"
+  local port="$smtp_port"
+  local user_enc pass_enc
+  user_enc="$(printf '%s' "$smtp_user"     | urlencode)"
+  pass_enc="$(printf '%s' "$smtp_password" | urlencode)"
+  local smtp_url="smtp://${user_enc}:${pass_enc}@${addr}:${port}"
+
+  # Ruby를 사용하여 YAML을 안전하게 로드/수정/덤프 (3층의 이스케이프 제거)
+  ruby - <<'RUBY' "$app_yml" "$smtp_url"
+require "yaml"
+require "psych"
+path, url = ARGV
+doc = YAML.safe_load(File.read(path), permitted_classes: [], aliases: true) || {}
+
+# 최상위 구조가 Hash이고 env가 있는지 확인
+unless doc.is_a?(Hash)
+  abort "containers/app.yml does not parse to a Hash"
+end
+doc["env"] ||= {}
+env = doc["env"]
+
+# 단일 줄 SMTP_URL 기록; 충돌을 피하기 위해 키별 변수 제거
+env["SMTP_URL"] = url
+%w[DISCOURSE_SMTP_ADDRESS DISCOURSE_SMTP_PORT DISCOURSE_SMTP_USER_NAME DISCOURSE_SMTP_PASSWORD].each { |k| env.delete(k) }
+
+# 다시 덤프. (Psych는 필요한 경우 문자열을 안전하게 인용부호로 처리합니다.)
+File.write(path, Psych.dump(doc))
+RUBY
+
+  # 고전적인 "사용자 이름이 비밀번호 앞에 오는" 실패에 대한 빠른 건전성 체크
+  python3 - <<'PY'
+import re, sys
+y = open("containers/app.yml","r",encoding="utf-8").read()
+m = re.search(r'^\s*SMTP_URL:\s*(?:"|\')?([^\r\n"\']+)', y, re.M)
+assert m, "SMTP_URL missing after write"
+creds = m.group(1).split('@',1)[0].split('://',1)[-1]
+assert ":" in creds, "SMTP_URL creds missing ':'"
+u, p = creds.split(':',1)
+assert not p.startswith(u), "Password appears prefixed by username"
+print("SMTP_URL looks sane.")
+PY
+}
+
-  # 키별 SMTP 항목 기록 (주소/포트/사용자 이름/비밀번호)
-  # (레거시: sed 치환을 통해 수행됨)
-  # 참고: 특수 문자와 함께 역사적으로 취약함
-  update_setting_yaml "DISCOURSE_SMTP_ADDRESS"  "$smtp_address"
-  update_setting_yaml "DISCOURSE_SMTP_PORT"     "$smtp_port"
-  update_setting_yaml "DISCOURSE_SMTP_USER_NAME" "$smtp_user"
-  update_setting_yaml "DISCOURSE_SMTP_PASSWORD" "$smtp_password"
-}
+  # (레거시 키별 기록은 SMTP_URL을 통한 YAML로 대체되어 제거됨)
+}

그리고 패치 2 -

templates/web.template.yml

(더 안전한 경로를 문서화하기 위해)

--- a/templates/web.template.yml
+++ b/templates/web.template.yml
@@ -68,6 +68,14 @@ params:
   DISCOURSE_SMTP_ENABLE_START_TLS: true
   #DISCOURSE_NOTIFICATION_EMAIL: noreply@example.com

+  ## 권장 단일 줄 SMTP 구성 (discourse-setup에 의해 설정됨):
+  ## 사용자 이름 및 비밀번호를 URL-인코딩; 예:
+  ##   SMTP_URL: "smtp://user%40example.com:p%40ss%3Aword@smtp.example.com:587"
+  ##
+  #SMTP_URL:
+
   ## SMTP_URL을 사용할 수 없는 경우, 대신 키별 변수를 설정할 수 있습니다.
   ## 값에 @, :, /, ", \ 또는 줄바꿈과 같은 문자가 포함된 경우 셸 도구를 사용하여
   ## 해당 줄을 편집하는 것은 취약할 수 있습니다.

왜 이것이 작동하는가 (그리고 무엇을 피하는가)

  • bash 레이어: 단순한 변수만 보간합니다; 비밀 정보는 sed 정규식이나 셸 eval을 통해 Python/Ruby로 전달되지 않고 stdin/argv를 통해 전달됩니다.
  • sed 레이어: 완전히 제거됨.
  • YAML 레이어: Ruby/Psych가 인용부호와 이스케이프를 적절히 처리합니다; 수동 인용부호 처리 없음.
  • SMTP 자격 증명: SMTP_URL의 %-인코딩은 인증을 위한 특수 문자를 인코딩하는 올바른 위치입니다.

키별 변수를 유지하기를 선호하는 경우, 동일한 Ruby-YAML 접근 방식을 사용하여 DISCOURSE_SMTP_*를 직접 설정하는 자매 패치를 제공할 수 있습니다(여전히 sed 없음), 하지만 SMTP_URL 경로는 가장 깔끔합니다. 왜냐하면 하나의 키, 하나의 기록, 하나의 인코딩 단계이기 때문입니다.
[/details]