콘텐츠로 이동

evo-dispatch 세션 중계 종단간 암호화(E2E) 규격 (초안 r2)

원천 · framework/evomind/web/workflow-project-evo-dispatch기준 · 릴리스 v0.1.0커밋 · 734af815파일 · docs/protocol/e2e.md기준선 · 2026-11-12

  • 대상: 세션 요청 공간 /mcp의 메시지만. 개발 요청(/mcp/dev)과 버그 신고(/mcp/bugs)는 범위 밖이다.
  • 기반 규격: client-protocol.md v1. 이 문서는 그 위의 선택 확장이며 schema_version은 "1" 그대로다.
  • 상태: 초안 r2 — r1은 (Human 승인), 서버는. r2는 워크그룹 자동 처리 표시(extensions.execution)를 서명 범위에 넣는다(§2.3). 사용자 결정 2026-11-03. 구현 Issue: (이 규격), (서버).
  • 알고리즘 조합 이름: evo-e2e-1

서버(dispatch 서버, DB, 운영자)가 메시지의 title·body·links를 읽지 못하게 한다. 결정된 범위(D1~D5)는 다음과 같다.

항목 정한 것
D1 막는 위협 서버가 가만히 읽는 경우(DB 유출, 운영자 열람). 서버가 공개키를 바꿔치기하는 능동 공격은 막지 못한다. 기기 지문 확인은 후속 Issue다
D2 보낸 쪽 서명 포함한다. 서버나 제3자가 가짜 요청을 끼워 넣지 못하게 한다
D3 웹 채팅 끝점 evo-service 백엔드(client_kind: evo-web). dispatch 서버에는 숨기지만 evo-service 서버는 읽을 수 있다
D4 전환 평문과 함께 쓰다가, 서버 설정 REQUIRE_E2E로 암호문만 받는다
D5 구현 순서 Evo CLI → evo-mac → evo-windows → evo-service 백엔드

못 지키는 것:

  • 메타데이터: 보낸 이·받는 이·시각·agent·workspace·kind·reply_to·글 크기는 서버가 평문으로 본다. 서버가 라우팅해야 한다.
  • 모델 제공자: 복호화된 평문은 모델 호출에 들어간다. 이 규격은 dispatch 서버를 막을 뿐이다.
  • 공개키 바꿔치기: 서버가 받는 기기의 공개키를 자기 것으로 바꾸고 중간에서 읽을 수 있다(§3.3).
  • 다운그레이드: REQUIRE_E2E를 켜기 전에는 서버가 암호문을 버리고 평문으로 바꿔 보내지 못할 뿐, 보내는 쪽이 평문을 고르는 것은 막을 수 없다.
  • 재전송: 서버가 서명된 같은 봉투를 다시 줄 수 있다(§5의 확인으로 줄인다).
용도 방식
내용 암호화 메시지마다 무작위 32바이트 내용 키 K. ChaCha20-Poly1305(RFC 8439), nonce 12바이트(부분마다 새로 무작위), 태그 16바이트
내용 키 전달 받는 기기마다 HPKE(RFC 9180) Base 모드로 K를 감싼다. KEM DHKEM(X25519, HKDF-SHA256)(0x0020), KDF HKDF-SHA256(0x0001), AEAD ChaCha20-Poly1305(0x0003). 감쌀 때마다 새 임시 키를 쓰고 sequence 0 한 번만 쓴다
보낸 이 인증 Ed25519(RFC 8032) 서명. HPKE Base는 보낸 이를 인증하지 않아 서명을 따로 붙인다
인코딩 모든 바이너리는 base64url, 패딩 없음(RFC 4648 §5)
  • 제목 부분: title의 UT 바이트(1~800바이트. 글자 수 200자는 보내는 쪽이 지킨다).
  • 본문 부분: UT JSON 객체 {"body": <문자열>, "links": [ ... ]}. links는 없으면 생략한다. 길이는 128KiB(131072바이트) 이하다. body 16384자·links 20개 같은 형식 규칙은 평문 규격(message.schema.json)과 같고 보내는 쪽과 받는 쪽이 검사한다(서버는 열 수 없다).

VER = evo-dispatch/e2e/1. 아래에서 ‖는 바이트 이어 붙이기, 0x00은 구분 바이트다. 문자열은 모두 UT이다.

  • ctx = sender_device ‖ 0x00 ‖ kind ‖ 0x00 ‖ to ‖ 0x00 ‖ reply_to ‖ 0x00 ‖ sent_at
    • kind는 request·reply. reply_to는 없으면 빈 문자열.
    • to는 받는 주소의 정규 문자열: user, agent:<이름>, workspace:<이름>, session:<client_kind>:<session_id>, workgroup:<workgroup_id>, workgroup_member:<workgroup_id>:<alias>(워크그룹 주소, workgroup.md. ).
    • 워크그룹 주소의 받을 기기는 dispatch_workgroups의 그 그룹 멤버 중 보내는 세션이 아닌 것(workgroup_member면 그 별칭 하나)이다. 활성 dispatch_sessions 행과 client_kind·session_id로 맞춰 기기 공개키를 찾고, 공개키가 없는 멤버가 하나라도 있으면 보내지 않는다.
    • sent_at은 봉투의 sent_at과 같은 문자열이다.
  • 제목 AAD = VER/title ‖ 0x00 ‖ ctx
  • 본문 AAD = VER/body ‖ 0x00 ‖ ctx
  • HPKE info = VER/hpke
  • HPKE AAD(기기마다) = VER/wrap ‖ 0x00 ‖ sender_device ‖ 0x00 ‖ recipient_device ‖ 0x00 ‖ kind ‖ 0x00 ‖ to ‖ 0x00 ‖ reply_to ‖ 0x00 ‖ sent_at
  • 서명 입력 = VER/sig ‖ 0x00 ‖ ctx ‖ 0x00 ‖ SHA-256(title_ct 원문 바이트) ‖ SHA-256(ct 원문 바이트)

2.3 r2: 실행 표시를 서명에 넣기 (봉투 v 2)

섹션 제목: “2.3 r2: 실행 표시를 서명에 넣기 (봉투 v 2)”

워크그룹 메시지의 extensions.execution(workgroup.md §4)은 평문으로 오가서, 서버나 중간자가 auto를 켜거나 hop을 낮출 수 있다. r2는 이 값을 서명 범위에 넣는다. 변경은 이것뿐이다.

  • 봉투 v가 2이면 VER는 evo-dispatch/e2e/2다. VER로 시작하는 모든 문자열(AAD·info·서명 입력)에 이 값을 쓰므로, r1만 아는 받는 쪽은 서명을 확인하지 못해 버린다(조용히 잘못 처리하지 않는다).
  • ctx의 끝에 한 필드가 붙는다: ctx = sender_device ‖ 0x00 ‖ kind ‖ 0x00 ‖ to ‖ 0x00 ‖ reply_to ‖ 0x00 ‖ sent_at ‖ 0x00 ‖ execution
  • execution은 메시지의 extensions.execution에서 만든 정규 문자열이다. 없으면 빈 문자열이다.
    • 있으면 auto=<0|1>;wg=<workgroup_id>;hop=<n>. auto는 true면 1, false면 0. wg는 소문자 UUID. hop은 생략했어도 0을 넣는 10진수(앞의 0 없음).
  • 보내는 쪽은 extensions.execution을 두는 메시지에 반드시 v 2를 쓴다. 두지 않는 메시지는 v 1을 그대로 써도 되고(execution이 빈 문자열인 v 2도 된다), 그러면 r1 클라이언트와 계속 통한다.
  • 받는 쪽은 받은 extensions.execution에서 같은 규칙으로 execution을 다시 만들어 서명을 확인한다. 서버나 중간자가 값을 바꿨으면 서명이 맞지 않아 버린다. 자동 처리는 v 2 봉투의 서명이 확인됐을 때만 한다(v 1 봉투의 실행 표시는 무시하고 사람에게 보여 준다).
  • 서버는 extensions.execution이 있는 암호화 메시지가 enc.v 2가 아니면 invalid_request로 거부한다(형식 검사뿐, 서명은 열지 않는다). v가 1·2 말고 다른 값이어도 invalid_request다.
  • 시험 벡터: r1 vector.json과 r2 vector-r2.json(워크그룹 주소 workgroup:<id>, 실행 표시 auto=1;wg=<id>;hop=0, 봉투 v 2)이 있다(§예시와 시험).

서명이 kind·to·reply_to·sent_at을 덮으므로, 서버가 요청을 답장으로 바꾸거나 다른 주소로 돌려 보내면 받는 쪽이 알아챈다.

  • 기기(device_id)마다 키 쌍 두 개를 만든다: 키 전달용 X25519(kem), 서명용 Ed25519(sign). 개인키는 기기 밖으로 나가지 않는다.
  • 보관: Evo CLI는 ~/.evo/의 0600 파일(device-id와 같은 방식), evo-mac은 Keychain, evo-windows는 OS 보호 저장(safeStorage), evo-service 백엔드는 DB에 서버 비밀로 감싸 보관한다(웹 채팅 사용자마다). 이는 권고이고 구현 Issue에서 정한다.
  • 키를 잃으면 그 기기로 온 안 읽은 메시지는 풀 수 없다(보존 기간이 짧아 영향이 작다). 새 키를 만들어 다시 올리면 된다.

활성 신호(POST /v1/presence)에 선택 항목 e2e를 싣는다. 스키마는 schemas/presence.schema.json이다.

"e2e": { "suite": "evo-e2e-1", "kem_public": "<43자>", "sign_public": "<43자>" }
  • kem_public·sign_public은 각각 32바이트 공개키의 base64url이다.
  • 서버는 device_id에 묶어 마지막 값을 저장한다. 같은 device_id가 키를 바꾸면 새 값으로 덮는다(키 교체).
  • e2e가 있는 세션만 암호화 메시지를 받을 수 있다. 없는 세션은 평문만 받는다.
  • 보내는 쪽은 보내기 직전에 dispatch_sessions를 불러 받을 기기의 공개키를 얻는다. 결과의 각 세션에 device_id와 (있으면) e2e가 추가된다.
  • 받는 쪽이 보낸 이의 서명 공개키를 알 방법: 서버가 dispatch_inbox·dispatch_read 결과에 enc가 있는 메시지마다 from_keys(보낸 기기의 e2e 값)를 채운다. 보낸 세션이 이미 끝났어도 키는 서버가 보존 기간 동안 device_id별로 가진다.
  • 서버가 준 공개키는 신뢰 근거가 아니다. 서버가 능동적으로 속이면 막지 못한다(D1). 받는 쪽은 처음 본 device_id의 키를 기억하고(TOFU), 이후 값이 바뀌면 사용자에게 알리는 것을 권고한다(SHOULD). 지문 확인 UX는 후속 Issue다.

스키마는 schemas/message.schema.json의 $defs/envelope다. dispatch_send가 받고, 메시지의 enc 필드로 저장·전달된다.

필드 내용
v 1, 또는 r2의 2(§2.3)
suite evo-e2e-1
sender_device 보낸 기기 device_id. 서버가 보낸 세션의 device_id와 같은지 본다
sent_at 보낸 시각(UTC, 평문 스키마의 timestamp 형식)
title_ct base64url(nonce 12 ‖ 암호문 ‖ 태그 16). 내용 키 K로 제목 AAD
ct base64url(nonce 12 ‖ 암호문 ‖ 태그 16). K로 본문 AAD. 본문 부분이다
ct_sha256 ct 원문 바이트의 SHA-256(base64url). ct가 빠진 inbox 결과에서도 서명을 확인하려는 값이다
recipients[] device_id, enc(HPKE 캡슐, 32바이트), wrapped_key(감싼 K, 48바이트)
sig 서명 입력(§2.2)에 대한 Ed25519 서명 64바이트
  • recipients[]는 1~64개. 같은 기기의 세션이 여럿이어도 기기당 한 항목이다.
  • dispatch_send에서는 ct가 필수다. 서버가 돌려주는 모양:
    • 받는 세션의 dispatch_inbox: ct를 빼고, recipients는 호출 기기 항목만 남긴다. from_keys를 붙인다. title은 없다.
    • 받는 세션의 dispatch_read: ct를 포함하고 recipients는 호출 기기 항목만 남긴다. from_keys를 붙인다. body는 없다.
    • 보낸 세션(dispatch_send 응답, dispatch_read 상태 조회): ct를 빼고 recipients 항목은 전부 둔다. from_keys는 없다.
    • 서명은 recipients를 덮지 않으므로 자르는 것이 서명을 깨지 않는다.
  • 평문 필드(title, body, links)와 enc를 동시에 두지 않는다. 둘 다 없거나 둘 다 있으면 invalid_request다.
  1. dispatch_sessions로 받을 세션을 정하고 그 기기들의 e2e 공개키를 얻는다. 받을 세션 중 e2e가 없는 것이 있으면 §7로 간다.
  2. 무작위 K(32바이트)를 만든다. 제목·본문 부분을 §2.2의 AAD로 각각 새 nonce(12바이트 무작위)와 함께 암호화한다.
  3. 받는 기기마다 HPKE로 K를 감싼다. 보내는 기기 자신의 다른 세션이 받는 세션이면 자기 기기도 받는 기기에 넣는다.
  4. 서명 입력을 만들어 sign 개인키로 서명한다.
  5. dispatch_send를 to, kind, reply_to, enc로 부른다(title·body·links는 두지 않는다).
  6. recipient_mismatch를 받으면 응답의 missing 기기 목록을 보고 1단계부터 다시 한다(받는 세션이 그사이 바뀐 것). 두세 번 되풀이하면 사용자에게 알린다.
  1. dispatch_inbox로 목록을 얻는다. enc 메시지는 title_ct·ct_sha256·sig와 내 기기 항목, from_keys를 준다.
  2. 서명을 먼저 확인한다. 서명 입력의 SHA-256(ct) 자리에는 ct_sha256를 쓴다. 확인에 실패하면 그 메시지는 버리고 제목을 보여 주지 않는다.
  3. 내 기기 항목의 enc·wrapped_key로 K를 푼 다음 title_ct를 풀어 제목을 보여 준다.
  4. dispatch_read로 ct를 받으면 SHA-256(ct)가 ct_sha256와 같은지 보고, K로 본문 부분을 풀어 JSON을 읽는다.
  5. 풀거나 검증이 실패하면 그 메시지는 쓸 수 없다. 사용자에게 알리고 평문으로 취급하지 않는다.
  6. 확인할 것(SHOULD): (sender_device, 본문 nonce) 중복이면 같은 메시지를 다시 받은 것으로 보고 버린다. sent_at이 서버의 created_at과 10분 넘게 다르면 의심 표시를 한다.

복호화한 내용도 실행 명령이 아니다. 기존 규칙(규격 §10)이 그대로 적용된다. 서명은 보낸 기기를 증명할 뿐 내용이 안전하다는 뜻이 아니다.

서버 구현은 다(설정 REQUIRE_E2E, 환경변수 이름 그대로). 서버는 봉투의 내용을 열지 않는다. 본다고 정한 것은 다음뿐이다.

  • 스키마 검사(필드 형식, 길이 상한). ct는 base64url 174800자 이하, 요청 본문은 기존 상한(256KiB) 이하다(64기기 × 약 200자를 더해도 안에 든다). 정의되지 않은 필드는 거부한다.
  • ct_sha256이 ct 원문의 SHA-256과 같은지(서버가 계산해 본다. 내용은 열지 않는다). 깨진 봉투를 일찍 거른다.
  • sender_device가 호출 세션의 device_id와 같다.
  • 받는 세션 집합(규격 §3)을 계산해, 그 세션들의 device_id가 모두 recipients[]에 있어야 한다. 하나라도 없으면 거부하고 저장하지 않는다(recipient_mismatch, 응답에 없는 device_id 목록 missing). recipients[]에 받는 세션이 아닌 기기가 더 있는 것은 허용한다.
  • 받는 세션 중 e2e 공개키가 없는 세션이 있으면 recipient_mismatch다(missing에 그 기기).
  • 서버 설정 REQUIRE_E2E=true이면 평문 dispatch_send를 e2e_required로 거부한다. 앱 기본값은 꺼짐이고, 배포 compose는 기본적으로 켠다(2026-11-04 전환 종료).
  • 로그·메트릭에 봉투 내용(title_ct, ct, wrapped_key, sig)과 개인키를 남기지 않는다. 공개키 전체도 남기지 않는다.
  • 저장·보존·읽음·만료·정리는 평문 메시지와 같다(규격 §7).
  1. 서버가 enc를 받을 수 있게 되고 REQUIRE_E2E는 꺼져 있다. 평문과 암호문이 함께 흐른다.
  2. 클라이언트는 받을 세션이 모두 e2e를 올렸으면 암호화해서 보낸다. 일부라도 못 하면 사용자에게 알리고 평문으로 보낼지 정하게 한다. 몰래 평문으로 내려가지 않는다. 클라이언트 구현에 따라 평문 선택지를 주지 않고 보내기를 막는 쪽도 있다(evo-windows, §11.4). 이 규격은 평문으로 내려가지 않는 것만 요구하고 선택지를 주는 방식은 정하지 않는다.
  3. 모든 클라이언트가 지원하면 REQUIRE_E2E=true로 켠다. 켜면 평문 dispatch_send와 e2e 없는 세션으로의 보내기가 거부된다. 2026-11-04 켰다(cli·mac·windows·web 지원 완료, android·ios는 클라이언트 없음). 배포 compose 기본값도 true다.
  4. 평문 메시지를 받는 쪽은 그대로 받는다(옛 클라이언트와 전환 기간의 호환).

기존 코드(규격 §9)에 다음을 더한다.

코드 HTTP 언제
recipient_mismatch 409 봉투의 recipients[]가 받는 세션의 기기를 다 덮지 못한다, 또는 받는 세션에 e2e 키가 없다. 응답에 missing(기기 device_id 목록, 정렬)이 있다
e2e_required 400 REQUIRE_E2E가 켜진 서버에 평문으로 보냈다

MCP tool의 오류 결과는 같은 모양(error, message, 그리고 missing)이다.

그 밖의 형식 오류(평문과 enc 동시 지정, 스키마 위반, 크기 초과, sender_device 불일치)는 invalid_request다.

examples/e2e/에 있다. 시험용 키만 쓴다(RFC 8032 7.1 TEST 1의 서명 키, RFC 9180 A.2의 X25519 키). 실제 키·토큰이 아니다.

파일 내용
vector.json 입력(평문, K, nonce, 임시 키)과 출력(봉투). 받는 기기 두 개
vector-r2.json r2 벡터. 봉투 v 2, to는 workgroup:<id>, inputs.execution·inputs.extensions가 서명된 실행 표시. 서명 입력 앞부분은 evo-dispatch/e2e/2/sig‖0x00‖sender_device‖0x00‖request‖0x00‖workgroup:<id>‖0x00‖(빈 reply_to)‖0x00‖sent_at‖0x00‖auto=1;wg=<id>;hop=0‖0x00‖… 이다
GenerateVector.java 벡터 생성기(JDK 25 표준 라이브러리). 먼저 RFC 9180 A.2의 값으로 HPKE 구현을 자체 점검한다
verify.mjs 독립 검증기(Node 표준 crypto만 사용, 생성기와 코드 공유 없음). 서명·ct_sha256·내용 키·제목·본문을 확인하고 변조·kind·to 바꿔치기가 거부되는지 본다
터미널 창
java docs/protocol/examples/e2e/GenerateVector.java > /tmp/vector.json # 재생성, vector.json과 같아야 한다
node docs/protocol/examples/e2e/verify.mjs # 전부 통과해야 한다
java docs/protocol/examples/e2e/GenerateVector.java r2 > /tmp/vector-r2.json # 재생성, vector-r2.json과 같아야 한다
node docs/protocol/examples/e2e/verify.mjs vector-r2.json # r2도 전부 통과해야 한다

새 구현은 vector.json의 envelope를 받는 쪽으로 풀어 inputs와 같은 제목·본문이 나오는지, 보내는 쪽으로 같은 입력에서 같은 봉투가 나오는지(임시 키·nonce를 고정한 시험 모드에서) 확인한다.

  • HPKE 지원: Go 표준 crypto/hpke(문서 확인, 도입 버전 미확인), CryptoKit(macOS 14 이상으로 기억하나 미확인, evo-mac 최소는 macOS 13), Node(내장 HPKE 없음, 위 verify.mjs처럼 X25519·HKDF·ChaCha20-Poly1305로 직접 구성 가능), Java(evo-service, 미확인). 모르는 곳은 위 벡터가 맞는지 보고 직접 구성해도 된다.
  • 기기 지문 확인과 키 바꿔치기 방어(후속 Issue).
  • 다중 기기의 키 교체·폐기 절차, 키 백업. 이 규격은 키를 잃으면 안 읽은 메시지를 잃는 것을 받아들인다.
  • 개발 요청·버그 신고의 암호화(범위 밖으로 결정).
  • 서버가 dispatch_sessions·inbox에 싣는 필드 이름은 에서 device_id, e2e, from_keys로 확정했다. examples/valid·invalid에 e2e를 실은 활성 신호 예시가 있다(서버 시험이 실제 서버로 보낸다).

11. 개발 서버 실측 기록 (2026-11-04)

섹션 제목: “11. 개발 서버 실측 기록 (2026-11-04)”

REQUIRE_E2E=true인 개발 서버에서 암호 메시지가 웹까지 실제로 흐르는 것을 확인했다. 아래는 규격 문장이 아니라 확인한 사실의 기록이다(§1~§10의 규범은 그대로다).

항목 값
서버 개발 서버(REQUIRE_E2E=true, 배포 compose 기본값)
보내는 곳 evo-cli 1.0.0-dev.1565, 개인용 로그인(evo-personal-auth)
받는 곳 일반용 웹 — evo-service 백엔드가 client_kind: evo-web으로 활성 신호를 보낸다(§1 D3). 브라우저 탭이 열려 있는 동안만
시험 계정 tmp-evo-check(시험 전용)
주소 to=user — 같은 사용자의 활성 세션 중 보낸 세션을 뺀 나머지
  1. 브라우저에서 웹 탭을 연다 → evo-web 세션이 dispatch_sessions에 나타난다(evo-cli가 확인).
  2. evo-cli가 dispatch_send로 암호 메시지 1건을 보낸다 → 응답은 "encrypted for 1 device(s)"(recipients[]에 받는 기기 1개), 메시지 id 63375e5e-9f0f-4f92-9e1d-599239d2076b.
  3. 웹 화면에서 본문이 복호화되어 표시된다(evo-service 확인).
  • 결과: 성공. 받는 세션이 evo-web 하나였고, 서버가 그 기기를 §6 규칙대로 recipients[]에 고정해 전달했다.
  • 서명 확인은 통과해야 본문이 열리는 구조(§5.2)라 본문 표시로 갈음한다. 웹 화면의 경고 문구는 따로 점검하지 않았다.
  • kind는 request로 보냈다. dispatch_send의 kind는 request·reply뿐이라 시험 메시지도 그중 하나다.

11.3 “웹 세션이 안 보인다” 진단 순서

섹션 제목: “11.3 “웹 세션이 안 보인다” 진단 순서”

이번에 원인이 세 번 갈렸다. 위에서부터 본다.

  1. 브라우저 탭이 열려 있는가 — 웹 활성 신호는 탭이 임대(lease)를 갱신하는 동안에만 evo-service 서버가 보낸다. 탭을 닫으면 90초 안에 세션이 사라진다(§4 유효 시간).
  2. 로그인한 계정의 issuer가 같은가 — 같은 사람이라도 조직용(evo-auth)과 개인용(evo-personal-auth) 로그인은 iss+sub가 달라 서로 다른 사용자다. 시험 계정은 개인용이다.
  3. 개발 서버를 방금 재배포했는가 — 서버가 메모리에 들고 있던 사용자 SSO 토큰이 사라져, 사용자가 다시 로그인하기 전까지 웹 활성 신호가 나가지 않는다. 재로그인하면 다시 보인다.
  4. 그 세션에 e2e 공개키가 있는가 — dispatch_sessions에서 e2e가 비어 있으면 암호 메시지를 받을 수 없어 보내기가 recipient_mismatch(missing)로 거부된다(§8).

보내는 쪽이 recipient_mismatch를 받았는데 dispatch_sessions에 세션은 보인다면 4번, 세션 자체가 안 보인다면 1~3번이다.

11.4 클라이언트 쪽 평문 거부 (evo-windows 회신, 2026-11-04)

섹션 제목: “11.4 클라이언트 쪽 평문 거부 (evo-windows 회신, 2026-11-04)”

evo-windows 개발 에이전트가 2026-11-04 회신(개발 요청 1d048058)에서 앞선 보고의 문장을 정정하며 자기 앱 동작을 알려 왔다. 규격 문장이 아니라 확인한 사실의 기록이다.

  • 모든 dispatch 호출이 e2eCall을 지난다. 기기 키가 있으면 암호화해 보내고, 키를 읽지 못하면(키 파일을 못 읽는 등) 서버로 보내기 전에 앱이 막고 dispatch: not sent — this device has no e2e key (<이유>); <평문 거부 안내>를 보여 준다.
  • 받는 세션에 키가 없을 때도 같은 형식으로 not sent — N receiving session(s) have no e2e key를 보여 준다.
  • 그래서 앱은 정상 동작에서 서버의 e2e_required(400)를 받지 않는다. 이 거부 문구는 앱의 단위 시험이 확인했고, 개발 서버에서 앱으로 평문을 보내 400을 받는 시험은 앱 구조상 만들 수 없다고 evo-windows가 밝혔다.
  • 서버 쪽 거부 경로는 evo-dispatch의 서버 시험 E2eRequiredTest가 덮는다(dispatch.e2e.required=true에서 평문 dispatch_send는 e2e_required이고 메시지를 저장하지 않는다).
  • §11.1~§11.3의 실측은 REQUIRE_E2E=true인 서버에서 한 것이다. 앞선 회신 10c6beae의 “서버 설정을 켜지 않은 상태에서의 시험”이라는 문장은 설정을 확인하지 않고 쓴 잘못된 서술이었고 evo-windows가 이를 정정했다.