research

ZeroClaw — Security Policy

ZeroClaw — Security Policy

Layer 3 학습. 실제 소스 코드 기반. 파일: src/security/policy.rs, src/security/mod.rs


전체 구조

SecurityPolicy
    │
    ├─ AutonomyLevel          ← ReadOnly / Supervised / Full
    ├─ 커맨드 허용/차단 정책    ← allowed_commands, 위험도 분류
    ├─ 경로 접근 정책          ← workspace_only, forbidden_paths, allowed_roots
    ├─ Rate limiting           ← ActionTracker (슬라이딩 윈도우, 1시간)
    └─ Tool operation gate     ← Read 항상 허용, Act는 autonomy + rate 체크

보조 모듈:
    audit.rs         — 보안 이벤트 로깅
    secrets.rs       — AES 암호화 자격증명 저장
    prompt_guard.rs  — 프롬프트 인젝션 방어
    leak_detector.rs — 응답 내 민감 정보 유출 감지
    pairing.rs       — 채널 장치 페어링 인증
    estop.rs         — 긴급 정지 (E-Stop)
    sandbox (Docker/Firejail/Bubblewrap/Landlock) — OS 레벨 격리

AutonomyLevel

pub enum AutonomyLevel {
    ReadOnly,    // 관찰만, 실행 불가
    Supervised,  // 실행 가능하지만 위험 작업은 승인 필요 (기본값)
    Full,        // 정책 범위 내 완전 자율 실행
}

실제 동작 차이:

| | ReadOnly | Supervised | Full | |---|---|---|---| | shell 명령 실행 | ❌ | ✅ (위험도 체크) | ✅ (위험도 체크) | | High risk 명령 | ❌ | 명시적 승인 필요 | 명시적 허용 시 가능 | | Medium risk 명령 | ❌ | 승인 필요 | 승인 없이 실행 | | Read tool | ✅ | ✅ | ✅ | | Act tool | ❌ | ✅ + rate limit | ✅ + rate limit | | system prompt | "read-only" 안내 | "ask before acting" | "execute directly" |


SecurityPolicy 필드

pub struct SecurityPolicy {
    pub autonomy: AutonomyLevel,           // 기본: Supervised
    pub workspace_dir: PathBuf,            // 작업 디렉토리
    pub workspace_only: bool,              // 기본: true (workspace 밖 접근 차단)
    pub allowed_commands: Vec<String>,     // 허용 명령어 목록
    pub forbidden_paths: Vec<String>,      // 금지 경로 목록
    pub allowed_roots: Vec<PathBuf>,       // workspace 외 허용 경로
    pub max_actions_per_hour: u32,         // 기본: 20
    pub max_cost_per_day_cents: u32,       // 기본: 500 (5달러)
    pub require_approval_for_medium_risk: bool, // 기본: true
    pub block_high_risk_commands: bool,    // 기본: true
    pub shell_env_passthrough: Vec<String>, // 허용 환경변수
    pub tracker: ActionTracker,            // 슬라이딩 윈도우 카운터
}

기본 allowed_commands (Unix):

git, npm, cargo, ls, cat, grep, find, echo, pwd,
wc, head, tail, date, df, du, uname, uptime, hostname
(Linux 추가: free)

기본 forbidden_paths:

/etc, /root, /home, /usr, /bin, /sbin, /lib, /opt,
/boot, /dev, /proc, /sys, /var, /tmp,
~/.ssh, ~/.gnupg, ~/.aws, ~/.config

커맨드 검증 파이프라인

validate_command_execution(command, approved)
    │
    ├─ 1. is_command_allowed() ─ 5중 차단 게이트
    │       ├─ ReadOnly → 즉시 false
    │       ├─ subshell 차단: `, $(), <(), >()
    │       ├─ redirect 차단: 언쿼트 > < (쓰기/읽기 경로 우회 방지)
    │       ├─ tee 차단 (pipe를 통한 파일 쓰기 우회 방지)
    │       ├─ 단독 & 차단 (백그라운드 체이닝, && 는 허용)
    │       └─ 세그먼트별 allowed_commands 검사
    │           (;, &&, ||, |, 개행으로 분리 — 쿼트 인식)
    │
    ├─ 2. command_risk_level()
    │       ├─ High: rm, sudo, curl, wget, ssh, shutdown, ...
    │       ├─ Medium: git commit/push/reset, npm install, touch, mv, ...
    │       └─ Low: 그 외
    │
    ├─ 3. block_high_risk_commands 체크
    │       → true이면 High 차단
    │       → 단, allowed_commands에 명시적으로 있으면 우회 허용
    │         ("*" 와일드카드는 명시적 허용 아님 → 우회 불가)
    │
    └─ 4. autonomy × approval 게이트
            High + Supervised + !approved → 에러
            Medium + Supervised + require_approval + !approved → 에러

핵심 설계 원칙:

와일드카드(*)로 모든 명령을 허용해도 block_high_risk_commands는 여전히 작동. rm을 명시적으로 허용한 경우만 High risk 게이트 우회 가능.


커맨드 파싱 — Quote-aware 쉘 렉서

"sqlite3 db \"SELECT 1; SELECT 2;\""
→ 세그먼트: ["sqlite3 db \"SELECT 1; SELECT 2;\""]   ← 쿼트 내 세미콜론 무시

"ls; rm -rf /"
→ 세그먼트: ["ls", "rm -rf /"]                      ← 두 번째 세그먼트 차단

"echo \"A>B\""
→ redirect 없음 (쿼트 내 > 무시)                    ← 허용

"cat $HOME/.ssh/id_rsa"
→ 언쿼트 $HOME → 차단                               ← 쉘 변수 확장 차단

차단하는 인젝션 패턴:

  • ; \n 세미콜론/개행 체이닝
  • | 파이프 (세그먼트로 분리해서 각각 검증)
  • && || 논리 연산자 체이닝
  • & 백그라운드 실행
  • ` $() 서브쉘
  • $VAR ${VAR} 쉘 변수 확장
  • <() >() 프로세스 치환
  • > < >> 리다이렉션
  • tee (파이프 + 파일 쓰기 우회)
  • find -exec find -ok (임의 명령 실행)
  • git config git -c git alias (설정 통한 실행)

경로 검증 — 다층 방어

is_path_allowed(path)
    │
    ├─ 1. null byte 차단 (\0)
    ├─ 2. ".." 컴포넌트 차단 (경로 순회)
    ├─ 3. URL 인코딩 순회 차단 (..%2f)
    ├─ 4. ~user 형식 차단 (~root, ~nobody 등)
    │      (단, ~ 와 ~/ 는 허용 — 자신의 홈)
    ├─ 5. 절대 경로이면:
    │       workspace_dir 하위 → ✅
    │       allowed_roots 하위 → ✅
    │       workspace_only=true → ❌
    │       (workspace_only=false → 아래 forbidden 검사로 계속)
    └─ 6. forbidden_paths 접두사 검사

is_resolved_path_allowed(resolved: &Path)  ← 캐노니컬 경로로 검증
    → 심볼릭 링크 탈출 방지용
    → workspace_dir.canonicalize() 기준 검사

우선순위 (중요):

workspace_dir > allowed_roots > forbidden_paths > workspace_only

즉, workspace 안에 있으면 forbidden_paths가 /home이어도 허용.


Rate Limiting — ActionTracker

pub struct ActionTracker {
    actions: Mutex<Vec<Instant>>,  // 최근 1시간 내 액션 타임스탬프
}
record() → 1시간 이전 항목 제거 → 현재 항목 추가 → 현재 카운트 반환
count()  → 1시간 이전 항목 제거 → 현재 카운트 반환 (기록 없음)

enforce_tool_operation(Read, ...) → 항상 Ok
enforce_tool_operation(Act, ...)  → !can_act() → Err
                                  → count > max_actions_per_hour → Err

기본 max_actions_per_hour = 20 — 꽤 보수적. 복잡한 작업 시 늘려야 함.


Tool과의 연결

// all_tools_with_runtime()에서 모든 도구에 Arc<SecurityPolicy> 주입
let security = Arc::new(SecurityPolicy::from_config(&config.autonomy, &workspace));
let tools = all_tools_with_runtime(Arc::new(config), &security, ...);

ShellTool 예시 (추정되는 패턴):

async fn execute(&self, args: Value) -> Result<ToolResult> {
    let command = args["command"].as_str().unwrap();
    let approved = args["approved"].as_bool().unwrap_or(false);

    // 커맨드 허용 여부
    self.policy.validate_command_execution(command, approved)?;

    // 경로 인수 검사
    if let Some(forbidden) = self.policy.forbidden_path_argument(command) {
        return Err(format!("Forbidden path: {forbidden}"));
    }

    // Act 오퍼레이션 → rate limit 체크
    self.policy.enforce_tool_operation(ToolOperation::Act, "shell")?;

    // 실제 실행
    ...
}

config.toml 설정

[autonomy]
level = "supervised"           # readonly / supervised / full
workspace_only = true          # 기본값
max_actions_per_hour = 20      # rate limit
block_high_risk_commands = true
require_approval_for_medium_risk = true

# 추가 명령어 허용
allowed_commands = ["git", "npm", "cargo", "ls", "cat", "grep",
                    "curl", "docker"]  # curl, docker 추가 예시

# workspace 외 추가 허용 경로
allowed_roots = ["/shared/data", "~/Desktop"]

# 채널(Telegram 등)에서 특정 도구 비활성화
non_cli_excluded_tools = ["shell", "file_write"]

내 로컬 프로젝트 권장 설정:

[autonomy]
level = "full"           # 로컬 개발 환경이면 Full
workspace_only = true
max_actions_per_hour = 100   # 작업량에 따라
block_high_risk_commands = true
# curl 필요하면 명시적으로 추가
allowed_commands = ["git", "npm", "cargo", "ls", "cat", "grep",
                    "find", "echo", "pwd", "wc", "head", "tail",
                    "date", "python3", "pip"]

보조 보안 모듈 개요

SecretStore (secrets.rs):

config.toml 내 API key 암호화 저장
secrets.encrypt = true 설정 시 활성화
redact() — 로그 출력 시 앞 4자 + "***"

PromptGuard (prompt_guard.rs):

LLM 입력/출력에서 프롬프트 인젝션 패턴 감지
"Ignore previous instructions", "You are now..." 등
GuardAction::Block → 도구 실행 차단

LeakDetector (leak_detector.rs):

LLM 응답에서 민감 정보 유출 감지
API key 패턴, 자격증명 형식 검출

EstopManager (estop.rs):

긴급 정지 — 모든 에이전트 활동 즉시 중단
채널에서 특정 명령으로 트리거

Sandbox (Docker/Firejail/Bubblewrap/Landlock):

OS 레벨 프로세스 격리
shell 명령을 샌드박스 안에서 실행
detect::create_sandbox() — 환경에 맞는 백엔드 자동 선택

prompt_summary() — LLM에 정책 노출

policy.prompt_summary()

출력 예시:

**Autonomy level**: Supervised
**Workspace boundary**: file operations are restricted to `/home/user/project`.
**Allowed shell commands**: `git`, `ls`, `cat`. Commands not on this list will be rejected.
**Forbidden paths**: `/etc`, `~/.ssh`. Any read/write/exec targeting these paths will be blocked.
**High-risk commands** (rm, kill, reboot, etc.) are blocked.
**Medium-risk commands** require user approval before execution.
**Rate limit**: max 20 actions per hour.

이 내용이 system prompt에 자동으로 추가돼서 LLM이 차단될 명령을 사전에 알 수 있어.


관련