Lafine 시스템 설계 · 기술 문서

Linux
용 RoamSwitch아키텍처 및 보안 백서

Linux용 RoamSwitch가 권한 경계에서 수행하는 작업을 설명하는 기술 문서입니다. 마케팅 언어를 피합니다. 배포된 패키지의 실제 동작과 비교하여 주장을 확인할 수 있으며 부록의 자체 점검도 가능합니다.

버전 v1.11.1 대상 RoamSwitch for Linux 1.11.1(커널 LPE 강화 / eBPF 런타임 방어 / 공격 프로세스 SIGSTOP 동결 / Falco 저부하 최적화 / DNS 터널링 탐지 / 포렌식 증거 보전 / 허니토큰 / 취약점 진단 pass_age 추적 / NIST CSF·CIS Controls 매핑 / 진단 재평가 / MCP 설정 감사) 요구 사항 systemd + nftables 발행 2026-10-08

§1

Linux용 RoamSwitch는 macOS RoamSwitch의 제로 트러스트 네트워크 방어 모델을 Linux(systemd + nftables 환경)에 제공하는 상주 네트워크 보안 및 시스템 진단 애플리케이션입니다.

이 버전은 클라이언트 시스템(워크스테이션, 랩톱, 단일 보드 컴퓨터)을 대상으로 하며 시스템이 네트워크 간에 이동할 때 시스템을 따르는 자율적인 동작을 가정합니다. 강화된 서버/헤드리스 인프라 버전(RoamSwitch Server Edition — 상시 기본 거부, 중요 경로 FIM 무결성, 원격 웹후크 및 정적 정책 적용)이 전용 서버 빌드로 제공됩니다.

접속 중인 Wi-Fi/유선 네트워크를 게이트웨이 MAC 주소 단위로 식별하여 nftables 방화벽 프로필을 자율적으로 전환합니다. 또한 신뢰할 수 없는 네트워크에서의 중간자 공격(MITM)에 대한 방어를 3개 계층으로 제공합니다. 구성은 VPN 터널 + 킬 스위치(WireGuard 또는 Tailscale, 주력 방어), 예방적 ARP/NDP 잠금, ARP 스푸핑 탐지입니다. 이와 함께 랜섬웨어 동작 탐지(fanotify + Shannon 엔트로피 + 카나리아), 피싱 연결을 경고하고 차단하는 수동 링크 가드(NFQUEUE + SNI), 포트 이상 가드, BadUSB 대응, 커널 권한 상승(LPE) 대응(Frag Gap CVE-2026-53362 완화책으로, 신뢰할 수 없는 네트워크에서 비특권 사용자 네임스페이스를 거부하는 기능을 선택적으로 켤 수 있습니다. 기본값은 꺼짐), eBPF 런타임 위협 센서(Falco/Tetragon) 연동을 통한 공격 프로세스의 밀리초 단위 동결(SIGSTOP) 및 자동 Air-Gap 격리, 27개 항목의 보안 상태 진단, Model Context Protocol(MCP) 서버를 기본으로 탑재합니다.

이 백서는 아키텍처, 위협 모델, 각 방어 메커니즘의 설계, 그리고 “완전 로컬 처리. 기본값에서 머신 밖으로 나가는 것은 서명된 탐지 데이터 요청뿐이며, 그 안에 사용자 정보는 전혀 포함되지 않는다”는 프라이버시 원칙이 코드 수준에서 어떻게 보장되는지 설명합니다. 운영자가 명시적으로 켜는 옵트인 기능(이벤트 전달 등)은 이 기본값의 예외이며, 아래 표와 §9.4에 모두 나열합니다. 홍보성 표현은 피했으며, 기재 내용은 배포 패키지(바이너리)의 실제 동작과 부록의 자체 점검으로 확인할 수 있습니다. RoamSwitch for Linux는 독점 프리웨어이며 소스 코드는 공개하지 않습니다(§11).

디자인 원칙
원리세부정보
원격측정 제로이 제품은 기본값에서 사용자·단말에 관한 정보를 외부로 일절 전송하지 않습니다. 텔레메트리, 분석, 크래시 리포트, 사용 통계, 원격 설정, 라이선스 인증 서버 중 어느 것도 존재하지 않습니다. 운영자가 명시적으로 켜는 옵트인 기능은 다음 행 “기본값의 예외”와 9.4에 나열한 것뿐입니다.
기본값의 예외(모두 옵트인)운영자가 명시적으로 켠 경우에 한해 다음 기능이 외부와 통신합니다. 목적지는 운영자가 설정한 것뿐이며 내장된 목적지는 없습니다. (1) 이벤트 전달(forward_enabled, 기본 OFF) — 탐지 이벤트를 운영자가 지정한 syslog / JSON Lines 파일 / 서명된 Webhook으로 전달합니다. (2) Fleet(Business 티어, 단말 측 코드만) — 등록하지 않은 단말은 아무것도 보내지 않으며 Fleet 호스트 이름의 이름 해석도 하지 않습니다. 관리 서비스는 미배포·미출시입니다. 사용자 조작으로 동작하는 업데이트 확인 등을 포함한 나머지는 모두 9.4에 나열합니다.
수신 전용 업데이트이 제품이 기본값으로 수행하는 외부 통신은 하루에 한 번 고정 URL(lafine.net/updates/v1)의 서명된 매니페스트를 GET하는 것뿐입니다. ClamAV가 설치되어 있으면 실행할 때마다 ClamAV 자체의 업데이트 도구 freshclam도 실행하지만, 그 통신은 ClamAV 측의 것으로서 §9.4에 별도로 기재했습니다. 매니페스트에서 가져오는 것은 링크 가드의 사기 사이트 목록, 본체 최신 버전, 커널과 OS 패키지(Debian/Ubuntu/Arch/RHEL 계열/SUSE) 및 언어 패키지(npm/PyPI/crates.io/RubyGems/Packagist/Go/Maven)의 CVE 맵, 실증형 취약점 진단용 CVE 맵, 타이포스쿼팅 탐지용 인기 npm 패키지 이름 목록입니다. 매일 받는 것은 매니페스트와 그 서명(GET 2회)뿐입니다. 링크 가드 목록·실증형 진단용 CVE 맵·인기 npm 목록은 매니페스트의 버전이 새로울 때만, 커널과 패키지 CVE 맵은 마지막 갱신에서 약 30일이 지났을 때만 받습니다. Ubuntu CVE 맵은 지원되는 버전의 Ubuntu 단말만 받으며, 아직 없는 단말은 30일을 기다리지 않고 다음 일일 실행에서 받습니다. 쿼리 문자열과 쿠키가 없고, 소프트웨어를 나타내는 헤더는 고정된 User-Agent: RoamSwitch-Updater뿐이며, 사용자에 관한 정보는 전혀 포함되지 않습니다. updates.enabled = false로 완전히 중지할 수 있으며, 이 경우 번들 데이터만으로 동작하고 옵트인 기능을 켜지 않는 한 외부 접촉은 0이 됩니다.
완전한 로컬 처리모든 평가, 탐지 및 감사는 로컬 시스템에서 완료될 때까지 실행됩니다. URL 안전 분석 및 비밀 스캐닝은 입력 내용을 어디에도 전송하지 않고 경험적으로 평가합니다.
최소 권한특권 작업은 root 데몬으로 한정되며, UI·CLI·MCP는 Unix 도메인 소켓 IPC로 요청합니다. 데몬은 기본값에서 TCP/UDP 소켓을 열지 않습니다(운영자가 켜는 옵트인 기능은 예외, §9.4). 링크 가드는 AF_NETLINK의 NFQUEUE verdict 채널(커널 ↔ 사용자 공간, 패킷 송출 없음)만 사용합니다. 상태를 변경하는 IPC 호출은 root 또는 로컬 로그인 세션이 있는 UID에서만 받습니다(1.10.4부터. 읽기 전용 호출은 이전과 같습니다).
실패로 종료Air-Gap(긴급 격리)은 데몬을 다시 시작해도 유지되며 해당 규칙이 외부에서 삭제되면 몇 초 내에 다시 확인됩니다.
오프라인 라이선스Pro/Business 라이선스는 Ed25519 서명 확인만으로 확인됩니다. 활성화 서버에 대한 호출이 발생하지 않습니다.

§2

Linux용 RoamSwitch는 주로 신뢰할 수 없는 네트워크 간에 로밍하는 노트북/모바일 워크스테이션을 대상으로 합니다.

범위 내의 공격자

  • 동일한 LAN의 공격자 — 공유 Wi-Fi의 포트 스캔, 중간자에 대한 ARP/NDP 스푸핑(악성 라우터 광고 포함), 악성 DHCP, MAC 플러딩 후 수동 스니핑, 개발 서버에 대한 무단 액세스 또는 실수로 0.0.0.0에 바인딩된 로컬 LLM API.
  • 악성 USB 장치(BadUSB) — 키보드를 가장하고 화면이 잠기기 전에 키 입력을 삽입합니다. 데이터 유출이나 악성 코드 운반을 위한 등록되지 않은 USB 대용량 저장 장치입니다.
  • 랜섬웨어/파일리스 악성 코드 — 다운로드한 파일을 통해 침입하여 사용자 문서를 대량 암호화합니다.
  • DNS 기반 C2 및 피싱 — C2 서버 또는 피싱 사이트에 도달하기 위해 이름 확인을 사용하는 악성 코드입니다.

범위 외

  • 이미 루트를 보유하고 있는 공격자 커널 또는 펌웨어 수준 루트킷.
  • 디스크를 물리적으로 제거하는 공격(전체 디스크 암호화 도메인, RoamSwitch는 암호화가 있는지 여부를 진단하지만 암호화하지는 않음)
  • AirDrop 스타일 근접 무선 공유 제어(Linux에는 OS 기본 메커니즘이 없으며 지원되지 않음)

§3

Linux용 RoamSwitch는 Rust로 구현되며, 패키지에는 8개의 실행 파일(roamswitch-daemon·roamswitch-app·roamswitch(CLI)·roamswitch-mcp·roamswitch-updater·roamswitch-honeytokens·roamswitch-incident-capture·실험적이며 기본값으로는 비활성인 roamswitch-xdp-gate)이 들어 있습니다. 공통 로직은 roamswitch-core 라이브러리에 있으며, roamswitchkit는 클라이언트 SDK입니다. 데몬과의 모든 통신은 /run/roamswitch/roamswitch.sock(Unix 도메인 소켓)을 거칩니다. 데몬, 앱, MCP 어느 쪽에도 TCP/UDP 수신 대기 소켓이 존재하지 않습니다. 앱이 bind하는 것은 중복 실행 방지용 Unix 소켓 1개뿐입니다.

# 사용자 공간(비특권)
roamswitch-app      GTK3 + AppIndicator 트레이
roamswitch (CLI)    쉘용 경량 프론트엔드
roamswitch-mcp      stdio JSON-RPC(AI 클라이언트가 실행)
      │
      │  AF_UNIX  /run/roamswitch/roamswitch.sock  (로컬 IPC 전용)
      ▼
roamswitch-daemon   root / systemd Type=notify + WatchdogSec=30
  · nftables 프로필 제어(inet roamswitch 테이블)
  · sentinel 루프(네트워크 판별 및 프로필 적용)
  · VPN 터널 + 킬 스위치(WireGuard: wg-quick / roamswitch_vpn, Tailscale: roamswitch_ts)
  · 예방적 ARP/NDP 잠금(neigh 테이블 고정) / 탐지
  · 포트 이상 가드(roamswitch_dev_guard)
  · fanotify 가드(랜섬웨어 / 악성코드)
  · 링크 가드(NFQUEUE — inet roamswitch_linkguard)
  · 카나리아 엔진 / 격리 Vault
  · 커널 LPE 대응(비특권 userns 거부는 선택 설정, 기본값 꺼짐)
  · eBPF 런타임 연동(/var/run/roamswitch/events.sock)
  · 공격 프로세스의 밀리초 단위 SIGSTOP 동결 및 Air-Gap 연동
  · USB 모니터링(evdev EVIOCGRAB — BadUSB) / 승인 대기열
  · Ed25519 라이선스 검증

roamswitch-updater  단독 systemd 타이머 헬퍼. HTTP/TLS 스택을
                    가진 유일한 크레이트(매일 서명된 매니페스트 GET)
roamswitch-honeytokens          데몬이 실행하는 보조 프로세스(미끼 자격 증명의 배치와 감시)
roamswitch-incident-capture     데몬이 실행하는 보조 프로세스(봉쇄 시 증거 번들 수집)
roamswitch-core     순수 로직 라이브러리(모든 크레이트 공통)
roamswitchkit       MCP 클라이언트 SDK
권한 분리
구성 요소특권역할
roamswitch-데몬루트(시스템, Type=notify + WatchdogSec=30)모든 권한 있는 작업. 단일 작성자로서 nftables, DNS 및 systemd 장치를 제어합니다.
roamswitch 앱로그인 사용자GTK3 GUI + AppIndicator 트레이. 구성 편집 및 시각화만 가능합니다. 모든 권한 있는 작업은 IPC를 거칩니다.
로밍스위치(CLI)로그인 사용자동일한 IPC 소켓을 사용하는 얇은 쉘 프런트 엔드.
로암스위치-mcpAI 클라이언트에 의해 생성된 프로세스stdio JSON-RPC. 읽기 전용 진단 정보를 제공합니다.

데몬과의 모든 통신은 /run/roamswitch/roamswitch.sock(디렉터리는 0755, 소켓은 0666)을 거칩니다. 접근 제어는 파일 권한이 아니라 접속 주체 확인으로 이루어집니다. SO_PEERCRED로 UID를 얻어 시스템 계정(UID 1~999와 nobody)의 접속은 거부하고, 상태를 변경하는 호출은 root 또는 로컬 로그인 세션을 가진 UID로만 제한합니다(§1). 사용자 설정은 ~/.config/roamswitch/config.json에 일반 텍스트 JSON으로 저장됩니다. 데몬은 root 권한으로 /home/*/.config/roamswitch/config.json을 순회하며 읽습니다(데몬은 HOME=/root로 동작하기 때문입니다). 읽는 것은 해당 계정 자신이 소유하고, 심볼릭 링크가 아니며, 다른 사용자가 쓸 수 없는 파일뿐입니다. 설정은 기기 밖으로 나가지 않으며, 계정 등록도 클라우드 동기화도 없습니다.

§4

센티넬 루프(3초 주기)는 기본 게이트웨이의 MAC 주소를 /proc/net/arp 및 ip neigh에서 읽어 config.json에 등록된 MAC과 대조합니다. SSID는 쉽게 위조될 수 있으므로 SSID 대신 MAC을 사용합니다. 프로필 전환 시의 데스크톱 알림에는 전환 이유(“등록되지 않은 네트워크이므로” / “등록된 네트워크 ‘〇〇’의 설정” / “네트워크에 연결되어 있지 않으므로”)를 포함합니다.

프로필
프로필상황nftables 동작
열기(신뢰할 수 있음)등록된 집 또는 회사 네트워크policy accept. 로컬 트래픽이 허용됩니다.
균형등록되지 않았지만 비교적 안전한 것으로 구성됨수신은 기본 거부. 확립된 연결·lo·ICMP/ICMPv6만 허용. 공개 포트 노출을 감시.
봉쇄(외출 보호)공용 Wi-Fi / 미등록 네트워크모든 인바운드 패킷을 스텔스 차단합니다. 공유 서비스는 자동으로 중지되고 신뢰 네트워크로 돌아오면 복원됩니다(기본적으로 켜짐, 언제든 끌 수 있음).

모든 규칙은 inet roamswitch이라는 전용 nftables 테이블에 격리되어 있으며 다른 방화벽 구성(ufw/firewalld)과 절대 충돌하지 않습니다. 수동 재정의 만료(1시간/2시간/4시간/자정까지/지워질 때까지/다음 연결 끊김까지)는 데몬에 의해 단일 작성자로 적용됩니다. GUI는 만료를 기록하지 않습니다. 다음 틱에서 데몬이 다시 작성한 config.json을 다시 읽고 이를 UI에 반영합니다(충돌 방지).

Air-Gap(긴급 네트워크 격리)

inet roamswitch 테이블의 입력 및 출력 후크 모두에 policy drop(우선순위 -100)을 설정하고 lo만 허용하는 전체 격리 모드입니다.

  • 실패 폐쇄 — 데몬을 다시 시작할 때 마커 파일(/run/roamswitch/airgap.active)을 감지하면 격리가 다시 확인됩니다.
  • 자가 치유 — 외부 프로세스가 nft delete table을 사용하여 규칙을 삭제하면 센티넬 루프가 몇 초 내에 다시 적용됩니다.
  • 낮은 확신도 트리거는 짧은 타이머로 해제 — 경고의 맥락 없이 누른 수동 Air-Gap(GUI 버튼이나 roamswitch airgap)과 같은 낮은 확신도 트리거는 짧은 타이머(airgap_low_confidence_secs, 기본 600초)로 전면 복구됩니다. 오조작이나 오탐으로 단말이 오래 네트워크에서 끊기지 않도록 하기 위함입니다. ARP 이상(lockdown 밖), 포트 이상, eBPF Error 이하 등급도 공통 상태 기계에서는 낮은 확신도로 분류되지만, 클라이언트에서는 이들이 Air-Gap을 자동으로 발동하지 않으며 알림·기록 또는 해당 포트의 차단에 그칩니다.
  • 높은 확신도 트리거는 조용히 복구되지 않음 — 카나리아 변조, eBPF Emergency/Alert(Critical은 개별 프로세스를 봉쇄하지 못했을 때), lockdown 중 게이트웨이와 관련된 ARP 스푸핑, ARP 경고에 대응해 누른 Air-Gap(그리고 옵트인한 경우에 한해 실행 레코더의 높은 심각도 규칙. §5)은 시간이 지나도 자동으로 복구되지 않습니다. 짧은 타이머 이후에는 매번 원인이 계속되는지(동결한 PID의 생존, 카나리아 재변조, ARP 이상 지속 등)를 다시 확인하고, 원인 소멸이 확인된 경우에만 알림과 함께 해제합니다. 확인할 수 없는 경우는 판정 불능을 포함해 “미해소”로 취급합니다. 랜섬웨어 엔트로피 버스트도 해당 프로세스의 SIGSTOP 동결과 알림에 더해 Air-Gap을 발동하며, 같은 방식으로 높은 신뢰도의 트리거로 취급합니다.
  • DEGRADED(축소) 모드 — 높은 확신도의 격리가 상한 시간(airgap_hard_cap_secs, 기본 3600초, 0 = 상한 없음)에 도달했는데도 사용자의 해제도 원인 소멸 확인도 없으면 DEGRADED로 전환됩니다. 클라이언트판의 DEGRADED도 FULL과 마찬가지로 루프백 이외를 전부 차단(수립된 연결도 통과시키지 않음)하며, 차이는 시간이 지나도 절대 자동으로 열리지 않는다는 점입니다. 해제는 로컬 사용자(GUI의 해제 버튼 또는 roamswitch emergency-restore) 또는 원인 소멸을 확인한 재확인뿐입니다. Air-Gap은 기본값에서 Wi-Fi 무선도 rfkill로 차단하며(airgapAutoRfkill), DEGRADED에서도 airgap_degraded_lift_rfkill을 true로 하지 않는 한 차단된 채로 유지됩니다.
  • 전환의 기록과 알림 — FULL에서 DEGRADED로의 전환, 각종 자동 해제, 사용자에 의한 해제는 모두 인시던트 타임라인에 기록되고 데스크톱 알림이 표시됩니다. 격리 상태는 /run/roamswitch/airgap_isolation.json에 저장되어 데몬을 재시작해도 유지됩니다(재시작하면 ARP 재확인의 연속 횟수가 처음부터 다시 시작되며, 해제에 불리한 fail-closed 쪽으로 기웁니다).
table inet roamswitch {
    chain input {
        type filter hook input priority -100; policy <accept|drop>;
        iif "lo" accept
        ct state established,related accept
        # open은 policy accept로 규칙 없음. balanced / lockdown은 policy drop(ICMP·ICMPv6 허용은 balanced만)
    }
    chain output {
        type filter hook output priority -100; policy <accept|drop>;
        # Air-Gap(격리) 중에는 같은 테이블을 다시 만든다. input / output 모두 policy drop, 허용은 lo뿐(확립된 연결도 통과시키지 않음)
    }
}

패시브 링크 가드(피싱 연결 감지)

데몬은 아웃바운드 연결이 어디로 가는지 감시하고 위험한 호스트에 대한 연결을 표시하거나 "명백한 피싱" 집합의 경우 차단합니다. 이는 사용자가 조치를 취해야 했던 이전의 URL 붙여넣기 검사기를 대체합니다.

  • Interception — 전용 nftables 테이블 inet roamswitch_linkguard은 output 경로를 연결하고 NFQUEUE을 통해 사용자 공간 작업자에 대한 DNS 질문과 신규/초기 웹 연결을 대기열에 넣습니다. NFQUEUE는 AF_NETLINK에 대한 커널‐사용자 공간 판정 채널입니다. 데몬은 소켓을 바인딩하지 않고 패킷을 생성하지 않습니다. 대기열 규칙은 bypass을 전달하므로 작업자가 실행 중이 아닌 경우 트래픽이 방해 없이 전달됩니다(Air-Gap은 별도의 하드 중지로 유지됩니다).
  • Hostname 소스 — DNS 질문 이름, TLS ClientHello SNI(DoH를 사용하는 브라우저는 여전히 호스트 이름으로 보호됨) 및 일반 텍스트 HTTP Host 헤더. 암호화된 ClientHello(ECH)는 일단 널리 배포되면 알려진 사각지대입니다.
  • Verdict(roamswitch-core, 순수 논리) — 호스트는 로컬 피싱/위협 피드 및 일련의 오프라인 경험적 방법(보호되는 브랜드의 IDN/혼동 가능한 동형어체, 비공식 도메인의 브랜드 토큰, 원시 IP 호스트, 고위험 TLD)에 대해 검사됩니다.
모드(linkGuard.mode)피드 히트 또는 브랜드 동형이의어기타 의심스러운 신호
off검사받지 않음검사받지 않음
warn연결은 최대 8개까지 유지 + 허용/차단 승인 프롬프트알림도 차단도 하지 않음
block(기본값)연결이 끊어졌습니다 + 허용 대화 상자(호스트당 한 번)알림도 차단도 하지 않음

하드 차단되는 것은 피드에 적중했거나 브랜드 동형 문자(homograph) 호스트뿐이며, 휴리스틱 단독으로 걸러낸 것은 모두 경고 취급의 판정에 머물고 차단도 알림도 하지 않습니다. warn 모드는 위험한 호스트에 대한 연결을 최대 8초 보류하여 사용자에게 확인을 구하며, 응답이 없는 채로 보류가 만료되면 fail-closed(패킷을 drop하고 판정은 캐시하지 않음)입니다. “확인에 응답 없음”을 “허용”으로 간주하지 않으며, 다음 접근 시에 다시 확인합니다. block 모드에서도 호스트 단위로 한 번만 알림과 허용 대화 상자를 표시합니다(같은 호스트에 대한 브라우저의 다수 연결로 알림이 넘치지 않습니다). 위험한 대상의 승인 대화 상자는 기본 버튼이 “차단 유지”이고 “허용”은 눈에 띄지 않는 색으로 표시됩니다. 차단된 호스트는 승인 대화 상자에서 “허용”을 선택하면 linkGuard.allowlist에 영구적으로 추가되며, 설정에서 제거할 때까지 통과합니다.

DNS는 검사하되 위조하지 않음: 차단 목록에 오른 DNS 질의는 알림만 생성하며, 강제 적용은 후속 TCP 연결에서 이루어집니다. RoamSwitch가 가짜 DNS 응답을 주입하는 일은 없습니다. 피드는 피싱/사기 도메인을 컴파일한 목록으로, 패키지에 포함되어 있으며 일일 업데이터(§11)로 갱신됩니다. 피드가 없어도 오프라인 휴리스틱은 동작합니다. 피드의 Ed25519 서명과 SHA-256은 일일 업데이터가 설치하기 전에 검증하며, 일치하지 않는 데이터는 채택하지 않습니다. 링크 가드는 설치된 피드(없으면 패키지에 포함된 것)를 읽어 들이고, 둘 다 없을 때는 “휴리스틱만”으로 동작합니다(“모두 허용”이 되지는 않습니다).

VPN 터널 + 킬 스위치(기본 MITM 방어)

레이어 2의 무결성에 의존하지 않는 Anti-MITM 보호. 신뢰할 수 없는 네트워크에서 신뢰할 수 있는 엔드포인트에 대한 상시 암호화 터널은 로컬 ARP/NDP 스푸핑, 불량 DHCP 및 수동 스니핑을 부적절하게 만듭니다. 즉, 공격자는 암호 텍스트만 볼 수 있습니다. ARP/NDP 잠금 및 아래 감지는 이 기능의 부차적인 기능입니다.

선택할 수 있는 두 개의 백엔드 — "WireGuard(구성 파일)" 또는 "Tailscale(종료 노드)". RoamSwitch는 자체 암호화를 구현하지 않으며 VPN 제공자가 아닙니다. 두 백엔드 모두 선택적 종속성입니다. 없는 경우 GUI에 설치 명령이 표시됩니다.

(A) WireGuard 백엔드

  • VPN 서버가 제공되지 않습니다 — 사용자가 제공하는 WireGuard 구성을 사용합니다: Mullvad, IVPN 또는 Proton VPN과 같은 공급자의 .conf 또는 사용자 자체 WireGuard 서버(VPS 또는 홈 서버). 기본 구성은 제공되지 않습니다.
  • Config — 데몬은 .conf의 유효성을 검사하고 이를 /etc/wireguard/roamswitch.conf(0600, 인터페이스 roamswitch)에 저장합니다. 다른 곳에서는 살 수 없습니다. Ubuntu 24.04+ / Debian 13의 AppArmor wg-quick 프로필은 wg-quick이 /etc/wireguard/ 외부에서 .conf을 읽는 것을 거부합니다. 심지어 루트로도 마찬가지입니다. 분할 터널 구성(0.0.0.0/0 / ::/0이 아닌 AllowedIPs)은 "모든 트래픽이 보호되지는 않음"으로 플래그가 지정됩니다.
  • Kill-switch — 전용 nftables 테이블 inet roamswitch_vpn(policy drop, 두 후크 모두) 은 루프백, 터널 인터페이스(roamswitch, oifname과 일치), 고정된 엔드포인트 IP에 대한 UDP, DHCP 및 ICMP을 제외한 모든 것을 삭제합니다. 터널이 시작되기 전에 전에 설치되므로(끝점은 먼저 getent ahosts을 통해 고정 IP로 확인됨) 누출 창이 없습니다. wg-quick up이 실패하면 킬 스위치가 계속 작동됩니다.

(B) Tailscale 백엔드

  • 이미 Tailscale을 실행하고 있는 사용자를 위한 것입니다. RoamSwitch는 가 아니라 실행 tailscale up / 로그인 / 설치 tailscaled을 수행합니다. — tailscale status --json를 읽고 tailscale set --exit-node=<node> / --exit-node=(지우기)을 실행합니다.
  • 출구 노드가 필요합니다 — MITM 방지 보호는 모든 트래픽을 라우팅하는 출구 노드에만 적용됩니다. 아무것도 구성되지 않으면 보호가 활성화되지 않으며 UI에 "보호되지 않음"이라는 경고가 표시됩니다. 선택한 노드가 오프라인이 되면 보호 기능이 자동으로 해제되고 사용자에게 알립니다. 데드 노드를 통해 모든 것을 라우팅하면 연결이 블랙홀됩니다.
  • Kill-switch (inet roamswitch_ts, policy drop) — Tailscale의 전송은 단일 엔드포인트 IP(DERP 릴레이 + 로밍 피어)에 고정될 수 없으므로 이는 WireGuard보다 loser입니다. 이는 루프백, 터널 인터페이스(tailscale0), CGNAT 범위 100.64.0.0/10 및 fd7a:115c:a1e0::/48, STUN(udp/3478), DERP(tcp/443), udp/41641, DHCP, ICMP, MagicDNS(100.100.100.100:53), 설정된 연결을 허용합니다. 그 밖의 모든 것(임의 UDP, 로컬 확인자에 대한 DNS, SMB, mDNS, 일반 텍스트 HTTP, 임의 TCP)은 삭제됩니다.
  • 킬 스위치 주의사항 — (1) WireGuard 백엔드(udp/3478, tcp/443, 전체 CGNAT /10)보다 허용 목록이 더 넓습니다. (2) 경로상의 공격자는 여전히 메타데이터(Tailscale이 사용 중, DERP 지역, 타이밍)를 보고 삭제/지연할 수 있지만 터널링된 페이로드를 읽을 수는 없습니다. (3) 직접 UDP가 차단되면 기밀성은 Tailscale의 자체 고정 인증서 DERP TLS에 있습니다. (4) MagicDNS가 꺼진 상태 + 로컬 확인자가 활성화된 동안 DNS가 중단됩니다(UI 경고). (5) 종속 포털은 활성화하기 전에 로그인해야 합니다.

공통. vpn_on_untrusted_enabled가 켜져 있고 lockdown(외출 시 보호)일 때만 작동합니다. open과 balanced에서는 터널을 만들지 않으며, Tailscale에서는 Exit Node를 해제하고 킬 스위치를 제거합니다(tailscale down은 하지 않습니다). 기본값 OFF·옵트인. 외부 통신의 목적지는 WireGuard/Tailscale 모두 사용자 자신의 VPN 엔드포인트/tailnet이며, lafine의 인프라와는 무관합니다(§9).

예방적 ARP/NDP 잠금

신뢰할 수 없는 네트워크가 연결되는 순간, MITM 공격자가 가장해야 하는 온링크 인프라의 MAC 주소는 커널 인접 테이블에 PERMANENT(정적) 항목으로 고정됩니다. 일단 고정되면 커널은 스푸핑된 ARP 응답/이웃 광고를 무시합니다.

  • 고정된 항목 — (1) IPv4 기본 게이트웨이, (2) IPv6 기본 라우터(종종 링크-로컬 fe80:: 주소), (3) 모든 온링크 DNS 확인자(공용 확인자는 게이트웨이를 통해 도달하고 건너뜁니다). IPv6 이웃은 ip -6 neigh replace ... nud permanent로 고정되어 있습니다.
  • 신뢰할 수 있는 네트워크는 고정되지 않습니다 — 따라서 홈 라우터 재부팅으로 인해 사용자가 블랙홀에 빠질 수 없습니다. 모든 네트워크 스위치(TOFU)에 다시 고정됩니다. 다음 번 재연결 시 합법적인 장애 조치가 선택됩니다. 고정된 항목은 /var/lib/roamswitch/arp_lock.json에 기록됩니다.
  • 기본적으로 켜짐(Community Edition 정책, macOS Pro 빌드와 동일한 분류). 토글: gateway_arp_lock_enabled.

ARP 스푸핑 탐지(알림 우선)

/proc/net/arp를 감시하여 하나의 MAC이 여러 IP에 대응하는 상태(중복 MAC), 그리고 게이트웨이 MAC의 수상한 급변을 탐지합니다. lockdown 중에는 게이트웨이가 관련된 탐지에서 통신을 전면 차단(Air-Gap)하고 알립니다. balanced/신뢰된 네트워크에서는 갑자기 차단하지 않고 알림만 내보내며, 사용자가 RoamSwitch에서 긴급 차단을 실행할 수 있습니다. 이는 (a) 공격자가 위조 ARP 패킷 하나로 사용자를 스스로 오프라인 상태로 만드는 DoS를 막고, (b) 라우터 재부팅이나 액세스 포인트 로밍으로 인한 오작동을 막기 위함입니다. 설정은 arp_spoof_guard_enabled입니다.

Air-Gap 해제 전 확인(ARP 재확인)

ARP 스푸핑이 원인인 Air-Gap(lockdown 중 탐지, 또는 ARP 경고에 대응해 누른 Air-Gap)은 다음을 만족할 때만 “원인 소멸”로 보고 해제합니다. 커널의 이웃 테이블(ip -j neigh show, 대체로 /proc/net/arp)과 기본 경로를 읽어, 기본 게이트웨이의 MAC이 발동 시 기록한 신뢰 MAC과 일치하고(상태가 REACHABLE/STALE/DELAY/PROBE/PERMANENT 중 하나이며 같은 MAC을 주장하는 다른 IPv4 호스트가 같은 디바이스에 없음), 그 상태가 연속 arp_recheck_consecutive회(기본 3) 그리고 arp_recheck_min_secs초 이상(기본 60) 지속되어야 합니다. 이웃 테이블 항목이 없거나 실패 상태이고 링크가 살아 있을 때(carrier 있음, operstate up, Wi-Fi는 rfkill 미차단)에 한해 게이트웨이로 ARP who-has를 한 번만(20초 이상 간격, AF_PACKET) 보내 확인합니다(arp_recheck_active_probe, 기본 true). 돌아온 응답에 신뢰 MAC 이외의 MAC이 하나라도 있으면 불일치입니다. 불일치, 항목 누락, 중복 MAC, 읽기 실패, 신뢰 MAC 미기록은 모두 “미해소”(fail-closed)이며 연속 횟수를 초기화합니다. 알려진 한계: 진짜 게이트웨이의 MAC을 복제하는 공격자는 캐시 판정으로도 능동 프로브로도, MAC 비교로는 어떻게 해도 탐지할 수 없습니다.

ARP 스푸핑 탐지는 게이트웨이가 관련될 때만 자동으로 Air-Gap을 발동합니다. 게이트웨이의 MAC이 바뀐 경우와 게이트웨이의 IP가 다른 IP와 하나의 MAC을 공유하는 경우가 이에 해당합니다. 다른 호스트의 MAC이 바뀌거나 한 기기가 여러 IP에 같은 MAC으로 응답하는 경우(가상 머신을 돌리는 PC, 별칭 주소가 있는 NAS 등)는 알림만 하고 차단하지 않습니다. 같은 규칙이 해제 판정에도 적용되어, 게이트웨이가 관련된 중복만 “원인이 계속됨”으로 셉니다. 1.10.7 수정 당시(2026-09-26) 실제 LAN에서 다른 기기가 위조한 게이트웨이 ARP를 보내자 약 1초 만에 Air-Gap이 발동했고, 공격을 멈춘 뒤 약 11분(기본 10분과 확인 60초) 후에 자동으로 해제되었습니다. 관련 없는 두 IP가 같은 MAC을 사칭하는 상태가 계속되어도 해제되었습니다(이 규칙을 적용하기 전에는 전면 차단이 발동하고 해제되지도 않았습니다).

포트 이상 감시

외부 LAN(백도어, C2 또는 개발 서버/로컬 LLM API가 실수로 0.0.0.0에 바인딩됨)에 새로 노출된 수신 포트를 자동으로 포함합니다. 판정은 "위험한 포트 번호"가 아닌 baseline-relative입니다. 활성화되면 현재 노출된 실행 파일이 "알려진" 것으로 학습됩니다. 그 후에는 새로 등장한 노출 실행 파일만 플래그가 지정됩니다(ID = 실행 파일 경로, 스냅은 snap:<name>으로 정규화됨). 시스템 데몬은 범위를 벗어납니다. 포함은 inet roamswitch_dev_guard 테이블을 통해 이루어집니다. lo 이외의 다른 포트에서 해당 포트로 TCP를 삭제합니다. 머신 자체는 이를 계속 사용합니다. 자동 격리 시 즉각적인 데스크탑 알림 및 확인 대화 상자("이 포트 허용"/"차단 유지")가 표시되어 원클릭 영구 화이트리스트 지정이 가능합니다. 포트 진단 화면에서 모든 포트를 개별적으로 허용하고 기준선을 다시 캡처할 수도 있습니다. 기본적으로 켜져 있습니다. 토글: port_anomaly_guard_enabled. macOS에서 포팅되었습니다.

DNS 터널링/데이터 유출 탐지

수동 링크 가드의 기존 DNS 경로 일부로서 항상 동작합니다(별도의 설정 토글은 없습니다). iodine·dnscat2 같은 DNS 터널링 도구에 특징적인 질의 형태(라벨 길이 40바이트 초과 및 Shannon 엔트로피 3.8 초과, 또는 TXT/NULL 레코드의 qtype) 중 하나에 해당하는 질의를 “의심스러운 형태”로 집계하며, 최근 60초 동안 이러한 질의가 10건에 도달한 경우에 한해 알립니다(동일한 apex domain에 대한 재알림에는 300초의 쿨다운을 두며, 알림이 이어지면 600초, 1200초, 상한 1800초로 늘어납니다. 쿨다운의 2배 이상 알림이 없으면 300초로 돌아갑니다). 검사 대상은 DNS(UDP/TCP 53번 포트)뿐이며 다른 트래픽은 검사하지 않습니다. 알림 전용이며 자동으로 차단하지 않습니다. clamav.net으로의 TXT 질의만은 freshclam이 최신성 확인에 사용하므로 집계하지 않습니다. 내부 NFQUEUE 규칙에는 bypass가 붙어 있어 탐지 프로세스가 중단되거나 응답하지 않아도 DNS 트래픽은 그대로 통과합니다. 이름 해석이 이 기능의 오작동으로 막히는 일은 없습니다(수동 링크 가드 자체와 동일한 fail-open 설계입니다).

알려진 한계. 하이픈으로 구분된 긴 호스트명(예: prod-us-east-1-web-server-node-0042)과, 목적지 도메인을 구분하지 않는 단일 전역 카운터로 TXT/NULL 버스트를 집계하는 방식이 여기서 오탐을 유발함을 실측으로 확인했습니다. 두 가지 모두 2026-09-23에 대응을 완료했습니다. 설정 가능한 entropy 허용 목록과, apex domain(QNAME 마지막 2개 레이블을 이용한 간단한 근사치로, Public Suffix List의 엄격한 구현은 아닙니다) 단위로 버스트 카운터의 키를 분리하는 방식으로 해결했습니다. 허용 목록에는 기본적으로 주요 CDN/클라우드와 PaaS의 접미사 16개(cloudfront.net·akamaiedge.net·akamaized.net·edgekey.net·edgesuite.net·fastly.net·azureedge.net·googleusercontent.com·amazonaws.com·r2.dev·workers.dev·vercel.app·netlify.app·supabase.co·firebaseapp.com·web.app)가 포함되어 있으며, 운영자는 LinkGuardCfg.dns_tunnel_entropy_allowlist에 자사 내부 도메인을 추가할 수 있습니다(roamswitch-app의 링크 가드 설정에 쉼표로 구분하여 입력하는 전용 입력란이 있습니다). 허용 목록이 면제하는 것은 라벨 길이+엔트로피에 의한 의심 판정뿐이며, TXT/NULL qtype 판정은 허용 목록 여부와 관계없이 적용되므로 허용 목록 도메인 아래에 터널을 위장하는 우회를 방지합니다(TXT만은 freshclam의 최신성 확인에 쓰이는 clamav.net 아래를 별도의 작은 제외 목록으로 집계하지 않습니다). 버스트 카운터는 apex domain 단위로 키가 분리되어 목적지를 구분하지 않는 단일 전역 카운터로 인한 오탐은 해소되었습니다(60초 슬라이딩 윈도라는 판정 로직 자체는 변경하지 않았습니다. 재알림 쿨다운은 300초에서 시작하여 알림이 이어지면 1800초까지 늘어납니다). iodine·dnscat2 같은 실제 악성 트래픽은 공격자가 관리하는 단일 도메인 아래에서 라벨과 qtype만 바꾸므로, apex domain 단위로 좁혀도 탐지력은 떨어지지 않습니다.

Docker 위험 감지 & 방화벽 우회 방지

Docker의 공개 포트(-p)는 호스트의 표준 방화벽을 우회하는 것으로 알려진 문제가 있어, DOCKER-USER 체인에 검사 규칙을 항상 삽입하여 허용되지 않은 외부 트래픽을 차단합니다(설정 불필요, 기본 항상 활성화). 또한 docker events를 실시간으로 감시하여 --privileged 컨테이너 시작이나 /var/run/docker.sock 마운트(컨테이너 탈출 위험)를 감지한 즉시 알려드립니다. 위험한 설정일 뿐 확인된 침해가 아니므로 자동 차단 없이 알림만 제공합니다.

적용 범위 — 이 DOCKER-USER 체인 검사는 iptables(IPv4), IPv6, Docker의 네이티브 nftables 백엔드를 지원하며 60초마다 다시 확인하고 스스로 복구합니다(데스크톱 에디션에서도 1.10.4부터 동일하며, 이전 버전은 iptables(IPv4) 백엔드만 대상이었습니다).

기밀 정보・API 키 유출 스캐너

OpenAI・Anthropic・AWS・GitHub・Slack・Stripe 등의 API 키와 SSH 개인 키를 섀넌 엔트로피와 패턴 매칭으로 감지합니다. roamswitch audit-secrets <text|파일|디렉터리>로 텍스트・파일뿐 아니라 디렉터리 전체를 재귀적으로 스캔할 수 있습니다(.git・node_modules 등은 자동 제외, 2MB 초과・바이너리 파일은 건너뜀). audit_secrets MCP 도구(text 또는 path를 전달)와 공개 SDK인 Rust 클라이언트 roamswitch-linux-kit(텍스트는 audit_secrets(), 파일과 디렉터리는 audit_secrets_path())로도 같은 탐지를 호출할 수 있습니다. 내용은 어디에도 전송되지 않습니다.

SSH 무차별 대입의 탐지와 차단(sshd가 동작 중일 때만)

클라이언트 버전의 데몬도 서버 버전과 같은 엔진을 돌립니다(ssh_guard_enabled, 기본 true). 차이는 세 가지입니다. 첫째, sshd가 동작 중일 때만 작동합니다. SSH 포트에 대기 중인 것이 있는지 1분마다 확인하고, 3회 연속 없으면 멈춥니다. sshd가 없는 단말에서는 로그를 흘려 읽지 않고 nftables 테이블에도 손대지 않습니다. 둘째, 설정은 config.json의, 서버 버전과 같은 10개 키(ssh_guard_*)와 같은 기본값이며 약 1분 안에 반영됩니다. 셋째, 알림은 데스크톱 알림과 클라이언트의 인시던트 기록이며 Air-Gap은 시작하지 않습니다. 실패가 임계치(기본 600초에 6회)에 도달한 접속 출처의 새 연결을 차단하고, 동시에 그 접속 출처가 이미 SSH에 맺어 둔 연결도 끊습니다(인증된 세션과 면제한 접속 출처는 끊지 않습니다). fail2ban(sshd jail 활성)이나 CrowdSec이 sshd를 지키고 있을 때는 탐지와 알림만 합니다. 목록 확인과 해제는 sudo roamswitch bans와 sudo roamswitch unban <IP>로 하며, GTK 화면에는 없고 config.json과 명령으로 다룹니다. 클라이언트의 기본 방어 수준에서는 수신이 닫혀 있어 SSH에 연결이 닿지 않을 수 있습니다.

최초 실행 설정 마법사 및 확인 UI

패키지는 /etc/xdg/autostart/roamswitch.desktop을 제공하므로 설치 후 첫 번째 로그인 시 설정 마법사가 시작됩니다(이후에는 트레이 상주에만 해당). 보안 가드를 활성화/비활성화하면 macOS 빌드(포트 이상, ARP 자동 격리, USB 저장소, BadUSB 키보드, Bluetooth)와 동일한 가드에 대한 확인 대화 상자가 표시됩니다. 이벤트 시간 결정(알 수 없는 USB 키보드, 등록되지 않은 USB 드라이브, 알 수 없는 포트 자동 차단, 긴급 Air-Gap)이 모달 대화 상자로 표시됩니다.

§5

섀넌 엔트로피 버스트 감지 · YARA 스캐닝

쓰기 이벤트를 PID 단위로 추적하여, 단일 프로세스가 5초 이내에 엔트로피 7.92 이상인 서로 다른 파일을 20개 이상 생성하면 랜섬웨어의 일괄 암호화로 간주하고 SIGSTOP으로 프로세스를 동결합니다. 아울러 데스크톱 알림과 인시던트 타임라인 기록을 수행하고 증거 번들을 수집합니다. 이 탐지에서도 Air-Gap(네트워크 전체 차단)을 발동합니다. 카나리아 변조나 eBPF의 높은 심각도 이벤트와 같은 높은 신뢰도의 트리거입니다.

실제 기기(Ubuntu 24.04, 커널 7.0)에서 40개 파일을 다시 쓰는 재현 프로그램을 돌려 측정했습니다(2026-09-26 측정이며, 대상은 부분 암호화 탐지를 추가한 1.10.7에 포함된 버전의 데몬입니다). 전체를 암호화하면 20개 파일을 넘긴 시점에 탐지하고 31번째 파일에서 동결했습니다. 프로세스 이름을 python3나 git으로 바꿔도 탐지합니다(허용 목록은 이름만이 아니라 실행 파일과 스크립트 이름의 일치도 확인합니다. 이전에는 이름만 보아 40개 파일을 모두 놓쳤습니다). cp -r, rsync -a, tar -x, Python 스크립트는 쓰기를 본 시점에 프로세스를 식별하므로 짧게 끝나도 오탐하지 않습니다(이전에는 판정 시점에 이미 종료되어 ‘알 수 없는 프로세스’로 알렸습니다). 32바이트 중 16바이트만 암호화하는 부분 암호화(LockBit 계열의 쓰기 방식)는 전체 평균 엔트로피가 7.92에 이르지 못하므로 이전에는 탐지하지 못했지만, 1.10.7부터 탐지합니다. 16바이트마다 난수 같은 블록과 문장 같은 블록이 번갈아 늘어선 형태를 식별하는 방식으로, 모의 파일의 94%를 탐지했고 일반 파일 19,997건 중 오탐은 2건(0.01%)이었습니다. 파일마다 간격을 두지 않고 전속력으로 쓰는 재현 프로그램은 20건의 판정보다 먼저 40건을 다 쓰기 때문에 멈춘 시점에 프로세스가 이미 끝나 있으며, 0.2초씩 간격을 두면 20건째에서 멈춥니다. 앞부분만 암호화하는 경우, 구간을 건너뛰며 암호화하는 경우, 암호화한 내용을 Base64로 써서 기록하는 경우는 탐지로 집계하지 않고 로그에만 기록하며 동결하지 않습니다(일반 파일을 오탐할 우려가 있기 때문입니다). 허용 목록에 있는 진짜 프로그램(python3 등)으로 작성된 랜섬웨어도 이름으로는 구별할 수 없습니다.

  • YARA 검사와 엔트로피 탐지의 분리 — YARA 악성 코드 탐지는 ~/.cache/나 node_modules/, 각종 아카이브를 포함하여 기록된 파일에 확장자에 의한 제외 없이 실행합니다(사용자 설정의 scan_exclusions, 격리 Vault, /dev/shm이나 메모리상·삭제된 파일, /proc·/sys·/dev 등의 위치, 그리고 데몬 자신, ClamAV, 데스크톱 기반 등 시스템 프로세스가 쓰는 것은 제외합니다. 읽는 것은 앞부분 256 KB입니다). 한편 엔트로피 암호화 버스트 판정에서는 처음부터 압축된 형식(.zip, .tgz, .tar.gz, .gz, .png, .jpg, .mp4, .wasm 등), 앞부분이 정상적인 압축·미디어 형식 그대로인 파일(1.10.25 이후), 정식 패키지 관리자(npm, npx, cargo, rustc, dpkg, apt, tar 등)의 캐시 작업을 제외하여, 정상 도구의 압축 해제 시 오탐으로 인한 SIGSTOP 동결을 방지합니다.
  • 동일한 파일의 반복 덮어쓰기(예: shred의 3회 패스)는 하나의 이벤트로 계산됩니다.
  • 중요 프로세스 보호 — systemd / dockerd / NetworkManager / sshd / gnome-shell 등을 하드코딩한 보호 대상 프로세스 목록(PROTECTED_PROCESS_NAMES)과 대조하여, 이름이 일치하고 실행 파일이 시스템 영역(/usr, /opt 등)에 있는 것은 결코 동결하지 않습니다(기기 전체의 정지를 방지). 이름만 비슷하게 한 /tmp나 /home의 프로그램은 보호되지 않습니다.

웹 애플리케이션 계층 탐지 규칙(웹셸·삽입 코드)

PHP·JavaScript·JSP·ASP 웹셸, 삽입된 난독화 코드, 이미지로 위장한 PHP, .htaccess를 이용한 실행 가로채기를 전용 규칙으로 탐지합니다. ELF·셸 대상의 내장 탐지와는 별개이며, 리터럴과 간격 상한이 있는 문자열 열, 두 가지 통계값(극단적으로 긴 한 줄, 거대한 Base64)만으로 쓰는 단순한 언어입니다. 정규식을 쓰지 않아 처리가 부풀거나 멈추지 않고, 악의적인 규칙 파일이라도 일어날 수 있는 것은 오탐뿐입니다. 실제 샘플에서 뽑은 시그니처나 특정 악성코드 이름이 붙은 시그니처는 없습니다. 공개된 수법(디코더 출력의 실행, 요청 값의 직접 실행, 업로드 디렉터리의 실행형 PHP, 이미지 속 PHP, .htaccess의 핸들러 지정, unescape로 조립한 삽입 스크립트, 브라우저 내 채굴 라이브러리)만 기술한 18개입니다.

규칙은 세 층입니다. 제품에 내장된 것, 일일 업데이터(11.3)가 서명 검증 후 설치하는 것, /etc/roamswitch/rules.d/*.json의 사용자 규칙(root 소유이며 그룹·타인이 쓸 수 없는 파일만 읽음). 심각도는 critical·high·medium·low이고, 승인·격리 대상은 critical과 high, medium 이하는 보고만 합니다. 자동 격리는 기본적으로 critical만(/etc/roamswitch/web_rules.conf의 quarantine)이며, 클라이언트판의 쓰기·실행 시 탐지는 기존처럼 승인 대화상자에서 사용자가 결정합니다. tests/·fixtures/·.git/ 아래는 테스트 코드가 같은 표현을 쓰기 때문에 기본적으로 제외됩니다.

탐지 후에는 /etc/roamswitch/hooks/post-detection에 실행 파일을 두면 탐지 내용(JSON)이 표준 입력으로 전달됩니다(root 소유, 쓰기 불가, 상위 디렉터리도 안전한 것만 실행하며, 셸을 거치지 않고 환경 변수는 최소한이며 30초 후 중단). 선택 사항인 계정 조치(기본 꺼짐)는 격리한 파일 소유자의 로그인 셸을 해제합니다. root, UID가 account_min_uid(기본 1000) 미만인 계정, 로그인 중인 사용자, 데몬 자신의 사용자에게는 아무것도 하지 않으며, 원래 셸을 기록해 roamswitch web-rules unlock-account로 되돌릴 수 있습니다. 오탐 실측은 WordPress 본체 2,409개, phpMyAdmin 3,356개, npm 의존성 14,743개 파일에서 0건이었습니다. 한계로, 읽는 것은 앞 256 KB(쓰기·실행 시)와 4 MB(스캔)뿐이고 변수 이름 조립('ev'.'al') 같은 분할은 따라가지 못하며, 실제 샘플이 없어 알려진 악성코드의 개별 시그니처는 포함하지 않습니다.

카나리아(미끼 파일)

미끼 파일은 사용자의 문서, 데스크톱, 다운로드, 사진 폴더 및 전용 디렉터리에 배치되며 3초 주기로 SHA-256 기준과 비교됩니다. 이름 바꾸기, 삭제, 잘림 또는 콘텐츠 변경은 즉시 Air-Gap을 트리거합니다. 데몬은 루트로 실행되기 때문에 /home/*을 열거하고 모든 실제 사용자 디렉터리에 카나리아를 배포합니다.

증거 보전 번들(포렌식 자동 수집)

랜섬웨어 엔트로피 버스트 탐지가 발동하거나, eBPF 런타임 위협 센서의 Critical 심각도 이벤트로 프로세스 단위 핀포인트 격리(SIGSTOP + 네트워크 차단)가 성립한 경우에 한해 roamswitch-incident-capture가 실행되어 증거 번들을 <상태 디렉터리>/incident-evidence/<타임스탬프>-<짧은 ID>/에 저장합니다. 수집하는 것은 기본 라우팅 인터페이스에서 tshark로 수행하는 경계 지정 30초 패킷 캡처(tshark가 없으면 건너뜀으로 기록)와, PID가 전달되었을 때의 대상 프로세스의 /proc/<pid> 스냅샷(status/cmdline/maps, 비밀 정보로 보이는 값을 [REDACTED]로 치환한 environ, 열려 있는 파일 디스크립터의 링크 대상 목록——내용은 수집하지 않습니다——, 조상 프로세스 계보)입니다. 개별 아티팩트 수집 실패는 치명적 오류로 처리하지 않고 그 사실만 기록한 뒤 나머지 수집을 계속합니다. eBPF 이벤트에서는 대상 프로세스의 PID를 전달합니다. 엔트로피 버스트 탐지에서는 쓰기를 수행한 프로세스를 특정했을 때 PID를 전달하며, 버스트 판정 시점까지 해당 프로세스가 종료되어 있었다면 PID 없이 실행하므로 패킷 캡처만 수집합니다. 핀포인트 격리가 불가능하거나 실패하여 호스트 전체 Air-Gap 격리로 대체되는 경우와, Emergency/Alert 계열 이벤트에 의한 호스트 전체 격리에서는 증거 수집이 실행되지 않습니다. 1.10.4까지의 배포 패키지에는 이 기능에 필요한 roamswitch-incident-capture가 포함되어 있지 않아, 패키지로 설치한 환경에서는 동작하지 않았습니다. 1.10.5부터 포함되었습니다.

수집 결과는 manifest.json에 정리되며, 번들 내 다른 모든 파일의 SHA-256 해시도 함께 기록됩니다(사후 변조 탐지를 위해). 번들은 자동으로 삭제되지 않습니다——roamswitch-incident-capture prune(기본 보존 기간 30일)을 명시적으로 실행했을 때만 오래된 번들이 삭제됩니다.

수집된 증거는 수집 시점에는 검증되지 않습니다. 이후 변조 여부를 확인하려면 roamswitch-incident-capture verify <번들 디렉터리> [--json]를 실행하십시오. 번들 내 현재 파일을 다시 해시하여 manifest.json에 기록된 값과 대조하고, 변경됨·누락·미기록(매니페스트에 없는 파일이 혼입됨) 중 하나로 각각 보고합니다.

알려진 한계 — 이 검증은 manifest.json의 SHA-256을 번들 밖에 있는 기록과 대조합니다. 기록은 두 곳입니다. 번들 밖의 해시 체인(evidence_anchor.log, 각 줄이 직전 줄에 이어지며 파일 시스템이 허용하면 추가 전용 속성을 붙임)과 시스템 로그(태그 roamswitch-evidence)입니다. 수집 후 manifest.json을 고쳐 쓰면 verify가 실패하고, 체인의 줄을 고치거나 지워도 체인이 끊어져 실패합니다. 다만 root 권한을 가진 공격자가 체인과 시스템 로그의 전송처를 모두 고쳐 쓸 수 있는 경우는 막지 못합니다. 시스템 로그를 다른 기기로 전송하는 운용을 권장합니다. 서명은 구현하지 않았습니다. 기록이 없는(이전 버전에서 수집한) 번들은 '기록 없음'으로 표시되며, 외부의 뒷받침이 없다는 뜻입니다.

허니토큰(자격 증명 미끼)

데몬 시작 시, 공격자와 자동화된 자격 증명 탈취 도구가 실제로 스캔하는 알려진 실제 경로——~/.aws/credentials, ~/.ssh/id_rsa, ~/.docker/config.json・~/.pypirc・~/.env.backup(호출한 사용자의 홈 디렉터리 하위)——에 ROAMSWITCH_HONEYTOKEN 마커가 포함된 가짜 자격 증명 파일을 멱등적으로 배치합니다. 해당 경로에 이미 실제 파일이 있으면 절대 덮어쓰지 않습니다(배치 전 항상 존재 여부를 확인). 허니토큰에 대한 실제 접근이 발생하면 관련된 추가 미끼를 연쇄적으로 배치하기도 합니다. 1.10.4까지의 배포 패키지에는 이 기능에 필요한 roamswitch-honeytokens이 포함되어 있지 않아, 패키지로 설치한 환경에서는 동작하지 않았습니다. 1.10.5부터 포함되었습니다. 1.11.1부터 RoamSwitch 자체가 실행하는 docker 명령은 빈 전용 디렉터리를 DOCKER_CONFIG로 지정해 시작하므로 미끼 ~/.docker/config.json에는 반응하지 않습니다. 다른 프로그램이 읽으면 지금까지처럼 감지합니다.

백그라운드 감시 프로세스가 알림 전용 fanotify(각 디코이에 대한 FAN_OPEN/FAN_ACCESS를 파일 단위로 감시)로 각 디코이에 대한 읽기·열기를 감시합니다. fanotify를 쓸 수 없는 환경(root가 아닌 실행 등)에서는 inotify로 전환합니다. 정상적인 프로세스가 이 파일들을 읽을 이유가 원래 없으므로, 접근은 침입자가 침해 이후 정찰(MITRE ATT&CK T1552)을 수행하고 있다는 강력한 신호로 간주하며, 접근한 프로세스를 특정하려 시도한 뒤 심각도 Critical로 인시던트 타임라인에 기록합니다(데스크톱 팝업 알림은 표시하지 않습니다. 이벤트 전달을 켜 두었다면 전달 대상이 됩니다). 다만 RoamSwitch 자신의 실행 파일에 의한 접근과, SSH 디코이에 대한 OpenSSH 클라이언트 계열(ssh, scp, sftp, ssh-add, ssh-agent)의 접근은 오탐을 피하기 위해 기록하지 않습니다. 실행 파일을 특정할 수 없는 접근은 기록합니다. 기록 전용이며, 접근한 프로세스의 동결이나 차단 등 추가 조치는 하지 않습니다.

알려진 한계 — ~/.ssh/id_rsa 디코이의 본문은 읽을 수 있는 텍스트를 포함하며, 정상적인 OpenSSH 개인 키의 base64 본문으로서는 유효하지 않은 형식 그대로입니다. 다만 대부분의 시크릿 스캐너는 -----BEGIN ... PRIVATE KEY----- 헤더의 정규식 일치만으로 플래그를 지정하므로, 실제 운영 환경에서의 탐지율에 미치는 영향은 제한적이라고 판단합니다. 또한 접근한 프로세스의 식별은 보통 fanotify 이벤트가 가진 PID로 수행합니다. fanotify를 쓸 수 없어 inotify로 전환한 경우에 한해 fuser를 호출하므로, 읽은 직후 즉시 종료되는 매우 짧은 수명의 프로세스는 PID·프로세스 이름을 확보하지 못할 수 있습니다. 이 경우 알 수 없음으로 처리할 뿐이며, 접근 자체의 탐지(이벤트 발생·기록)에는 영향을 주지 않습니다.

fanotify 모니터링 · 격리 보관소 · ClamAV

  • 데몬은 FAN_CLASS_CONTENT(허가 모드. 불가하면 FAN_CLASS_NOTIF)로 /home·/tmp·/var/tmp·/dev/shm을 파일 시스템 단위 마크로 감시하며(1.10.18 이후. 마운트 단위 마크는 파일 시스템 단위가 받아들여지지 않을 때의 대체 수단입니다), 쓰기 완료 알림은 / 전체에도 파일 시스템 단위로 설정합니다. 1.10.4부터 허가 이벤트는 실행(FAN_OPEN_EXEC_PERM)에만 적용하고 open은 알림으로 처리합니다(fanotify_open_perm으로 이전 동작으로 되돌릴 수 있습니다). 이미 정상으로 판정된 실행 파일은 60초간 캐시하며, 데몬 자신의 자식 프로세스는 감시에서 제외합니다.
  • 알림 우선·확인 전 격리 없음(fanotify 가드) fanotify 가드는 YARA/ClamAV의 단순한 적중만으로는 확인 없는 격리나 FAN_DENY를 하지 않습니다. 실제 시그니처 탐지는 /run/roamswitch/approvals.json에 모달 요청(격리/허용/나중에)을 쌓고, 사용자가 확인할 때까지 Vault에는 들어가지 않습니다. 실제 execve만 pre_exec_blocking_enabled가 켜져 있을 때 FAN_DENY합니다. 승인 타임아웃은 페일오픈입니다. 다운로드 감시(기본값은 ~/Downloads와 ~/Desktop. watched_folders로 추가)는 별도 경로로, YARA 또는 ClamAV가(EICAR를 제외하고) 탐지한 파일을 확인 없이 그 자리에서 격리 Vault로 옮기고 알립니다.
  • EICAR과 같은 테스트 서명은 알림 기록에만 남깁니다 — 업계 표준 테스트 문자열은 알림을 표시하지 않고 알림 기록에만 남기며, fanotify 가드와 다운로드 가드 모두에서 격리되거나 차단되지 않습니다.
  • 사용자가 구성한 scan_exclusions 경로 목록(절대 경로, 재귀적으로 적용됨, 레거시 clamav_exclusions 포함)은 YARA 및 ClamAV 스캐너 모두에서 적용됩니다. 프롬프트의 "허용"과 격리 탭의 "복원"이 모두 여기에 추가되므로 동일한 파일에 플래그가 다시 지정되지 않습니다.
  • 탐지된 실제 악성코드(fanotify 가드에서는 사용자가 확인한 것)는 root 전용 /var/lib/roamswitch/quarantine/로 옮깁니다(이전에는 각 사용자의 ~/.local/share/roamswitch/quarantine/였으며, 이전 저장 위치에서는 시작 시 자동으로 이관합니다). Vault 디렉터리는 root 소유의 0700, 검체는 root 소유의 0400(읽기 전용·실행 불가)입니다. 사용자는 데몬의 IPC를 통해서만 조작할 수 있으며, 자신이 격리한 항목만 목록 조회·복원·삭제할 수 있습니다. 복원은 호출한 사용자의 권한으로 강등된 자식 프로세스가 0644 파일로 기록합니다(기존 파일은 덮어쓰지 않습니다).
  • 허가 이벤트 루프는 설정 파일을 읽지 않습니다——설정 읽기는 전용 백그라운드 스레드가 Mutex<GuardConfig> 스냅샷으로 수행하며, 설정에 대한 핫 패스 처리는 잠금 + 복사뿐입니다(실행하려는 파일의 앞부분을 읽어 판정하는 처리는 별개입니다). FAN_OPEN_EXEC_PERM을 지연시켜 기기를 멈추게 하지 않기 위한 설계상의 불변 조건입니다.
  • ClamAV가 있으면(clamav_enabled, 기본값으로 활성) 다운로드 감시 대상 폴더(기본값은 ~/Downloads와 ~/Desktop)에 나타난 새 파일을 내장 YARA에 이어 로컬 DB로 검사합니다. .exe 등 Windows용 실행 형식은 알리고, .sh·.deb·.elf 등 Linux 배포 형식은 이력에 기록만 하며, 위험한 AI 모델(.pkl·.pt)은 출처와 함께 알립니다. ClamAV는 선택적 종속성이며, 설치되어 있지 않아도 다른 방어는 동작합니다. 설치되어 있으면 일일 업데이터(§11)가 freshclam을 실행하여 시그니처 DB를 최신 상태로 유지합니다.

프로세스 실행 레코더(로컬 전용)

netlink 프로세스 커넥터(proc connector)로 exec와 uid 변화를 기록하는 로컬 전용 기록 기능입니다. 기록하는 것은 실행 파일 경로, 명령줄 인수(최대 64개·1개 512자·합계 4096자로 잘라내며 제어 문자는 이스케이프), 부모 프로세스 계보, uid, 작업 디렉터리, 컨테이너 ID, 실행 파일의 SHA-256 등입니다. 기본값은 활성이지만 메모리가 1GiB 미만인 머신에서는 기본값이 비활성입니다(exec_recorder_enabled 명시 설정이 우선). 자체 CPU 사용은 1코어의 3% 이내(exec_recorder_cpu_budget_pct)로 억제하며 nice 10으로 동작합니다. 프로세스 커넥터를 열 수 없으면 저빈도 /proc 스캔으로 축소됩니다(수명이 짧은 프로세스는 놓칩니다).

  • 저장 — /var/lib/roamswitch/exec_log에 8MiB 단위 세그먼트로 나눈 JSON Lines(0600)로 저장합니다. 기본 보존은 200MB / 14일입니다(exec_recorder_retention_mb / exec_recorder_retention_days). 해시 체인을 붙여 변조를 탐지할 수 있지만 방지는 할 수 없습니다(root를 탈취한 공격자는 체인 전체를 다시 쓸 수 있음). 검색은 roamswitch events search|tree|export|verify이며, AI 클라이언트용 읽기 전용 MCP 도구는 search_exec_events / get_process_tree입니다.
  • 알림 전용 상관 규칙(EXR-001~010) — 웹 서비스에서의 셸 기동, /tmp·메모리상 파일(memfd)·삭제된 파일에서의 실행, 다운로드 직후 실행, base64/eval 원라이너, setuid 경로가 없는 uid 0 획득, cron/systemd 설정 직후에 놓인 바이너리 실행, /etc/ld.so.preload 변경 후 실행, 로컬 스냅샷(btrfs, LVM)이 snapper나 timeshift 같은 알려진 관리 도구 외의 주체에 의해 삭제된 것(EXR-008, 1.10.11), 같은 실행 주체가 30초 이내에 8종류 이상의 공격 도구를 which 등으로 찾은 것(EXR-009, 1.11.0), python이나 node 같은 스크립트 처리계가 2분 이내에 3종류 이상의 공격 도구를 실행한 것(EXR-010, 1.11.0. sshd·login·터미널 아래에서 사람이 입력한 경우는 대상 외)의 10종이며, 각 규칙에 MITRE ATT&CK 기법 ID를 붙입니다. 규칙은 알리기만 하며 단독으로는 격리하지 않습니다. 운영자가 exec_rules_isolation_optin(기본값 false)을 명시적으로 켠 경우에 한해 높은 심각도의 규칙이 Air-Gap의 높은 확신도 트리거로 취급됩니다(§4).
  • 단말 밖으로 보내지 않음 — 기록의 원시 로그는 전달되지 않습니다. 이벤트 전달(§9.4)을 켠 경우에 한해 규칙 알림(심각도·규칙 ID·프로세스 이름·요약)과 세그먼트 봉인 해시만이 운영자가 설정한 목적지로 전송됩니다.

자격 증명 파일 접근 감시(기본값 ON, 알림 전용)

기본적으로 켜져 있습니다(credential_access_watch_enabled가 true. false로 하면 멈추며, 멀웨어 가드 화면의 스위치로도 전환할 수 있습니다). SSH 개인 키(~/.ssh/id_*), ~/.aws/credentials, ~/.config/gcloud/ 아래의 자격 증명 파일(credentials.db 등 4종), 주요 브라우저의 자격 증명/쿠키 저장소를 예상 밖의 프로그램이 열면 알립니다(알림 전용). fanotify의 알림 클래스 FAN_OPEN을 소수의 파일에 개별로 설정할 뿐이며, 차단·동결·거부는 전혀 하지 않고 커널은 응답을 기다리지 않습니다. 연 프로그램의 실행 파일 이름을 종류별 허용 목록(ssh / git / gpg-agent, 브라우저, aws / terraform / kubectl 등)과 대조하고, 같은 (프로그램, 파일)의 재알림은 10분간 억제합니다. 백업 도구(restic, borg 등)와 인덱서(tracker, baloo 등)는 처음부터 허용되어 있습니다. 그 밖에 알림을 원하지 않는 프로그램은 config.json의 credential_access_watch_allow에 추가합니다. AWS 자격 증명에 대해서는 boto3 스크립트나 Ansible처럼 python/python3/ansible로 동작하는 도구도 허용합니다(gcloud에는 python/python3를 허용합니다). 다만 python -c 같은 원라이너는 알립니다. 이 허용의 대가로, python 스크립트 파일로 동작하는 자격 증명 탈취 프로그램도 알림이 나오지 않습니다. node나 ruby 같은 다른 인터프리터, grep이나 rclone 같은 범용 검색·동기화 도구는 같은 형태로 자격 증명을 훔치는 프로그램이 동작할 수 있으므로 허용 목록에 넣지 않았고, 따라서 알림이 나올 수 있습니다. 다만 cat이나 head 같은 아주 짧은 읽기로, RoamSwitch가 실행 파일을 특정하기 전에 프로그램이 종료된 경우에는 알림이 나오지 않을 수 있습니다(어느 정도 계속 동작하는 프로그램의 읽기는 알립니다). 이 한계를 피하기 위해 허가 클래스 fanotify를 사용하지는 않습니다. 시스템이 멈출 우려가 있기 때문입니다. 설정은 약 30초 이내에 반영되며 재시작은 필요 없습니다.

파일 변조 알림에 덧붙이는 침입 경로

클라이언트 버전의 중요 파일 변조 감시(FIM)와 의존성 락파일 변조 감시의 알림에도, 변경한 프로세스(계정·실행 파일·명령)와 그 프로세스가 속한 SSH 로그인(접속 출처·시각·인증 방식)을 덧붙입니다(config.json의 fim_intrusion_context_enabled, 기본 true, 외부로는 아무것도 보내지 않습니다). 인증 정보 파일 접근 감시의 알림에는 그 프로세스가 SSH 로그인에 속할 때만 한 줄을 더합니다. 동작 방식, 신뢰하는 값, 세 가지 답(찾음/SSH 로그인은 찾지 못했습니다/로그인 경로를 확인하지 못했습니다)의 구분은 서버 버전과 같습니다. 확인하지 못한 것을 로그인이 아니라고 쓰지는 않습니다. 기록은 /var/lib/roamswitch/fim_intrusions.json과 MCP verify_fim의 recentIntrusionContext에 남습니다.

커널 로컬 권한 확대(LPE) 선점 및 강화

공격자나 맬웨어가 권한이 없는 사용자의 기반을 확보하면 심층 방어 기능을 통해 Linux 커널 취약점을 악용하여 루트 권한을 얻는 것을 방지합니다.

  • 비특권 User Namespace의 취급(Frag Gap CVE-2026-53362 대응) — 현대 Linux 커널 권한 상승 취약점의 상당수는 비특권 사용자가 unshare(CLONE_NEWUSER)를 호출하여 만든 사용자 네임스페이스 안에서 초기화되지 않은 구조체나 오래된 네트워크 서브시스템에 부정하게 접근하는 데서 비롯됩니다. 진단은 user.max_user_namespaces와 kernel.unprivileged_userns_clone의 상태를 확인하여 표시하지만, 브라우저, Flatpak, Electron, 루트리스 컨테이너 등이 동작하지 않게 될 수 있으므로 Client 버전에서는 비활성화를 권장하지 않으며 원클릭 강화에도 포함하지 않습니다(user.max_user_namespaces = 0으로 되어 있는 경우에는 표준값으로 되돌리도록 안내합니다). 비활성화는 사용자 네임스페이스 생성이 필요한 공격 경로만 막는 임시 조치이며 그 밖의 경로에는 효과가 없습니다. 신뢰할 수 없는 네트워크에 있는 동안만 kernel.unprivileged_userns_clone = 0으로 하는 동작은 guard.yaml의 adaptive_security.untrusted_network_actions.auto_enable_userns_restriction으로 명시적으로 켰을 때만 적용됩니다(기본값은 꺼짐입니다. 이 설정 항목이 없는 커널에서는 아무것도 하지 않습니다). Server Edition은 기본적으로 영구 적용합니다(harden_userns_enabled). 근본적인 해결책은 패치된 커널로 업데이트하고 재부팅하는 것입니다.
  • Yama LSM 메모리 보호 — kernel.yama.ptrace_scope = 1(또는 2)을 시행하면 승인되지 않은 프로세스가 ptrace을 통해 연결되거나 /proc/$pid/mem을 통해 메모리를 읽는 것을 방지하여 브라우저 세션과 GPG/SSH 개인 키를 보호합니다.
  • 코어 덤프 억제 — fs.suid_dumpable = 0을 설정하면 민감한 프로세스가 일반 텍스트 메모리 덤프를 디스크에 기록하지 못해 충돌이 발생하는 것을 방지할 수 있습니다.
  • /tmp & /dev/shm noexec Mount Defense — 페이로드 삭제 및 실행 공격을 방지하기 위해 RoamSwitch는 신뢰할 수 없는 네트워크에 연결할 때 noexec,nosuid,nodev을 사용하여 /tmp 및 /dev/shm을 다시 마운트합니다.

eBPF 런타임 위협 센서 및 자율 프로세스 동결(데스크톱 eBPF 가드)

RoamSwitch 클라이언트 데몬에는 커널 내 최신 eBPF 센서(Falco/Tetragon/BTF)와의 기본 통합이 포함되어 있습니다.

# eBPF 런타임 위협 탐지 및 자율 요격 흐름
[악성 프로세스 (LPE/악성코드)]
    │ 불법 시스템 콜 실행 (Frag Gap 악용 시도)
    ▼
[Linux 커널 (eBPF 프로브)]
    │ eBPF 링 버퍼 (2MB 경량 설계)
    ▼
[Falco (RoamSwitch Optimized)]
    │ 메모리 내 UNIX 소켓 직접 통신 (/var/run/roamswitch/events.sock)
    ▼
[roamswitch-daemon (root)]
    ├─► ① 공격 프로세스의 밀리초 단위 동결: kill(pid, SIGSTOP) [상태: Ts]
    ├─► ② 네트워크 차단: Critical은 해당 프로세스의 사용자만 (roamswitch_isolate), Emergency/Alert는 nftables input/output drop [Air-Gap 전개]
    └─► ③ 데스크톱 긴급 알림: GTK3 모달 & libnotify 알림
  • 직접 UNIX 도메인 소켓 IPC(/var/run/roamswitch/events.sock) — Falco 또는 기타 eBPF 센서의 경고는 디스크 로그에 기록하지 않고 메모리 내 UNIX 소켓을 통해 직접 수신되므로 디스크 I/O 대기 시간이 제거되고 1초 미만의 응답 전달이 가능합니다.
  • 밀리초 단위의 자율 대응(프로세스 동결 및 네트워크 차단) — 수신한 이벤트의 우선순위가 Critical(커널 LPE, 권한 상승, 랜섬웨어 암호화 등)이면 데몬은 해당 PID에 SIGSTOP을 보내 프로세스를 완전히 정지(상태: Ts)시키고, 그 프로세스의 실제 사용자(컨테이너의 경우 해당 cgroup)가 내보내는 통신만 roamswitch_isolate 테이블에서 차단하는 “핀포인트 격리”를 수행하며, 데스크톱 알림을 발송합니다. PID를 특정할 수 없는 경우, 보호 대상 프로세스라서 동결을 거부한 경우, root 프로세스 등에서 격리에 실패한 경우, 그리고 우선순위가 Emergency/Alert인 경우에는 inet roamswitch 테이블에 policy drop(Air-Gap)을 전개하고, PID를 알면 SIGSTOP도 보냅니다. Critical 미만의 이벤트는 차단하지 않고 이력에 기록합니다.
  • 데스크톱용 저부하 & 로그 비대화 방지 튜닝(RoamSwitch Optimized) 노트북과 워크스테이션의 배터리 소모·CPU 부하·디스크 고갈을 억제하기 위해, Falco를 도입하는 환경을 위한 최적화 프로필(99-roamswitch-optimized.yaml)과 logrotate 설정을 마련해 두었습니다. Client 버전의 패키지와 install.sh의 데스크톱 모드는 Falco를 도입하지 않으며 이 프로필도 배치하지 않습니다(Server Edition의 패키지와 install.sh --server가 배치합니다). Client에서 Falco를 사용하는 경우에는 사용자가 Falco를 직접 도입하고, 필요하면 이 프로필을 직접 배치합니다. 주요 설정은 다음과 같습니다.
    • 심각도 필터링(priority: warning) — 일상적인 알림/정보 이벤트의 95%를 필터링합니다.
    • Kernel Early Drop(drop_failed_exit: true) — 실패한 syscall 종료를 커널 드라이버에서 직접 삭제하여 컨텍스트 스위치와 CPU 로드를 절반으로 줄입니다.
    • 컴팩트 링 버퍼(cpus_for_each_buffer: 2) — 링 버퍼 크기를 2MB로 줄이고 상주 RAM을 30~50MB로 유지하며 CPU 사용량을 1% 미만으로 유지합니다.
    • 필수 로그 회전 용량(/etc/logrotate.d/roamswitch-falco) — 7세대 압축 회전으로 엄격한 50MB 한도를 적용합니다.
  • 독립형 강화 대 eBPF 확장의 하이브리드 복원력 — Falco가 없어도 커널 sysctl(Yama의 ptrace 제한·코어 덤프 억제·네트워크 보호. 프로필을 전환할 때마다 적용)·nftables 프로필·fanotify·카나리아 감시로 자율 방어합니다. Falco를 도입하면 실시간 eBPF 이벤트에 의한 제로데이 요격·프로세스 동결이 더해져 최고 강도의 방어를 실현합니다.

§6

USB 이벤트가 모니터링됩니다. 키보드급(HID) 장치와 USB 대용량 저장소가 추적됩니다. 기본적으로 꺼짐(출하 isEnabled = false, macOS USBKeyboardGuard / USBStorageGuard과 일치). 첫 실행 마법사 또는 USB 탭에서 선택하세요. 활성화하면 확인 대화 상자가 표시됩니다.

악성 USB 키보드(키스트로크 주입)

  • 커널 수준의 물리적 포트 승인 취소 없음(중요). 이전 구현(1.0.6 이하)에서는 등록되지 않은 키보드에 대해 0을 /sys/bus/usb/devices/*/authorized에 썼고 물리적으로 연결을 끊었습니다. 결합된 키보드+마우스 수신기(Raspberry Pi 또는 미니 PC에서 일반적)에서는 마우스 연결도 끊어지고 내장 입력이 없는 시스템이 잠깁니다. 이제 RoamSwitch는 HID 인터페이스(bInterfaceClass 03)를 노출하는 모든 장치에 authorized=0 쓰기를 강력히 거부하고, 데몬은 시작 시 이전 버전이 차단한 시스템을 자가 복구하기 위해 모든 authorized==0 HID 장치를 다시 인증합니다.
  • 키 입력은 소프트웨어에서 삭제됩니다. 등록되지 않은 키보드를 감지하면 해당 장치의 /dev/input/eventN에서 독점적인 evdev EVIOCGRAB이 사용됩니다(VID:PID와 일치하는 /proc/bus/input/devices에 kbd 처리기가 있는 노드만 해당). 마우스 노드는 그대로 유지됩니다. 이는 macOS CGEventTap에 해당하는 Linux입니다. 장치에 전원이 계속 공급됩니다.
  • 안전 — 입력할 다른 키보드(내장 키보드 또는 승인되고 연결된 USB 키보드)가 있는 경우에만 잡기가 수행되므로 새 키보드가 하나 있는 기계는 절대 잠기지 않습니다.
  • 승인 대화상자 — 그랩과 동시에 /run/roamswitch/approvals.json에 승인 요청을 쌓고 GUI가 모달(허용/거부)을 표시합니다. 허용하면 화이트리스트에 등록하고 그랩을 해제합니다. 승인되지 않은 채 3분이 지나도 기본적으로 키보드를 해제하지 않습니다(1.10.4부터. 이전 버전은 3분 후 페일오픈이었으며, usb_keyboard_timeout_release를 켜면 이전 동작으로 되돌릴 수 있습니다).

악성 USB 대용량 저장 장치

등록되지 않은 USB 대용량 저장 장치(HID가 없는 것)가 삽입되고 가드가 활성화되어 있으면 authorized=0으로 보류하고(HID가 없으므로 안전) 승인 대화 상자를 표시합니다. 신뢰할 수 있는 네트워크인지 여부는 따지지 않습니다. 마운트된 드라이브나 스왑으로 사용 중인 드라이브(사용 중인 외장 디스크, 라이브 USB 등)는 갑작스러운 분리와 같아져 손상될 우려가 있으므로 보류하지 않고 로그에만 남깁니다. 허용하면 재승인 + 화이트리스트 등록, 거부하면 보류를 유지합니다. macOS 버전은 1.7.4에서 이 “읽기 전용 보류 + 승인 프롬프트” 방식으로 통일했습니다.

식별 및 기득권 부여

HID 판정은 bInterfaceClass == "03"과 /proc/bus/input/devices의 Handlers= 줄로 합니다. 콤보 수신기(하나의 VID:PID에 kbd와 mouse 섹션이 모두 있는 것)는 VID:PID로 병합합니다. 기존 키보드를 허용 목록에 등록하는 grandfathering은 데몬이 아니라 GUI가 수행합니다. 최초 설정 마법사에서 활성화했을 때(그리고 가드가 활성화되어 있고 최초 설정이 끝났는데도 연결된 키보드가 허용 목록에 하나도 없는 기존 환경으로의 최초 마이그레이션 시), 그 시점에 연결되어 있는 키보드를 허용 목록에 등록합니다. USB 탭에서 나중에 활성화해도 등록되지 않습니다. 데몬 시작 시 연결되어 있어도 등록되지 않은 키보드는 시작 전에 교체되었을 가능성이 있으므로 미등록으로 취급하며, 입력할 수 있는 다른 키보드가 있으면 grab 대상이 됩니다.

§7

roamswitch-core의 LinuxHealthChecker가 27개 항목을 평가하여 0~100점의 점수와 S/A/B/C 등급(100점이 S, 85~99점이 A, 70~84점이 B, 그 미만이 C), 항목별 개선 조언을 생성합니다. GTK 대시보드·CLI(roamswitch status)·MCP(get_security_report) 어디에서든 27개 항목 모두의 제목·세부 정보·권장 사항이 사용자의 언어로 표시됩니다(10개 언어 지원).

평가 항목
카테고리예시 항목
디스크 / 부팅LUKS / dm-crypt 암호화, UEFI 보안 부팅
접근 통제LSM(AppArmor/SELinux), SSH/sudo 구성 감사
커널 강화sysctl 강화, 비특권 User Namespace 상태 확인(Frag Gap 대응. Client 버전에서는 비활성화를 권장하지 않음), Yama의 ptrace 제한, 코어 덤프 제어, /tmp 및 /dev/shm의 noexec
업데이트자동 보안 업데이트 구성
네트워크ARP 스푸핑 모니터링, 게이트웨이 ARP/NDP 잠금 상태, 노출된 포트 감사, Wi-Fi 암호화 강도
악성코드ClamAV(EICAR 실동작 탐지 테스트 포함) / fanotify(가드의 실제 가동 여부와 큐 오버플로로 인한 이벤트 누락도 확인) / 엔트로피 모니터링 / noexec / 보조 프로그램(허니토큰과 증거 수집)의 설치 확인
브라우저Firefox/Chrome/Chromium 세이프 브라우징 설정
DNS / USB위협 방지 DNS, USB 버스의 제로 트러스트 상태
컨테이너 및 취약점Docker 소켓 마운트 노출, 특권 컨테이너(--privileged) 감사, 컨테이너 런타임 격리 수준(runc / gVisor / Kata), 실행 중인 커널의 알려진 CVE 노출

모든 검사는 로컬 파일 및 명령 출력(getenforce, mokutil, resolvectl, ss, …) 읽기를 기반으로 합니다. 어떤 종류의 외부 쿼리도 없습니다.

신뢰 인식 등급. RoamSwitch가 신뢰할 수 있는 (홈) 네트워크에 고의적으로 not 적용하지 않도록 강화 — 게이트웨이 MAC를 영구로 고정(라우터 재부팅 시 LAN을 블랙홀하게 함), /tmp 및 /dev/shm noexec 다시 마운트(패키지 빌드 및 일부 설치 프로그램 중단) 및 기본 범위에서 위협 보호 DNS — 빨간색 "강화되지 않음"이 아닌 설명과 함께 녹색으로 표시되며 신뢰할 수 없는 네트워크에서는 자동으로 적용된다는 점을 참고합니다. 런타임 정보는 데몬이 매 주기마다 작성한 /run/roamswitch/state.json(active_level / network_trusted / fanotify_ready)에서 나옵니다. fanotify 항목은 단순히 커널이 지원하는 것이 아니라 가드가 실제로 표시되어 실행 중인지(/run/roamswitch/fanotify.ready) 확인합니다. 노출된 포트 항목은 데몬의 라이브 프로필을 읽습니다(MCP 하드 코드가 사라졌습니다).

항목별 강화. 자동 수정(커널 sysctl/Yama/코어 덤프, /tmp noexec, USB 제로 트러스트, 게이트웨이 ARP 고정, 위협 방지 DNS, fanotify 가드가 다운되었을 때 데몬 다시 시작)이 있는 실패한 항목에는 GTK 대시보드에 "🔧 강화" 버튼이 있습니다. 데몬은 루트로 실행되므로 데몬을 다시 시작하는 것 외에는 pkexec이 필요하지 않습니다.

NIST CSF 2.0 / CIS Controls v8 매핑

27개 항목 각각에 NIST CSF 2.0의 기능/범주 식별자(예: PR.DS-01, DE.CM-09)를 부여하고, 확실하게 대응시킬 수 있는 항목에 한해 CIS Controls v8 통제 번호(예: 3.11, 4.1)도 함께 표기합니다. 대응이 모호한 항목에는 억지로 부여하지 않고 미매핑 상태로 둡니다. MCP(get_security_report)와 CLI의 JSON 출력(roamswitch status --json)에는 이 매핑(nistCsf/cisControl)이 포함됩니다. GUI와 Client 버전 CLI의 일반 표시에는 나오지 않습니다(Server 버전의 roamswitch status --server 표시에는 나옵니다). 이 대응은 컴플라이언스 대응이 필요한 운영자·감사 담당자가 RoamSwitch의 각 점검 항목을 기존 프레임워크에 기계적으로 대응시킬 수 있게 하기 위한 것입니다.

§8

roamswitch-mcp는 stdio로 JSON-RPC를 주고받는 MCP 서버로, Claude Desktop, Cursor, Antigravity 같은 AI 클라이언트가 실행합니다. 모든 도구는 읽기 전용이며 대부분 로컬에서 완결됩니다. 외부 호스트에 연결할 수 있는 것은 기본값이 비활성인 run_npm_audit_signatures뿐입니다. 진단 평가(get_security_report와 verify_security_findings)에서는 LAN에 아무것도 보내지 않으며, 게이트웨이 확인도 ARP 캐시 등 로컬 정보를 참조할 뿐입니다. ping이 나가는 것은 게이트웨이의 MAC을 다시 알아내는 다른 처리(데몬의 ARP 감시, 긴급 복구, Sensor 페어링, get_guard_status 등)일 때입니다.

주요 공개 도구(전체 목록: MCP 연동 설정)
도구목적
get_security_report27개 항목 평가, 점수, 권장사항
verify_security_findingsget_security_report의 항목(checkId)을 현재 상태로 다시 평가하여, 항목마다 stillPresent(아직 해당), resolved(해소 또는 설계상 해당 없음), inconclusive(판정 불가) 중 하나와 언어에 의존하지 않는 사유 코드를 반환합니다. 수정 후 실제로 해결되었는지 확인하기 위한 도구이며, 권한 부족, 특권 헬퍼 미연결, Docker에 접근할 수 없는 등의 이유로 측정하지 못한 항목은 안전하다고 보고하지 않고 inconclusive로 처리합니다. checkIds를 생략하면 모든 항목이 대상입니다(지정은 Mac 50개, Linux 64개까지이며, Linux는 isServer도 지정할 수 있습니다). Mac의 host_firewall, network_stealth_mode, gateway_arp_lock, malware_scanning, dns_threat_guard, usb_zero_trust는 RoamSwitch 앱 내 설정값으로 합격 여부가 정해지므로, 이 항목들은 앱 내 설정을 반영한 것이며 OS의 실제 상태를 다시 측정한 것이 아닙니다. 읽기 전용이며 외부 호스트에는 연결하지 않고 복구나 설정 변경도 하지 않습니다. Linux에서는 LAN에도 아무것도 보내지 않고 로컬 캐시에서 게이트웨이를 읽기만 합니다. Mac에서는 게이트웨이의 MAC 주소를 알아내기 위해 LAN 안의 게이트웨이에 ICMP ping을 보통 1회(MAC을 알아내지 못하면 몇 번까지) 보낼 수 있습니다.
audit_mcp_configsAI 클라이언트의 MCP 설정 파일(Claude Desktop, Claude Code, Cursor, Windsurf, VS Code, Cline, Gemini CLI와 등록된 프로젝트 폴더의 .mcp.json 등)을 읽어 위험한 설정을 지적합니다. 호출마다 확인 없이 자동 승인되는 도구(auto_approve), loopback이 아닌 호스트의 평문 http:// 서버(url 필드뿐 아니라 --server http://… 처럼 시작 인수로 전달한 접속 대상도 포함. remote_plain_http), 셸이나 인터프리터 한 줄 명령으로의 실행(shell_wrapper), 버전을 고정하지 않은 패키지나 이미지를 가져오는 npx / uvx / docker run 등의 실행(unpinned_package_launcher), --privileged 또는 Docker 소켓·루트 파일 시스템을 마운트한 docker run(container_host_access), 시작할 때 추가 코드를 불러오게 하는 환경 변수(LD_PRELOAD, NODE_OPTIONS의 --require 등. 이름은 대소문자를 구분하지 않고 값은 반환하지 않음; code_loading_env), 인증 없는 공격 도구 백엔드인 HexStrike AI를 가리키는 설정(hexstrike), 그룹이나 다른 사용자가 쓸 수 있는 설정 파일(world_writable_config), Claude Code의 enableAllProjectMcpServers(auto_trust_project_servers), 웹 페이지·메일·채팅·Issue처럼 다른 사람이 쓴 글을 읽는 서버와 명령 실행·파일 변경이 가능한 서버가 한 설정 파일에 함께 있는 구성(간접 프롬프트 인젝션의 전제가 되는 조합. 잘 알려진 패키지 이름으로 판정하며 서버 도구의 내용은 보지 않음; injection_exposure)을 검출합니다. 인수가 없고 읽기 전용이며 네트워크에는 아무것도 보내지 않습니다. 환경 변수나 헤더 값, 인수, 비밀 정보는 반환하지 않고 서버 이름, 명령 이름, URL 호스트, (레지스트리 이름인 경우) 패키지·이미지 이름만 반환합니다. 해석하지 못한 파일은 unreadableFiles에 나열되며 감사하지 않았습니다.
audit_agent_setupAI 에이전트에게 읽히는 지시 파일과 스킬(~/.claude의 CLAUDE.md·commands·agents·skills, ~/.codex, ~/.cursor/rules, ~/.gemini, ~/.agents, ~/.openclaw, 등록된 프로젝트 폴더의 CLAUDE.md·AGENTS.md·.cursorrules 등)과 OpenClaw의 openclaw.json을 읽어 위험한 내용과 설정을 지적합니다. 지시·스킬 파일에서는 보이지 않는 Unicode 태그 문자에 숨은 문장(hidden_tag_characters), 너비가 없는 문자나 글자 방향을 바꾸는 제어 문자(invisible_characters), 이전 지시를 무시하거나 사용자에게 숨기라는 표현(override_instruction), curl … | sh처럼 내려받아 그대로 실행하는 표현(remote_script_exec), 인코딩된 문자열을 풀어 실행하는 표현(encoded_payload_exec), 키와 자격 증명 저장소 참조(credential_access) 및 그 근처의 외부 전송·수신용 서비스 주소(credential_exfil_shape, exfil_endpoint), 재부팅 후에도 남는 장치 설정(persistence_instruction), 스킬에 들어 있는 컴파일된 코드(skill_precompiled_code), 다른 사용자가 쓸 수 있는 파일(agent_file_writable)을 검출합니다. HTML 주석 안의 내용은 렌더링하면 보이지 않으므로 더 높게 평가합니다. OpenClaw에서는 인증 꺼짐(openclaw_auth_disabled), 네트워크에서 닿는 대기 방식(openclaw_wide_bind), 24자 미만의 토큰(openclaw_weak_token), 모든 오리진 허용(openclaw_origin_wildcard), 켜진 dangerously… 스위치(openclaw_dangerous_flag), 모두를 신뢰하는 프록시 설정(openclaw_broad_trusted_proxy, openclaw_proxy_trust_loosened), 누구에게나 열린 DM·그룹 정책(openclaw_open_dm), mDNS의 full(openclaw_mdns_full), 숨겨진 자체 진단 결과(openclaw_audit_suppressed), 느슨한 권한(openclaw_loose_permissions)을 검출합니다. 모든 규칙은 결정적이며, 일치는 "사람이 읽어 볼 곳"일 뿐 악의를 단정하는 것이 아닙니다. 인수가 없고 읽기 전용이며 네트워크에는 아무것도 보내지 않습니다. 파일 본문이나 비밀 정보는 반환하지 않고(토큰은 길이만), 규칙 이름, 줄 번호, URL 호스트, 설정 키 경로만 반환합니다. 읽지 못한 파일은 unreadableFiles에 나열되며 감사하지 않았습니다.
get_exposed_ports노출된 포트 및 해당 방화벽 차단 상태
get_guard_status현재 보호 수준 및 방어 모듈 상태
audit_url_safety경험적 URL 안전성 분석(대상을 가져오지 않음)
감사_비밀텍스트 내 API 키/비밀 감지(텍스트를 전송하지 않음)
audit_security_logs로컬 저널/인증 로그 집계
get_quarantine_status / get_canary_status검역과 카나리아 주
get_app_help내장된 지식 베이스 검색
run_active_vuln_scan127.0.0.1 한정, 비파괴 방식의 실증형 취약점 검증입니다. 다른 호스트에는 연결하지 않지만 네트워크 요청을 보내는 도구이므로 기본값은 비활성이며 설정에서 옵트인해야 합니다.
run_package_cve_scan설치된 패키지(Mac: Homebrew / Linux: dpkg・dnf・zypper・pacman)를 로컬 CVE 맵과 대조합니다. 네트워크 통신은 전혀 하지 않습니다. Linux에서는 패키지 목록을 가져오지 못했거나 시간 초과가 발생하면 '해당 없음'으로 처리하지 않고 complete가 false가 되며 incompleteReason에 사유가 담깁니다(끝내지 못한 결과는 이상 없음이 아닙니다). Ubuntu(20.04·22.04·24.04·26.04)에서는 Ubuntu 전용 데이터로 소스 패키지 이름과 소스 버전을 사용해, Ubuntu가 수정 버전을 이미 공개한 CVSS 7.0 이상의 CVE만 대조하므로 '해당 없음'이 알려진 CVE가 없다는 뜻은 아닙니다. 결과에는 ecosystem과 release가 추가되고 검출마다 binary(설치된 바이너리 패키지 이름)가 담기며, Ubuntu Pro로만 고칠 수 있는 수정에는 proOnly가 붙습니다. Ubuntu 파생판(Linux Mint, Pop!_OS 등)과 지원하지 않는 버전은 complete가 false가 되고 incompleteReason에 사유가 담기며 Debian 데이터로 바꾸지 않습니다.
run_package_cve_scan_languagesnpm / PyPI / crates.io / RubyGems / Packagist / Go / Maven 등의 의존성 락파일을 동일한 로컬 CVE 맵과 대조합니다. 네트워크 통신은 전혀 하지 않습니다.
get_vulnerability_scan_historyscan-vulns --confirm이 기록한 각 프로브에 대해, 마지막으로 실제 실행·확인된 이후 경과한 일수(pass_age_days)를 반환합니다.
verify_fim고정된 23개의 핵심 시스템 파일, 인증·cron·systemd·SSH 설정 등 12개 디렉터리 바로 아래의 모든 파일, RoamSwitch 자체 바이너리, 각 사용자의 authorized_keys를 다시 해시하여 기준선과 비교해 변조와 새 파일을 검증합니다. root로 실행하면 결과에 추가 필드 recentIntrusionContext도 포함됩니다. 최근 변경마다 변경한 프로세스(계정·실행 파일·명령)와 그 프로세스가 속한 SSH 로그인(접속 출처·로그인 시각·인증 방식), 또는 확인하지 못한 이유가 들어갑니다.
get_file_scan_guard_status파일 스캔 가드(ClamAV)의 설정과 그것이 사용하는 격리 Vault의 상태를 반환합니다(Server Edition용 도구입니다. 클라이언트 버전의 멀웨어 가드 설정이 아닙니다).
get_port_anomaly_incidents포트 이상 가드의 베이스라인 확보 상태, 현재 자동 차단 중인 포트, 최근 50건까지의 인시던트를 반환합니다. 현재 자동 차단 중인 포트는 타임스탬프가 없는 현재 상태의 스냅샷이며, 타임스탬프가 있는 인시던트 이력과는 다르다는 점을 응답 내 주석 필드로 명시하고 있습니다.
get_ebpf_incidentseBPF 런타임 가드의 현재 격리 상태와 발동 사유가 된 인시던트 이력을 반환합니다. Server Edition에서는 각 기록에 탐지부터 대응까지의 시간(밀리초. detect_to_receive_ms, receive_to_action_ms, detect_to_action_ms)이 더해지고, 결과에는 Falco 하트비트의 상태(heartbeat)도 포함됩니다.
get_ssh_guard_incidentsSSH 무차별 대입 가드의 상태와 기록을 반환합니다. 차단까지 하는지 탐지만 하는지(다른 방어 도구가 sshd를 지키고 있을 때 등), 지금 차단 중인 접속 출처와 남은 시간, 최근 기록(차단함, 탐지만 함, 실패를 거듭한 접속 출처에서 로그인에 성공함 중 하나. 최대 200건)을 포함합니다. 시도된 사용자 이름 자체는 Linux에서는 저장하지 않고(개수만), Mac에서는 무해화하여 최대 5개까지 반환합니다. Mac은 최근 SSH 로그인(접속 출처·사용자·인증 방식·키 지문)도 반환합니다. 읽기 전용이며 차단을 해제하는 MCP 도구는 없습니다. Linux에서는 차단 목록을 root만 읽을 수 있으므로 일반 사용자로 실행하면 건수만 반환됩니다. Linux는 Server Edition과, sshd가 동작 중인 Client Edition에서 쓸 수 있습니다. Mac은 Pro 전용입니다.
get_resource_guard_incidents리소스 고갈·프로세스 이상 탐지 가드의 이력(메모리 누수/크래시 루프)을 신뢰도 등급과 함께 반환합니다(이 가드는 Server Edition의 것입니다. 클라이언트 버전에서는 동작하지 않으므로 결과는 항상 비어 있습니다).
get_notification_historyRoamSwitch가 보낸 알림(보안 로그 감사 이상, ClickFix 탐지 등)의 기록을 최근 1주일간, 최신순으로 반환합니다.
get_incident_timeline각 가드의 탐지를 하나의 시계열로 통합해 반환합니다(프로세스 계보·MITRE ATT&CK 태그 포함, 실험적).

실증형 취약점 진단은 확인된 각 프로브에 대해 마지막으로 실제 실행·확인된 이후 경과한 일수(pass_age)도 기록하여, "기능이 존재한다"는 것과 "실제로 최근 확인되었다"는 것을 구분합니다. 예약 실행 기능은 아직 없으므로 이 값은 실제로 스캔을 실행한 빈도에만 의존합니다. 동일한 경과 일수는 CLI(roamswitch vuln-status)·GUI·MCP(get_vulnerability_scan_history) 어디에서든 확인할 수 있어, 운영자나 감사 담당자가 기능의 존재만으로 최근 점검을 추정하지 않고 실제 마지막 확인 시점을 파악할 수 있습니다.

MCP 서버는 스스로 네트워크를 열지 않습니다. 데몬의 Unix 소켓(/run/roamswitch/roamswitch.sock)에 직접 연결하는 것은 get_sensor_audit_results뿐이며, 다른 도구는 roamswitch-core의 로직을 직접 호출하거나 데몬이 기록한 상태 파일을 읽을 뿐입니다(roamswitchkit는 반대 방향으로, CLI 등이 roamswitch-mcp를 자식 프로세스로 실행하기 위한 SDK입니다). 예외는 기본값이 비활성인 두 도구입니다. run_active_vuln_scan은 127.0.0.1, 즉 호스트 자신에게 비파괴 프로브를 보냅니다. run_npm_audit_signatures는 npm 명령을 실행하며 npm이 npm 레지스트리에 조회합니다(MCP 바이너리 자체에는 HTTP / TLS 스택이 없습니다). 진단 평가(get_security_report와 verify_security_findings)에서는 LAN에 아무것도 보내지 않으며, 게이트웨이 확인도 ARP 캐시 등 로컬 정보를 참조할 뿐입니다. ping이 나가는 것은 게이트웨이의 MAC을 다시 알아내는 다른 처리(데몬의 ARP 감시, 긴급 복구, Sensor 페어링, get_guard_status 등)일 때입니다.

로컬 LLM 실전 훈련에서 발견된 오귀속 문제와 그 수정
2026년 9월, 이 MCP 서버를 사용해 로컬 LLM(Qwen 3.8 27B)에게 사고 조사를 맡기는 실전 훈련(fire drill)을 진행하던 중, 모델이 get_port_anomaly_incidents가 반환하는 데이터를 잘못 해석하여 무관한 과거의 포트 차단 기록을 현재 진행 중인 사고의 서사에 끌어들이는 사례를 발견했다. 원인은 타임스탬프가 없는 '현재 차단 중인 포트'의 스냅샷과, 타임스탬프가 있는 실제 사고 이력이 같은 응답 안에서 구분 없이 반환되고 있었기 때문이다. 수정 사항으로, 어느 쪽이 시계열 데이터이고 어느 쪽이 스냅샷인지를 명시하는 주석 필드를 추가했다. 수정 후 동일한 시나리오를 같은 모델로 다시 실행한 결과, 모델은 주석의 문구를 인용하며 과거의 포트를 현재 사고에 귀속시키는 것을 명시적으로 거부했다. 자동 회귀 테스트와 실제 LLM 재현 확인 양쪽에서 수정의 효과를 검증했다.

§9

RoamSwitch for Linux가 기본값에서 수행하는 외부 통신은 한 종류뿐입니다. 일일 업데이터가 서명된 매니페스트를 GET하는 것입니다(§11). 그 밖의 진단·탐지·감사는 모두 로컬에서 완결됩니다. 운영자나 사용자가 명시적으로 켜거나 조작했을 때만 발생하는 통신은 9.4에 나열합니다. RoamSwitch for Linux는 독점 프리웨어로 소스를 공개하지 않으므로, 아래 검사는 lafine이 수행한 것이며 결과는 9.2에, 부록 C의 절차(배포 패키지만으로 재현 가능)를 제시합니다.

9.1 감사방법

  1. 크레이트 격리 — HTTP 클라이언트/TLS 스택을 허용하는 것은 단 하나의 크레이트, 타이머로 구동되는 업데이터 바이너리를 빌드하는 roamswitch-updater뿐입니다. scripts/audit_no_network.sh(CI에서 게이트로 실행)는 roamswitch-core·roamswitch-daemon·roamswitch-app·roamswitch-mcp·roamswitchkit(및 CLI·Server 계열 크레이트) 중 하나가 reqwest / ureq / rustls / native-tls / openssl-sys 같은 HTTP 클라이언트/TLS 스택을 끌어오거나, roamswitch-updater와 9.4에 든 파일 이외의 .rs가 TcpStream / TcpListener / UdpSocket / to_socket_addrs / lookup_host를 사용하면 빌드를 실패시킵니다. 유일한 추가 허용은 eBPF 센서(Tetragon)에 대한 UNIX 도메인 소켓 gRPC를 위해 roamswitch-core에만 허용한 tonic / hyper이며, TLS와의 병용은 같은 스크립트가 금지합니다.
  2. 종속성 트리 검사 — 전체 Cargo.lock이 원격 측정/분석/충돌 보고 SDK(sentry, opentelemetry, …)에 대해 검색되었습니다.
  3. roamswitch-updater 검토 — 단일 요청 대상, 쿼리 문자열과 쿠키의 부재, 소프트웨어를 나타내는 헤더가 고정된 User-Agent뿐이라는 점 및 다운로드하는 모든 항목에 대한 Ed25519 확인.
  4. 하위 프로세스 열거 — 모든 Command::new(...) 호출 사이트가 검토되었습니다.

9.2 결과

감사 결과
감사항목결과
HTTP/TLS 스택roamswitch-updater에만 (ureq + rustls)에만 존재합니다. core / daemon / app / mcp / kit에는 없음
원격 측정/분석/충돌 보고 SDK존재하지 않음 어디에도 없음
TcpStream / TcpListener / UdpSocketroamswitch-updater 외에는 9.4에 나열한 옵트인 기능 파일 4개에만 존재
사용 중인 소켓 APIAF_UNIX(로컬 IPC) / AF_NETLINK(NFQUEUE verdict 채널·nftables·ip neigh 제어 — 송출 없음) / AF_INET은 roamswitch-updater와 9.4의 옵트인 기능에서만 사용(TLS/HTTP는 시스템의 openssl/curl 서브프로세스에 위임) / AF_PACKET은 ARP 재확인(§4)이 게이트웨이로 ARP who-has를 하나 보내는 경우에만(LAN 내). VPN 터널(§4)을 켜면 wg-quick(WireGuard 백엔드) 또는 tailscaled(Tailscale 백엔드) — 둘 다 데몬이 아닌 서브프로세스 — 가 사용자 자신의 엔드포인트/tailnet으로 송출
일일 업데이터약 24시간마다 GET https://lafine.net/updates/v1/manifest와 서명 manifest.sig의 2회(데이터 본체는 버전이 새로울 때만 추가로 가져옴). 쿼리 문자열 없음, 쿠키 없음, 고정된 User-Agent: RoamSwitch-Updater(소프트웨어를 나타내는 유일한 헤더), 기기별 식별자 없음. 모든 페이로드를 Ed25519로 검증. updates.enabled = false로 완전히 중지
VPN 터널(§4)기본적으로 꺼져 있습니다. 활성화되면 wg-quick은 Lafine과 관련되지 않은 사용자 자신의 .conf에 있는 엔드포인트 에 WireGuard 터널을 구축합니다. Rust 소켓 API가 아닌 getent ahosts(glibc NSS)를 통한 엔드포인트 호스트 이름 확인
라이선스 활성화오프라인 Ed25519 서명 확인. 활성화 서버와 통신하지 않음
결론

RoamSwitch for Linux가 기본값에서 시작하는 외부 통신은 한 종류뿐입니다. 하루 한 번의 수신 전용 서명 매니페스트 확인과, 새 버전이 있을 때의 서명된 탐지 데이터 다운로드입니다. 어느 쪽에도 사용자·단말에 관한 정보는 전혀 담겨 있지 않습니다. 운영자나 사용자가 명시적으로 켜거나 조작하지 않는 한, 제품의 다른 어떤 부분도 외부 서버에 접촉하지 않습니다(9.4). 근접 테이블 고정이나 ARP 감시를 위해 LAN 안으로 보내는 ping(9.3의 #2)은 대상이 루프백·사설·링크 로컬·ULA 주소로 한정되며, 공개 주소로는 보내지 않습니다.

9.3 외부 대면 작업의 전체 목록

직접·간접을 불문하고 로컬 머신 밖으로 패킷이 나갈 수 있는 동작 중 기본값에서 발생하는 것(아래 표. 운영자·사용자가 켜거나 조작했을 때만 발생하는 것은 9.4):

#작동목적지트리거전송되는 내용
1일일 업데이터 — GET /v1/manifest와 그 서명. 각 데이터는 버전이 새로울 때만(커널·OS 패키지·언어 CVE 맵은 약 30일에 1회 확인. Ubuntu CVE 맵이 아직 없는 Ubuntu 단말은 다음 일일 실행에서 받음)lafine.net/updates/v1(CDN)systemd 타이머. 부팅 15분 후에 시작되며 이후 약 24시간마다(최대 3시간의 무작위 지연 있음). updates.enabled = false 또는 roamswitch-update.timer 비활성 시에는 발생하지 않음일반 HTTPS GET: 쿼리 문자열 없음, 쿠키 없음, 고정된 User-Agent: RoamSwitch-Updater, 사용자 또는 시스템에 대한 정보 없음. 서명된 매니페스트 + 피드 수신
1afreshclam(ClamAV 자체 업데이트 프로그램), ClamAV가 설치될 때 일일 업데이트 프로그램에 의해 호출됩니다.ClamAV 프로젝트의 CDN#1의 일부로. 그 밖에 사용자가 화면의 “바이러스 정의 지금 업데이트 (freshclam)” 버튼을 눌렀을 때도 실행ClamAV 자체의 트래픽(별도 프로젝트) RoamSwitch만 실행합니다.
2ping -c 1 -w 1 -W 1 <게이트웨이 IP>(같은 종류의 ping을 IPv6 라우터와 LAN 안의 DNS 서버 앞으로도 각 1회)LAN의 기본 게이트웨이. ARP/NDP 고정 확인에서는 IPv6 기본 라우터와 resolv.conf(systemd-resolved에서는 /run/systemd/resolve/resolv.conf)에 기재된 DNS 서버 중 루프백·사설·링크 로컬·ULA 주소인 것. 공개 리졸버(위협 방어 DNS인 Quad9 등)에는 패킷을 보내지 않음게이트웨이는 ARP 감시 등에서 MAC이 필요한데 ARP 캐시·근접 테이블·15초 이내의 보관본 어디에도 없을 때만(같은 대상에는 8초에 1회까지). IPv6 라우터와 DNS 서버 앞으로는 게이트웨이 ARP 고정(gateway_arp_lock_enabled, 기본값 켜짐)이 근접 테이블로의 고정을 다시 수행할 때(신뢰 네트워크 외로 전환할 때와 설정을 전환할 때)ICMP 에코 요청 1회만(ping의 표준 데이터 포함). DNS 확인 없음
3시스템 브라우저 실행(xdg-open)https://roamswitch.com/linux(제품 사이트), https://lafine.net/(운영자 사이트, 이용 약관, 문의), https://github.com/lafine1211/roamswitch-support(릴리스 노트와 지원)사용자가 '릴리스 노트 열기'와 같은 버튼을 클릭하는 경우에만RoamSwitch 프로세스는 소켓을 열지 않습니다. OS 브라우저에 URL을 전달합니다.
4resolvctl dns …—(RoamSwitch 자체의 통신은 발생하지 않음)신뢰할 수 없는 네트워크의 위협 방지 DNS(기본적으로 켜짐, 끌 수 있음). <iface>은 기본 경로(유선 + 무선 동시, lo / tailscale* / wg* / docker* / veth* 제외)를 전달하는 모든 물리적 NIC입니다. dns_scope = always_on일 때 신뢰할 수 있는 네트워크에도 적용됩니다. 모든 감시 주기를 조정했습니다.OS의 리졸버 설정을 변경할 뿐입니다. RoamSwitch 자체는 DNS 쿼리를 보내지 않습니다. 설정 후에는 이 NIC의 이름 확인이 선택한 위협 방어 DNS(기본값은 Quad9)로 향합니다
5패키지 관리자 업데이트배포판의 apt / dnf / zypper / pacman 저장소사용자가 apt upgrade 등을 실행하거나 GUI의 “지금 업그레이드” 버튼(pkexec을 통해 apt-get / dnf / zypper / pacman 실행)을 눌렀을 때apt 계열에서는 로컬 apt-cache policy 출력을 읽고 패키지 관리자를 실행할 뿐입니다. dnf 계열에서는 업데이트 탭을 열었을 때 등에 dnf check-update를 실행하여 설정된 저장소에 조회할 수 있습니다(zypper는 zypper list-updates, pacman은 pacman -Qu). 어느 쪽이든 lafine과는 무관
6VPN 터널(§4) — WireGuard 백엔드: 터널 wg-quick 빌드/Tailscale 백엔드: tailscale set --exit-node은 출구 노드를 통해 모든 트래픽을 라우팅합니다.사용자 자신의 VPN 엔드포인트 / tailnet(WireGuard: .conf의 엔드포인트, Tailscale: 선택한 종료 노드 및 DERP 릴레이)vpn_on_untrusted_enabled 및 네트워크를 신뢰할 수 없는 경우. 기본적으로 꺼짐암호화된 WireGuard/Tailscale 트래픽; 목적지는 라피네와 무관한 사용자 본인의 VPN 서버/테일넷입니다. getent ahosts(glibc NSS)을 통한 WireGuard 엔드포인트 확인; Tailscale은 tailscaled(별도의 프로세스)에 의해 구동됩니다.

위에 든 것 이외의 RoamSwitch의 모든 기능(nftables 제어, neigh 테이블의 ARP/NDP 고정(ip neigh, 패킷 송출 없음), 링크 가드(NFQUEUE, AF_NETLINK만), 포트 스캔(ss 출력 해석), ARP 감시, 건전성 진단, 엔트로피 감시, 카나리아, fanotify, USB 감시(evdev 그랩), 격리, 로그 감사, URL·비밀 정보 감사)은 로컬 파일 읽기와 서브프로세스 출력 해석만으로 완결됩니다. §4의 VPN 터널은 “사용자가 명시적으로 설정한 외부 통신”이며 그 목적지는 사용자 자신의 VPN 서버로, lafine의 인프라로는 어떤 데이터도 흐르지 않습니다. 9.4의 각 기능의 목적지는 9.4에 기재합니다.

9.4 옵트인 또는 사용자 조작 시에만 외부에 접촉하는 동작

9.3의 표는 기본값에서 발생하는 동작입니다. 다음은 코드상 소켓을 열거나 외부 프로그램(curl/openssl/npm)에 통신을 맡기는 곳 중, 운영자·사용자가 명시적으로 켜거나 조작했을 때만 동작하는 것입니다. scripts/audit_no_network.sh는 소켓 API 사용을 event_forward.rs·webhook_guard.rs·active_vuln_scan.rs·web_vuln_scan.rs·sensor_pairing.rs 5개 파일에만 허용하며(webhook_guard.rs는 기본값이 꺼짐인 설정 forward_webhook_block_private_addresses를 켰을 때만 Webhook 대상의 이름을 시스템 리졸버로 조회하여 내부 주소가 아닌지 확인하는 것이며 접속은 하지 않습니다. 그 밖에 tests/fleet_roundtrip.rs는 테스트 전용으로 127.0.0.1의 모의 서버에만 접속), 다른 소스가 TcpStream 등을 사용하면 빌드가 실패합니다. 이 기능들에서도 HTTP/TLS 크레이트는 추가되지 않으며, TLS는 시스템의 openssl, HTTP는 시스템의 curl 서브프로세스에 맡깁니다.

  • 이벤트 전달(event_forward.rs) — 기본 OFF(forward_enabled = false)이며 전송처도 운영자가 직접 설정한 목적지뿐입니다. 내장된 목적지는 없습니다. 전송처 종류는 syslog(RFC 5424 또는 CEF, UDP/TCP/TLS), JSON Lines 파일, HMAC-SHA256 서명이 붙은 Webhook입니다. TLS syslog는 시스템의 openssl s_client, Webhook은 시스템의 curl(URL·서명 등 비밀은 표준 입력의 설정으로 전달하며 명령줄 인수에는 싣지 않음)로 처리하고, 데몬에는 TLS/HTTP 크레이트가 없습니다. 전송되는 것은 호스트 이름, 이벤트 ID, 시각, 종류, 심각도, 규칙, 조치, PID, 프로세스 이름, 요약 항목뿐입니다(파일 내용이나 실행 기록의 원시 로그는 포함하지 않음). TCP/TLS/Webhook으로 전달되지 못한 이벤트는 0600 디스크 큐(기본 20MB/72시간 상한)에 쌓아 재전송합니다.
  • Fleet(Business 티어·단말 측만)(roamswitch-core/src/fleet/) — 단말 측 코드는 본체에 포함되지만 등록하지 않은 단말은 아무것도 보내지 않으며 Fleet 호스트 이름의 이름 해석도 하지 않습니다(워커는 로컬 상태 파일 하나를 읽을 뿐입니다). 등록은 운영자가 roamswitch fleet enroll을 실행했을 때만 이뤄집니다. 관리 서비스 측은 미배포·미출시이며 현재 접속처가 없습니다. 코드에 있는 설계상의 보장은 다음과 같습니다. (a) 단말이 받는 것은 조직이 서명한 정책 설정 데이터뿐이며, 등록 시 고정한 조직 공개 키로 검증하고 검증에 실패한 정책은 적용하지 않습니다. 조직의 서명 키는 고객이 보유하는 설계이며 lafine은 보유하지 않습니다. (b) 원격 명령 경로는 없습니다(격리·해제·수집·실행을 단말에 명령하는 엔드포인트·필드·코드 경로를 단말 측에 만들지 않았습니다). (c) 정책은 로컬 하한을 약화시킬 수 없습니다(가드 비활성화나 해제·긴급 복원의 방해는 스키마상 표현할 수 없으며 값은 “더 엄격한 쪽”을 채택). 이벤트 전달 목적지를 바꿀 수 있는 것도 로컬 root가 허용한 호스트(fleet_forwarding_allowed_hosts, 기본값은 빈 목록)로 한정됩니다. (d) 단말이 보내는 것은 앱 버전, OS 종류, 격리 상태(정상/격리 중), 적용된 정책 버전, 건전성 점수(0~100), 키 세대라는 최소한의 상태 요약과 서명 헤더의 단말 ID·조직 ID뿐입니다. 통신은 시스템의 curl(TLS)로 하며 등록한 단말에 한합니다.
  • Sensor 페어링(sensor_pairing.rs) — 기본 OFF(sensor_pairing_enabled). 운영자가 입력한 Sensor 주소로 직접 접속할 뿐이며 내장된 목적지는 없습니다. TLS 1.3(시스템의 openssl, 인증서 지문 고정)을 지원하지만, 핀이 없는 구형 페어링은 경고와 함께 평문으로 폴백합니다. Sensor는 별개 제품이며 이 문서의 Zero Telemetry의 대상이 아닙니다.
  • 실증형 취약점 진단(active_vuln_scan.rs / web_vuln_scan.rs) — 기본값은 비활성이며 설정에서 옵트인해야 합니다. 이 단말판의 경로는 자기 머신에서 관측한 대기 포트에 대한 127.0.0.1로의 프로브로 한정됩니다(모듈 자체 테스트로 고정).
  • GUI의 “업데이트 확인” 버튼 — 사용자가 버튼을 눌렀을 때만, apt 계열 배포판에서는 GUI가 시스템의 curl로 https://lafine.net/apt/dists/stable/main/<arch>/Packages를 한 번 GET하여 최신 버전을 조사합니다(타임아웃 4초. 비밀을 포함하지 않는 공개 URL이며 식별자나 쿼리 문자열은 붙이지 않습니다). dnf·zypper·pacman 계열에서는 curl을 사용하지 않고 각각의 패키지 관리자에 조회합니다(9.3의 #5).
  • npm 서명 검증 — roamswitch audit-npm-signatures <DIR> --confirm은 설정 npm_audit_signatures_enabled(기본 false)를 켜고 사용자가 --confirm을 붙여 실행했을 때만 동작하며, npm 자체가 registry.npmjs.org에 문의합니다.

§10

  • 계정 없음 — 사용자 등록, 로그인, 이메일 주소가 요청되지 않았습니다.
  • 구성은 로컬 전용입니다 — ~/.config/roamswitch/config.json에 일반 텍스트로 저장되며 시스템을 떠나지 않습니다. 클라우드 동기화가 없습니다.
  • 평가 결과는 로컬에만 있습니다 — 보안 보고서 및 로그 감사 결과는 화면에만 표시되며, 사용자가 선택한 경우 로컬 파일로 내보냅니다.
  • 라이센스 키는 오프라인으로 확인됩니다 — Pro/Business 라이선스 토큰에는 Ed25519 서명이 포함되어 있으며 공개 키로 확인됩니다. lafine 서버에는 쿼리가 수행되지 않습니다.
  • 충돌 보고 없음 — 패닉 상태에서는 스택 추적이 어디에도 전송되지 않습니다.
  • 일일 업데이트 프로그램은 귀하에 대해 아무것도 보내지 않습니다 — 쿼리 문자열과 쿠키가 없고, 장치별·설치별 식별자가 없으며, 소프트웨어를 나타내는 헤더는 고정된 User-Agent: RoamSwitch-Updater뿐입니다. 보고서가 아닌 다운로드입니다. 링크 가드 판정, 차단된 호스트 및 검색 결과는 로컬 활동 보기/저널에만 기록되며 전송되지 않습니다.
  • 위협 보호 DNS를 사용하는 동안, DNS 질의는 외부 리졸버에 도달합니다. 위협 보호 DNS(§4.5. 기본적으로 켜짐, 대상은 신뢰할 수 없는 네트워크뿐)는 OS의 DNS 리졸버를 Quad9(9.9.9.9. Cloudflare의 1.1.1.2도 선택할 수 있음)로 향하게 합니다. 이 동안의 질의는 RoamSwitch가 아니라 OS가 해당 리졸버로 보내며, RoamSwitch 자신은 아무것도 보내지 않습니다. 끄려면 dns_enabled를 false로 설정합니다.
  • 알림 기록은 7일 후 자동 삭제되며 길이도 제한 — 전송한 알림의 기록은 로컬에 저장되며, 기록할 때마다 7일이 지난 항목을 삭제합니다. 한 건의 제목은 200자, 본문은 2000자까지로 잘라냅니다(데스크톱 알림 팝업은 본문 1000자까지). 이 기록은 이벤트 전달(기본 OFF)을 켜지 않는 한 단말 밖으로 나가지 않습니다.
  • 실행 기록도 로컬 전용 — 프로세스 실행 레코더(§5)의 기록은 기본값에서도 단말 안(/var/lib/roamswitch/exec_log, 0600)에만 저장됩니다. 명령줄 인수를 포함하므로 보존 기간(기본 14일)과 용량(기본 200MB)에 따라 자동으로 삭제합니다.

§11

에디션
에디션에 대한가격라이센스
커뮤니티 에디션Linux 개인용·사내 업무용(대수 제한 없음)무료독점 프리웨어(번들 EULA)
비즈니스(예정)차량 관리, 정책 배포, 서명된 내부 아파트, 조직을 위한 SLA유료상업용 라이센스. 동일한 바이너리 + 계층 게이트

Business 티어 제공을 시작하는 시점에, macOS 버전 Pro Lifetime 구매자에게는 본인의 Linux 기기에서 Business 기능을 무상으로 부여할 방침입니다. 현재 Business 티어는 제공하지 않으며 관리 서버도 없습니다.

유통 및 공급망

채널세부정보
아파트https://lafine.net/apt (stable main). Release는 RSA-4096 키로 서명되었습니다.
RPMhttps://lafine.net/rpm(Fedora/openSUSE 별도). repomd.xml GPG 서명
AURroamswitch-bin(준비 중. AUR이 신규 계정 등록을 중단했으므로 당분간은 https://lafine.net/linux/dl/PKGBUILD에서 makepkg로 설치. lafine.net/linux/dl의 tarball을 sha256과 분리 서명(validpgpkeys)으로 검증)
타르볼https://lafine.net/linux/dl/에 x86_64 / aarch64의 .tar.gz + .sha256 + .asc(저장소 서명 키에 의한 분리 서명). 소스 저장소는 비공개이므로 일반 사용자용 배포처는 GitHub Releases가 아니라 이곳입니다
  • 고정된 glibc 바닥 — 릴리스 바이너리는 Debian 12(bookworm, glibc 2.36) 컨테이너 내부에 구축되었습니다. 이는 Debian 12/Ubuntu 22.04 이상에서 기호 버전 불일치를 방지합니다. 2.36 이상을 요구하는 종속성이 도입되면 "glibc 바닥 확인" CI 단계가 빌드에 실패합니다.
  • 서명 키 관리 — apt/rpm 저장소 서명을 위한 개인 키는 GitHub Actions 암호화된 비밀에만 존재합니다. 공개 키만 저장소에 커밋됩니다.

11.3 일일 업데이트

roamswitch-updater는 systemd 타이머(roamswitch-update.timer, 부팅 15분 후에 동작하고 그 뒤 24시간마다, 최대 3시간의 무작위 지연)로 실행되는 단독 바이너리입니다. 기본값에서 외부 배포 URL에 접촉하는 유일한 구성 요소입니다(9.4의 옵트인 기능 제외). 매 실행마다:

  1. GET https://lafine.net/updates/v1/manifest 및 해당 분리된 서명.
  2. Sparkle 앱 업데이트 키와 별도로 전용 위협 피드 서명 키를 사용하여 매니페스트의 Ed25519 서명을 확인하세요. 피드는 서버 측 CI에 의해 매일 다시 서명되므로 개인 키는 CI 비밀에 보관됩니다. 범위를 피드로 지정한다는 것은 유출로 인해 공격자가 잘못된 차단 목록(싱크홀/경고 - 코드 경로 없음, 앱 업데이트 경로 없음)만 제공할 수 있음을 의미합니다. 앱 업데이트 서명 키는 macOS 키체인을 벗어나지 않습니다. 확인되지 않은 매니페스트는 삭제됩니다. 업데이트 프로그램은 서명되지 않은 데이터로 대체되지 않습니다.
  3. 위협 피드 버전이 변경된 경우 피드를 다운로드하고 SHA-256 및 Ed25519 서명을 확인한 후 링크 가드에 대해 원자적으로 설치합니다(§4). 그 밖의 데이터는 매니페스트의 버전이 새로울 때만 받습니다(실증형 진단용 CVE 맵과 인기 npm 패키지 이름 목록). 커널, OS 패키지, 언어 생태계의 CVE 맵은 마지막 갱신에서 약 30일이 지났을 때만 받습니다. Ubuntu CVE 맵은 지원되는 버전의 Ubuntu에서만 받으며, 아직 없는 단말은 30일을 기다리지 않고 다음 일일 실행에서 받습니다. 모두 서명을 확인한 뒤에 설치합니다.
  4. 매니페스트 버전이 새로우면 웹 애플리케이션 계층 탐지 규칙(§5)도 가져옵니다. SHA-256과 Ed25519 서명을 검증하고 규칙으로 읽을 수 있는지도 확인한 뒤 원자적으로 설치합니다. 읽을 수 없는 파일은 설치되지 않으며 동작 중인 규칙은 교체되지 않습니다. 새로운 접속처는 늘지 않고 같은 매니페스트 가져오기에 포함됩니다. Server Edition에서는 cve_kernel_map_updates_enabled를 따르며, 끄면 통신이 전혀 일어나지 않습니다.
  5. ClamAV가 설치된 경우 freshclam을 호출합니다.
  6. 알림을 위한 최신 앱 버전을 기록합니다(업데이터 never는 앱을 설치하지 않습니다. Linux에서는 사용자가 apt upgrade을 실행합니다).

어느 단계든 실패(오프라인, 잘못된 서명, freshclam 없음)하면 updates.json의 lastError에 기록되고 프로세스는 항상 exit 0으로 종료되므로, 타이머 실행이 systemd의 실패 알림을 내는 일은 없습니다. 메커니즘 전체는 updates.json의 "enabled": false 또는 systemctl disable --now roamswitch-update.timer로 비활성화할 수 있으며, 그 경우 패키지에 번들로 포함된 데이터만으로 동작합니다. 위협 피드는 패키지에 포함되어 있기도 하므로 새로 설치한 직후부터 기능하며, 업데이터를 비활성화해 두어도 apt upgrade로 갱신됩니다.

§12

  • OS: Ubuntu 22.04 / 24.04 이상, Debian 12 이상, Linux Mint / Pop!_OS / elementary / Zorin, Fedora / RHEL 계열, openSUSE, Arch 계열(AUR은 준비 중이며 PKGBUILD로 설치), Raspberry Pi OS 64-bit(Bookworm), Ubuntu for Raspberry Pi
  • 아키텍처: x86_64 / aarch64
  • 필수: systemd, nftables, iproute2(ip neigh, ARP/NDP 고정), conntrack, libnotify(데스크톱 알림)(systemd 외에는 .deb / .rpm 종속성에 의해 자동으로 설치)
  • 선택 사항: clamav(맬웨어 검사), nmcli(Wi-Fi 암호화 판정), iw(무선 정보 대체), bluez(Bluetooth 가드), systemd-resolved(위협 방어 DNS 설정), lsof(수신 대기 포트 탐지 대체), nmap(능동적 취약점 검증 보조), wireguard-tools(VPN 터널 + 킬 스위치의 WireGuard 백엔드, §4), tailscale(동 Tailscale 백엔드, §4), polkit / pkexec(GUI에서의 업그레이드, 제거, 데몬 재시작)
  • 지원되지 않음: 시스템화되지 않은 배포판(Alpine / Void / Devuan)
  • 디스플레이: 1280×720 이상(창 및 글꼴은 화면 크기에 맞게 자동 조정되며 소형 Raspberry Pi 디스플레이 지원)

부록 A

상자목적네트워크
serde / serde_json구성 및 IPC 직렬화없음
토키오비동기 런타임(features = ["full"]. 기본값에서 사용하는 것은 Unix 소켓뿐)없음(기본값에서 TCP 미사용. 옵트인 Sensor 페어링은 예외)
추적 / 추적 구독자로깅(로컬/저널링)없음
이 오류 / 어쨌든오류 처리없음
정규식문자열 구문 분석(진단, CSS 크기 조정)없음
URLURL 구문 분석(audit_url_safety 경험적 방법)없음(가져오지 않음)
크로노날짜/시간 처리없음
ed25519-달렉라이선스 서명과, 업데이터의 매니페스트·위협 피드 등 서명된 데이터의 오프라인 검증(옵트인 Sensor 페어링에서도 사용)없음
base64 / sha2라이센스 토큰, 카나리아 해시없음
libcchown / kill / 시스템콜없음
이드나동형이의어 감지를 위한 IDN/퓨니코드 호스트 디코딩없음
nfq(데몬 전용)링크 가드를 위한 NFQUEUE 바인딩 — 순수 Rust, AF_NETLINK없음(판정 채널, 송신 없음)
열다“릴리스 노트 열기”나 EULA·지원 페이지 링크를 열기 위해 xdg-open을 호출프로세스 자체는 통신하지 않습니다.
GTK3 / ksni(앱 전용)GUI 및 트레이없음
serde_yaml/etc/roamswitch/guard.yaml 읽기(core)없음
tomlCargo.lock / poetry.lock 분석(언어 패키지 CVE 스캔, core)없음
md-5링크 가드의 JA3 핑거프린트 해시(인증이나 무결성 용도가 아님, core)없음
bs58암호화폐 개인 키의 Base58 체크(비밀 정보 탐지, core)없음
rand옵트인 Sensor 페어링의 단말 키 생성(core)없음
tonic / tonic-prost / prost / tower / hyper-utilTetragon 센서와의 gRPC. Unix 도메인 소켓만, TLS 없음(core)없음(Unix 소켓만)
flate2RHEL 계열 패키지 CVE 맵의 gzip 압축 해제(updater만. 순수 Rust)없음(압축 해제만)
inotify허니토큰(자격 증명 미끼)에 대한 접근 감시(roamswitch-honeytokens)없음
aya실험적인 XDP 부트 게이트 로더(roamswitch-xdp-gate. daemon이 참조)없음(bpf() / netlink만)
urlencoding / gdk / gdk-pixbuf / glib / pangoGUI의 URL 디코드와 GTK3 주변 크레이트(app만)없음
ureq + Rustls(roamswitch-updater 전용)일일 업데이터의 HTTPS GET(매니페스트, 서명, 갱신된 데이터)트리의 유일한 HTTP/TLS 스택입니다. 업데이터 바이너리로 제한됨
요점

roamswitch-core·roamswitch-daemon·roamswitch-app·roamswitch-mcp·roamswitchkit에는 인터넷으로 나가는 HTTP 클라이언트도 TLS 스택도 텔레메트리 SDK도 나타나지 않습니다. roamswitch-core가 가진 tonic / hyper는 Tetragon과의 Unix 도메인 소켓상 gRPC용이며, TLS가 없고 네트워크로는 나가지 않습니다. ureq + rustls는 roamswitch-updater에만 존재합니다(CI의 scripts/audit_no_network.sh가 강제). 텔레메트리 SDK는 어디에도 없습니다.

부록 B

table inet roamswitch {                 # 프로필 방화벽(open / balanced / lockdown)
    chain input {
        type filter hook input priority 0; policy <accept|drop>;
        ct state established,related accept
        iif "lo" accept
        ip protocol icmp accept            # open은 policy accept로 규칙 없음. balanced / lockdown은 policy drop(ICMP·ICMPv6 허용은 balanced만)
    }
    chain output {
        type filter hook output priority 0; policy accept;
    }
}

table inet roamswitch {                 # Air-Gap(격리) 중에는 같은 테이블을 다시 만든다. input / output 모두 policy drop, 허용은 lo뿐(확립된 연결도 통과시키지 않음)
    chain input  { type filter hook input  priority -100; policy drop; iif "lo" accept }
    chain output { type filter hook output priority -100; policy drop; oif "lo" accept }
}

table inet roamswitch_linkguard {        # §4.5 — 프로필 테이블과 독립
    chain output {
        type filter hook output priority 0; policy accept;
        udp dport 443 reject with icmpx type port-unreachable   # block_quic이 활성화되었을 때만(기본값은 꺼짐)
        meta l4proto udp udp dport 53      queue num 92 bypass
        meta l4proto tcp tcp dport 53      queue num 92 bypass
        meta l4proto tcp tcp dport { 80, 443 } ct state new,established ct original packets 1-14 queue num 92 bypass   # 각 연결의 처음 14개 패킷만 검사 대상으로 보냄
    }
}

table inet roamswitch_dev_guard {        # §4 포트 이상 가드 / 수동 포트 격리, 별도 테이블
    chain input {
        type filter hook input priority -10; policy accept;
        iif != "lo" tcp dport { … } drop
    }
}

table inet roamswitch_portscan {        # 포트 스캔 탐지 및 차단(기본적으로 켜짐)
    chain block  { type filter hook input priority -200; policy accept; ip saddr @blocked_ips ct state new log … drop }
    chain detect { type filter hook input priority -100; policy accept; iifname != "lo" ct state new tcp flags syn log … }
}

table inet roamswitch_egress_block {     # 악성 IP로의 아웃바운드 통신 차단 목록(기본적으로 켜짐)
    chain output  { type filter hook output  priority -150; policy accept; ip daddr { … } log … drop }
    chain forward { type filter hook forward priority -150; policy accept; ip daddr { … } log … drop }
}

table inet roamswitch_docker_guard {     # Docker 공개 포트에 의한 방화벽 우회 대책(Docker의 네이티브 nftables 백엔드가 있을 때만)
    chain forward { type filter hook forward priority -10; policy accept; … iifname { … } drop }
}

table inet roamswitch_isolate {          # eBPF 탐지 시의 프로세스 단위 차단(cgroup / UID 지정)
    chain process_block { type filter hook output priority -100; policy accept; … drop }
}

table inet roamswitch_vpn {              # §4 WireGuard 백엔드 킬 스위치
    chain output {
        type filter hook output priority -150; policy drop;
        oifname "lo" accept
        oifname "roamswitch" accept
        meta l4proto { icmp, ipv6-icmp } accept
        udp sport 68 udp dport 67 accept          # DHCP
        ip  daddr { <endpoint v4> } udp dport <port> accept
        ip6 daddr { <endpoint v6> } udp dport <port> accept
        ct state established,related accept
    }
    chain input  { type filter hook input priority -150; policy drop; … }  # 대칭
}

table inet roamswitch_ts {               # §4 Tailscale 백엔드 킬 스위치(더 느슨함)
    chain output {
        type filter hook output priority -150; policy drop;
        oifname "lo" accept
        oifname "tailscale0" accept
        meta l4proto { icmp, ipv6-icmp } accept
        udp sport 68 udp dport 67 accept          # DHCP(v6 쪽도 대칭)
        ip  daddr 100.64.0.0/10 accept            # tailnet CGNAT
        ip6 daddr fd7a:115c:a1e0::/48 accept
        ip  daddr 100.100.100.100 udp dport 53 accept   # MagicDNS
        ip  daddr 100.100.100.100 tcp dport 53 accept
        udp dport 3478 accept                     # STUN
        udp dport 41641 accept                    # WireGuard 직접 연결
        tcp dport 443 accept                      # DERP 폴백
        ct state established,related accept
    }
    chain input  { type filter hook input priority -150; policy drop; … }  # 대칭
}

게이트웨이/IPv6 라우터/DNS 확인자(§4)의 ARP/NDP 고정은 nftables가 아닌 커널 인접 테이블(ip neigh replace … nud permanent)에서 에서 수행됩니다. 고정된 항목은 /var/lib/roamswitch/arp_lock.json에 기록됩니다.

부록 C

이 문서의 모든 주장은 귀하의 컴퓨터에서 확인할 수 있습니다. 아래 명령은 roamswitch 패키지가 설치된 호스트에서 실행됩니다.

C.1 아웃바운드 트래픽 확인

데몬/앱/MCP 바이너리는 HTTP/TLS 라이브러리를 연결하지 않습니다:

for b in roamswitch-daemon roamswitch-app roamswitch-mcp; do
  ldd "$(command -v $b)" | grep -iE 'ssl|crypto|nghttp|curl' && echo "  ^ in $b"
done
# → 출력 없음. HTTP/TLS 스택은 roamswitch-updater에만 정적으로 링크되어 있습니다.

실행 중인 데몬에는 인터넷 소켓이 없습니다:

sudo lsof -p "$(pgrep -x roamswitch-daem)" -a -i
# → 출력 없음 = TCP/UDP 인터넷 연결이 하나도 없음
sudo lsof -p "$(pgrep -x roamswitch-daem)" -a -U | grep roamswitch
# → /run/roamswitch/roamswitch.sock과 events.sock(eBPF 탐지 이벤트 수신용)뿐(링크 가드는 AF_NETLINK 소켓을
#   추가하지만, 이는 커널 채널이며 인터넷 소켓이 아닙니다)

RoamSwitch로 인해 발생하는 유일한 송신은 일일 업데이트입니다:

watch -n 5 'sudo ss -tupn | grep -E "roamswitch-(daem|app|mcp)" || echo "(none)"'
# → 항상 "(none)". 이 3개는 인터넷 소켓을 전혀 열지 않습니다.
systemctl list-timers roamswitch-update.timer
sudo systemctl start roamswitch-update.service
journalctl -u roamswitch-update.service -n 20 --no-pager
# → lafine.net/updates/v1로의 HTTPS GET(매니페스트, 서명, 갱신된 데이터)만 수행하고 종료한다. ClamAV가 설치되어 있으면 freshclam이 ClamAV 미러에도 접속한다.

해제하고 완전히 침묵을 확인하세요:

sudo systemctl disable --now roamswitch-update.timer
# 또는 /var/lib/roamswitch/updates.json을 "enabled": false로 편집
sudo strace -f -e trace=network -p "$(pgrep -x roamswitch-daem)" 2>&1 | grep -i 'connect('
# → AF_UNIX / AF_NETLINK로만 connect. AF_INET / AF_INET6는 전혀 나타나지 않습니다.

C.2 재현 가능한 방어 검증(Docker 테스트 스위트)

RoamSwitch의 각종 방어 메커니즘(Air-Gap 차단, 방화벽 강제 삭제 시의 자가 복구, 랜섬웨어 탐지, /tmp noexec, Yama LSM, 호모그래프 탐지 등)은 공식 지원 저장소(roamswitch-support)에서 공개되는 Docker 테스트 스위트를 사용하여 직접 재현 실행할 수 있습니다. 스위트는 독립된 네트워크 네임스페이스와 컨테이너 안의 파일만 조작하므로 호스트의 네트워크와 파일에는 영향을 주지 않습니다. 다만 --privileged로 실행하기 때문에 Yama의 ptrace_scope나 suid_dumpable처럼 커널 전체에서 공유되는 설정은 호스트에도 반영되며, 컨테이너를 삭제해도 되돌아가지 않습니다. 일회용 환경에서 실행하십시오.

# 검증 스위트 가져오기 및 재현 실행(공식 배포 패키지를 사용해 격리된 컨테이너 내에서 자동 검증)
git clone https://github.com/lafine1211/roamswitch-support.git
cd roamswitch-support/test/docker
docker build -t roamswitch-test .
docker run --rm --privileged roamswitch-test

테스트 컨테이너는 공식 APT 저장소(lafine.net/apt)에서 배포 패키지를 자동으로 가져와, 독립된 네트워크 네임스페이스와 마운트 네임스페이스 안에서 검증을 실행하여 판정합니다. 공개 스위트는 pentest.sh(PENT-1~7의 7개 시나리오)와 selftest.sh(Air-Gap, 자가 복구, 카나리아, 버스트 탐지, 격리 Vault 등 데몬의 파괴적 셀프 테스트)입니다. 아래 표의 ID는 검증 기록상의 일련번호이며 스크립트 내의 점검과 일대일로 대응하지는 않습니다. BadUSB(SP-6), 링크 가드의 각 모드(SP-7 계열), ARP 고정과 VPN 킬 스위치(SP-8~10), 커널 강화의 개별 확인(SP-11 계열), PENT-8·9는 공개 스위트에 포함되어 있지 않으며, 실제 기기·독립 netns·이벤트 수동 주입 등 별도의 절차로 확인한 항목입니다.

(1) 자율 네트워크 방어 및 Air-Gap Self-Healing

아이디시나리오검증방법합격 기준
SP-3Air-Gap 활성화 → 송신 중단enable_air_gap IPC 보내기policy drop은 출력 후크에 적용됩니다. 모든 아웃바운드 트래픽이 차단됨
SP-3b방화벽 강제 제거 및 자가 복구nft delete table inet roamswitch을 통한 제거 규칙Anti-Clobber 자가 치유 기능은 5초 이내에 드롭 규칙을 복원합니다.
SP-3c데몬 재시작 시 페일클로즈됨Air-Gap 중에 데몬을 종료(selftest.sh에서는 kill에 의한 SIGTERM)하고 재시작송신 삭제 정책은 다시 시작해도 원활하게 유지됩니다.
SP-3d승인된 Air-Gap 복구승인된 disable_air_gap IPC 보내기삭제 정책 해제, 모든 마커 파일 제거, 송신 복원
SP-8예방적 ARP/NDP 고정격리된 netns 게이트웨이 환경에서 활성화 → 스푸핑된 무상 ARP 전송게이트웨이 인접 항목은 PERMANENT로 유지됩니다. 스푸핑된 ARP가 무시됨
SP-9VPN 킬 스위치(WireGuard)별도의 netns에 있는 Live WireGuard 서버, .conf → vpn_up 가져오기핸드셰이크는 inet roamswitch_vpn을 통해 완료됩니다. 터널 사망 시 누출 없음
SP-10VPN 킬 스위치(Tailscale)스텁 tailscale CLI + 별도의 netns, tailscale0 IF터널 외부 일반 텍스트 TCP/80 및 로컬 DNS가 차단되었습니다. DERP 및 MagicDNS 허용

(2) 커널 및 마운트 강화

아이디시나리오검증방법합격 기준
SP-11a네트워크 sysctl 강화apply_kernel_sysctl_hardening IPC 실행rp_filter=1, tcp_syncookies=1, accept_redirects=0 목표 값에 도달
SP-11b메모리 보호 및 Yama LSMptrace_scope 및 suid_dumpable 확인kernel.yama.ptrace_scope=1(스누핑 차단), fs.suid_dumpable=0(코어 덤프 없음)
SP-11c/tmp 및 /dev/shm noexec 방어apply_mount_hardening IPC 실행/tmp 및 /dev/shm이 noexec,nosuid,nodev을(를) 다시 마운트했습니다. 실행이 거부됨
SP-11d합법적인 사용자 실행이 보존됩니다.표준 홈 디렉터리(~/)에서 스크립트 실행합법적인 개발자/관리자 작업 흐름을 방해하지 않고 방해 없이 실행됩니다.

(3) 다층 악성코드 및 랜섬웨어 방어 및 카나리아

아이디시나리오검증방법합격 기준
SP-1카나리아 변조 감지~/Documents/에서 미끼 카나리아 파일(.xlsx)을 자르거나 이름을 바꿉니다.몇 초 안에 변조가 감지되어 즉각적인 Air-Gap이 발생합니다.
SP-2랜섬웨어 대량 암호화 버스트 감지단일 프로세스로 여러 개의 고엔트로피 파일을 빠르게 생성SIGSTOP로 인해 위반 작업자 프로세스가 정지되었습니다. Air-Gap이 트리거됨
SP-2bshred에는 거짓양성이 없습니다.테스트 파일 전체에서 shred -n 3 -u 실행허용 목록 일치로 오탐지 감지 또는 정지 방지
SP-2c중요한 프로세스는 동결되지 않습니다.버스트 감지 중 동결 대상 논리 검증중요 데몬(dockerd, systemd, containerd)은 동결되지 않습니다.
SP-4EICAR 테스트 문자열은 알림 전용입니다.~/Downloads/에 EICAR 테스트 문자열 쓰기알림 없이 알림 기록에만 남습니다. 파일은 자동으로 격리되지 않습니다.
SP-4b실제 악성코드 서명 확인 우선감시 폴더 외부에 실제 서명 쓰기모달 승인 대화 상자를 표시합니다. 사용자 확인 시에만 격리
SP-4c검역소 권한 강화Vault 디렉터리 및 샘플 파일의 권한 검사0700로 설정된 Vault 디렉터리, 0400로 설정된 격리된 샘플
SP-4d권한 있는 IPC 안전 원본 파일 복원restore_quarantine_file IPC 보내기파일이 사용자 권한(0644)으로 원래 경로로 복원되고 제외 항목에 추가되었습니다.

(4) 패시브 링크 가드 및 피싱 방어

아이디시나리오검증방법합격 기준
SP-7mode = block 강력한 집행피드 목록 또는 blocklistExtra 호스트에 연결TLS 레이어에서 삭제됨(SNI 구문 분석됨) 목록에 없는 안전한 호스트가 HTTP 200에 도달함
SP-7bmode = warn 페일클로즈 보류사용자 응답 없이 경고 호스트에 연결(시간 초과 8초)시간 초과 시 패킷을 삭제합니다(fail-closed). 명시적인 승인이 있는 경우에만 허용됨
SP-7cmode = off 완전 분해링크 가드 비활성화inet roamswitch_linkguard 테이블이 완전히 제거되었습니다.

(5) BadUSB 완화 및 장치 제어

아이디시나리오검증방법합격 기준
SP-6연결된 키보드 할아버지데몬 시작 시 연결된 키보드 확인기존 장치는 유효합니다. 후속 핫플러그만 확보됨

(6) 공격을 모방한 셀프 체크 항목(PENT-1~7은 공개 Docker 스위트, PENT-8·9는 별도의 수동 검증)

아이디공격 벡터시뮬레이션 세부정보방어/탐지 기준
펜트-1C2 유출Air-Gap에서 C2로의 송신 HTTP/TCP 시도모든 패킷은 netfilter에서 삭제됩니다. 누출 제로
펜트-1bFW 강제 삭제nft delete table inet roamswitch을(를) 사용한 제거 규칙Anti-Clobber 자가 치유 기능으로 몇 초 내에 규칙을 복원합니다.
펜트-1cAir-Gap 안전 복원인증된 disable_air_gap IPC 발행삭제 규칙이 제거되었습니다. 마커가 삭제되었습니다. 연결이 복원되었습니다
펜트-2/tmp 백도어/tmp에서 삭제된 백도어 스크립트 실행noexec 마운트 보호(Permission denied)에 의해 거부됨
펜트-2b/dev/shm 공유 메모리/dev/shm에 배치된 바이너리 페이로드 실행noexec 마운트 방어에 의해 차단됨
펜트-2c사용자 공간 보존합법적인 사용자 디렉터리(/home/tester/)에서 스크립트 실행방해받지 않는 실행; 작업흐름 중단 제로
펜트-3ptrace 메모리 스누핑비밀/토큰 추출을 목표로 하는 PTRACE_ATTACHYama LSM kernel.yama.ptrace_scope=1에 의해 거부됨
펜트-3b코어 덤프 비밀 스크래핑메모리 코어 덤프를 얻기 위해 프로세스 충돌을 유발합니다.fs.suid_dumpable=0에 의해 방지됨
펜트-4카나리아 변조~/Documents/의 제로아웃 카나리아 미끼 파일크기 이상 및 해시 변화 실시간 감지
펜트-4b카나리아 대량 삭제카나리아 미끼 파일 대량 삭제파일 사라짐 즉시 감지해 사건 제기
펜트-5IP 주소 스푸핑스푸핑된 소스 IP로 패킷 전송rp_filter=1(역방향 경로 필터)에 의해 삭제됨
펜트-5bSYN 폭주 DoS반개방 TCP 연결 플러드tcp_syncookies=1에 의해 보호되는 상태 테이블
펜트-5cICMP 경로 하이재킹가짜 ICMP 리디렉션 패킷 삽입accept_redirects=0로 인해 무시되었습니다.
펜트-6키릴 문자 동음이의어 피싱gооgle.com(키릴 동형이의어)로 이동합니다.LinkGuard에 의해 식별됨, 점수 40(위험/블록)
펜트-6b원시 IP 및 일반 텍스트 트래픽원시 IP에 대한 직접 아웃바운드 연결안전 위험 요인으로 정확하게 표시됨
펜트-7OpenAI API 키 유출코드 또는 로그에 sk-proj-... 토큰 노출Shannon 엔트로피 분석을 통해 감지 및 마스크 처리
펜트-7bGitHub 토큰 유출코드에 ghp_... 개인 토큰 노출토큰 패턴과 엔트로피 검증으로 일치
펜트-8커널 LPE(Frag Gap) 선점권한이 없는 unshare -U -r(CVE-2026-53362 시도)v1.1.0 측정에서는 user.max_user_namespaces = 0에 의해 Operation not permitted로 즉시 거부. 현행 Client Edition은 이 거부를 기본적으로 수행하지 않음(guard.yaml의 auto_enable_userns_restriction은 기본값이 비활성. Server Edition은 상시 적용)
펜트-9eBPF 위협 센서 및 프로세스 정지Falco eBPF 소켓을 통해 주입된 중요/긴급 경고밀리초 내에 SIGSTOP을 통해 프로세스가 정지되었습니다(상태: Ts). Air-Gap 배포

검증의 종류에 대하여, 위 표 중 PENT-2·2b(noexec), PENT-3·3b(Yama, 코어 덤프), PENT-5·5b·5c(rp_filter, SYN 쿠키, ICMP 리디렉션)는 RoamSwitch가 커널과 mount의 표준 기능을 설정하고, 그 설정이 적용되어 있는지 확인하는 항목입니다. PENT-8(사용자 네임스페이스 금지)도 같은 종류이지만 v1.1.0 시점의 기록이며, 현행 Client Edition은 기본적으로 적용하지 않습니다. RoamSwitch 고유의 탐지·차단 로직을 공격 측에서 시험한 항목은 PENT-1·1b·1c(Air-Gap과 자가 복구), PENT-4·4b(카나리아), PENT-6·6b(링크 가드), PENT-7·7b(비밀 정보 탐지), PENT-9(eBPF 탐지 후 동결과 차단)입니다. PENT-2c는 일반 작업이 깨지지 않는지 확인하는 항목입니다.

(7) 셀프 체크 총괄(Falco 없음 [독립형] 및 Falco 있음 [eBPF 연동])

격리된 Docker 검증 환경(Target: Ubuntu 24.04 + RoamSwitch Client Edition, Attacker: Nmap / curl / hping3 등)에서 실시한, 공격을 모방한 포괄적인 셀프 체크의 결과 총괄입니다. 아래 표는 Client v1.1.0을 2026-09-06에 측정한 결과이며, 2026-09-25에는 1.10 계열 소스로 빌드한 수정판에서 클라이언트판 스위트 11개 항목 중 11개 항목이 성공했습니다(공개판이 아닙니다. 공개 저장소의 audit/RESULTS-PENTEST-2026-09-25.ja.md 참조).

검증항목Falco 없음(독립형 강화)Falco 사용(eBPF 강화)국방 평결
외부 포트 스캔 저항(Nmap SYN)PASS(100% 필터링됨 / Lockdown에서 삭제)PASS(100% 필터링됨 / Lockdown에서 삭제)완전한 외부 스텔스
외부 직접 액세스 차단(TCP/80, 3306)PASS(시간 초과 감소/0 응답)PASS(시간 초과 감소/0 응답)인바운드 노출 제로
커널 LPE(프래그 갭) 완화합격(v1.1.0 측정. user.max_user_namespaces = 0 환경에서 unshare를 차단. 현행 Client Edition에서는 기본적으로 꺼져 있음)PASS(eBPF 경고 + SIGSTOP 동결 + Air-Gap)심층방어 검증됨
무단 포트 감지(비정상)PASS (0.0.0.0 바인드 플래그가 높음 위험)PASS (0.0.0.0 바인드 플래그가 높음 위험)100% 감지 및 유도
로그 팽창 및 CPU 오버헤드 방지제로 로그 공간(제거됨)PASS (95% 노이즈 컷 / 2MB 버퍼 / 50MB 캡)데스크톱 최적화

C.3 알려진 제한사항

한계영향완화
일부 커널에서는 tmpfs(/tmp)의 FAN_MARK_MOUNT이 이벤트를 전달하지 않습니다./tmp 아래의 파일 쓰기는 fanotify에 의해 모니터링되지 않습니다./tmp은(는) 임시 폴더이며 모니터링되는 사용자 폴더가 아닙니다. 랜섬웨어는 ~/을 목표로 합니다. 표시 실패는 명시적으로 기록됩니다.
컨테이너 오버레이fs/tmpfs에서는 fanotify 전달이 없습니다.컨테이너 내 엔트로피 버스트 감지를 검증할 수 없습니다.실제 하드웨어(ext4)에서 검증되었습니다. 프로브를 통해 하네스 자동 건너뛰기
RoamSwitch의 nftables 작업은 Docker의 iptables-nft과 netfilter 공간을 공유합니다.RoamSwitch 규칙 작업은 Docker의 NAT 규칙을 방해할 수 있습니다.전용 테이블(inet roamswitch 외 roamswitch_*)로 분리되지만, nft flush ruleset 등의 전체 조작은 피해야 함
링크 가드는 TLS SNI를 통해서만 브라우저의 DoH를 확인합니다. ECH(Encrypted Client Hello)는 이를 숨깁니다.ECH가 널리 배포되면 ECH 지원 사이트에 대한 ECH 사용 연결을 호스트 이름으로 식별할 수 없습니다.로컬 차단 목록과 휴리스틱은 여전히 DNS/HTTP 및 비 ECH TLS에 적용됩니다. DNS 위협 보호(§4.5, 기본적으로 켜짐)는 확인자 경로를 포함합니다.
updates.json의 enabled가 false인 경우, 피드의 최신성은 릴리스 주기에 따라 달라짐업데이트를 끄면 피드는 마지막 apt upgrade만큼만 최신 상태가 됩니다.심각도가 가장 높은 탐지(브랜드 동형이의어)에는 피드가 필요하지 않습니다. 항상 최신 범위를 유지하려면 다시 선택하거나 위협 방지 DNS를 활성화하세요.
이 문서는 RoamSwitch for Linux v1.11.1(커널 LPE 강화 / eBPF 런타임 보호 / 공격 프로세스 SIGSTOP 동결 / Falco 저부하 최적화 / 실시간 모니터링 Vault 격리 및 권한 IPC 원본 복원 / Air-Gap 자가 복구 메커니즘 / DNS 터널링 탐지 / 포렌식 증거 보전 / 허니토큰 / 취약점 진단 pass_age 추적 / NIST CSF·CIS Controls 매핑 / 진단 재평가 / MCP 설정 감사) 시점의 구현을 기준으로 합니다. 항목별 변경 이력은 roamswitch-support의 CHANGELOG를 참조하십시오. 최신 정보는 roamswitch.com/linux를 참조하십시오.