macOS 앱이 “손상되어 열 수 없습니다”라고 나올 때: 테스트부터 서명·공증까지

직접 개발한 macOS 앱을 다른 Mac에서 실행했을 때 다음과 같은 경고가 나타날 수 있다.

“앱이 손상되어 열 수 없습니다. 해당 앱을 휴지통으로 이동해야 합니다.”

이 문구가 항상 파일의 물리적인 손상을 뜻하는 것은 아니다. 코드 서명이 없거나 깨졌고, 공증되지 않았거나, 다운로드 격리 상태에서 Gatekeeper 검사를 통과하지 못했을 때도 비슷한 경고가 나타날 수 있다.

하지만 반대로 무조건 서명 문제라고 단정해서도 안 된다. Apple은 앱이 실제로 변경·손상됐거나 인증이 취소됐고, 알려진 악성 콘텐츠가 감지된 경우에도 실행을 차단한다고 설명한다. 따라서 해결 순서는 “일단 보안 기능을 끄기”가 아니라 경고의 종류를 구분하고 서명 상태를 확인하는 것부터 시작해야 한다.

이 글은 두 상황을 분리해 다룬다.

  • 내가 만든 앱을 내 Mac이나 테스트 Mac에서 잠시 실행하는 경우
  • 웹사이트, GitHub, 이메일, DMG 등으로 일반 사용자에게 배포하는 경우

두 상황의 해결책은 같지 않다.

먼저 확인할 핵심 결론

상황적절한 대응
출처를 모르는 앱이 “Mac을 손상시킴” 또는 악성코드 경고를 표시우회하지 말고 삭제하거나 개발자에게 확인
내가 방금 빌드한 앱이 테스트 Mac에서 차단서명·격리 속성·Gatekeeper 판정을 진단
신뢰할 수 있는 미공증 앱을 한 번 실행시스템 설정의 그래도 열기를 우선 사용
일반 사용자에게 외부 배포Developer ID 서명, Hardened Runtime, 공증, 검증이 필요
공증까지 했는데 일부 Mac에서 실패실제 배포 파일과 공증 파일이 같은지, 중첩 코드 서명이 완전한지 확인

Apple의 최신 지원 문서는 “손상됨” 경고가 실제 손상이나 변조를 뜻할 수도 있다고 안내한다. 따라서 인터넷에서 받은 알 수 없는 앱에 xattr 명령을 적용하는 방식은 권장할 수 없다.

Gatekeeper가 검사하는 것

Gatekeeper는 App Store 밖에서 받은 앱, 플러그인, 설치 패키지가 신뢰할 수 있는지 확인하는 macOS 보안 체계다. Apple의 Gatekeeper 보안 안내에 따르면 다운로드한 소프트웨어를 처음 열 때 다음 내용을 확인한다.

  • 등록된 개발자가 서명했는가
  • Apple 공증을 받았는가
  • 서명 이후 코드가 변경되지 않았는가
  • 알려진 악성 콘텐츠가 없는가
  • 사용자가 실제로 실행하려던 파일인가

브라우저나 메신저, 클라우드 저장소에서 내려받은 파일에는 출처 정보가 붙는다. 이 때문에 같은 .app이라도 빌드 폴더에서 직접 실행할 때는 열리고, ZIP으로 올렸다가 다시 다운로드하면 차단될 수 있다.

이 차이는 유용하다. 배포 파일을 실제 다운로드 경로로 시험해야 사용자 환경에서 발생할 문제를 발견할 수 있기 때문이다.

경고 문구부터 구분하기

macOS 경고는 버전과 원인에 따라 표현이 달라진다.

“인터넷에서 다운로드한 앱입니다. 열겠습니까?”

정상적으로 서명·공증된 앱도 처음 실행할 때 이 확인창이 나타날 수 있다. 사용자가 의도한 다운로드인지 확인하는 정상 절차다.

“개발자를 확인할 수 없습니다” 또는 “악성 소프트웨어를 확인할 수 없습니다”

개발자 서명이나 공증을 확인할 수 없다는 의미다. 내가 만든 테스트 앱이고 변조되지 않았음을 알고 있다면 일시적으로 실행을 허용할 수 있다.

“앱이 손상되어 열 수 없습니다”

서명이 깨졌거나 앱이 서명 후 변경됐을 가능성이 있다. 전송·압축 과정에서 실제로 손상됐을 수도 있다. 우회하기 전에 codesign 검증부터 해야 한다.

“Mac을 손상시킵니다” 또는 악성코드 감지

Apple은 악성 콘텐츠나 취소된 인증이 감지될 때 이런 경고를 표시한다고 설명한다. 이 경우에는 Open Anyway나 격리 속성 제거로 우회하지 않는다.

안전한 로컬 테스트 순서

내가 직접 빌드했거나 해시와 전달 경로를 확실히 신뢰하는 앱에만 다음 순서를 사용한다.

1. 시스템 설정의 “그래도 열기”를 먼저 사용한다

앱을 한 번 열어 경고를 확인한 다음 다음 경로로 이동한다.

  1. 시스템 설정
  2. 개인정보 보호 및 보안
  3. 아래쪽 보안 영역
  4. 차단된 앱 옆의 그래도 열기
  5. 인증 후 다시 열기

Apple 사용 설명서에 따르면 이 버튼은 앱 실행을 시도한 뒤 약 한 시간 동안 표시될 수 있다. 조직에서 관리하는 Mac이라면 MDM 정책 때문에 선택지가 보이지 않을 수도 있다.

2. 격리 속성과 서명을 확인한다

먼저 격리 속성 존재 여부를 본다.

xattr -l "/Applications/MyApp.app"

서명 구조와 무결성을 검증한다.

codesign --verify --deep --strict --verbose=4 "/Applications/MyApp.app"

서명 상세 정보를 확인한다.

codesign -dv --verbose=4 "/Applications/MyApp.app"

Gatekeeper의 현재 판정도 확인한다.

spctl --assess --type execute --verbose=4 "/Applications/MyApp.app"

대표적인 결과는 다음과 같이 해석할 수 있다.

결과의미
code object is not signed at all코드 서명이 없음
a sealed resource is missing or invalid서명 후 내부 파일이 변경·누락됨
rejected, no usable signatureGatekeeper가 사용할 수 있는 서명을 찾지 못함
accepted, source=Notarized Developer IDDeveloper ID 서명과 공증이 인정됨

codesign --deep검증할 때 중첩 코드를 재귀 확인하는 용도로 사용할 수 있다. 배포용 코드를 서명할 때 무조건 --deep을 사용하는 것과는 다르다.

3. 격리 속성 제거는 신뢰할 수 있는 테스트 앱에만 쓴다

내가 만든 앱이고 파일이 변조되지 않았음을 확실히 아는 경우, 테스트 목적으로 격리 속성을 제거할 수 있다.

xattr -dr com.apple.quarantine "/Applications/MyApp.app"
open "/Applications/MyApp.app"

이 명령은 앱을 올바르게 서명하거나 공증하는 것이 아니다. 단지 다운로드 출처에 따른 검사 트리거를 제거한다. 따라서 사용자에게 “실행이 안 되면 이 명령을 입력하라”고 안내하는 것은 배포 문제를 사용자에게 떠넘기는 방식이다.

4. Ad-hoc 서명은 마지막 로컬 진단 수단이다

서명이 전혀 없는 자체 빌드가 실행 가능한지만 확인하려면 임시 Ad-hoc 서명을 사용할 수 있다.

codesign --force --deep --sign - "/Applications/MyApp.app"

이 방식은 기존 서명을 바꾸며, 일반 사용자에게 신뢰 가능한 개발자 신원을 제공하지 않는다. 복잡한 앱에서는 권한과 중첩 서명을 잘못 덮을 수 있으므로 배포용 해결책으로 사용하면 안 된다.

Developer ID 인증서만 받으면 해결될까?

아니다. Developer ID Application 인증서는 “이 코드를 누가 서명했는가”를 증명하는 수단이다. 인증서를 발급받았다는 사실만으로 기존 앱에 서명이 자동 적용되거나 공증되는 것은 아니다.

App Store 밖으로 안정적으로 배포하려면 일반적으로 다음 흐름이 필요하다.

완성된 앱 빌드
→ 중첩 실행 코드부터 안쪽에서 바깥쪽으로 서명
→ 최상위 앱 서명
→ 서명 검증
→ ZIP·DMG·PKG 패키징
→ Apple 공증 제출
→ 승인 결과와 로그 확인
→ 공증 티켓 스테이플링
→ 최종 배포 파일 재생성
→ 실제 다운로드 환경에서 테스트

Apple은 Developer ID 배포 안내에서 App Store 밖 앱에는 Developer ID 서명과 공증을 사용하도록 안내한다. 공증은 App Store 심사와 같지 않다. Apple 서비스가 알려진 악성코드와 흔한 코드 서명 문제를 자동 검사하고 티켓을 발급하는 절차다.

정식 배포 절차

1. 인증서와 개인 키를 확인한다

Apple Developer Program에서 Developer ID Application 인증서를 발급하고, 서명하는 Mac의 Keychain에 인증서와 대응하는 개인 키가 함께 있어야 한다.

security find-identity -v -p codesigning

정상적인 identity는 다음과 비슷하게 보인다.

Developer ID Application: Developer Name (TEAMID)

PKG 설치 프로그램은 앱 서명과 다른 Developer ID Installer 인증서가 필요하다.

2. Hardened Runtime과 필요한 Entitlement를 설정한다

공증에는 Hardened Runtime이 필요하다. Xcode 프로젝트라면 Signing & Capabilities에서 활성화하고, 앱 기능에 실제로 필요한 entitlement만 추가하는 것이 가장 안전하다.

커스텀 빌드에서 단순 앱을 CLI로 서명하는 기본 형태는 다음과 같다.

codesign \
  --force \
  --options runtime \
  --timestamp \
  --sign "Developer ID Application: Developer Name (TEAMID)" \
  "MyApp.app"

Electron의 JIT, Helper 앱, XPC Service, 로그인 항목처럼 구조가 복잡하면 프레임워크의 공식 빌드·서명 도구를 우선 사용해야 한다. entitlement를 넓게 추가하는 대신 실제 실행 기능에 필요한 예외만 설정한다.

3. 중첩 코드는 안쪽에서 바깥쪽으로 서명한다

.framework, .dylib, Helper 앱, XPC Service, 플러그인, 네이티브 모듈, 독립 실행 파일은 각각 적절한 entitlement로 먼저 서명한다. 마지막에 최상위 .app을 서명한다.

Apple의 배포용 코드 서명 문서는 서명 작업에서 codesign --deep을 사용하지 말라고 명시한다. 같은 옵션과 entitlement가 모든 중첩 코드에 잘못 적용되거나, 비표준 위치의 코드를 놓칠 수 있기 때문이다.

4. 서명을 검증한다

codesign --verify --deep --strict --verbose=4 "MyApp.app"
codesign -dv --verbose=4 "MyApp.app"

Authority, TeamIdentifier, Timestamp, Runtime과 entitlement를 확인한다. 이 단계가 실패하면 패키징이나 공증으로 넘어가지 않는다.

5. 공증용 파일을 만든다

ZIP을 사용한다면 앱 번들 구조와 메타데이터를 유지하도록 ditto를 쓸 수 있다.

ditto -c -k --keepParent "MyApp.app" "MyApp.zip"

공증 서비스는 ZIP, DMG, PKG를 받을 수 있다. 실제 배포 형식에 맞는 파일을 제출하는 편이 재현성을 높인다.

6. notarytool 인증 정보를 Keychain에 저장한다

Apple Account 방식은 일반 비밀번호가 아니라 앱 암호를 사용한다.

xcrun notarytool store-credentials "myapp-notary" \
  --apple-id "developer@example.com" \
  --team-id "YOUR_TEAM_ID" \
  --password "APP_SPECIFIC_PASSWORD"

CI에서는 App Store Connect API Key도 사용할 수 있다. Apple의 notarytool 워크플로 문서는 두 인증 방식과 Keychain profile 사용법을 설명한다.

7. 제출하고 로그를 확인한다

xcrun notarytool submit "MyApp.zip" \
  --keychain-profile "myapp-notary" \
  --wait

결과가 Accepted가 아니면 submission ID로 로그를 확인한다.

xcrun notarytool log "SUBMISSION_ID" \
  --keychain-profile "myapp-notary"

자주 발생하는 원인은 다음과 같다.

  • 서명되지 않은 중첩 실행 코드
  • Hardened Runtime 누락
  • secure timestamp 누락
  • 잘못된 인증서
  • 부적절한 entitlement
  • 서명 후 파일 변경
  • 잘못된 번들 구조

Apple의 공증 문제 해결 문서는 로그의 구체적인 오류부터 고치는 방식을 권장한다.

8. 공증 티켓을 스테이플링하고 다시 패키징한다

ZIP으로 앱을 제출했다면 승인 후 원본 앱에 티켓을 스테이플링한다.

xcrun stapler staple "MyApp.app"
xcrun stapler validate "MyApp.app"

ZIP 자체에는 티켓을 스테이플링하지 않는다. 스테이플링된 앱으로 최종 ZIP을 다시 만든다.

ditto -c -k --keepParent "MyApp.app" "MyApp-final.zip"

Stapling은 Mac이 Apple 서버에 연결할 수 없는 상황에서도 공증 티켓을 확인하도록 돕는다. DMG나 PKG를 최종 배포한다면 해당 컨테이너의 공증·스테이플링도 배포 흐름에 맞게 처리한다.

9. 최종 파일을 다시 검증한다

codesign --verify --deep --strict --verbose=4 "MyApp.app"
spctl --assess --type execute --verbose=4 "MyApp.app"
xcrun stapler validate "MyApp.app"

그다음 실제 배포 URL에서 파일을 다운로드해 실행한다. 개발 폴더의 원본만 실행해서는 Gatekeeper 경로를 재현할 수 없다.

공증했는데도 “손상됨”이 나오는 흔한 이유

서명 후 파일이 바뀌었다

아이콘, 설정 파일, JavaScript bundle, 실행 권한, framework를 서명 뒤에 변경하면 봉인된 리소스 해시가 달라진다. 코드 서명은 빌드가 완전히 끝난 다음 수행해야 한다.

공증한 파일과 배포한 파일이 다르다

공증 후 다시 빌드했거나 다른 ZIP을 업로드한 경우다. 공증 승인부터 최종 패키징까지 artifact를 명확히 추적해야 한다.

내부 Helper나 framework 서명이 빠졌다

최상위 앱만 서명해도 codesign 명령이 성공한 것처럼 보일 수 있다. --deep --strict 검증과 공증 로그로 중첩 코드 문제를 찾는다.

인증서만 있고 개인 키가 없다

다른 Mac으로 .cer 파일만 옮기면 인증서 이름은 보여도 서명할 수 없다. 인증서와 개인 키가 함께 있는 signing identity가 필요하다.

entitlement가 빌드 구조와 맞지 않는다

JIT나 library validation 예외가 필요한 런타임도 있지만, 불필요한 권한은 보안을 약화하고 문제 분석을 어렵게 한다. 프레임워크 공식 설정과 공증 로그를 기준으로 최소 권한만 적용한다.

앱 번들 구조가 잘못됐다

실행 코드가 예상하지 않은 폴더에 있거나 Info.plist의 실행 파일 이름과 실제 파일이 다르면 Gatekeeper 경고처럼 보이는 실행 실패가 발생할 수 있다.

배포 전 깨끗한 환경에서 테스트하기

개발자의 Mac에는 이전 실행 허용 기록, 인증서, 격리 속성 제거, 캐시가 남아 있다. 그래서 개발 Mac에서 열린다는 사실만으로 배포 성공을 판단하면 안 된다.

최종 테스트에는 다음 조건을 포함한다.

  • 앱을 설치한 적 없는 사용자 계정 또는 Mac
  • 개발 인증서가 설치되지 않은 환경
  • 실제 브라우저 다운로드
  • 기본 Gatekeeper 설정
  • 온라인과 오프라인 실행
  • 지원 대상 Apple Silicon·Intel 환경
  • 지원하는 최소 macOS 버전
  • 자동 업데이트를 사용한다면 업데이트 artifact까지 별도 검증

가상 머신은 반복 테스트에 유용하지만, 시스템 확장, 로그인 항목, 권한 요청, 보안 하드웨어 연동이 있다면 실제 Mac에서도 확인한다.

실무 체크리스트

로컬 테스트

  • 경고가 단순 미확인 개발자 경고인지 악성코드 경고인지 구분
  • codesign --verify --deep --strict로 서명 확인
  • spctl --assess로 Gatekeeper 판정 확인
  • 신뢰할 수 있는 자체 빌드일 때만 그래도 열기 또는 격리 속성 제거
  • Ad-hoc 서명을 배포용 서명으로 오해하지 않기

외부 배포

  • Developer ID Application identity와 개인 키 확인
  • Hardened Runtime과 최소 entitlement 적용
  • 중첩 코드를 안쪽부터 서명
  • 최상위 앱 서명 후 검증
  • 최종 artifact를 notarytool로 공증
  • 승인 티켓 스테이플링
  • 스테이플링된 앱으로 최종 ZIP·DMG·PKG 생성
  • 실제 다운로드와 깨끗한 Mac 환경에서 테스트

정리

“앱이 손상되어 열 수 없습니다”라는 경고는 실제 파일 손상일 수도 있고, Gatekeeper가 서명·공증·무결성을 확인하지 못한 결과일 수도 있다. 먼저 경고 종류와 codesign, spctl 결과를 확인해야 한다.

내가 만든 앱을 잠시 테스트할 때는 시스템 설정의 그래도 열기가 가장 안전한 우선순위다. xattr 제거와 Ad-hoc 서명은 출처와 무결성을 확실히 아는 로컬 빌드에서만 제한적으로 사용한다.

일반 사용자에게 배포할 때의 해법은 우회 명령이 아니다. 완성된 artifact를 올바른 순서로 서명하고, Hardened Runtime을 적용하고, Apple 공증과 티켓 스테이플링을 거친 바로 그 파일을 배포하는 것이 핵심이다.

Apple 공식 자료