ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 에이전트가 내 폰과 다른 서버까지 손을 뻗을 때 — OpenClaw가 노드 권한을 통제한 방법
    IT 2026. 7. 12. 23:00
    에이전트가 내 폰과 다른 서버까지 손을 뻗을 때 — OpenClaw가 노드 권한을 통제한 방법

    오픈소스 개인 AI 어시스턴트 OpenClaw를 집 서버에 올려 쓰다 보면, 곧 한 가지 욕심이 생긴다. "에이전트가 이 서버에서만 일하지 말고, 내 폰으로 사진을 찍거나 위치를 읽고, 빌드 전용 머신에서 명령을 실행하면 좋겠다"는 것이다. 그런데 이걸 허용하는 순간 무서운 질문이 따라온다 — 어느 기기가, 무엇을, 누구의 승인으로 할 수 있는가? OpenClaw의 Control UI(웹 관리 화면)에 있는 "Nodes(노드)" 메뉴는 정확히 이 질문에 답하는 화면이다. 이 글은 노드라는 개념이 왜 나왔고, 페어링·capabilities·exec allowlist라는 세 겹의 통제로 그 위험을 어떻게 가뒀는지 따라간다.

    배경 — 에이전트가 한 호스트를 벗어나려 한다

    OpenClaw의 핵심은 게이트웨이(Gateway)다. 게이트웨이는 텔레그램·왓츠앱 같은 메신저 메시지를 받고, 모델(LLM)을 돌리고, 도구 호출을 라우팅하는 서버 프로세스다. 처음엔 이 게이트웨이 한 대가 모든 걸 한다. 하지만 사람이 에이전트에게 기대하는 일은 점점 한 호스트 밖으로 새어 나간다 — "지금 거실 카메라로 사진 한 장 찍어 줘", "내 안드로이드 폰의 최근 알림 좀 읽어 줘", "빌드 서버에서 uname 돌려서 OS 버전 확인해 줘".

    이런 일은 게이트웨이가 떠 있는 서버에서는 불가능하다. 카메라도, 폰의 알림도, 다른 머신의 셸도 그 서버엔 없기 때문이다. 그래서 OpenClaw는 노드(node)라는 개념을 도입했다. 노드는 게이트웨이에 붙는 주변 기기(peripheral) — macOS·iOS·안드로이드 앱이거나, UI 없이 도는 헤드리스 노드 호스트(다른 리눅스/윈도우 머신) — 이고, 자기가 할 수 있는 명령을 게이트웨이에 알려 준다. 노드는 게이트웨이가 아니다. 메신저 메시지는 항상 게이트웨이에 도착하고, 노드는 "손발" 역할만 한다.

    diagram

    다이어그램 설명. 이 그림은 게이트웨이 한 대를 중심으로 여러 노드가 매달린 구조를 보여준다. 사용자의 메시지는 항상 가운데 게이트웨이에 도착하고, 게이트웨이가 모델을 돌리다가 "카메라가 필요하다" 같은 판단이 서면 적합한 노드에게 그 명령을 위임한다. 폰 노드는 카메라·위치·알림처럼 그 기기에만 있는 기능을, 다른 머신의 헤드리스 노드는 그 머신의 셸 명령을 맡는다. 핵심 패턴은 "두뇌(게이트웨이)는 하나, 손발(노드)은 여럿"이다. 놓치기 쉬운 함정은 노드를 또 다른 게이트웨이로 착각하는 것 — 노드는 메시지를 받지도, 모델을 돌리지도 않는다. 그래서 이 가치는 분명하다. 에이전트의 능력이 한 서버의 물리적 한계를 넘어 사용자의 기기 생태계 전체로 확장된다.

    핵심 문제 — "연결되면 다 된다"가 곧 보안 사고다

    여기서 위험이 시작된다. 노드가 게이트웨이에 연결됐다고 해서 무엇이든 시킬 수 있으면, 그건 곧 재앙이다. 구체적인 실패 시나리오 셋을 보자.

    • 아무 기기나 붙는다: 같은 Wi-Fi에 있는 누군가의 폰이 "나 노드야" 하고 연결하면, 그 폰의 카메라·위치·알림이 곧장 에이전트에게 노출된다.
    • 위험한 명령이 기본으로 열린다: 카메라로 사진 찍기(camera.snap), 화면 녹화(screen.record) 같은 사생활 침해 명령이 페어링만 하면 다 된다면, 한 번의 잘못된 프롬프트로 몰래 녹화가 돌 수 있다.
    • 임의 셸 실행: 다른 머신의 노드에서 system.run(임의 셸 명령 실행)이 무제한이면, 에이전트가 환각으로 만들어 낸 위험한 명령(rm -rf 같은)이 그 머신에서 그대로 실행된다.

    여기엔 본질적인 트레이드오프가 있다. 노드를 자유롭게 쓸수록 에이전트는 강력해지지만, 그만큼 공격 표면(attack surface, 공격자가 침투할 수 있는 진입점의 총합)이 넓어진다. 단순히 "연결 = 허용"으로 두면 편하지만, 토큰 하나 새어 나가면 사용자의 사생활과 다른 머신 전체가 위태로워진다. 그래서 OpenClaw가 풀어야 했던 문제는 "능력은 주되, 그 능력을 기기별·명령별로 잘게 쪼개 명시적으로만 열리게 하는 것"이다.

    해결 방법 — 세 겹의 통제: 페어링 · capabilities · exec allowlist

    OpenClaw는 이 문제를 한 번에 풀지 않고 세 개의 독립된 관문으로 쌓았다. 기기를 신뢰하는가(페어링), 그 기기가 무엇을 할 수 있다고 선언했는가(capabilities), 셸 명령이라면 정확히 어떤 명령만 허용하는가(exec allowlist)다. Control UI의 Nodes 메뉴는 이 세 관문을 각각 하나의 카드(섹션)로 보여준다.

    1관문 — 기기 페어링(device pairing)

    노드가 게이트웨이에 처음 연결하면, 자기 기기 신원(device identity)을 제시한다. 게이트웨이는 이걸 바로 받아들이지 않고 승인 대기(pending) 요청으로 만든다. 검토·승인은 게이트웨이 쪽에서 이뤄진다 — 노드(폰)에는 승인 화면이 없고, 사용자가 게이트웨이의 Control UI나 CLI(openclaw devices approve)에서 직접 승인해야 비로소 그 기기는 "paired(짝지어진)" 상태가 된다. 신뢰의 결정권은 연결을 거는 기기가 아니라, 게이트웨이를 쥔 사람에게 있다.

    diagram

    다이어그램 설명. 이 그림은 노드가 신뢰받는 기기가 되기까지의 흐름과, 승인 시 부여되는 권한 범위(scope)가 요청한 명령의 위험도에 따라 갈라지는 모습을 보여준다. 위에서 아래로 읽으면 된다 — 노드가 신원을 제시하면 곧장 들어오는 게 아니라 대기열에 서고, 사용자가 명시적으로 승인해야 통과한다. 통과 후가 중요한데, 승인 시 게이트웨이(오픈클로)에게 부여되는 권한 범위가 노드가 선언한 명령의 위험도에 따라 갈린다 — 아무 명령도 제공하지 않는 노드를 연결할 때는 게이트웨이에게 가장 낮은 권한(operator.pairing)만 부여하면 된다. 셸 실행(system.run) 기능을 제공하는 노드는 "관리자 권한을 가진 게이트웨이만 내 셸을 호출할 수 있다"는 조건을 걸기 때문에, 게이트웨이는 관리자 권한(operator.admin)을 갖고 있어야 그 기능을 쓸 수 있다. 이 패턴을 택한 이유는, 승인이라는 한 번의 행동에 위험도를 곧장 묶어 두기 위함이다. 함정은 토큰 교체로 권한을 몰래 올리려는 시도다 — 페어링 기록은 "승인된 역할 계약"이라서, 토큰을 갈아 끼워도 승인받지 않은 더 높은 역할로 올라갈 수 없다. 신뢰는 토큰이 아니라 사용자의 승인에 묶인다는 게 이 단계의 가치다.

    한 가지 실무적인 단서. 위 그림은 기기가 스스로 나타나 대기열에 서는 수동 승인 경로다. 그런데 폰을 붙이는 가장 흔한 길인 QR setup code로 연결하면 별도의 검토 화면이 뜨지 않는다 — 그래도 승인이 생략된 건 아니다. openclaw qr이 찍어 주는 setup code 안에는 짧은 수명의 일회용 bootstrapToken이 들어 있고, 게이트웨이는 이 토큰을 유효한 인증으로 받아들인다. 즉 사용자가 그 코드를 직접 만들어 폰으로 스캔한 행위 자체가 "검토·승인"이라, 게이트웨이가 따로 물을 게 없어 곧장 paired가 된다. 대신 이 fast-path가 자동으로 내주는 권한은 의도적으로 좁다 — 읽기·쓰기·승인 위임 정도이고, 관리자(operator.admin)나 페어링 위임(operator.pairing) 같은 높은 권한은 여기서 절대 자동으로 붙지 않는다. 그 권한이 필요하면 별도의 operator 승인 절차를 다시 거쳐야 한다. setup code 없이 LAN·tailnet에서 기기가 제 발로 붙는 경우에만 위 그림의 수동 대기열을 탄다.

    2관문 — capabilities와 명령 정책(게이트웨이가 따지는 두 조건)

    노드가 자기 능력을 게이트웨이에 알리는 건 연결할 때 딱 한 번이다 — 노드가 명령 목록(connect.commands)을 스스로 신고하면 게이트웨이가 그걸 저장해 두고(페어링 계약으로 고정), 이후 명령을 부를 때마다 노드에 "이거 할 수 있어?"라고 다시 묻지 않는다. 그 캐시된 목록을 게이트웨이가 자기 안에서 들여다볼 뿐이다.

    그래서 명령 하나가 실행되려면 게이트웨이가 자기 안에서 두 조건을 동시에 따진다 (노드 왕복 없음). 첫째, 그 명령이 노드가 신고한 목록에 들어 있는가(기기가 내놓겠다고 한 것). 둘째, 게이트웨이의 정책이 그 명령을 허용하는가(운영자가 모델에게 허락한 것). 둘은 순서가 있는 관문이 아니라 교집합이다 — 어느 쪽이 먼저랄 것 없이 둘 다 참이어야 통과한다. 서로 다른 주체(기기 vs 운영자)를 지키므로 중복이 아니다.

    diagram

    다이어그램 설명. 이 그림은 노드 명령 하나가 실행되기까지 게이트웨이가 자기 안에서 따지는 두 조건을 보여준다. 핵심은 맨 위 — 이 판정에는 노드 왕복이 없다. 게이트웨이는 노드가 연결 때 신고해 둔 목록(캐시)과 자기 정책만으로 즉석에서 결정한다. 두 조건은 순서가 있는 관문이 아니라 나란히 놓인 교집합이다. 조건 A는 "노드가 그 능력을 내놓겠다고 신고했나"(안 신고한 명령은 애초에 부를 수 없다), 조건 B는 "게이트웨이 정책이 그 명령을 모델에게 허용하나"이다 — 화면 그리기(canvas.*) 같은 안전한 명령은 기본 허용되지만, 카메라 촬영·화면 녹화처럼 사생활을 건드리는 명령은 allowCommands따로 켜 줘야만 열린다. 두 조건을 나눈 이유는 책임을 가르기 위함이다 — 무엇을 내놓을지는 노드(기기)가, 그중 무엇을 모델에 허락할지는 게이트웨이(운영자 정책)가 쥔다. 가장 놓치기 쉬운 함정은 차단 목록(denyCommands)의 우선순위다. 이 목록에 올린 명령은 기본 허용이든 allowCommands로 켰든 무조건 막힌다. "막는 규칙이 항상 이긴다(deny wins)"는 보안의 기본 원칙이 여기 박혀 있다. 그 가치는, 사용자가 "이것만은 절대 안 됨"을 한 줄로 못 박을 수 있다는 안심이다.

    Control UI의 노드 카드는 각 노드를 한 줄로 보여주면서, 그 노드가 paired인지·연결됐는지(connected/offline)와 함께 노드가 선언한 capabilities·명령들을 칩(chip) 형태로 나열한다. 즉 "이 기기가 무엇을 할 수 있다고 주장하는가"를 한눈에 점검할 수 있다. 능력을 바꾼 노드는 옛 페어링을 거부하고 새 요청을 다시 승인해야, 게이트웨이가 갱신된 명령 목록을 저장한다 — 능력 변경도 사용자의 재승인을 거치게 한 것이다.

    여기서 한 가지 짚어 둘 게 있다. 2관문은 게이트웨이 안에서, 모델이 애초에 무엇을 시도할 수 있는지를 정하는 통제다. 즉 두뇌(게이트웨이)가 손발(노드)에게 내릴 수 있는 명령의 목록을 명령 "이름" 단위로 긋는다 — camera.snap이나 system.run 같은 명령을 이 노드에 부칠 수 있는가를 allowCommands/denyCommands로 가를 뿐, 허용 아니면 거부이고 대화형 승인(ask)은 여기 없다. camera.snap처럼 셸 실행이 아닌 명령은 이 2관문이 마지막 문이라, 게이트웨이가 허용하면 곧장 노드로 간다. 그런데 system.run만은 사정이 다르다. 2관문을 통과했다는 건 "게이트웨이가 모델에게 system.run을 부를 자격을 줬다"는 뜻일 뿐이고, 실제로 어떤 셸 명령 문자열이 그 머신에서 도느냐(/usr/bin/ls인가 rm -rf인가)는 아직 아무도 판정하지 않았다. 그 판정은 실제 노드 본인이 다음 관문에서 따로 내린다. 그래서 둘은 중복이 아니라 주체와 층위가 다르다 — 2관문은 게이트웨이가 "모델에게 허용할 명령 이름"을, 3관문은 노드가 "자기 머신에서 돌려도 될 셸 명령 한 줄"을 본다. 대화형 승인(ask)도 노드가 들고 있는 3관문에만 있다.

    3관문 — exec allowlist(셸 명령 화이트리스트)

    가장 위험한 명령은 system.run, 즉 어떤 머신에서 임의의 셸 명령을 실행하는 것이다. 셸 명령은 두 군데서 돌 수 있다 — 게이트웨이가 떠 있는 그 서버 자신(host=gateway), 아니면 다른 머신의 노드 호스트(host=node)다. 후자라면 게이트웨이는 exec 호출을 노드 호스트로 전달한다. 여기서 통제의 핵심은 exec allowlist — 어떤 명령(실행 파일 경로)만 허용할지 적은 화이트리스트다. 중요한 건, 이 승인 기록이 중앙(게이트웨이)에 모이는 게 아니라 명령이 실제로 도는 머신마다 따로(~/.openclaw/exec-approvals.json) 저장된다는 점이다. 게이트웨이 서버 자신도 하나의 exec 호스트라 자기 파일을 갖고, 각 노드 호스트도 자기 파일을 갖는다. "이 머신에서 무엇이 돌 수 있나"는 언제나 그 머신 본인이 들고 있는 셈이다.

    diagram

    다이어그램 설명. 이 그림은 다른 머신에서 셸 명령을 실행하려 할 때 거쳐야 하는 결정 흐름을 보여준다. 모델이 노드에서 실행을 요청하면 게이트웨이는 그 요청을 노드 호스트로 전달할 뿐이고, 보안 모드를 판단하는 주체는 노드 호스트 자신이다 — 게이트웨이는 자기 정책으로 노드의 셸을 통제하지 않는다. 노드 호스트가 자기 exec-approvals.json을 읽어 보안 모드에 따라 갈린다. "거부" 모드는 무엇이든 막고, "전체 허용" 모드는 모든 걸 열며(완전히 신뢰하는 노드에만 써야 한다), 가운데 "화이트리스트" 모드가 실무의 핵심이다 — 노드에 미리 등록한 명령만 통과시키고, 목록에 없으면 다시 "물어보기" 모드의 처분으로 넘어간다. 이때 사람에게 띄우는 승인 프롬프트는 게이트웨이가 채팅 채널로 중계하지만, 허용·거부의 기준 자체는 노드가 들고 있다. 이 구조를 택한 이유는, 위험한 능력일수록 기본값을 닫아 두고(deny가 기본) 명시적 등록이나 사용자 승인을 거치게 하기 위함이다. 놓치기 쉬운 안전장치가 왼쪽 아래에 숨어 있다 — 승인된 셸 실행이라도 OpenClaw는 명령이 다루는 구체적 파일 하나를 고정해 두고, 실행 직전 그 파일이 바뀌면 실행을 거부한다(TOCTOU, 검사 시점과 사용 시점 사이에 대상이 바뀌는 공격을 막는 방어). 그 가치는, "한 번 승인했으니 이제 뭐든 된다"는 빈틈을 메운다는 데 있다.

    Control UI의 Exec approvals 카드는 이 정책을 직접 편집하는 화면이다. 정책 파일이 머신마다 따로 있으니, 먼저 어느 머신의 정책을 편집할지 대상(Target)으로 고른다 — 게이트웨이 서버 자신(host=gateway에서 도는 명령)이거나 특정 노드다. 게이트웨이를 고르면 그 편집은 게이트웨이 로컬 파일에 바로 쓰이고, 노드를 고르면 편집 요청이 그 노드로 전달돼 노드 쪽 파일에 쓰인다(exec.approvals.set vs exec.approvals.node.set으로 갈린다). 그렇게 대상을 정한 뒤, 에이전트별로 보안 모드(deny/allowlist/full)·물어보기 모드(off/on-miss/always)·프롬프트 불가 시 폴백을 따로 설정한다. 화이트리스트 항목은 대소문자 무시 glob 패턴(/usr/bin/* 같은 와일드카드 매칭)으로 적고, 각 항목이 마지막으로 언제·어떤 명령으로 쓰였는지까지 보여 준다 — 권한이 실제로 어떻게 쓰이는지 감사(audit)할 수 있게 한 것이다.

    기기 바인딩 — 어느 노드에서 실행할지 못 박기

    노드가 여럿이면 "exec 명령이 도대체 어느 노드에서 도는가"가 모호해진다. OpenClaw는 exec 노드 바인딩으로 이를 못 박는다. 전역 기본 노드를 정하거나, 에이전트마다 다른 노드로 묶을 수 있다.

    diagram

    다이어그램 설명. 이 그림은 셸 실행 요청이 어느 노드로 향할지 결정하는 우선순위를 보여준다. 위에서 내려오며 가장 구체적인 설정부터 적용된다 — 에이전트마다 전용 노드를 지정해 두었으면 그곳에서, 그게 없고 전역 기본 노드만 있으면 거기서, 아무것도 없으면 가용한 노드 중 아무 데서나 돈다. 이 우선순위 패턴(구체적 설정이 일반 설정을 이긴다)을 택한 이유는, 예컨대 빌드 작업 에이전트는 빌드 서버 노드에, 일상 비서 에이전트는 데스크톱 노드에 묶어 책임을 분리하기 위함이다. 함정은 "아무 노드나 허용"을 무심코 켜 두는 것 — 노드가 늘어날수록 명령이 의도치 않은 기기에서 돌 수 있으니, 민감한 에이전트일수록 특정 노드로 명시적으로 묶는 편이 안전하다. Control UI의 바인딩 카드는 system.run을 제공하는 노드만 후보로 보여 주어, 셸을 못 도는 노드에 잘못 묶는 실수를 원천 차단한다.

    결과 — 능력은 넓히되, 위험은 칸막이에 가둔다

    세 관문이 합쳐지면, "에이전트가 폰과 다른 서버까지 손을 뻗는다"는 강력함과 "그 손이 함부로 움직이지 못한다"는 안전이 동시에 성립한다. Control UI의 Nodes 메뉴 하나에서 이 모든 통제를 점검·편집할 수 있다.

    위험 시나리오 어느 관문이 막나
    같은 Wi-Fi의 낯선 기기가 노드로 붙음 페어링 — 사용자 승인 없이는 paired 안 됨
    토큰을 갈아 끼워 권한을 몰래 상승 페어링 — 승인된 역할 계약을 넘을 수 없음
    몰래 카메라 촬영·화면 녹화 명령 정책 — allowCommands로 켜야만 열림, denyCommands가 항상 우선
    다른 머신에서 임의 셸 명령 실행 exec allowlist — 등록된 명령만, 그나마 대상 파일 고정 검증
    명령이 엉뚱한 노드에서 실행됨 노드 바인딩 — 에이전트별·전역으로 실행 노드 고정

    정리하면, OpenClaw의 노드 설계는 "기본은 닫혀 있고, 열 때는 기기별·명령별로 명시적으로만"이라는 한 문장으로 요약된다. 이것이 사용자에게 주는 가치는 단순한 기능 확장이 아니다 — 폰·노트북·빌드 서버를 에이전트의 손발로 빌려주면서도, 각 기기가 정확히 무엇까지 할 수 있는지를 사용자가 한 화면에서 통제하고 감사할 수 있다는 신뢰다. 자율적인 AI 에이전트가 진짜로 쓸 만해지려면, 능력의 확장만큼이나 그 능력의 칸막이가 촘촘해야 한다. 노드 메뉴는 바로 그 칸막이를 사람이 직접 손으로 세우고 들여다보는 자리다.


    이 글은 생성형 AI의 도움을 받아 작성되었습니다. 원본 자료를 기반으로 AI가 초안을 생성하고, 작성자가 검토·편집하였습니다.

Designed by Tistory.