본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 05. 30. 13:39

5개의 업스트림 추적, 파서 퍼징, 그리고 프론트 도어: llm-cli-gateway의 변경 사항

요약

llm-cli-gateway 프로젝트의 최신 업데이트를 다룹니다. 5개 벤더 CLI의 업스트림 변경 사항을 추적하는 계약 관리 기능, 파서 안정성을 위한 퍼징 도입, 그리고 새로운 웹사이트 출시를 포함합니다.

핵심 포인트

  • 업스트림 CLI 플래그 변경을 감지하는 계약 추적 시스템 도입
  • fast-check를 이용한 JSON 및 인자 새니타이저 퍼징 강화
  • 의존성 하한선 상향(Zod 4, TypeScript 6 등) 및 Sigstore 서명 지원
  • 에이전트 우선 방식으로 구축된 llm-cli-gateway.dev 웹사이트 공개

지난 두 개의 포스트는 여러분이 호출할 수 있는 기능들에 관한 것이었습니다: 5개 제공업체에 걸친 캐시 인식 스포닝 (cache-aware spawning)과 그 이전 단계에 대한 내용이었습니다. 이번 포스트는 주로 도구(tool)로서 나타나지 않는 부분들에 관한 것입니다. 각기 다른 주기로 출시되는 5개의 벤더 CLI (vendor CLIs)를 래핑(wrap)할 때, 흥미로운 실패 모드(failure mode)는 여러분의 코드에 있는 버그가 아니라, 그 5개의 CLI 중 하나가 여러분도 모르게 플래그(flag)를 변경하는 것입니다. 따라서 이번 주에 반영된 작업은 움직이는 업스트림(upstreams)의 속도에 맞추고, 신뢰할 수 없는 출력을 파싱(parse)하는 부분을 강화하며, 마침내 프로젝트에 프론트 도어(front door)를 제공하는 것에 관한 것입니다. v1.16.0부터 v1.16.2까지 태그가 지정되어 출시되었습니다. 업스트림 추적(upstream-tracking) 및 소켓 강화(Socket-hardening) 작업(v1.17.0 및 v1.17.1로 변경 로그 기록됨)과 fast-check 퍼징(fuzzing) 패스 및 의존성 하한선(dependency-floor) 상향 작업이 main 브랜치에 반영되었으며 다음 릴리스에 포함될 예정입니다. 또한, 프로젝트의 새로운 프론트 도어인 llm-cli-gateway.dev 웹사이트가 현재 라이브 상태입니다.

요약 버전: 이제 게이트웨이는 각 제공업체 CLI의 업스트림 계약(upstream contract)을 체크인된 아티팩트(artefact)로 추적합니다. 계약 테이블은 CI에서 실행되는 테스트에 의해 고정되며, 오프라인 npm run upstream:contracts 게이트를 통해 필요할 때마다 재검증합니다. 또한 권고용 npm run upstream:scan -- --live 명령은 업스트림 변경 로그(changelogs)에 접속하여 실제 상황이 변했을 수 있는 부분을 표시하므로, 드리프트(drift)가 사용자의 기기에서 요청 실패로 나타나는 대신 제가 실행하는 체크 과정에서 드러나게 됩니다. 이제 fast-check 퍼징 패스가 신뢰할 수 없는 바이트를 다루는 세 가지 파서(parsers), 즉 제공업체 JSON/JSONL, Linux /proc, 그리고 CLI 인자 새니타이저(argument sanitizer)를 집중적으로 테스트합니다. 릴리스 태그는 전용 워크플로우를 통해 Sigstore 서명을 받을 수 있으며, 선택 사항이었던 Redis 레이어는 제거되었습니다. 또한 main 브랜치의 의존성 하한선은 Zod 4 / TypeScript 6 / ESLint 10으로 상향되었습니다. 마지막으로 llm-cli-gateway.dev에 에이전트 우선(agent-first) 방식으로 구축된 실제 웹사이트가 생겼습니다. 이제 MCP 클라이언트는 하나의 URL을 읽어 스스로를 설정할 수 있습니다.

Long version은 아래에 있으며, 지난번과 동일한 구성으로 문제점, 변경 사항, 현재 기능, 그리고 주의 사항을 뒤로 숨기지 않고 앞부분에 명시했습니다.

움직이는 5개의 업스트림 (계약 추적 섹션)

동기 부여가 된 사건을 언급할 가치가 있는데, 이것이 논증의 핵심이기 때문입니다. Mistral의 Vibe CLI가 --output-format을 삭제하고 대신 --output text|json|streaming을 도입했습니다. 게이트웨이(gateway) 자체 코드에는 아무런 문제가 없었습니다. 몇 주 동안 내보내던 플래그가 spawn의 반대편(업스트림)에서 단순히 사라졌을 뿐입니다. v1.16.1에서 해당 호출을 수정했으며(또한 plaintext, stream-jsonstreaming으로 이어지는 기존 MCP 별칭 매핑을 유지하여 누구의 설정도 깨지지 않게 했습니다), 하지만 사용자의 기기에서 런타임 오류로만 나타나는 단 한 줄의 플래그 이름 변경은 제가 CI(지속적 통합) 단계에서 반드시 잡아내고 싶은 유형의 문제입니다.

따라서 업스트림 추적 작업(v1.17.0으로 변경 로그에 기록되었으며, main 브랜치에 반영됨)은 계약(contract)을 일급 객체(first-class)이자 체크인된 요소로 만듭니다:

  • 지원되는 각 CLI인 claude, codex, gemini, grok, mistral은 그 정보의 진실이 어디에 있는지 설명하는 **유지 관리 기술 (maintenance skill)**을 갖게 됩니다 (Claude Code의 마크다운 변경 로그, Codex의 GitHub 릴리스 피드 및 제품 변경 로그, Gemini CLI 변경 로그, xAI 마크다운 릴리스 노트 등).

  • 각 제공업체의 argv/env 동작(플래그, 출력 모드, 세션/재개 규칙, 금지된 플래그 등)에 대한 단일 진실 공급원(single source of truth)은 src/upstream-contracts.ts에 있는 계약 테이블이며, 이는 인자(argument) 및 환경 변수(env) 검증기에 의해 실행됩니다. 이와 함께 docs/upstream/provider-sources.dag.toml은 스캐너의 소스 맵 (source map) 역할을 합니다. 즉, 어떤 변경 로그/릴리스 페이지를 어떻게 감시할지를 정의합니다. 이 두 가지는 의도적으로 분리되어 있으며, 테스트(upstream-sources.test.ts)를 통해 그 분리를 고정합니다. 소스 맵은 계약 테이블의 메타데이터와 바이트 단위로 동기화 상태를 유지하며, TOML이 기계적인 계약 표면을 다시 인코딩하지 않음을 단언(assert)합니다. 소스 맵의 드리프트(drift)는 빌드 실패(red build)를 의미합니다. TOML은 플래그 이름 변경이 거쳐 가야 하는 대상이 아닙니다.

  • scripts/upstream-scan.mjs는 두 개의 npm 스크립트를 지원합니다. npm run upstream:contracts오프라인 (offline) 게이트로, 네트워크 연결 없이 번들링된 픽스처(fixtures)와 보고서/TOML-sync 체크를 다시 실행합니다. npm run upstream:scan 역시 기본적으로는 네트워크를 사용하지 않습니다. 하지만 --live 플래그를 전달하면 (npm run upstream:scan -- --live) 추적 중인 업스트림의 변경 로그(changelogs)와 플래그를 가져오며, 실제 상황이 우리보다 앞서 나갔을 가능성을 조언해 줍니다. (두 스크립트 모두 현재 CI 게이트에 연결되어 있지는 않으며, 제가 직접 실행하는 도구들입니다. 하지만 TS-contract-vs-source-map 동기화는 CI 테스트로 작동합니다.)

솔직한 주의 사항을 말씀드리자면, 라이브 스캔(live scan)은 권고 사항일 뿐 권위 있는 결과는 아닙니다. 이는 어디를 살펴봐야 할지 알려줄 뿐, 이름이 변경된 플래그를 자동으로 패치(auto-patch)하지 않으며 앞으로도 그럴 일은 없을 것입니다. 왜냐하면 CLI의 인터페이스(surface)가 변경되는 것은 스크립트가 조용히 적응해야 할 대상이 아니라, 사람이 직접 읽고 추론해야 하는 영역이기 때문입니다. 바뀐 점은, 이제

  • Provider JSON / JSONL 파서 (parsers) 유효한 JSONL 스트림과 쓰레기(garbage) 데이터가 섞인 스트림을 사용하여 퍼징 (fuzzing)을 수행하며, 파서가 절대 예외를 던지지 않고 유효하지 않은 결과 형태를 유출하지 않음을 단언합니다. 크래시 (crash) 발생 중 절반만 작성된 라인을 방출하는 프로바이더 (provider)는 성능이 저하되어야지, 잘못된 형태의 객체를 상위로 전파해서는 안 됩니다.
  • Linux /proc 파서 (parsers) 프로세스 상태 모니터는 생성된 자식 프로세스의 상태를 추적하기 위해 /proc/<pid>/stat (상태 및 CPU 틱)과 /proc/<pid>/status (VmRSS)를 읽습니다. 여기서의 속성 (property)은 어떠한 쓰레기 /proc 콘텐츠도 NaN 프로세스 지표를 생성하지 않는다는 것입니다.
  • CLI 인자 산정기 (CLI argument sanitizer) 속성은 투박하지만 중요합니다: 대시(-)로 시작하는 값은 항상 거부됩니다. 이것이 인자 주입 (argument-injection) 방어 기제입니다. 게이트웨이 (gateway)는 shell: true로 CLI를 호출하지 않지만, -로 시작하여 argv 배열로 미끄러져 들어가는 호출자 제공 값은 자식 프로세스에 의해 값이 아닌 플래그 (flag)로 읽힐 수 있습니다. 퍼저 (fuzzer)의 역할은 해당 검사를 통과하는 입력 문자열이 없도록 보장하는 것입니다.

이것들은 속성 (properties)이지 예시가 아닙니다. fast-check는 제가 추측하는 대신 적대적 입력 (adversarial inputs)을 생성하며, 이것이 핵심입니다. 저는 파서들이 이제 올바르다고 증명되었다고 주장하는 것이 아닙니다. 프로바이더가 잘못된 빌드를 배포하는 날이 아니라, 매 실행 시마다 명백한 유형의 잘못된 입력들이 테스트된다는 점을 주장하는 것입니다.

서명된 태그 (Signed tags), 더 작은 공격 표면 (surface), 더 새로운 하한선 (floor)

공급망 (supply-chain) 및 의존성 (dependency) 계층에서의 몇 가지 사항들이 있으며, 이 중 어느 것도 기능 (feature)은 아니지만, 모두 언급할 가치가 있습니다.

Sigstore 태그 서명 (Sigstore tag signing). npm 배포물은 이미 OIDC 게시 경로를 통해 Sigstore 출처 (provenance)를 포함하고 있습니다. 1.16.0 사이클부터는 전용의 수동 트리거 방식인 sigstore-tag.yml 워크플로우 (매번 릴리스 시 자동으로 실행되는 것이 아니라, 특정 태그를 대상으로 의도적으로 실행되는 workflow_dispatch)를 통해 릴리스 태그 (tags) 자체에도 동일한 처리를 적용할 수 있습니다. 이 워크플로우는 태그가 계속 가리켜야 하는 정확한 커밋 SHA에 고정된 상태로, gitsign 서명을 사용하여 태그를 재생성하며, 오프라인 Rekor 모드에서 실행됩니다. 이를 통해 릴리스의 git 히스토리를 배포된 아티팩트 (artefact)만큼 검증 가능하게 만들 수 있습니다.

Socket shellAccess, 무시하기보다는 문서화하기. 게이트웨이의 존재 이유 자체가 자식 프로세스 (child processes)를 실행하는 것이므로, Socket은 매 릴리스마다 이를 플래그 (flag)로 표시합니다. v1.17.1에서는 이 경고를 무시하는 대신, socket.yml 내에 작성된 근거와 함께 억제 (suppress) 처리하였으며, README에 제한된 쉘 액세스 (shell-access) 설명을 유지했습니다. 따라서 리뷰어는 매 버전 업데이트마다 발생하는 소란스러운 경고를 보지 않으면서도 그 근거를 확인할 수 있습니다. 이 차이는 중요합니다. 근거가 체크인된 억제된 경고는 감사 (auditable)가 가능하지만, 기록이 없는 억제된 경고는 그저 숨겨진 것에 불과합니다.

선택적 의존성 (optional dependency) 하나 감소. v1.16.0에서는 PostgreSQL 기반 세션 관리자에서 선택적 Redis/ioredis 계층을 제거했습니다. 이는 거의 아무도 사용하지 않는 레버였으며, 모든 선택적 의존성은 사용 여부와 관계없이 유지보수 및 공급망 (supply-chain) 비용을 발생시킵니다. Postgres 경로는 더 단순하며 의존성 표면 (dependency surface)도 더 작습니다.

더 높아진 하한선. 다음 릴리스를 앞두고 main 브랜치에서 툴체인 (toolchain)이 발맞추어 상향되었습니다: Zod 4, TypeScript 6, ESLint 10 (ESLint 10이 강제하는 lint-config 마이그레이션 포함), @types/node 25, 그리고 새로운 컴파일러와 린트 (lint) 설정에서 드러난 데드 코드 (dead-code) 정리 작업이 포함되었습니다. (이들은 아직 v1.17.x 패키지에는 포함되어 있지 않으며, 다음 컷 (cut)에서 배포될 예정입니다.) 화려하지는 않지만, 두 번의 메이저 업데이트를 방치하면 바로 부패하기 시작하는 바로 그런 작업들입니다.

프론트 도어 (웹사이트)

이번 주 전까지 이 프로젝트의 프론트 도어(front door)는 GitHub README와 npm 페이지였습니다. 이제는 이 포스트가 올라온 시점을 기준으로 llm-cli-gateway.dev가 라이브 상태이며, 흥미로운 설계 결정은 이 사이트가 에이전트 우선 (agent-first) 방식으로 구축되었다는 점입니다.

전제는 다음과 같습니다. MCP 서버를 설치할지 여부를 평가하는 주체가 마케팅 문구를 읽는 인간이 아니라, URL을 읽는 에이전트(agent)가 되어가고 있다는 것입니다. 따라서 이 사이트는 이를 사후 고려 사항이 아닌 기본 경로로 취급합니다.

  • /install.md는 일반 마크다운(markdown) 형식으로 작성되어 에이전트가 읽을 수 있는 설치 지침이며, 홈페이지의 헤드라인 호출 문구(call to action)는 말 그대로 "https://llm-cli-gateway.dev/install.md를 읽고 llm-cli-gateway를 MCP 서버로 사용할 수 있도록 직접 설정하세요." 입니다.
  • /llms.txt는 압축된 검색(retrieval) 진입점이며, /.well-known/agent.json은 도구가 HTML을 스크래핑(scraping)하지 않고도 파싱(parse)할 수 있는 구조화된 메타데이터(레지스트리 이름 io.github.verivus-oss/llm-cli-gateway, 전송 방식(transport), 실행 명령(launch command))를 제공합니다.
  • /sitemap.md는 검색을 수행하는 모든 것을 위해 이 세 가지를 하나로 연결합니다.

인간을 대상으로 하는 측면은 의도적으로 지루하게 설계되었습니다. 이 사이트는 정적 Cloudflare Pages 사이트(wrangler.toml, 출력 디렉토리 site/)이며, _headers 파일에 script-src 'self', frame-ancestors 'none' 등을 포함한 엄격한 콘텐츠 보안 정책(Content-Security-Policy, CSP)을 적용하여 배포됩니다. 또한 JavaScript는 외부 또는 네트워크 호출을 전혀 하지 않습니다. 분석 도구(analytics), 런타임에 로드되는 제3자 폰트, 외부로 정보를 전송하는(phoning home) 기능이 전혀 없습니다. "CLI가 네이티브 자격 증명(credentials)을 유지하고 로컬에서 실행된다"는 것이 프로젝트의 핵심 가치인 만큼, 마케팅 사이트가 조용히 트래커(tracker)를 로드한다면 그 논거를 스스로 훼손하는 꼴이 될 것입니다. 그래서 그렇게 하지 않습니다.

또한 이번 주에 프로젝트의 첫 번째 제대로 된 마크(mark)를 갖게 되었습니다. 터미널 프롬프트(다른 모든 것을 실행하는 >_)에서 그려낸 황금색 게이트웨이 "G"가 @ 스타일의 링에 둘러싸인 형태입니다. 이것은 사이트의 파비콘(favicon)이며, 이 포스트 상단의 소셜 카드(social card)를 장식합니다.

주의할 점이 하나 있습니다(언제나 그렇듯): 이 사이트는 새로 만들어졌으며, 에이전트 설치(agent-install) 경로는 그 이면에 있는 설치 사양(install spec)만큼만 유효합니다. npx -y llm-cli-gateway를 통한 stdio 방식이 전체 실행 표면(launch surface)이며, 설치 문서는 코드와 함께 리포지토리(repo) 내에서 버전 관리되므로 코드가 변경될 때 함께 이동합니다.

다음 단계

더 많은 제공자(providers)들이 추가될 것이므로, 다음 업스트림 스캔(upstream scan) 반복 단계에서는 권고 사항(advisory)의 실시간 확인을 제가 직접 실행하는 것을 기억해야 하는 작업이 아니라, 예약된 작업(scheduled job)이 실행하고 보고하는 방식으로 만들 예정입니다. 그리고 퍼징(fuzzing) 단계는 현재 의도적으로 좁게 설정되어 있습니다(파서 3개). 현재의 속성들이 몇 주간의 성공적인 실행(green runs)을 거치고 나면, 세션 저장소(session-store)와 설정 로더(config-loader) 경로가 명백한 다음 목표가 될 것입니다.

더 큰 과제는 XState Store v4 (@xstate/store) 통합입니다. 이는 세션이나 플라이트 레코더(flight recorder) 내부에 위치하는 것이 아니라, 이들과 나란히 배치되는 내구성이 있고 검사 가능한 에이전트 상태(agent-state) 표면입니다. 이를 통해 오케스트레이션 에이전트(orchestrating agent)는 이미 비동기 작업(async jobs)이 그러하듯 재시작 후에도 유지되는, 스키마 제약이 있는 작은 워크플로 상태(이벤트, 가드, 스냅샷)를 보유할 수 있습니다. 제가 초안을 작성한 설계는 작업들이 사용하는 것과 동일한 SQLite를 통해 지속되며, 설정(config) 뒤로 전체 표면을 제어합니다(비활성화된 백엔드는 도구를 등록하지 않으며, 이는 비동기 작업 레이어가 이미 따르고 있는 구조적 불변성(structural invariant)과 동일합니다). 또한 상태 정의를 에이전트가 제공하는 코드가 아닌, 데이터 전용 DSL로 유지합니다. 이것은 현재 디스크 상의 계획(docs/plans/ 하위)일 뿐이며, 출시된 도구는 아닙니다. 솔직히 말해서, 이 모든 것이 적용되기 전에 저장소 불변성(storage-invariant)과 임의 코드 실행 금지(no-arbitrary-code) 문제를 먼저 해결하고 싶습니다.

여기까지 읽어주셔서 감사합니다. 언제나 그렇듯, MIT 라이선스입니다.

llm-cli-gateway는 MIT 라이선스입니다. 웹사이트: llm-cli-gateway.dev | npm: llm-cli-gateway | GitHub: verivus-oss/llm-cli-gateway

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0