지웠을 터인 claude -p 가 남아 있었다 ── C3 v2.6.1〜v2.7.0
요약
C3(Claude Code Conductor)의 최신 버전 업데이트 내용을 다루며, v2.7.0에서 발견된 `claude -p` 잔존 문제와 보안 개선 사항을 중점적으로 설명합니다. 특히 과거 Parallel Orchestra (PO) 폐지 과정에서 간과되었던 세션 종료 시 Stop hook 내부에 여전히 `claude -p`가 남아있었음을 밝히고, 이를 Agent 방식이 아닌 Skill 방식으로 구현하려 했던 경험담을 공유했습니다. 또한 v2.6.1에서는 PowerShell 인젝션 방어 강화, LLM 자식 프로세스 공격 표면 축소, 프롬프트 히스토리의 민감 정보 마스킹 등 중요한 보안 감사(security-audit) 수정 사항들을 적용했음을 안내합니다.
핵심 포인트
- v2.7.0에서 세션 종료 시 Stop hook 내부에 `claude -p`가 잔존하는 문제가 발견됨.
- 이 문제를 해결하기 위해 Agent 방식으로 요청했으나, Claude가 Skill 형태로 구현하여 실제 동작에 문제가 발생함.
- v2.6.1에서는 PowerShell 인젝션을 방어하기 위해 base64 인코딩을 적용하고, 민감 정보 마스킹 기능을 강화함.
- LLM 자식 프로세스의 공격 표면 축소 및 코드 품질 개선 등 정기적인 보안 감사(security-audit) 결과가 반영됨.
이전 기사: https://zenn.dev/satoh_y_0323/articles/db51bb73a97c19
C3 GitHub: https://github.com/satoh-y-0323/claude-code-conductor / PyPI: https://pypi.org/project/claude-code-conductor/ / 공식 문서: https://satoh-y-0323.github.io/claude-code-conductor/
본 기사의 범위: v2.6.1의 정기 security-audit (보안 감사) 수정과, v2.7.0에서 claude -p 를 완전히 제거한 이야기. 덤으로 permission_handler (권한 핸들러)의 사용 편의성 개선도 소개합니다.
서론
이전 기사(v2.5.0〜v2.6.0)에서는 Codex CLI와의 병렬 리뷰를 통해 "Claude가 놓친 진짜 취약점을 Codex가 찾아냈다"는 이야기를 했습니다.
이번에는 조금 성격이 다릅니다.
C3는 v2.5.0에서 Parallel Orchestra (PO)라는 별도 프로세스 방식을 폐지하고, claude -p (헤드리스 (headless) 기동)를 거의 사용하지 않는 구성으로 이행했습니다. "이것으로 claude -p 는 사라졌다"라고 생각하고 있었는데, Stop hook (중지 훅)의 깊은 곳에 조용히 살아남아 있었다 ── 그런 이야기입니다.
게다가 "Agent (에이전트)로서 다시 구현해 달라"고 의뢰했더니, Claude가 Skill (스킬)로서 구현해 버렸다는 결말도 있습니다.
v2.6.1: 정기 security-audit 결과 적용
v2.7.0 이야기를 하기 전에, 직전 릴리스를 가볍게 소개합니다.
v2.6.1은 "API 변경 없음 · 정기 security-audit 결과를 수정할 뿐"인 패치 릴리스입니다.
보안 수정 3건
[SR-INJ-002] PowerShell의 EncodedCommand화
지난번 Codex가 지적한 PowerShell 인젝션 (injection) 대책의 연장선입니다. Windows 알림을 보내는 부분에서 safe_msg를 f-string에 직접 삽입하던 방식을 폐지하고, base64.b64encode로 변환한 뒤 -EncodedCommand로 전달하도록 변경했습니다. ', 백틱(), $`를 포함하는 메시지에서도 인젝션이 불가능해졌습니다.
[SR-AI-001] LLM 자식 프로세스의 공격 표면 축소
consolidate_memory.py가 claude -p로 서브 프로세스 (subprocess)를 기동하는 부분에서, --dangerously-skip-permissions 플래그를 제거하고 --tools ""로 모든 도구를 무효화했습니다. 이 시점에서는 아직 claude -p 자체는 남아 있었지만, 권한은 최소화했습니다.
[SR-V-001] prompt-history의 비밀 정보 마스킹
select_tier.py가 프롬프트 앞부분 200자를 .claude/logs/prompt-history.jsonl에 저장할 때, API 키 · 토큰 · 패스워드에 해당하는 7가지 패턴을 ***로 마스킹하도록 했습니다.
코드 품질 수정 14건
sqlite3.connect를 직접 작성했던 부분의 _BUSY_TIMEOUT_MS 통일, 원자적 (atomic) 쓰기 누락 보완, 타입 분기 명시화 등. 모두 security-audit 에이전트에 의한 정기 스캔으로 검출된 사항들입니다.
v2.7.0 메인 스토리: 지웠을 터인 claude -p 가 남아 있었다
배경: PO 폐지로 지웠다고 생각했다
C3는 v2.1.0〜v2.5.0의 4일간 5개 릴리스를 통해 Parallel Orchestra (PO)를 완전히 폐지했습니다. PO는 별도 프로세스로 에이전트를 기동하는 방식으로, 핵심 구현이 claude -p 호출이었습니다.
폐지 후에는 Claude Code의 인터랙티브 세션 (interactive session) 내에서 Agent 도구를 사용하는 방식으로 완전히 이행했습니다. claude -p의 차례는 없어졌어야 했습니다.
Stop hook에 잠복해 있던 잔해
그런데, 세션 종료 시 동작하는 Stop hook 안에 아직 살아있는 claude -p가...
있었습니다.
.claude/hooks/consolidate_memory.py
는 세션 종료 시 과거 7일간의 세션 파일을 집약하여 llm_summary.md를 업데이트하는 처리를 담당하고 있습니다. 이 '집약' 부분에서 LLM을 호출할 필요가 있었고, 그 구현으로서 claude -p를 통해 서브프로세스 (Subprocess)를 기동하고 있었던 것입니다.
PO를 폐지했을 때, 이 hook은 '에이전트 오케스트레이션 (Agent Orchestration)이 아닌 단순한 LLM 호출'로 취급되었기 때문에 간과되었습니다.
# consolidate_memory.py의 구 구현 (간략화)
def run_llm_summary(prompt: str) -> str:
result = subprocess.run(
...
Agent로 구현해달라고 요청했더니, Skill로 구현되었다
"Stop hook에서 claude -p를 폐지하고, 대신 Agent로서 LLM 요약을 실행하도록 구현해달라"고 요청했습니다.
Claude가 Wave 0에서 구현한 것은 .claude/skills/summarize-memory/SKILL.md였습니다.
Skill로 구현되어 버린 것입니다.
언뜻 보기에는 그럴싸하지만, Skill은 동작하지 않습니다. Skill 툴은 LLM의 컨텍스트 (Context) 내에서 마크다운을 읽어들이는 메커니즘이며, 자식 프로세스 (Child Process)를 기동하지 않습니다. Stop hook은 Python 스크립트로서 Claude Code와 별개의 프로세스로 동작하기 때문에, "Skill을 호출한다"는 개념이 성립하지 않습니다.
Stop hook (Python 서브프로세스)
↓ 호출 불가
Skill 툴 (Claude Code 세션 내의 LLM 읽기)
다시 만들기: exit 2 + Agent(run_in_background=True)
올바른 구현은, Stop hook이 Claude Code 본체에 대해 "Agent를 기동해달라"고 지시를 전달하는 것입니다.
Claude Code의 hook 사양에는 exit 2 + stderr를 통해 LLM으로 피드백을 반환하는 기능이 있습니다. 이를 사용하면 hook이 종료된 후 Claude가 stderr의 내용을 시스템 메시지 (System Message)로 받아들여 다음 액션을 취합니다.
# session_stop.py의 신 구현 (간략화)
def main():
if not _needs_summary():
...
그리고 .claude/agents/summarize-memory.md를 신규 생성하여, LLM 요약 절차를 Agent 정의로서 이식했습니다 (구 SKILL.md의 내용을 그대로 사용할 수 있습니다).
Stop hook (Python) → exit 2 + stderr
↓ Claude Code가 수신
Claude → Agent(run_in_background=True, subagent_type="summarize-memory")를 기동
...
이 방식의 장점은 Stop hook이 블로킹 (Blocking) 없이 종료될 수 있다는 점입니다. 구 구현은 claude -p의 완료를 기다린 후에 hook이 반환되었기 때문에, 세션 종료 시마다 수 초에서 십수 초의 대기가 발생했습니다. 신 구현에서는 hook이 즉시 종료되며, 요약은 백그라운드 (Background)에서 실행됩니다.
요약 필요성을 기계적으로 판정
함께, 매번 LLM을 호출하지 않기 위한 메커니즘도 정비했습니다.
구 구현은 쿨다운 (Cooldown, 60분)으로 스로틀링 (Throttling)을 하고 있었지만, "60분 이내에 2번 세션을 종료하면 요약이 업데이트되지 않는다"는 문제가 있습니다.
신 구현에서는 os.path.getmtime()으로 파일의 수정 시각을 비교합니다.
def _needs_summary() -> bool:
"""세션 파일이 llm_summary.md보다 최신이면 요약이 필요함"""
summary_mtime = _safe_mtime(SUMMARY_PATH)
...
"세션 파일이 업데이트되어 있으면 요약이 필요하고, 그렇지 않으면 불필요하다"는 판단을 LLM 없이 수행할 수 있습니다. 쿨다운이라는 자의적인 시간 제한이 사라지고 단순해졌습니다.
결과: C3에서 claude -p 가 사라졌다
이 변경을 통해 consolidate_memory.py 의 LLM 관련 함수 및 상수 11개가 삭제되었으며, 파일이 -400행 이상 슬림해졌습니다. C3의 전체 코드베이스에서 claude -p 호출이 완전히 사라졌습니다.
기타 개선 사항: permission_handler 사용 편의성 향상
버튼이 포함된 토스트 알림으로 원클릭 auto_allow
Claude Code가 "이 명령어를 실행해도 되겠습니까?"라며 권한 확인 다이얼로그를 띄우는 경우가 있습니다. 매번 승인해야 하는 명령어가 있다면 permission_rules.json의 auto_allow에 추가해 두면 확인 절차가 스킵됩니다.
지금까지는 permission_rules.json을 직접 열어서 패턴을 작성해야 했습니다. Bash(git status*)와 같은 와일드카드 (wildcard) 패턴을 스스로 생각해서 쓰는 것은 은근히 번거로운 일입니다.
v2.7.0부터는 Windows 알림에 "자동 승인에 추가: Bash(git status *)" 버튼이 추가되었습니다.
[권한 확인] Bash: git status --short
[승인] [거부] [자동 승인에 추가: Bash(git status*)]
↑ 클릭 시 permission_rules.json に 追記
permission_handler.py가 도구 이름과 인수를 바탕으로 와일드카드 패턴을 자동으로 추정합니다.
| 도구 | 추정 패턴 예시 |
|---|---|
Bash | 명령어 시작 부분 1~2 토큰에 와일드카드 부가 |
Write / Edit / Read | 상위 디렉토리 /** |
WebFetch | domain:hostname |
버튼 클릭을 통한 permission_rules.json 쓰기는 아토믹 (atomic, mkstemp + os.replace)하게 이루어지므로, 병행 실행 중인 다른 프로세스가 파일을 읽고 있더라도 파일이 손상되지 않습니다.
auto_allow 리스트 상한 100건
auto_allow에 무제한으로 패턴을 추가할 수 있게 되면, 의도하지 않은 명령어까지 자동 승인될 리스크가 높아집니다. 따라서 100건을 초과하면 에러가 발생하는 크기 제한을 설정했습니다. 일상적인 개발 용도라면 100건을 초과하는 일은 거의 없을 것입니다.
worktree 병렬 실행 시 파일 가져오기 수정
parallel-agents 스킬로 worktree를 사용한 병렬 실행을 할 경우, 메인 브랜치로의 파일 가져오기 (merge)가 일부 케이스에서 실패하는 문제가 있었습니다.
git check-ignore -q를 통한 .gitignore 체크를 포함한 분기 하이브리드 방식으로 변경하여 구조적으로 수정했습니다. 이와 함께 worktree_guard.py의 CWD 기반 자동 활성화 기능을 통해, worktree 외부로의 Write/Edit를 exit 2로 차단하는 보호 기능이 일관되게 작동하도록 했습니다.
C3가 지향하는 것
여기서 C3의 설계 사상에 대해 잠시 말씀드리겠습니다.
C3는 "업무 애플리케이션 개발 팀이 사용하는 휴먼 인 더 루프 (Human-in-the-loop)형 개발 프레임워크"를 목표로 만들어졌습니다.
키워드는 두 가지입니다.
휴먼 인 더 루프 (Human-in-the-loop)
C3의 워크플로우에는 각 단계 사이에 반드시 인간의 확인 포인트가 존재합니다.
히어링 → [승인] → 설계 → [승인] → 계획 → [승인]
→ 구현 (TDD wave) → [승인] → 리뷰 → [승인] → 완료
"AI가 자율적으로 계속 움직였으면 좋겠다"는 용도에는 적합하지 않습니다. 각 단계에서 AI가 내놓은 결과물을 인간이 확인하고 수정할 수 있는 구조를 의식적으로 유지하고 있습니다.
이것은 취미적인 선택이 아니라, 업무에서의 이용을 고려했을 때의 필연입니다. 업무 애플리케이션에는 "왜 이 설계로 했는가", "이 트레이드오프 (trade-off)를 왜 선택했는가"라는 판단 포인트가 있으며, 그 부분은 AI에게 전적으로 맡길 수 없습니다. 리포트 파일 (architecture-report / plan-report / code-review-report / security-review-report / test-report)을 생성하여 인간이 읽고 판단한 뒤 다음으로 진행하는 설계는 바로 그 때문입니다.
자동화 프레임워크가 아님
"C3로 CI/CD를 자동화하고 싶다", "코드를 작성해서 자동으로 배포하고 싶다"라는 용도에는 더 적합한 도구들이 있습니다 (OpenHands나 AutoCodeRover 등).
C3가 잘하는 것은 "개발 사이클 속에서 인간과 AI가 협조하며 품질을 높여가는" 장면입니다.
리포트의 축적, 패턴의 학습, 정기적인 security-audit (보안 감사), 과거의 실패한 접근 방식의 기록 ── 이러한 개발 프로세스의 지식 관리를 기계적으로 서포트하는 것이 C3의 역할입니다.
버전 정리
| 버전 | 날짜 | 주요 변경 사항 |
|---|---|---|
| v2.6.1 | 2026-05-15 | security-audit 정기 감사 수정 (보안 3건 · 품질 14건) |
| v2.7.0 | 2026-05-16 | claude -p 완전 제거 (Stop hook를 background Agent 방식으로 이행), permission_handler 버튼 포함 알림, auto_allow 상한, worktree 가져오기 수정 |
마치며
"지웠을 터인 claude -p"
는 정기적인 security-audit와 꾸준한 코드 재고 조사를 통해 발견할 수 있었습니다.
Claude에게 "Agent로서 구현해줘"라고 의뢰했더니 Skill로 구현되어 버린 것은, Skill과 Agent의 메커니즘 차이를 C3 자체가 정확하게 전달하지 못했다는 의미이기도 합니다. SKILL.md와 agents/*.md의 용도 구분을 CLAUDE.md에 명시함으로써 재발은 방지할 수 있을 것으로 보입니다.
이러한 "AI가 실수하는 장면을 기록·수정해 나가는" 프로세스 자체가 C3를 사용한 개발 사이클이 됩니다.
링크
- C3 GitHub: https://github.com/satoh-y-0323/claude-code-conductor
- C3 PyPI: https://pypi.org/project/claude-code-conductor/
- C3 공식 문서: https://satoh-y-0323.github.io/claude-code-conductor/
- 이전 기사 (Claude가 놓친 취약성을 Codex가 발견한 이야기, v2.5.0〜v2.6.0): https://zenn.dev/satoh_y_0323/articles/db51bb73a97c19
AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기