Skip to content

fix(sessions): harden full-access follow-ups - #167

Merged
Yoonwoo-Ha merged 4 commits into
mainfrom
fix/full-access-followups
Sep 21, 2026
Merged

Yoonwoo-Ha merged 4 commits into
mainfrom
fix/full-access-followups

Conversation

@Yoonwoo-Ha

@Yoonwoo-Ha Yoonwoo-Ha commented Sep 21, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • distinguish BRE-only escapes such as grep '\(a\|b\)' from ordinary inline math while allowing units such as speed \(v\)/s
  • memoize Markdown normalization and consolidate the external spawn CLI type onto the shared contract
  • tag full-access tmux sessions, propagate the tag through discovery, and retain a visible sidebar warning badge
  • add the server-authoritative CHATMUX_DISABLE_FULL_ACCESS=1 kill switch to capability discovery and spawn enforcement
  • document the deployment control and persistent warning behavior in the provider guide and localized READMEs

Follow-up to #165, based on the 1.12.0 main from #166.

Verification

  • npm run verify on 1.12.0
    • Rust: 29 passed
    • server: 1,863 passed
    • real tmux/PTY: 50 passed
    • client: 746 passed
    • lint, identity check, and production builds passed

@Yoonwoo-Ha

Copy link
Copy Markdown
Collaborator Author

리뷰 (#165 후속)

브랜치를 체크아웃해서 normalizeLatexMathDelimiters 를 직접 실행하고, full-access 전파 경로를 따라 읽었습니다.

#165 리뷰에서 남긴 항목 전부 구현 확인 ✅

지적 이 PR
Markdown.tsxcontentuseMemo 없음 useMemo([children]) 추가
ExternalSpawnCli 타입 이중 정의 ExternalLocalCliKind = ExternalSpawnCli 로 통합
full-access 세션을 생성 이후 식별할 수 없음 tmux @chatmux_full_access 태그 → discovery 전파 → 사이드바 경고 배지
배포 단위 킬스위치 없음 CHATMUX_DISABLE_FULL_ACCESS=1
서버 capability 가 클라이언트에서 미사용 UI 가 /api/providers/capabilities 를 읽도록 변경, 실패 시 fail-closed

특히 꼼꼼했던 부분:

  • 킬스위치가 3중입니다 — capability 응답, 라우트, spawnExternalCliSession 자체.
  • stopAgentProcessInPane 에서 @chatmux_full_access 도 함께 제거해 좀비 배지를 막습니다.
  • classifyExternalSessionspane.taggedKind === kind 가드.
  • host snapshot 필드가 11 → 12 개로 늘었는데 legacyLiveFormatfields.length < 11 이라 구버전 peer(11 필드)에서도 깨지지 않습니다.
  • mergeExternalDiscoveryRows 가 재사용 메타데이터에서 fullAccess 를 명시적으로 버려, 배지가 이전 상태를 물고 가지 않습니다.

🟡 BRE 판별기가 엉뚱한 위치를 검사합니다

src/components/chat/utils/chatFormatting.ts:49

inlineMathShouldNormalize\(\) 사이 본문만 검사합니다. 그런데 BRE 에서 \+, \?, \{n,m\}, 역참조 \1~\9 는 그룹 뒤에 붙는 후위 연산자입니다.

grep '\(a\)\1'        → $a$\1         ❌ 잡지 못함
grep '\(ab\)\{2,3\}'  → $ab$\{2,3\}   ❌
grep '\(ab\)\+'       → $ab$\+        ❌
grep '\(abc\)'        → $abc$         ❌ (연산자 없는 순수 그룹)
grep '\(a\|b\)'       → RAW           ✅ (본문 안에 있는 유일한 토큰)

그리고 유일하게 동작하는 \| 가 하필 LaTeX 와 충돌합니다. \| 는 노름 기호(‖)입니다.

the norm \(\|x\|\)      → RAW  ❌
\(\|x\|_2 \le 1\)       → RAW  ❌
lines \(a \| b\)        → RAW  ❌ (평행)
the set \(\{1,2\}\)     → RAW  ❌ (interval 정규식과 충돌)

\(\{1, 2\}\) 처럼 쉼표 뒤에 공백이 있으면 통과하지만, 공백이 없으면 걸립니다.

제안: 본문 대신 문맥을 보기

\( 바로 앞이 '" 면 거부하고(셸에서 따옴표로 감싼 패턴), \) 뒤 짧은 구간에서 후위 BRE 연산자를 찾는 방식입니다.

function inlineMathShouldNormalize(text: string, openIndex: number, closeIndex: number): boolean {
  const before = text[openIndex - 1] ?? '';
  // A shell-quoted pattern is a regex, not math.
  if (before === "'" || before === '"') return false;
  // BRE postfix operators follow the group, not its body.
  const after = text.slice(closeIndex + 2, closeIndex + 8);
  return !/^\\(?:[+?]|[1-9]|\{\d+(?:,\d*)?\\\})/.test(after);
}

로컬에서 붙여 확인한 결과입니다.

  • BRE 5종(plain / alternation / backref / interval / plus) → 전부 RAW
  • LaTeX 5종(norm / \|x\|_2 / parallel / set / spaced set) → 전부 변환
  • sed 's/\(foo\)/bar/' → RAW 유지 (여는 쪽 / 가드가 그대로 처리)
  • speed \(v\)/s → 변환 유지
  • 이전 라운드의 회귀 케이스 23개(펜스 크로싱, 펜스 내부, 인라인 코드, \[1\], \[--flag=value\], 들여쓴 display 수식 등) → 전부 동일

이 방식이면 \| 를 BRE 신호로 쓰지 않아도 되므로 노름 표기가 되살아납니다.


🟡 킬스위치가 '1' 만 인식합니다

server/modules/providers/services/full-access-spawn-policy.ts:7environment[FULL_ACCESS_DISABLE_ENV] === '1'.

운영자가 CHATMUX_DISABLE_FULL_ACCESS=true 로 설정하면 조용히 무시되고 full-access 가 그대로 동작합니다. 안전 스위치는 실패 방향이 반대여야 합니다. 비어 있지 않은 값은 0 / false 를 제외하고 모두 "비활성화"로 보거나, 최소한 인식하지 못한 값에 경고 로그를 남기는 쪽을 권합니다.


사소한 것

  • SidebarNewSession/api/providers/capabilities 를 새로 호출하는데, useChatProviderState 가 이미 같은 엔드포인트를 부르고 있습니다. 중복 요청이라 공유할 여지가 있습니다.
  • tmux 태그는 set-option -t <session> 이라 세션 스코프입니다. 같은 tmux 세션의 다른 페인도 같은 태그를 읽습니다. 기존 @chatmux_cli_kind 와 동일한 구조이고 taggedKind === kind 가드가 있으며, 틀리더라도 "과하게 경고하는" 안전한 방향이라 블로커는 아닙니다. 기록만 남깁니다.

full-access 하드닝 쪽은 완성도가 높아 그대로 가도 좋습니다. 수식 쪽 BRE 판별기만 한 번 더 손보면 좋겠습니다.

(검증 방법: PR HEAD 체크아웃 후 normalizeLatexMathDelimiters 직접 실행. 레포 전체 테스트 스위트는 돌리지 않았고, PR 본문의 npm run verify 결과와 CI 에 의존합니다.)

@Yoonwoo-Ha

Copy link
Copy Markdown
Collaborator Author

리뷰 코멘트를 9d0aaae에 반영했습니다.

  • 인라인 수식 판별을 본문 토큰이 아니라 셸 따옴표 문맥과 \\) 뒤 BRE 후위 연산자로 변경했습니다.
  • 제안된 짧은 suffix slice는 \\{2,3\\}도 잘릴 수 있어, 길이 제한 없이 인덱스로 \\+, \\?, \\1~\\9, \\{m,n\\}을 판별하도록 구현했습니다.
  • quoted/plain/alternation/backref/interval/plus/question BRE와 norm/parallel/set LaTeX 회귀 테스트를 추가했습니다.
  • CHATMUX_DISABLE_FULL_ACCESS는 trim 및 소문자화 후 빈 값, 0, false만 해제값으로 보고 다른 비어 있지 않은 값은 fail-safe로 비활성화합니다.
  • capability 및 실제 spawn 거부 테스트와 영문/한글/일문/중문 문서를 갱신했습니다.
  • capabilities 중복 요청과 tmux session-scope 태그는 리뷰에서 비블로커로 분류된 기존 동작을 유지했습니다.

검증: npm run verify 통과

  • Rust 29
  • server 1,863
  • real tmux/PTY 50
  • client 748
  • typecheck, lint, identity, production build 통과

@Yoonwoo-Ha

Copy link
Copy Markdown
Collaborator Author

추가 경계 사례를 b856e57에 반영했습니다.

  • 닫는 BRE 그룹 직후의 \\|도 continuation operator로 판별합니다.
  • pattern \\(a\\)\\|b가 수식으로 변환되지 않는 회귀 테스트를 추가했습니다.
  • 따옴표 가드, capabilities 중복 fetch, tmux session-scope 태그는 논의한 대로 유지했습니다.

최종 로컬 검증: npm run verify 통과 (Rust 29, server 1,864, real tmux/PTY 50, client 748, typecheck/lint/identity/build).

@Yoonwoo-Ha

Copy link
Copy Markdown
Collaborator Author

✅ LGTM

b856e57 까지 확인했습니다. 제기한 항목이 모두 정리됐습니다.

BRE 판별기

제안은 slice(closeIndex + 2, closeIndex + 8) 고정 윈도우 + 정규식이었는데, 제자리 문자 스캔으로 구현한 쪽이 더 낫습니다. 긴 interval 이 잘리지 않고, 매칭마다 부분 문자열을 뜨지도 않습니다.

최종 HEAD 에서 재확인한 결과입니다.

셸 패턴 — 보존

grep '\(abc\)' file            → RAW
grep '\(a\|b\)' file           → RAW
grep '\(a\)\1' file            → RAW
grep '\(ab\)\{2,3\}' file      → RAW
grep '\(ab\)\+' file           → RAW
grep "\(ab\)\?" file           → RAW
pattern \(a\)\|b               → RAW   (b856e57 에서 추가)
pattern \(ab\)\{12,345\}       → RAW   (따옴표 없는 긴 interval)
sed 's/\(foo\)/bar/'           → RAW

LaTeX — 정상 변환

the norm \(\|x\|\)             → $\|x\|$
\(\|x\|_2 \le 1\)              → $\|x\|_2 \le 1$
lines \(a \| b\)               → $a \| b$
the set \(\{1,2\}\)            → $\{1,2\}$
see \(x\)\text{ok}             → $x$\text{ok}
speed \(v\)/s                  → $v$/s
a \(x\) \| \(y\) b             → $x$ \| $y$

닫는 괄호 뒤의 \| 만 BRE 로 보고 본문 안의 \| 는 건드리지 않아서, 노름 표기가 그대로 살아 있습니다.

이전 라운드의 회귀 케이스 23건(펜스 크로싱, 펜스 내부, 인라인 코드, \[1\], \[--flag=value\], 들여쓴 display 수식 등) 전부 동일하고, 백슬래시 10만 개 5.3 ms 로 선형도 유지됩니다.

킬스위치

trim + 소문자화 후 0 / false / 빈 값만 "켜짐" 으로 보는 fail-safe 방향으로 바뀌었고, 테스트에 ' FALSE ', 'yes', 'unexpected' 까지 들어갔습니다. 기존 테스트도 '1''true' 로 바꿔 새 파싱을 실제로 태우는 점이 좋습니다. README 4개와 provider 가이드도 함께 갱신됐습니다.

full-access 하드닝

첫 리뷰에서 확인한 내용 그대로 유지됩니다 — 3중 킬스위치(capability / 라우트 / spawnExternalCliSession), stopAgentProcessInPane 의 태그 정리, taggedKind === kind 가드, 11 → 12 필드 확장에도 구버전 peer 호환, mergeExternalDiscoveryRows 의 stale fullAccess 제거.

미반영 (기록용, 블로커 아님)

  • SidebarNewSessionuseChatProviderState/api/providers/capabilities 를 각각 호출하는 중복 요청.
  • tmux 태그가 set-option -t <session> 이라 세션 스코프인 점. 기존 @chatmux_cli_kind 와 동일한 구조이고, 틀리더라도 과하게 경고하는 안전한 방향입니다.

(검증 방법: PR HEAD 체크아웃 후 normalizeLatexMathDelimiters 직접 실행. 레포 전체 테스트 스위트는 돌리지 않았고 CI 결과에 의존합니다.)

@Yoonwoo-Ha
Yoonwoo-Ha merged commit b258b57 into main Sep 21, 2026
5 checks passed
@Yoonwoo-Ha
Yoonwoo-Ha deleted the fix/full-access-followups branch September 21, 2026 07:01
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.

2 participants