Skip to content

Latest commit

 

History

History
99 lines (82 loc) · 6.94 KB

16_commands.md

File metadata and controls

99 lines (82 loc) · 6.94 KB

16. Commands — 서버 제어 커맨드 런타임 (v2 클라이언트 모델)

책임: 서버가 클라이언트를 통일 제어하는 타입 커맨드 프로토콜. Python(Pydantic)=단일 진실원 → TS 타입 생성 + 커맨드 발행. 클라 = 제너릭 인터프리터. 해소: "북마크릿 업데이트 지옥" — 로직을 클라에서 제거, 전부 서버 커맨드로.

v1(현 loader.js의 payload.script eval)을 대체하는 목표 모델. 북마크릿은 기본적으로 v1·v2 동일(순수 로더, 불변) — 단 v2엔 인라인 형태(D-csp-script-src)가 추가로 있다. 상세: docs/00_ARCHITECTURE.md D-csp-script-src.


0. 원칙

  1. 북마크릿 = 순수 로더(불변): <script src=서버/agent/loader.js?...> 만. 영원히 갱신 불필요.
  2. loader.js = 서버 제공 TS 인터프리터(ES2017 컴파일). 서버만 바꾸면 모든 클라 다음 로드 때 최신.
  3. 커맨드 = 타입 데이터(eval 아님). 드문 커스텀만 script escape-hatch.
  4. 단일 소스 = Python Pydantic → commands.d.ts 생성(순수 파이썬 codegen, Node 런타임 無).
  5. 컴파일 타깃 = ES2017(대상 폰 전부 지원, async 네이티브). tsc/esbuild는 dev/CI 1회 → loader.js 체크인.

1. 커맨드 봉투

서버가 큐에 넣고 클라가 처리: window._aaq.push(command). command = {cmd, ...args, v?}.

2. 커맨드 어휘 (Pydantic Literal union)

cmd args 역할
collect {selector, fields[]} DOM 추출
on-click {selector, then: Command[]} 엘리먼트 클릭 → 후속 커맨드
define-button {label, on: Command[]} UI 버튼 생성(인터랙티브 수집)
beacon {to, data} .js/이미지 비콘 전송(CORS-free). to는 서버 오리진만(§8, D-beacon-origin)
sync {cursor} 서버 허브 date-delta pull
set {key, value} localStorage JSON array 누적
script {code} escape-hatch(eval). 기본 차단 — opt-in 필요(§8, D-script-optin)

3. 타입 codegen (Python → TS, Node 無)

class CollectCmd(BaseModel):  cmd: Literal["collect"]; selector: str; fields: list[str]
class BeaconCmd(BaseModel):   cmd: Literal["beacon"];  to: str; data: dict
Command = Annotated[CollectCmd | BeaconCmd | ..., Field(discriminator="cmd")]
# gen_dts(): Command union → docs/generated/commands.d.ts (순수 파이썬 문자열 생성)
  • 클라 TS가 commands.d.ts import. Python 커맨드 변경 → 재생성 → drift 시 클라 TS 컴파일 실패로 적발.
  • 발행 시 서버는 같은 Pydantic 모델로 검증(잘못된 커맨드 발행 불가).

4. 클라 인터프리터 (specs/50)

import type { Command } from "./commands";          // Python 생성
const handlers: { [K in Command["cmd"]]: (c: Command)=>void } = { collect, onClick, beacon, ... };
window._aaq = window._aaq || [];
const orig = window._aaq.push;
window._aaq.push = (c: Command) => { handlers[c.cmd]?.(c); };  // 로드 후 즉시 dispatch
(window._aaq as any).forEach?.((c)=>window._aaq.push(c));      // 버퍼 드레인
  • 핸들러 레지스트리만 클라에. 무엇을·언제·어떤 값은 서버 커맨드가 지시.
  • 새 동작 = loader.js 재컴파일·재배포(서버만). 북마크릿·통합 무변경.

5. site-id — 인덱스 매핑 (★D-siteid)

  • site_id = 불투명 안정 인덱스(surrogate, 예 s_001). 호스트네임을 키로 직접 안 씀.
  • sites: site_id PK · name(가변 라벨) · created_at. site_hosts: hostname PK · site_id(N:1 별칭).
  • 자동 매핑: 첫 호스트네임 등장 → site_id 생성·매핑. 수동: 관리자 발급·별칭 추가·name 변경.
  • collections는 site_id로 키 → 호스트네임 변경/사이트 재편에도 과거 데이터 불변.

6. 전송 — CORS-free (D-18 일관)

  • beacon = <script src=...js>(script-src) 또는 new Image().src=/api/v1/collect.json?d=(img-src). connect-src/CORS 무관.

7. 동기화 — 서버 허브 (그들의 검증된 패턴)

  • 쿠키 = cursor + 요약(작음). localStorage = site_id별 JSON array(본체).
  • sync 커맨드: cursor(>1h)→서버가 그 사이 delta 반환→localStorage 누적·cursor 갱신. 서버=정본, 클라=캐시. 충돌 없음.

8. 인터프리터 보안 가드 (★ 전수 리뷰 반영 — 신뢰 경계 하드닝)

인터프리터의 신뢰 경계는 "컨트롤 서버 응답"이다. 컨트롤 서버 장악/MITM 시 주입된 모든 페이지에서 코드 실행·데이터 유출이 가능하므로, 위험한 두 커맨드에 코드 가드를 둔다.

  • script opt-in (D-script-optin): handleScript는 기본 no-op. 로더 자기 src의 ?allowScript=1(운영자가 북마크릿/로더 URL에 직접 명시)일 때만 new Function(code)() 실행. 미허용 시 console.warn 후 무시. → "광범위 eval"을 구조적으로 비활성, 필요 시에만 명시 opt-in.
  • requestClipboard opt-in (실험적, read_clipboard 액션용): 로더 자기 src에 ?requestClipboard=1이 명시된 경우에만, 로더 초기화 시점(북마크릿 클릭의 user-activation 윈도 안)에 navigator.clipboard.readText()를 1회 호출한다 — 결과는 버리고 오직 브라우저 네이티브 clipboard-read 권한 프롬프트를 사용자 앞에 띄우는 용도. 기본 비활성(대부분 사이트는 클립보드가 불필요 — 뜬금없는 권한 팝업 방지). 사람이 그 프롬프트에서 직접 "허용"을 눌러야 승인되며, 자동화가 대신 누를 수 없다.
  • beacon 오리진 제한 (D-beacon-origin): handleBeacon은 c.to가 SERVER_ORIGIN으로 시작할 때만 전송. 타 오리진은 드롭+경고 → 페이지 데이터의 임의 외부 exfil 채널 차단.
  • 중복 주입 가드 (D-client-crypto): 같은 페이지에 v2 로더가 두 번 들어와도 자가 commands.js 로드·폴링 등 네트워크 부작용은 1회만(__arachneV2Loaded).
  • 난수: client_id·task_id·JSONP 콜백명은 crypto.getRandomValues 우선(예측 차단).

이 가드들은 loader.ts(단일 소스) → bun build → loader.v2.js 산출에 박힌다. 빌드 신선도는 GT-interp/GT-build로 보증.

Negative (하지 말 것)

  • ❌ 호스트네임을 키로 직접 사용(반드시 site_id 인덱스) ❌ 커맨드/로직을 클라에 하드코딩(서버 발행)
  • ❌ 광범위 script eval(opt-in 게이트 우회) ❌ beacon을 타 오리진으로 전송(서버 오리진만)
  • ❌ Node를 Python 런타임에 의존(codegen은 순수 파이썬, tsc는 dev/CI).

Golden Test 매핑

  • GT-cmd: 커맨드 Pydantic 검증(유효/무효) + 인터프리터 dispatch(핸들러 호출).
  • GT-dts: gen_dts()가 유효 .d.ts 생성; 모델 변경 시 클라 TS 컴파일 실패(drift 감지).
  • GT-siteid: 호스트네임→site_id 자동 매핑; name 변경 후에도 collections의 site_id 불변.