개발환경/macOS

맥 IPinside 완전 삭제: 앱·데몬·INTEREZEN CA 루트 인증서까지 지우는 스크립트

GitHub Actions 결제를 하나카드로 하다가 IPinside를 설치하게 됐습니다. 설치 흔적을 분석해 보니 System 키체인에 루트 인증서까지 들어 있었습니다. 점검 모드가 있는 삭제 스크립트를 공유합니다.

발단: GitHub Actions 분수를 다 썼습니다

운영하는 GitHub Organization에서 이번 달 GitHub Actions 무료 사용량을 다 써버렸습니다. 결제 수단을 등록해야 했는데, 평소 해외 결제용으로 쓰던 트래블월렛 카드로는 결제가 되지 않았습니다. 그래서 하나카드로 다시 시도했습니다.

하나카드 결제 인증 단계에서 익숙한 화면이 나왔습니다. "보안 프로그램을 설치해 주세요." 받은 파일은 NWS_IPinsideAgent.dmg였습니다. 결제는 해야 하니 일단 설치했습니다.

결제는 끝났습니다. 그런데 macOS 시스템 설정의 로그인 항목에 이런 줄이 새로 생겼습니다.

interezen co., Ltd.
1개의 항목: 1개의 항목이 모든 사용자에게 영향을 줌

"모든 사용자에게 영향을 줌"은 사용자 계정 단위가 아니라 시스템 전체에서 root로 도는 데몬이라는 뜻입니다. 결제 한 번 하자고 이걸 상주시킬 이유는 없습니다. 다만 앱만 휴지통에 버리면 무엇이 남는지 알 수 없었습니다. 그래서 Claude Code에게 "이거 설치된 거 완전히 다 삭제해줘"라고 맡겼습니다.

분석: 실제로 무엇이 설치됐나

1. 패키지 영수증부터 확인

macOS .pkg 설치기는 어떤 파일을 깔았는지 영수증(receipt)을 남깁니다. 삭제할 대상을 추측하지 않고 이 목록에서 시작하는 게 가장 확실합니다.

$ pkgutil --pkgs | grep -i interezen
com.interezen.IPinside

$ pkgutil --files com.interezen.IPinside
Applications/IPinside.app
Applications/IPinside.app/Contents/MacOS/IPinside
Applications/IPinside.app/Contents/MacOS/NWSDaemon
Applications/IPinside.app/Contents/Resources/Interezen.tgz
Applications/IPinside.app/Contents/Resources/InterezenSSLServerCert.ext
Applications/IPinside.app/Contents/Resources/openssl
Applications/IPinside.app/Contents/Resources/openssl.cnf
...
Library/LaunchDaemons/com.interezen.nwsdaemon.plist

파일 경로는 두 군데뿐입니다. 하지만 눈에 띄는 게 있습니다. 앱 안에 **openssl 바이너리와 SSL 서버 인증서 확장 설정 파일(InterezenSSLServerCert.ext)**이 들어 있습니다. 설치 과정에서 인증서를 직접 만들어 어딘가에 넣는다는 신호입니다. 그리고 이 작업은 영수증의 파일 목록에 나타나지 않습니다.

2. 데몬 설정

$ cat /Library/LaunchDaemons/com.interezen.nwsdaemon.plist
<key>Label</key>
<string>com.interezen.nwsdaemon</string>
<key>ProgramArguments</key>
<array>
    <string>/Applications/IPinside.app/Contents/MacOS/NWSDaemon</string>
</array>
<key>KeepAlive</key>
<true/>

KeepAlive가 true입니다. 활성 상태 보기에서 프로세스를 강제 종료해도 launchd가 바로 다시 띄웁니다. 참고로 이 plist의 수정일은 2018년 11월 24일이었습니다. 이 데몬 설정은 적어도 8년 가까이 그대로라는 얘기입니다.

3. 키체인에 숨어 있던 루트 인증서

영수증에 없는 흔적을 찾으려고 System 키체인과 로그인 키체인의 인증서를 전부 꺼냈습니다(총 16개). 그리고 주체(subject)를 하나씩 확인했습니다.

[email protected], CN=INTEREZEN CA, OU=INTEREZEN,
        O=INTEREZEN, L=SEOUL, ST=SEOUL, C=KR
issuer [email protected], CN=INTEREZEN CA, ...

INTEREZEN CA 자체 서명 루트 인증서가 System 키체인에 들어 있었습니다. subject와 issuer가 같으니 루트 CA입니다. 로컬 데몬이 https://127.0.0.1로 브라우저와 통신하려고 만든 것으로 보입니다(추정).

이 인증서가 이 글의 핵심입니다. 시스템이 신뢰하는 루트 CA가 하나 늘었다는 건, 그 CA의 개인키를 가진 쪽이 어떤 도메인의 인증서든 발급할 수 있고 내 Mac이 그걸 믿을 수 있다는 뜻입니다. 앱을 지워도 키체인 항목은 그대로 남습니다. 그래서 "앱만 드래그해서 삭제"로는 정리가 끝나지 않습니다.

홈 폴더의 Preferences, Caches, Application Support도 뒤져 봤습니다. interezen, ipinside 관련 파일은 없었습니다.

정리하면 설치 흔적은 네 가지입니다.

항목 위치
앱 본체 /Applications/IPinside.app
root 데몬 (로그인 항목에 보이던 것) /Library/LaunchDaemons/com.interezen.nwsdaemon.plist
루트 인증서 INTEREZEN CA /Library/Keychains/System.keychain
설치 영수증 com.interezen.IPinside

삭제: 함정 하나

삭제 스크립트를 만들고 Claude Code 프롬프트에서 ! 접두사로 실행했더니 바로 막혔습니다.

$ ! sudo bash uninstall_ipinside.sh
sudo: a terminal is required to read the password; either use the -S option
to read from standard input or configure an askpass helper
sudo: a password is required

Claude Code의 ! 명령은 TTY가 없는 환경에서 실행됩니다. 그래서 sudo가 비밀번호를 물어볼 곳이 없습니다. 해결책은 macOS의 GUI 비밀번호 창을 쓰는 것이었습니다.

osascript -e 'do shell script "bash /path/to/uninstall-ipinside-mac.sh --apply" with administrator privileges'

이렇게 실행하면 익숙한 macOS 관리자 비밀번호 창이 뜨고, 입력하면 root 권한으로 실행됩니다. 결과는 다음과 같았습니다.

[1] stop daemon
[2] remove files
/Library/LaunchDaemons/com.interezen.nwsdaemon.plist
removed /Applications/IPinside.app
[3] remove INTEREZEN CA root certificate
[4] forget pkg receipt
Forgot package 'com.interezen.IPinside' on '/'.
done

삭제 후 다시 확인했습니다.

gone /Applications/IPinside.app
gone /Library/LaunchDaemons/com.interezen.nwsdaemon.plist
receipts gone
--- pkg:   none
--- cert:  security: SecKeychainSearchCopyNext: The specified item could not be found in the keychain.
--- proc:  none

남은 흔적은 없었습니다. 로그인 항목의 "interezen co., Ltd." 줄은 설정 화면의 캐시라서 재로그인이나 재부팅 후 사라집니다.

가져다 쓰는 스크립트

제 환경에서 쓴 스크립트는 인증서 SHA-1 해시를 직접 박아 두었습니다. 하지만 IPinside가 앱 안의 openssl로 인증서를 만드는 구조라면 해시가 PC마다 다를 수 있습니다. 그래서 공개용 스크립트는 인증서를 이름으로 찾은 뒤 해시를 뽑아 지우게 바꿨습니다.

또 하나, 기본 동작은 점검만 하도록 했습니다. 무엇이 지워질지 먼저 확인한 다음 --apply로 실행하는 방식입니다.

#!/bin/bash
# IPinside (interezen) 완전 삭제 스크립트 for macOS
#   점검만:  bash uninstall-ipinside-mac.sh
#   삭제:    sudo bash uninstall-ipinside-mac.sh --apply
set -u

APPLY=0
[ "${1:-}" = "--apply" ] && APPLY=1

APP="/Applications/IPinside.app"
DAEMON_LABEL="com.interezen.nwsdaemon"
DAEMON_PLIST="/Library/LaunchDaemons/${DAEMON_LABEL}.plist"
PKG_ID="com.interezen.IPinside"
KEYCHAIN="/Library/Keychains/System.keychain"
CERT_NAME="INTEREZEN CA"

if [ "$APPLY" -eq 1 ] && [ "$(id -u)" -ne 0 ]; then
  echo "삭제 모드는 sudo가 필요합니다: sudo bash $0 --apply"
  exit 1
fi

# 이름으로 찾은 인증서들의 SHA-1 해시 (설치마다 다를 수 있어 하드코딩하지 않음)
cert_hashes() {
  security find-certificate -a -c "$CERT_NAME" -Z "$KEYCHAIN" 2>/dev/null \
    | awk '/^SHA-1 hash:/ {print $3}'
}

echo "== 점검 결과 =="
[ -e "$APP" ]          && echo "[발견] $APP"          || echo "[없음] $APP"
[ -e "$DAEMON_PLIST" ] && echo "[발견] $DAEMON_PLIST" || echo "[없음] $DAEMON_PLIST"
pkgutil --pkg-info "$PKG_ID" >/dev/null 2>&1 && echo "[발견] 설치 기록 $PKG_ID" || echo "[없음] 설치 기록 $PKG_ID"
HASHES=$(cert_hashes)
if [ -n "$HASHES" ]; then
  for h in $HASHES; do echo "[발견] 루트 인증서 '$CERT_NAME' (SHA-1 $h)"; done
else
  echo "[없음] 루트 인증서 '$CERT_NAME'"
fi

if [ "$APPLY" -eq 0 ]; then
  echo
  echo "점검만 했습니다. 지우려면: sudo bash $0 --apply"
  exit 0
fi

echo
echo "== 삭제 =="
# KeepAlive=true 데몬이라 프로세스만 죽이면 launchd가 되살린다 → 등록부터 해제
launchctl bootout "system/$DAEMON_LABEL" 2>/dev/null && echo "데몬 등록 해제"
pkill -f "$APP" 2>/dev/null
rm -f "$DAEMON_PLIST" && echo "삭제: $DAEMON_PLIST"
rm -rf "$APP" && echo "삭제: $APP"
for h in $HASHES; do
  security delete-certificate -Z "$h" "$KEYCHAIN" && echo "삭제: 인증서 SHA-1 $h"
done
pkgutil --forget "$PKG_ID" >/dev/null 2>&1 && echo "삭제: 설치 기록 $PKG_ID"

echo
echo "완료. 로그인 항목의 'interezen' 표시는 재로그인/재부팅 후 사라집니다."

사용법

# 1) 파일로 저장한 뒤 점검 (root 권한 불필요)
bash uninstall-ipinside-mac.sh

# 2) 삭제 — 터미널 앱에서
sudo bash uninstall-ipinside-mac.sh --apply

# 2') 또는 TTY가 없는 환경(Claude Code의 ! 명령 등)에서
osascript -e 'do shell script "bash ~/uninstall-ipinside-mac.sh --apply" with administrator privileges'

이미 지운 제 Mac에서 점검 모드를 돌리면 이렇게 나옵니다.

== 점검 결과 ==
[없음] /Applications/IPinside.app
[없음] /Library/LaunchDaemons/com.interezen.nwsdaemon.plist
[없음] 설치 기록 com.interezen.IPinside
[없음] 루트 인증서 'INTEREZEN CA'

점검만 했습니다. 지우려면: sudo bash uninstall-ipinside-mac.sh --apply

스크립트 설계에서 신경 쓴 부분

  • 삭제 순서: 데몬 등록 해제(launchctl bootout)를 먼저 하고 그다음에 파일을 지웁니다. 순서가 반대면 KeepAlive가 걸린 데몬이 지워진 바이너리를 계속 다시 띄우려고 시도합니다.
  • 인증서는 해시로 지정해서 삭제: security delete-certificate -c "이름"도 쓸 수 있습니다. 하지만 이름이 겹치는 인증서가 여러 개면 어떤 게 지워질지 애매합니다. 그래서 이름으로 찾되, 실제 삭제는 SHA-1 해시(-Z)로 하나씩 정확히 합니다.
  • sfltool resetbtm은 넣지 않았습니다: 로그인 항목 표시를 바로 없애는 명령이지만, IPinside뿐 아니라 모든 앱의 백그라운드 항목 승인을 초기화합니다. 다른 앱이 다시 허용을 요청하게 되므로, 재로그인으로 해결되는 표시 하나 때문에 쓸 이유가 없습니다.

덧붙임

  • 하나카드처럼 IPinside를 요구하는 사이트에 다시 들어가면 재설치하라고 나올 수 있습니다. 필요할 때 깔고, 쓰고, 이 스크립트로 지우는 흐름으로 쓰면 됩니다.
  • 다른 국내 보안 프로그램도 같은 방식으로 확인할 수 있습니다. 순서는 pkgutil --pkgs로 영수증 확인 → /Library/LaunchDaemons·LaunchAgents 확인 → 키체인 루트 인증서 확인입니다. 마지막 단계가 가장 자주 빠집니다.
  • 분석과 스크립트 작성은 Claude Code에게 맡겼습니다. 설치 흔적을 추측하지 않고 영수증 → plist → 키체인 순서로 증거를 모은 다음, 사람이 비밀번호만 넣게 한 분업이 꽤 괜찮았습니다. 여러 GitHub 계정과 Organization을 오가며 작업하는 환경은 폴더별 GitHub 계정 분리 글에, Claude Code 사용량 관리는 Claude Code·Codex·Gemini 사용량 대시보드 글에 정리해 두었습니다.