Skip to content

호스티드에서 러너 자격을 클라우드에 절대 올리지 않음 — 선택권이 아니라 구조로 강제 - #325

Merged
beyondworks merged 3 commits into
mainfrom
fix/creds-never-hosted
Aug 29, 2026
Merged

호스티드에서 러너 자격을 클라우드에 절대 올리지 않음 — 선택권이 아니라 구조로 강제#325
beyondworks merged 3 commits into
mainfrom
fix/creds-never-hosted

Conversation

@beyondworks

Copy link
Copy Markdown
Owner

지시 (유건 2026-08-29)

"열쇠를 볼 수 있는 건 사용자 본인이어야 한다 — 선택권이 아니라 무조건."

직전 #323이 넣은 회사 단위 옵트아웃(credSync 토글)을, 호스티드(Argo 클라우드)에서는 구조적 강제로 승격한다.

변경

  • hostedCredsOff() = !(loadSyncCreds() && serviceCredsAllowed()) — 세션 모드(호스티드) 판정(순수 env, 플랜 조회와 독립).
  • syncCompany 함수 내부const noSecrets = !!opts.noSecrets || hostedCredsOff(); — 강제를 호출부가 아니라 함수 안에 걸어 새 호출 경로가 생겨도 우회 불가(자격 증명 클라우드 동기화 — 정직 고지 + 회사 단위 옵트아웃(클라우드 사본 회수) #323 검수가 잡은 HIGH-1 계열 원천 차단). 회수·불가시·마커 보호 로직 전부 재사용.
  • 서비스 모드(셀프호스트·워커 = 자기 인프라·자기 열쇠)는 credSync 토글이 그대로 선택권.
  • UI: connections route가 credHosted 내림. CredSyncRow가 호스티드면 토글 대신 "이 기기에만" + 정직 문구. 서비스 모드만 토글.
  • 테스트 H1·H2: opts·credSync 무관하게 호스티드는 자격 미push + 과거 사본 자동 회수. 함수 내부 게이트 변이 red 실증.

검수 반영 (2라운드, 최종 APPROVED)

  • HIGH-2(free 회수 미실행): 호스티드 무료 계정은 cycle에서 syncCompany가 스킵되고 RLS도 자격 쓰기를 거부해 과거 사본 회수가 실제로는 안 된다. 절대 문구 "누구도 볼 수 없습니다"를 "새로 저장되는 자격은 본인만"으로 정확히 축소하고, free 회수 보류 caveat 원복. 근본(free 즉시회수 RLS 예외)은 이월.
  • 워커 경로: 워커(ARGO_TENANT_OWNER)는 hostedCredsOff=false라 자격이 갈 수 있는데 문구가 "새 기기와 동일"로 서술했다. 워커 언급 제거 — 자격 접근이 전제인 위임 모델이라 이 보장 밖(별도 트랙)임을 docs에 명시.

코드가 실제 보장하는 것 (검수 확인)

호스티드에서 자격은 free/pro 무관하게 새로 클라우드에 올라가지 않는다hostedCredsOff()가 플랜과 독립이라, syncCompany가 실행되는 모든 인스턴스에서 자격 3종이 불가시. 과거 사본 회수는 Pro·체험 한정(무료는 보류, Pro·체험 복귀 시 실행).

이월 (코드 트랙, 문서에 명시)

  • 무료 상태에서의 즉시 회수 = RLS에 자격-마커 예외(마이그레이션 + 라이브 검증 필요)
  • 워커 위임 동의 = E2EE 트랙과 함께

검증

전체 1017/1017 · next build · 격리 서버(:3113) 호스티드 UI 실측(토글 소거·"이 기기에만"). 분리 보안 검수 2라운드 APPROVED. 라이브 Supabase RLS 응답은 미검증(SQL 원문·코드 경로 추적 — 자격 없음).

🤖 Generated with Claude Code

beyondworks and others added 3 commits August 29, 2026 11:41
유건 지시 2026-08-29: "열쇠를 볼 수 있는 건 사용자 본인이어야 한다 — 선택권이 아니라 무조건."
호스티드(Argo 클라우드) 모드에서는 자격 3종(.secrets.json·connections.json·mcp.json)이 어떤
경로로도 클라우드에 올라가지 않도록 구조적으로 강제한다.

- hostedCredsOff() = 세션 모드(호스티드) 판정. syncCompany 내부에서 noSecrets에 OR로 합류 —
  강제를 호출부가 아니라 함수 안에 걸어 새 호출 경로가 생겨도 우회 불가(보관 전파가 opts를
  빠뜨렸던 HIGH-1 계열의 원천 차단). 회수·불가시·마커 보호 로직 전부 재사용.
- 서비스 모드(셀프호스트·워커 = 자기 인프라·자기 열쇠, 운영자=본인)는 credSync 토글이 그대로
  선택권으로 남는다.
- UI: connections route가 credHosted 내림. SyncCard의 CredSyncRow가 호스티드면 토글 대신
  "이 기기에만" 배지 + 정직 문구("운영자 포함 본인 외 아무도 볼 수 없음"). 서비스 모드만 토글.
- i18n: offHelp를 모드 무관 참으로 재교정(자격은 클라우드로 안 감), credHostedNote·
  credDeviceOnly 신설, credNote는 셀프호스트 전용으로 한정. 전부 ko/en.
- docs/privacy-sync.md 재작성: 자격=호스티드 미동기(본인만) / 회사 데이터=봉투 암호화로 가되
  열쇠는 서버(운영자 복호화 가능, E2EE는 별도 트랙)로 범주 분리.
- 테스트 H1·H2: opts·credSync 무관하게 호스티드는 자격 미push + 과거 사본 자동 회수.
  함수 내부 게이트 변이 red 실증(H1·H2 red). 기존 PIN/T1~T7은 서비스 모드 의미론으로 명시.

전체 1005/1005 · next build 통과 · 격리 서버(:3113) 호스티드 UI 실측(토글 소거·"이 기기에만").

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
분리 보안 검수 CHANGES_REQUESTED: 코드 강제(함수 내부 게이트)는 견고하나 문구가 코드보다 앞섰다.
코드가 실제 보장하는 것에 정합화한다(근본 코드 수정은 이월 — 아래).

- HIGH-2(free 회수 미실행): 호스티드 무료 계정은 cycle에서 syncCompany가 스킵(freePlan &&
  !restoring)되고 RLS도 자격 쓰기를 거부해, 과거 사본 회수가 실행되지 않는다. 절대 문구
  "누구도 볼 수 없습니다"를 "새로 저장되는 자격은 …"으로 한정하고, free 회수 보류 caveat 원복
  (이전 PR credOffHelp가 이미 알던 HIGH-2 교훈을 호스티드 문구에서 반복하지 않게).
- 워커 경로: 워커(ARGO_TENANT_OWNER)는 hostedCredsOff=false라 자격이 갈 수 있는데 문구가
  "새 기기와 동일(자격 미도달)"로 서술했다. 워커 언급 제거 — 워커는 자격 접근이 전제인 위임
  모델이라 이 보장 밖(별도 설계 트랙)임을 docs에 명시.
- credHostedNote(ko/en)·docs/privacy-sync.md 동기 수정. 코드 변경 없음(문구만).

이월(코드 트랙): (1) 무료 상태에서의 즉시 회수 = RLS에 자격-마커 예외(마이그레이션+라이브 검증
필요), (2) 워커 위임 동의 = E2EE 트랙과 함께. 완전한 "모든 과거 사본까지 무조건 본인만"은 이 둘.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
재검수 APPROVED의 LOW 관찰: '표시가 없어도 회수는 진행됩니다' 첫 문장이 단독으로는 무조건형
이라 다음 소제목이 정정하기 전까지 오해 소지. 첫 문장에 '동기화가 도는 상태(Pro·체험)' 조건을
인라인으로 넣어 문장 단독으로도 정확하게.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@beyondworks
beyondworks merged commit 1cffb11 into main Aug 29, 2026
2 checks passed
@beyondworks
beyondworks deleted the fix/creds-never-hosted branch August 29, 2026 02:47
beyondworks added a commit that referenced this pull request Aug 29, 2026
* feat(e2ee): P1 — 켜기·기기 승인(지문 대조)·복구 코드·재봉인·잠김 게이트

권고안 승인분(E2EE-DESIGN.md §6~§7)의 단계 1 실행 코드. DEK 보유가 곧 스위치(디스크 사실 파생).

- 켜기(enable): 이 기기가 DEK 생성 → 자기 랩(wrapped_deks)·복구 랩(recovery_wraps) 서버 기록(전부
  암호문) → 성공 후에만 로컬 DEK 확정(반쪽 활성 방지) → 전 회사 재봉인 마커 + nudge. 복구 코드는
  1회만 반환·서버 무저장. 플랜 게이트(Pro·체험 — free는 재봉인 push가 RLS에 막혀 "켰는데 암호화
  안 됨"이 되므로 사전 정직 차단, #325 HIGH-2 원칙).
- 기기 승인(approve): DEK 보유 기기가 대상 공개키로 랩해 wrapped_deks에 기록. 사용자는 양 화면의
  지문(SAS) 대조 후 승인 — 서버의 공개키 바꿔치기는 지문 불일치로 드러난다. 새 기기는 claim
  (자기 랩 회수·cycle 60초 자동 시도)으로 잠김 해제. 복구 코드 폴백(recover) 포함.
- v3 전량 봉인: DEK 보유 기기의 push는 파일·매니페스트 전부 v3. **매니페스트 봉인이 곧 게이트** —
  열쇠 없는 기기(구버전 포함)는 매니페스트 개봉이 보류로 떨어져 회사 동기화가 통째로 안전 정지
  (파일 단위 오염·오삭제 원천 불가, 기존 "매니페스트 읽기 실패 보류" 경로 재사용).
- 재봉인(reseal): 켠 직후 1회, 무변경 파일도 blob을 v3로 되덮음(메타·base 불변 — 타 기기 diff
  무영향). WS_ROOT 마커(.e2ee-reseal.json, 셸 금고 등재)로 회사별 재시도.
- 기기 제거(revoke): 랩·공개키 행 삭제(재등록 가능). DEK 회전은 후속 — UI가 사실 그대로 고지.
  pubkey 최초 등록 후 불변(ignoreDuplicates — §10.5 하이재킹 차단), keyMismatch 정직 표기.
- UI: 설정 E2EE 카드(꺼짐/잠김/켜짐 3상태, 구버전 확인 체크, 복구 코드 1회 모달, 지문 대조 안내).
  i18n ko/en 전량.
- 테스트 6종(복구 왕복·관용 입력·지문·claim·v3 전환·잠김 보류·재봉인) + 변이 red 2종(sealFor
  dek 분기·reseal 분기).

전체 스위트 fail 0 · next build 통과. 라이브(상주) 검증은 배포 후: 카드 렌더·GET 계약 확인 —
실제 켜기는 전 기기 업데이트 후 유건 판단(구버전 혼재 시 그 기기 동기화가 잠김 상태로 정지).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(e2ee): P1 분리 검수 반영 — 세대 다운그레이드 게이트·제거 고지 정정·부분 실패 자가 치유

- HIGH-1(실증됨): 열쇠 없는 기기의 정상 사이클이 재읽기에서 v3 매니페스트를 만나도 catch로 삼키고
  자기 평문 매니페스트를 되써 게이트를 다운그레이드했다. 재읽기를 두 단계로 분리 — 네트워크 실패는
  기존 관용, **받았는데 열 수 없는 봉투 세대(isEnvelopeGeneration)는 사이클 전체 보류**(다음 사이클
  초기 읽기가 정식 잠김 경로로 수렴). 회귀 테스트 = 검수 재현 시나리오 그대로(초기 읽기 1회 평문·
  재읽기 v3 → 보류 + 원격 v3 유지), 게이트 제거 변이 red 실증.
- HIGH-2: 기기 제거 고지가 사실과 반대("새 동기화를 열 수 없게 됩니다")였다 — 행 삭제는 "새 열쇠
  미배포"일 뿐, 이미 열쇠 받은 기기는 로컬 열쇠+세션이 살아있는 한 계속 연다. 문구를 사실대로
  정정하고 실효 조치(세션 차단·비밀번호 변경)를 안내. 제거·승인에 확인 모달(승인 = 지문 대조 확인).
- MEDIUM-1: enable 반쪽 잔재(자기 랩만 기록 후 복구 랩 실패)가 "다른 기기에서 켜짐" 오판으로 영구
  교착 — 판정에서 자기 행 제외 + 자기 랩 존재 시 openDekWrap로 같은 DEK 이어가기(재시도 = 자가 치유).
- MEDIUM-2: 재봉인 완결을 파일 실패 0으로 조건화 — 종전엔 개별 push 실패 파일이 영구 구세대로
  남았다(마커 잔존 → 회사째 재시도).
- LOW: recover 5초 스로틀(scrypt CPU 증폭 방지), TRIAL_DAYS 상수 사용(하드코딩 드리프트 제거).

테스트 7/7(세대 게이트 신설 포함) · 전체 스위트 fail 0 · next build 통과.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant