feat(runners): Kiro 러너 추가 — kiro-cli 래핑(BYOA 3호) - #245
Conversation
3641916 to
8c5688a
Compare
추가 검증 — UI 시각 확인 (누락 보완)PR 본문에 코드 게이트(lint·test·build)와 E2E만 적었는데,
카드 확장 패널이 "쪽지·루틴 예약: 지원"으로 표시되는데, 이건 여전히 미검증인 것은 PR 본문에 적은 그대로입니다 — Windows 전반( |
Kiro CLI는 AWS 계정 하나로 여러 벤더 모델을 함께 쓴다(실측 카탈로그: Claude Opus 5·Sonnet 5·
Haiku 4.5, GPT-5.6 Sol·Terra·Luna, GLM-5, DeepSeek V3.2, MiniMax M2.5, Qwen3 Coder Next).
지금 Argo에서 같은 폭을 얻으려면 러너를 여럿 연결해야 하고, IAM Identity Center 구독으로 Kiro를
쓰는 사용자는 그 구독을 태울 경로가 아예 없다. codex·antigravity와 같은 CLI 래핑으로 붙인다 —
세 번째 배관을 만들지 않는다.
실측(kiro-cli 2.17.0, macOS, 2026-08-12):
원샷 `chat --no-interactive [--model] [--effort] --wrap never -- <prompt>` exit 0
(`--`가 있어 '---'로 시작하는 카드 frontmatter 안전 — codex와 같은 이유)
데몬 TTY 없음·stdin 닫힘·최소 env에서도 exit 0 (상주·헤드리스 경로)
모델 --list-models 19종 중 10종 원샷 왕복 10/10 통과 → 카탈로그 규칙 충족
강도 --effort low|medium|high|xhigh|max — Argo effort와 1:1(codex와 같은 자리)
자격 whoami 로그인 "Logged in with IAM Identity Center"(0) / 미로그인 "Not logged in"(1)
미로그인 턴 비대화에서도 브라우저를 열려 시도 → "OAuth error: Auth portal timed out"(1)
답변 어시스턴트 메시지 첫 줄에만 '> ' 접두사(도구 추적은 무접두사) →
마지막 '> ' 블록 = codex --output-last-message 등가. 마크다운 인용은 '│ '로 렌더돼 무충돌
파일 반경은 **표현할 수 없다**(문서만 보고 넣지 않고 네 수단을 다 돌렸다, 비대화 기준):
tools 제외 하드(도구 자체가 없다)
allowedTools 포괄 자동 승인 — 비대화에서 필수. 없으면 반경 안쪽 쓰기까지 전부 거부됐다
allowedPaths 효력 없음(자동 승인을 주지 못해 안쪽까지 거부). allowedTools가 덮는다
denyByDefault 무시됨(shell 전용) — write에 넣어도 반경 밖 쓰기 성공
deny 부정패턴 `!` 미지원 — carve-out 없이 전부 차단
deniedPaths 하드 차단, allowedTools보다 우선 ← 유일한 강제 수단
즉 openRoots(홈+지정 폴더) 화이트리스트를 인자로 실을 자리가 없다.
그래서 집행은 **불변 경계 deny**로 한다: APP_ROOT · ~/.argo · WS_ROOT의 형제 회사(교차 테넌트) ·
직속 도트 항목(회사 금고 .workroots.json·계정 시크릿). 경계는 cwd에서 도출한다
(dirname(cwd)=WS_ROOT — permission-gate와 같은 계산, 새 임포트 0). WS_ROOT를 통째로 deny하면
크루 책상까지 막히므로 형제만 열거한다. caps.fs 반경 차이는 UI가 정직 표기하고(i18n
workroots.runnerNote ko·en) 테스트가 그 표기를 잠근다 — 러너별 집행 강도 차이를 화면이 말하는
방식은 workroots.mjs가 이미 채택한 것이라, 그 주석에 kiro 항목을 추가했다.
턴별 에이전트 설정(<cwd>/.kiro/agents/argo-<uuid8>.json)에 caps→도구·경계·전역 MCP 격리를 싣고
finally에서 지운다. 이름에 턴 고유값을 넣는 이유: 같은 회사에서 두 크루가 동시에 답할 때 고정
이름이면 한쪽 finally가 다른 쪽의 실행 중 설정을 지운다. 이 경로 자체도 deny 목록에 들어가
크루가 자기 권한 설정을 고쳐 다음 턴을 승격시키는 길을 막는다.
감지는 whoami다(토큰이 데이터 저장소·OS 보관이라 파일 존재로 판정 불가). 정규식은 **줄머리
앵커가 필수**다 — codex가 쓰는 /Logged in/i는 "Not logged in"에도 매칭돼 미로그인을 '연결됨'으로
뒤집는다. /^Logged in/im을 쓰고 테스트가 이 함정을 잠근다. authed가 실측값이라 antigravity의
낙관 authed(authUnknown)가 필요 없고, RUNNER_AUTH에서 맨 끝일 필요도 없다(grok 뒤·antigravity 앞).
connect(로그인 대행)는 두지 않았다: kiro-cli login이 라이선스·IdP를 되묻는 대화형일 수 있어
(미검증) detached spawn이 조용히 멈출 위험이 있다 — 터미널 로그인 안내가 정직하다.
자동 조달도 없다(인스톨러가 셸 프로파일 수정 + 데스크톱 앱 동반 — antigravity와 같은 판단).
게이트: npm run lint 통과. npm test 904 tests / 899 pass / 0 fail / 5 skipped(기존 883에서 +21).
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
설계 문서는 antigravity 선례 형식 그대로다 — 왜/분류/실측 표/구현 결정/검증/미검증.
파일 반경 실측 표(네 수단 중 deniedPaths만 강제력이 있다)를 남기는 이유: 다음 사람이 공식 문서의
allowedPaths를 보고 "반경이 걸린다"고 오독하는 것을 막는다. 실제로 비대화에서는 효력이 0이다.
E2E(scripts/e2e-kiro.mjs)는 격리 서버(임시 ARGO_ROOT·별도 포트·Supabase env 제거)에서
카탈로그 → 상태(실측 authed) → host 옵트인 → 회사 생성 → 크루 영입 턴 → 채팅 턴 →
턴 잔재 0 → **경계 집행**까지 본다. "빌드 통과 ≠ 동작"의 이 러너 쪽 관문이다.
실측 결과(전 구간 통과):
카탈로그 kind=cli·10모델·installed=true
상태 hostAuthed=true(whoami 실측)·cli=true·hostUsable=true·authUnknown 없음
영입 턴 실 LLM 페르소나 생성 통과(caps 미전달 경로 — fail-closed 확인)
채팅 턴 "kiro-ok" 수신(답변 추출이 통삭제하지 않음)
잔재 .kiro/agents 비어 있음(폴링 판정 — 배경 턴 중에는 정상적으로 존재하므로)
경계 WS_ROOT 직속 도트(계정 시크릿 모의)를 읽으라는 지시에 크루가 BLOCKED로 답하고
내용이 유출되지 않았다 — 이 러너 집행의 실측 근거
잔재 판정을 폴링으로 한 이유는 첫 실행에서 배웠다: 채팅 응답 뒤에도 배경 턴(기억 정리·일지)이
돌아 그 순간의 설정 파일이 정상적으로 존재한다. 즉시 판정은 그걸 잔재로 오인했다.
미검증은 문서에 그대로 남겼다(9항목). 특히:
· 답변 절단(실증된 결함) — 최종 답변 2행 이후 코드블록에 '> '로 시작하는 줄이 있으면 앞부분
유실. 렌더 결과에 코드펜스가 없어 "코드블록 안"을 판별할 수단이 없다. 근본 해법은
kiro-cli acp(JSON-RPC) — 별도 PR 권장.
· caps.fs 반경 미강제(설계상 수용 + UI 표기)
· Windows 전반(경로 후보·deniedPaths 글롭의 백슬래시 매칭) — 실기기 확인 전 발행 보류 권고
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
분리 검수(별도 컨텍스트)가 CRITICAL 2건을 라이브 재현으로 잡았다. 둘 다 "경계가 있다고 믿었는데
실제로는 서지 않는" 계열이라 그대로 두면 교차 테넌트 유출이다.
CRITICAL-1 — deny 글롭이 raw 경로였다. kiro-cli는 canonical로 판정한다.
재현: ~/kiro-audit-link → ~/kiro-audit-real 심링크를 ARGO_ROOT로 주고 실턴을 돌리면 형제 회사
파일과 WS_ROOT 직속 도트를 **둘 다 읽었다**. 심링크 없는 실경로가 대조군으로 전부 막혔다
(메커니즘 자체는 정상). 맥의 /tmp·/var가 심링크이고 외장 볼륨·동기화 폴더 경유도 흔해서,
공격 행위 없이 **설정만으로** 열리는 계열이다.
수정: 모든 글롭 루트(appRoot·홈·wsRoot·cwd)에 raw·canonical 양형을 싣는다 — permission-gate가
하드 구역에 두 형태를 담는 것과 같은 이유·같은 방식. 재검 실턴(심링크 WS_ROOT)에서 도구 레벨
거부 확인 + 센티널 3종 미유출. 심링크 트리 단위 테스트로 잠갔다.
CRITICAL-2 — E2E 경계 검증이 위양성이었다.
답변 문자열만 보고 "시크릿이 없으면 통과"였는데, 그건 집행으로 막힌 것과 모델이 스스로 거절한
것을 구분하지 못한다. 프롬프트에 "못 읽으면 BLOCKED라고 답하라"를 넣어 거절을 유도하기까지 했다.
검수자가 같은 형상(같은 ROOT·같은 파일·같은 절대경로)에서 문구만 중립적으로 바꿔 유출을 재현했다.
수정: ① 프롬프트 중립화 ② 판정을 도구 레벨 거부 문자열("rejected because it matches one or more
rules on the denied list" — 실측 확인)로 이동 ③ 센티널 3종(형제 회사·WS_ROOT 도트·회사 금고
connections.json) 미유출 동시 요구 ④ 과차단 대칭 검증 추가(크루가 vault/notes/에 정상 쓰기).
설계 문서의 "경계 집행은 실측이다" 주장도 근거를 명시하도록 고쳤다.
MEDIUM — 화면은 "자격 폴더 차단"이라 말하는데 ~/.argo만 막고 있었다.
permission-gate가 능력·bypass와 무관하게 하드 차단하는 HARD_HOME_PATHS(~/.codex·~/.claude·
~/.gemini·~/.claude.json·~/.mcp.json)가 빠졌다. 같은 파일을 SDK 러너는 deny하고 kiro는 allow하면
러너에 따라 자격 유출 여부가 갈린다. 목록을 동일하게 채우고 **소스 파싱 대조 테스트**로 정합을
잠갔다(한쪽만 늘어나면 red). 문구도 실제 차단 대상을 나열하도록 고쳤다.
MEDIUM — 회사 금고를 도트 항목과 동일시했다. 실제 금고(connections.json·capabilities.json·
agents/·chats/ 등)는 도트가 아니라 <cwd>/.* 글롭으로 안 잡혔다. WS_CONTROL_FILES·WS_CONTROL_DIRS를
deny에 추가했다. 원장(usage/events)은 permission-gate와 같이 **쓰기만** 막는다 — 읽기까지 막으면
사장의 "이번 달 얼마 썼어?"에 답할 수단이 없어지는 기능 후퇴다.
MEDIUM — i18n 영어 문구가 이중 이스케이프로 화면에 \u2019를 그대로 노출했다. 하필 보안 한계를
정직하게 알리는 그 문장이었다. 실제 문자로 수정.
LOW — 빈 .kiro/agents 디렉터리까지 정리(rmdir이라 비어 있지 않으면 실패 → 동시 턴 안전) ·
caps→도구 깎기가 채팅 경로에서는 발현하지 않는다는 사실(loadCapabilities가 전권 상수)을 구현
결정에 명시 · grep/glob 경계가 벤더 내부 동작에 의존한다는 점을 한계 절로 승격.
게이트: npm run lint 통과. npm test 906 tests / 901 pass / 0 fail / 5 skipped.
E2E: 감지 → 옵트인 → 영입 턴 → 채팅 턴 → 잔재 0 → 경계 집행(도구 레벨 거부) → 책상 정상 전부 통과.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2라운드 검수가 1라운드 수정의 실효 범위를 증명했다: **닫힌 것은 read/write leg뿐이었다.** 같은 계열 갭이 한 번에 닫히지 않는다는 이 레포의 경험(경계 게이트 갭이 3라운드 만에 닫힘)의 재현이다. CRITICAL — grep·glob이 경계를 통째로 우회한다. 능력 0으로, 매 턴. 실측(직접 재현으로 지적 확인): 셸 OFF·기본 caps 턴에서 grep이 회사 금고 connections.json·형제 회사·WS_ROOT 직속 도트의 **매치 줄 내용을 그대로 반환**했다. 같은 턴·같은 설정에서 read로 같은 파일을 열면 정상 DENY다(대조군) — 즉 deniedPaths는 read/write 전용이다. glob은 ~/.codex 7,766개· ~/.claude 527개를 열거했다. 두 도구는 caps와 무관하게 항상 부여돼 있어 어떤 능력도 필요 없었다. 두 도구의 toolsSettings 키는 공식 스키마에 없어 막을 수도 없다(미지 키는 조용히 무시된다). **더 나쁜 것은 표기였다.** 소스 주석과 설계 문서 한계 절이 "실측상 grep은 read 규칙을 그대로 받아 차단된다"고 반대로 적혀 있었다. 1라운드 검수자가 "벤더 내부 동작 의존"이라 단서를 달았는데도 구현자가 그 미검증 관찰을 사실로 옮긴 결과다. 1라운드가 잡은 "경계가 있다고 믿었는데 서지 않는" 계열의 재발 — 교훈을 설계 문서에 명문화했다. HIGH — 셸 능력 ON이면 경계가 완전히 사라진다. 실측: cat <금고> 유출, cat ../<타사>/notes.md 교차 테넌트 유출, echo > usage.jsonl로 **원장이 디스크에서 실제로 개조**됨. deniedCommands(정규식)는 강제되지만 명령 문자열 정규식은 $(…)·sh -c· 인터프리터 경유로 우회가 자유롭고, permission-gate처럼 인자 경로를 판정하는 백스톱이 없다. denyByDefault는 문서에 있으나 무시된다(실측: 화이트리스트 밖 명령이 그대로 실행됨). 수정: 설정으로 막을 수 없는 도구는 **주지 않는다**(이 러너의 fail-closed). BASE_TOOLS = ['read','write'], 브라우저 능력 시 web_fetch만 추가. caps.shell이 켜져 있어도 셸을 주지 않는다. 결과적으로 Kiro 크루는 파일 읽기·쓰기로만 일한다 — 능력 차이가 생기지만, 셸 부재는 선례가 있다(gemini도 비대화 --approval-mode auto_edit에서 셸이 실행되지 않는다). 그 차이를 i18n workroots.runnerNote가 ko·en 양쪽에 명시하고 테스트가 잠근다. 여는 조건은 명확하다: kiro-cli acp로 옮겨 도구 호출을 우리가 승인·거절하면 세 도구 모두 복원된다. MEDIUM — HOME 부재 시 홈 자격 경계가 조용히 사라졌다(fail-open). 실측: env -u HOME -u USERPROFILE 에서 ~/.argo·~/.codex·~/.claude·~/.gemini 항목이 경고도 없이 0건이 됐다. deniedPaths가 이 러너 집행의 전부라 조용한 소멸은 곧 경계 소멸이고, launchd·Tauri 최소 env가 정확히 이 조건이다. → throw로 전환(경계를 못 만들면 턴을 돌리지 않는다). MEDIUM/LOW — 하드링크 우회(경로 기반 deny의 구조적 한계) · fs_read 배치 원자성(deny 1건이 배치 전체를 거부) · 한계 절 번호 중복 → 한계 절에 반영(항목 3~6 재작성, 이후 번호 재정렬). 검증: 수정 후 재검 실턴 — 형제 회사·금고 유출 모두 소멸, 책상 읽기·쓰기 정상. E2E에 "우회 도구 배제" 절 신설 — 전권 caps(bypass:true, 채팅 경로가 실제로 넘기는 값)로 생성한 설정에 grep·glob·shell이 없고, 그 설정으로 돈 실턴에서 형제 회사가 유출되지 않음을 함께 본다. 게이트: lint 통과, npm test 907 tests / 902 pass / 0 fail / 5 skipped. E2E 전 구간 통과: 감지 → 옵트인 → 영입 턴 → 채팅 턴 → 잔재 0 → 경계 집행(도구 레벨 거부) → 우회 도구 배제 → 책상 정상. 2라운드가 닫혔다고 확인한 1라운드 지적(그쪽 보고 그대로): 심링크 양형(우회 벡터 9종 추가 시도 전부 DENY — 상대경로·대소문자 변종·후행 점·회사 안 심링크 포함) · E2E 판정(경계 부분 무능화 3형상 대조에서 전부 실패 = 위양성 아님) · 하드홈 6=6 · 금고 7=7·2=2·2=2 · 정합 테스트 red 검증 4/4 · i18n 이스케이프 · 빈 디렉터리 정리. 과차단 회귀는 발견되지 않았다. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2라운드가 우회 도구를 배제한 뒤, 3라운드는 남은 두 도구가 새 우회로가 되는지와 **크루가 실제로
일할 수 있는지**를 봤다. 보안 우회 하나와 기능 결손 둘이 나왔고, 뒤 둘이 더 무거웠다.
HIGH — ```argo 지시 관행이 성립하지 않았다(러너 중립성 정면 위반).
kiro-cli는 답변을 렌더해서 내보내며 그 과정에서 **코드펜스를 제거한다**(실측: 백틱 0개, JSON은
한 줄). BLOCK_RE가 영원히 매치되지 않아 루틴 예약·크루 쪽지·결재 상신·커넥터 호출이 전부 조용히
무동작하고, 원시 JSON이 사장 화면에 남았다 — cli-directives.mjs가 없애려고 만들어진 바로 그 증상
("크루가 예약했다고 말만 한다")이 러너만 바꿔 재발한 것이다. 그 파일 헤더의 "어떤 러너를 쓰던 같은
환경이어야지"(유건 지시)와 충돌한다.
수정: 펜스 없는 변종을 파서에 추가. **정규식이 아니라 스캔**으로 했다 — 커넥터 지시의 args는 중첩
오브젝트라 게으른 매치는 안쪽 }에서 멈추고 탐욕 매치는 뒷 문장을 삼킨다. 첫 { 부터 각 } 후보까지를
차례로 JSON.parse 해 처음 성공한 지점을 취하면 중첩 깊이와 무관하게 정확하다.
오탐 방지: action 키가 있을 때만 지시로 보고, 없으면 원문을 그대로 남긴다(산문 손실 0).
여러 블록은 **원문 순서**로 되돌려 붙인다(잘라내기는 뒤에서 앞으로 해야 인덱스가 안 밀리는데,
그대로 두면 "쪽지 뒤 예약"이 "예약 뒤 쪽지"가 된다).
검증: 실제 kiro 출력으로 왕복 확인(action=mail 파싱, 화면 잔여 0). E2E에 이 왕복 절을 신설해
벤더가 렌더러를 바꾸면 red가 뜨게 했다.
HIGH — 크루 프롬프트가 매 턴 거짓을 말했다.
caps는 전권 상수(capabilities.mjs)라 caps.shell로는 판정할 수 없어 kiro 턴도 "셸 명령: 허용."을
받았다. 크루가 막히자 사장에게 "터미널에서 ls -la를 직접 실행하세요"로 떠넘기는 것이 관측됐다.
작업 폴더 안내도 "등록하면 다음 턴부터 열린다"였는데 이 러너는 반경을 강제하지 않는다.
설계 문서는 "UI가 정직 표기한다"고 적었지만 **설정 UI만 고쳐졌고 크루 프롬프트는 안 고쳐진**
상태였다 — 크루가 그 문장의 1차 소비자다.
수정: noShell(gemini·kiro) 플래그로 셸 문구와 백그라운드·타임아웃 규율 주입을 함께 갈랐고,
작업 폴더 단서를 ko·en 양쪽에 넣었다. **부수 효과로 gemini의 오주입도 해소**됐다 — gemini는 셸이
없는데 "백그라운드 출력은 턴이 끝나면 사라진다" 같은 셸 규율을 받고 있었다.
HIGH(수용) — read Directory 모드 재귀 열거로 금지 구역 **경로**가 유출된다(내용은 보호).
조상 사슬 deny로 진입점을 봉인하려 했으나 불가능했다: kiro-cli의 deny는 정확 경로가 아니라
**디렉터리 포함** 판정이라(실측) WS_ROOT를 막으면 크루 책상까지 죽는다(`/` 한 줄로 전부 차단 —
자기 회사 파일 읽기도 거부됨). 시도하고 되돌린 사실과 함께 한계 5번으로 남겼다. 영향 범위는
다른 회사의 이름·파일 구조이며 자격·본문은 보호된다. 근본 해법은 kiro-cli acp.
MEDIUM — deny 파일 내용이 write 진단 diff로 stdout에 찍힌다.
fs_write가 deny 검사 **전에** 기존 내용을 렌더한다(~/.codex/auth.json 토큰 전문 관측). 정상 턴은
마지막 '> ' 블록만 취해 버려지지만, 접두사 없는 폴백이 stdout 전문을 답변으로 돌리면 대화 정본에
저장돼 다음 턴 맥락으로 재주입된다. → 폴백 경로에서만 도구 추적 줄을 걷어낸다(정상 턴의 답변
본문은 손대지 않는다 — 테스트가 양쪽을 잠근다).
MEDIUM — 쓰기 노출을 문서·UI가 축소 표기했다("열 수 있습니다 / can be opened").
실측은 쓰기까지 된다: 사용자 Documents에 파일 생성, WS_ROOT 안에 새 회사 폴더 트리 생성(부모
디렉터리 자동 생성). → 양쪽을 "읽고 쓸 수 있습니다 / read and written"으로 정정.
LOW — 전역 skills 상속이 이미 재현됨(미실측으로 적어 뒀던 것, 프롬프트 인젝션 표면) · 답변 절단
결함이 생산 경로에서 재현됨 → 한계 절 라벨 격상.
게이트: lint 통과. npm test 910 tests / 905 pass / 0 fail / 5 skipped.
E2E: 감지 → 옵트인 → 영입 → 채팅 → 잔재 0 → 경계 집행 → 우회 도구 배제 → 지시 블록 왕복 →
책상 정상 전 구간 통과.
3라운드가 닫혔다고 확인한 2라운드 지적: grep·glob·shell 배제(전권 caps에서도 부재, 실턴 자기보고
fs_read·fs_write·web_fetch뿐) · homeDir() fail-closed · 하드링크·배치 원자성·번호. 우회 재시도
8종(금고·형제·도트·홈 자격·`..` 상위탈출·쓰기·web_fetch file://·화이트리스트 밖 도구·MCP) 전부 DENY.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
4라운드는 공용 코드 회귀(3라운드가 cli-directives.mjs·chat.mjs를 건드렸다)와 발행 가능성을 봤다.
공용 회귀는 없었다 — 펜스 경로 5종 종전 동작, SDK 경로는 runner를 안 넘겨 무변, codex·antigravity
무변, gemini 변화는 수정(셸 없는데 백그라운드 규율을 받고 있었다). 차단 1건이 나왔다.
HIGH(차단) — 3라운드의 진단 유출 수정이 발현 경로를 빗나갔다.
거부된 도구 호출 뒤 kiro-cli는 다음 어시스턴트 메시지를 추적 줄 **끝에 개행 없이** 붙인다:
`Reading file: … (using tool: read)> 파일 접근이 차단되었습니다…`
줄머리 판정만 쓰던 스크럽이 이 경계를 놓쳐 last가 앞쪽 '> '를 가리키고, 중간 추적 줄이 답변에
전부 실렸다(거부 턴 3/3 재현, 허용 턴 0/2). 3라운드가 넣은 TOOL_TRACE_RE는 표지 부재 폴백에서만
돌아 이 경로에 닿지 않았다 — 코드 주석과 설계 문서가 "정상 턴은 마지막 '> ' 블록만 취해 자연히
버려진다"고 단정했는데 거부 턴에서 거짓이었다. 내 초기 실측 로그에도 `(using tool: write)> ISO_DONE`
형태가 있었는데 줄머리로만 읽고 지나쳤다.
수정: 표지를 ① 문서 시작 ② 개행 뒤 ③ `(using tool: X)` 접합부 셋으로 확장. 접합부를 임의의 `)`
뒤로 넓히지 않은 이유는 산문의 괄호 뒤 인용이 표지로 오인돼 답변이 절단되기 때문이다(그 경계도
테스트로 잠갔다). 거부 턴 3종(형제 회사·금고·홈 자격) 실턴에서 추적 잔존 0·센티널 유출 0 확인.
MEDIUM — 펜스 없는 스캔이 일반 코드펜스 **안**의 예시 지시를 실행했다(전 러너 영향).
크루가 "이렇게 쓰면 됩니다"라고 문서에 보여준 예시가 실제 쪽지·예약이 된다. 스캔 전에 펜스
구간을 계산해 그 안의 후보는 건너뛴다(원문은 그대로 남는다).
LOW 반영:
· readBareObject 스캔 상한 8KB — } 후보마다 JSON.parse라 이론상 O(n²)다. 266KB 답변에서 2.6초
블로킹이 실측됐고 상한 후 1ms.
· 폴백 스크럽의 번호 diff 규칙에 공백 2칸 이상 요구 — 산문 "- 1: 항목"이 지워지던 것.
· 펜스 없는 경로의 bad 계약을 주석에 명문화. 펜스라는 명시 표지가 없어 "지시하려다 틀린 것"과
"그냥 산문"을 구분할 수 없으므로 원문 보존이 맞다(조용한 소실보다 사장이 원시 JSON을 보는 게 낫다).
· kiro 파일시스템 프롬프트 자기모순 제거 — "등록하면 다음 턴부터 열린다" 직후 "그렇게 안내하지
마라"였다. 이제 kiro에는 그 안내를 애초에 넣지 않는다(다른 러너 문구 무변).
· UI에 타사 **구조**(폴더·파일 이름) 열거 가능성 명시 — 내용은 차단되지만 이름은 보인다는 사실이
빠져 있어, 설계 문서 한계 5번이 피하려던 독법을 화면이 만들고 있었다.
· 설계 문서 게이트 수치 갱신.
남긴 것: 공용 파서 변경의 회귀 테스트가 test/kiro-runner.test.mjs에만 있다. 파일 위치상 kiro
전용으로 읽히지만 잠그는 대상은 공용 계약이다 — 메인테이너가 원하면 cli-directives 테스트로
옮기는 것이 맞다(이 PR에서는 한 파일에 모아 두는 쪽을 택했고, 설계 문서에 그 사실을 남겼다).
게이트: lint 통과. npm test 911 tests / 906 pass / 0 fail / 5 skipped.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
4라운드가 닫았다고 본 결함이 **생산 채팅 경로에서 재현**됐다. 접합부 형태가 더 있었다.
HIGH(차단) — 접합부 열거가 두 라운드 연속 부족했다.
4라운드는 `(using tool: X)`만 봤는데 실제로는 두 형태가 더 있었다:
① 인자형 `(using tool: read, max depth: 0, max entries: 1000, excluding: defaults)`
② 배치형 `↱ Operation 3: Reading file: /x, all lines> …` ← (using tool:) 자체가 없다
하네스 ② 6/6, ① 1/1, 생산 경로(/api/companies/{ws}/chat) 각 1/1 재현. 유출물은 도구 추적 줄 +
**금지 파일의 전체 경로**(형제 회사·WS_ROOT 도트·~/.codex/auth.json)이고 chats/에 저장돼 다음 턴
맥락으로 재주입됐다. 파일 내용은 8턴 전부 미유출.
수정: 접합부를 더 열거하는 접근을 버렸다 — 두 번 실패한 방법이다. 검수 권고대로 구조를 바꿨다.
· 추적 줄을 **먼저 식별해 분리**한다(줄머리 패턴 + `(using tool:` 포함 여부). 식별된 줄 안에서만
첫 `> ` 앞을 잘라 개행을 넣으므로 산문의 `> `(비교 연산·인용)를 표지로 오인하지 않는다.
· 추출 후 추적 줄을 **항상** 걷어낸다. 거부·배치 경로에서는 추적이 메시지 **뒤에도** 붙어
(`Command fs_read is rejected …` + 규칙 목록) 표지 판정만으로는 답변에 남는다.
검증: 합성 12종(단순·여러 줄·접합부 4형태·거부 후행·쓰기 diff·마크다운 인용·산문 괄호·비교
연산·표지 부재) + 실턴 3종(디렉터리 열거·배치 읽기·쓰기 거부) 전부 원시 추적 0.
대가: 답변 본문의 kiro식 번호 diff(`+`+공백 2칸 이상+숫자+`:`)가 지워진다. 그 규칙은 fs_write가
거부 **전에** 기존 파일 내용을 렌더하는 자리라 유출 위험이 가장 크므로 살리는 쪽을 택했고,
좁은 산문 손실을 한계 7번으로 명시했다. 일반 목록(`- 1: …`)·공백 1칸은 규칙 밖이다.
MEDIUM — 미닫힘 펜스가 뒤 펜스와 짝지어 진짜 지시를 삼켰다(재현 1/1). ~~~ 펜스도 미인식.
정규식 대신 줄 단위로 여닫고 ~~~를 함께 인식한다. 미닫힘 펜스는 구간을 만들지 않는다 — 파일 끝까지
보호 구간으로 늘리면 답변 끝의 실제 지시(관행상 마지막에 붙는다)가 앞쪽 깨진 펜스 하나로 무시된다.
검증 5종(닫힌 펜스 예시 무시·미닫힘 뒤 지시 실행·~~~ 예시 무시·펜스 밖 지시 실행·연속 펜스 뒤
지시 실행) 전부 통과.
LOW — i18n 문구의 ** 마크다운이 평문으로 노출됐다(이 사전에서 ** 사용은 이 PR이 유일했다).
1라운드의 \u2019 노출과 같은 클래스라 같은 방식으로 제거.
공용 코드 회귀: 5라운드가 정량 대조로 없음을 확인했다 — parseDirectives 18케이스 중 14 동일·4 차이
(전부 의도된 신규 경로), commonDirectives 180조합 중 156 동일·24 차이(gemini 6·kiro 18, gemini
차이는 3라운드가 의도한 셸 규율 제거). claude·codex·antigravity·glm·kimi·openrouter·grok 바이트 동일.
게이트: lint 통과. npm test 911 tests / 906 pass / 0 fail / 5 skipped.
E2E 전 구간 통과(감지 → 옵트인 → 영입 → 채팅 → 잔재 0 → 경계 집행 → 우회 도구 배제 →
지시 블록 왕복 → 책상 정상).
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
6라운드가 5라운드 유출 수정을 확인했다(생산 채팅 경로 6벡터, reply·chats/ 양쪽 원시 추적 0, 센티널 0, 위양성 아님 — raw stdout에 원시 추적이 존재함을 대조 캡처). 대신 **그 수정이 새로 들여온 과삭제**가 잡혔고, 답변이 조용히 파괴되는 결함이라 유출과 동급으로 다뤘다. CRITICAL — 상시 추적 필터가 정상 중첩 목록을 삭제했다. 거부 규칙 목록(` - /path`)과 정상 마크다운 중첩 불릿은 형태가 같다. 단독 줄 규칙으로 두니 "폴더 아래 파일 보여줘" 같은 상용 답변에서 부모 한 줄만 남고 중첩 4줄이 전부 사라졌다(실측). 사용자 화면과 대화 정본(chats/) 양쪽에서 답변이 영구 소실되고 다음 턴 맥락으로 재주입된다. 회귀의 뿌리: 4라운드(116a28d)는 같은 패턴을 표지 부재 폴백에서만 적용했는데, 5라운드(17ca20c)가 필터를 "항상" 적용하면서 정상 답변 경로까지 사정권에 들어왔다. 수정: 거부 규칙 목록만 **문맥 의존**으로 — `Command X is rejected` 머리 줄 직후 연속 구간에서만 걷어낸다. 나머지 추적 규칙은 그대로 상시 적용(유출 차단 유지). 검증: 합성 13종 + 실턴(3단 중첩 목록 요청)에서 ` - gamma.md`까지 온전히 보존. 유출 3벡터 재검에서 원시 추적 0 유지. 부수 — 영문 산문 통삭제. `Reading`·`Creating:`·` - Completed in`만 보던 규칙이 "Reading files is safe." / "Creating: a plan for Q3" / "- Completed in 3 days"를 지웠다. 각 규칙이 **경로나 형식을 함께 요구**하도록 좁혔다: `Reading file:` + 비공백, `Creating: …/…`, ` - Completed in 0.1s` 형태. 실제 추적(경로 포함)은 그대로 제거된다. 게이트: lint 통과. npm test 911 tests / 906 pass / 0 fail / 5 skipped. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
7라운드가 6라운드 차단 사유(과삭제)와 5라운드 유출이 생산 채팅 경로에서 모두 해소됐음을 확인하고 APPROVE_WITH_NITS로 판정했다 — 과삭제 재시도 6벡터(중첩 목록 3단·마크다운 트리·여러 문단 초안·표· 산문 트랩·코드블록 diff)에서 목록·트리·문단·표 전부 보존, 유출 4벡터에서 원시 추적 0(도구가 실제로 돈 턴을 디스크로 증명한 이중 확인 포함). 받은 지적 3건 중 둘을 여기서 닫는다. HIGH — `Batch \w+` 규칙만 도구명 요구가 빠져 있었다. 같은 커밋이 Reading·Creating·Completed에는 "경로나 형식을 함께 요구"를 넣었는데 이 규칙만 누락돼, 줄머리 `Batch processing finished.` / `Batch size is 32.` 같은 정상 영문 한 줄이 조용히 삭제됐다 (raw 대조로 확증: raw 존재 → 스크럽 후 소실). 실제 추적은 `Batch fs_read` 형태이므로 도구명으로 좁혔다. 한국어·`Batched…`는 애초에 무영향이었다. LOW — 거부 머리 줄 뒤 빈 줄이 구간을 유지해 다음 정상 중첩 불릿까지 지웠다. kiro는 거부 규칙 목록을 빈 줄 없이 연속 출력하므로(실측) 빈 줄에서 구간을 닫는 것이 안전하다. "거부 블록 → 빈 줄 → 정리 결과 목록" 형태가 온전히 남는다. LOW(수용) — 번호 diff가 코드블록 안에서도 삭제된다. 렌더러가 코드펜스를 제거해 스크럽이 "코드블록 안"을 판별할 수 없다 — 한계 1·7번과 같은 뿌리이며 kiro-cli acp 이관으로 함께 닫힌다. 문서에 명시. 게이트: lint 통과. npm test 911 tests / 906 pass / 0 fail / 5 skipped. build 통과. E2E 9단계 전 구간 통과. 유출 3벡터·중첩 목록 실턴 재검 유지. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
8c5688a to
cb58655
Compare
v0.1.41로 리베이스 (베이스가 같은 영역으로 움직였습니다)
겹친 부분을 확인한 결과 제가 고친 라인과는 무접촉이었습니다 — 새 변경은 새로 들어온 "기본 러너" 셀렉터는 추가 배선이 필요 없습니다. 리베이스 후 재검증:
파일·증감(18 files, +1570/-29)과 커밋 9개 구성은 그대로입니다. |
왜
Kiro CLI(
kiro-cli)는 AWS 계정 하나로 여러 벤더 모델을 함께 쓴다 — 실측 카탈로그에 Claude(Opus 5·Sonnet 5·Haiku 4.5), GPT-5.6(Sol·Terra·Luna), GLM-5, DeepSeek V3.2, MiniMax M2.5, Qwen3 Coder Next가 한 자격 아래 있습니다. 지금 Argo에서 같은 폭을 얻으려면 러너를 여럿 연결해야 하고, IAM Identity Center / Builder ID 구독으로 Kiro를 쓰는 사용자는 그 구독을 Argo에 태울 경로가 없습니다.분류는 BYOA(CLI 래핑 + 권한 근사 정직 표기) — codex·antigravity와 같은 계열이고 세 번째 배관을 만들지 않았습니다.
kind:'cli'+isCliRunner디스패치를 그대로 재사용합니다.설계·실측·한계 전문:
docs/kiro-runner-design.md실측 (kiro-cli 2.17.0, macOS, 2026-08-12)
chat --no-interactive [--model] [--effort] --wrap never -- <prompt>exit 0--list-models19종 중 등재한 10종 원샷 왕복 10/10 통과--effort low|medium|high|xhigh|max— Argoeffort와 1:1whoami→ 로그인Logged in with IAM Identity Center(0) / 미로그인Not logged in(1)OAuth error: Auth portal timed out(1)<cwd>/.kiro/agents/<name>.json+--agent·mcpServers:{}+useLegacyMcpJson:falseantigravity와 갈리는 지점: 자격을
whoami로 실제 확인할 수 있어 낙관authed(authUnknown)가 필요 없고, 그래서RUNNER_AUTH맨 끝일 필요도 없습니다(grok 뒤·antigravity 앞).⚠
/Logged in/i는"Not logged in"에도 매칭됩니다. 줄머리 앵커(/^Logged in/im)가 필수이고, 그 함정을 테스트가 잠급니다.파일 반경 — 네 수단 중 하나만 강제된다
toolsSettings에 경로 규칙이 있다는 공식 문서를 그대로 믿지 않고 넷을 다 돌렸습니다(카탈로그 규칙의 "문서만 보고 추가 금지"와 같은 태도). 비대화 기준입니다.tools목록 제외allowedToolsallowedPathsallowedTools의 포괄 신뢰가 경로 규칙을 덮는다(문서 우선순위와 일치)denyByDefault!deniedPathsallowedTools보다 우선 ← 유일한 강제 수단즉
openRoots(홈+지정 폴더) 화이트리스트를 실을 자리가 없습니다. 그래서 집행은 불변 금지 구역 deny로 했습니다 —APP_ROOT·HARD_HOME_PATHS(~/.argo·~/.codex·~/.claude·~/.gemini·~/.claude.json·~/.mcp.json) · WS_ROOT의 형제 회사 · 회사 금고(WS_CONTROL_FILES/DIRS, 원장은 쓰기만) · 직속 도트. 목록은permission-gate.mjs와 같은 값이고 소스 파싱 대조 테스트가 정합을 잠급니다.caps.fs반경 차이는i18n settings.workroots.runnerNote가 ko·en 양쪽에 정직 표기하고(workroots.mjs의 러너별 집행 절에 kiro 항목 추가), 테스트가 그 표기를 잠급니다.두 함정이 있었습니다. ① kiro-cli는 canonical 경로로 deny를 판정합니다 — raw만 실으면 심링크 경유
ARGO_ROOT(맥의/tmp·/var)에서 경계가 통째로 열립니다. raw·canonical 양형을 싣습니다. ②deniedPaths는read·write에만 섭니다 —grep은 금고·형제 회사의 매치 줄을 그대로 반환하고(같은 턴의read는 정상 DENY),glob은~/.codex7,766개를 열거하고,shell은 원장까지 개조했습니다. 설정으로 막을 수 없으니 도구를 주지 않습니다(read·write+ 브라우저 능력 시web_fetch). 셸 부재는 gemini 선례가 있고(비대화에서 셸 미실행), 그 능력 차이를 UI가 명시합니다.공용 코드를 건드린 두 곳 (검토 요청)
kiro만으로 끝나지 않는 변경이 둘 있습니다. 둘 다 kiro 없이는 발현하지 않지만 파일은 공용입니다.
src/cli-directives.mjs— kiro-cli는 답변을 렌더해서 내보내며 코드펜스를 제거합니다(백틱 0개, JSON은 한 줄).BLOCK_RE가 영원히 매치되지 않아 루틴 예약·쪽지·결재·커넥터가 전부 조용히 무동작하고 원시 JSON이 사장 화면에 남았습니다 — 이 파일이 없애려고 만들어진 바로 그 증상의 러너별 재발이라 고쳐야 했습니다. 펜스 없는 변종을 스캔으로 추가했고(중첩args때문에 정규식으로는 부정확),"action"키가 있을 때만 지시로 보며 없으면 원문을 보존합니다. 일반 코드펜스 안의 예시는 건너뜁니다.src/chat.mjs—CAPABILITIES가 전권 상수라caps.shell로는 판정할 수 없어 kiro 턴도 "셸 명령: 허용."을 받았고, 크루가 막히자 사장에게 "터미널에서 직접 실행하세요"로 떠넘기는 것이 관측됐습니다.noShell(gemini·kiro) 플래그로 셸 문구와 백그라운드 규율 주입을 갈랐습니다. 부수 효과로 gemini의 오주입도 해소됩니다 — gemini는 셸이 없는데 백그라운드·타임아웃 규율을 받고 있었습니다.두 변경의 회귀는 5라운드 검수가 정량 대조로 확인했습니다:
parseDirectives18케이스 중 14 동일·4 차이(전부 신규 경로),commonDirectives180조합 중 156 동일·24 차이(gemini 6·kiro 18). claude·codex·antigravity·glm·kimi·openrouter·grok는 바이트 동일합니다.검증
npm run lint통과 ·npm test911 tests / 906 pass / 0 fail / 5 skipped(main 883에서 +28) ·next build통과node scripts/e2e-kiro.mjs, 격리ARGO_ROOT+ 별도 포트 + Supabase env 제거) 9단계 전 구간 통과:카탈로그 → 상태(실측
authed) → host 옵트인 → 회사 생성 → 크루 영입 턴 → 채팅 턴 → 턴 잔재 0 → 경계 집행 → 우회 도구 배제 → 지시 블록 왕복 → 책상 정상알려진 한계 (설계 문서 14항목 중 무거운 것)
>로 시작하는 줄이 있으면 앞부분이 유실됩니다(생산 경로 재현). 지시 블록 문제와 뿌리가 같습니다.readDirectory 재귀 열거로 금지 구역의 경로가 노출됩니다(내용은 보호). 진입점(조상 사슬) 봉인을 시도했으나 kiro-cli의 deny가 디렉터리 포함 판정이라 WS_ROOT를 막으면 크루 책상까지 죽습니다 — 되돌리고 한계로 남겼습니다.caps.fs반경 미강제 · 셸·검색 도구 부재(능력 차이) · 하드링크 우회 · 전역 steering·skills 상속(프롬프트 인젝션 표면, 에이전트 설정으로 못 끕니다)deniedPaths글롭의 백슬래시 매칭이 실기기 미확인입니다. 실패하면 경계가 조용히 열리므로 그 환경 발행은 보류를 권합니다.위 넷 중 앞 셋은
kiro-cli acp(Agent Client Protocol, JSON-RPC over stdio) 이관으로 함께 닫힙니다 — 도구 호출을 우리가 승인·거절하므로 SDK 러너와 같은 강도가 되고 파싱도 사라집니다. 별도 PR로 제안합니다.커밋
feat1 +docs1 + 검수 반영fix7. 커밋 간 빌드·테스트를 유지했고, 검수 라운드별 지적과 실측 근거를 커밋 메시지에 남겼습니다.첫 기여라 배관·표기 관행이 어긋난 곳이 있으면 알려주시면 맞추겠습니다. 특히 공용 코드 두 곳(
cli-directives.mjs·chat.mjs)의 접근 방식과, 파일 반경을 "불변 경계 deny + 정직 표기"로 처리한 판단에 대한 의견을 듣고 싶습니다. 범위가 과하다면 kiro 전용 파일만 남기고 쪼개겠습니다.