Skip to content

Audit what stayed behind after an uninstall (moraine) - #78

Open
heznpc wants to merge 1 commit into
mainfrom
feat/moraine
Open

Audit what stayed behind after an uninstall (moraine)#78
heznpc wants to merge 1 commit into
mainfrom
feat/moraine

Conversation

@heznpc

@heznpc heznpc commented Aug 19, 2026

Copy link
Copy Markdown
Owner

소프트웨어를 지워도 그것이 등록한 것은 남습니다. 그중 문제가 되는 건 앱이 사라진 뒤에도 계속 머신에 작용하는 쪽입니다. 언인스톨보다 오래 사는 기록 두 개를 읽고 상관 판정을 냅니다.

소스 내용
pkgutil 영수증 설치된 적 있는 모든 패키지 + payload 생존 여부
트러스트 도메인 admin/user 스토어의 루트 인증서 + 신뢰 범위

핵심은 상관 판정입니다. 설치한 패키지의 payload가 사라진 채로 신뢰받는 루트 = 이미 머신에 없는 벤더를 여전히 보증하는 인증서. 어느 한 소스만으로는 못 내는 결론입니다.

이 맥에서의 첫 실행 결과

INNORIX.CA [admin] — orphaned — installing package has no payload left · all policies
  self-signed · CA:TRUE · 2048-bit | 2018-07-10 → 2028-07-07 (688d left)
  installed by: com.innorix.innorixexagent.ca.pkg (+8), 2026-05-01 13:35

한국 파일 전송 플러그인이 2026-05-01에 깔면서 넣은 자체서명 루트입니다. 앱·LaunchAgent·프로세스는 전부 사라졌는데 인증서만 2028년까지 무조건 신뢰 상태로 남아 있었습니다.

macOS 신뢰 규약의 함정을 명시적으로 다룹니다: 사용 제약 배열이 비어 있으면 "설정 안 됨"처럼 읽히지만 실제로는 모든 정책에 대한 루트 신뢰입니다. 정반대로 읽히는 값이라 코드에 이름을 붙이고 테스트로 고정했습니다.

영수증이 없는 루트는 unattributed로 보고하고 혐의를 씌우지 않습니다 — MDM 프로파일·기업 와이파이·수동 임포트는 영수증이 없는 게 정상입니다. 큰 패키지는 파일 샘플링하고 샘플링 사실을 항상 보고합니다. 아무것도 쓰지 않고 아무것도 지우지 않습니다.

선행 도구는 읽고 비교했습니다 (추측 아님)

AppCleaner mole moraine
방향 타깃 구동 (앱을 끌어다 놓음) 타깃 구동 + 카탈로그 청소 잔여물 구동 (주인이 없어도 시작)
pkgutil 영수증 지울 파일로 취급 lib/core/pkg_receipts.sh/usr/local·/opt 앱 탐색용, payload 생존 판정 없음 영수증마다 생존 판정
트러스트 스토어 바이너리에 SecTrust/SecCertificate 심볼 0건 dump-trust-settings 0건 핵심 소스
삭제 함 (확인 + dry-run) 안 함

AppCleaner의 위치 목록(BundlePaths.plist + 바이너리의 약 70개 디렉터리)은 실제로 잘 정비돼 있습니다. 다만 파일 스윕으로는 트러스트 스토어 내용에 닿지 못합니다.

README 정정도 포함

Orca(stablyai/orca)의 workspace cleanup이 scree와 같은 명령(rev-list --count HEAD --not --remotes)으로 미푸시 작업 판정을 합니다. src/main/ipc/workspace-cleanup-git-evidence.ts를 직접 받아 확인했습니다. 따라서 README의 "no other tool asks"는 워크트리 항목에 대해 거짓이었습니다.

  • 판정 기능은 그대로 둡니다. cleanup 안전 장치가 여기 얹혀 있고, canary 흡수 근거도 "scree가 이미 한다"였지 "아무도 안 한다"가 아니었습니다. 둘 다 남이 같은 걸 하느냐와 무관합니다.
  • 바뀐 건 근거 문장뿐입니다. 선행 도구를 실명으로 인정하고, 실제 차이(어느 도구로 돌렸든 머신 전체 스윕 + main 벗어난 primary checkout + 디렉터리가 사라진 레지스트리 항목)를 씁니다.

검증

  • pytest tests/ -q473 passed (moraine 25 + MCP 4번째 도구 4 신규)
  • compileall, release_smoke.py 통과
  • 실제 맥에서 CLI 구동 확인 (영수증 41, 트러스트 루트 2, orphaned 1)
  • MCP는 moraine_report로 노출, 등록 시점 읽기 전용 계약이 나머지 셋과 동일하게 게이트

Swift UI 노출은 범위 밖이라 봉인 런타임 제외 목록에 조건과 함께 등록했습니다.

Removing software does not remove what it registered, and the leftovers that
matter are the ones that keep acting on the machine after the app is gone.
Two records outlive the uninstall and neither is a file sweep:

  pkgutil receipts        every package ever installed, and whether its
                          payload still exists
  trust domains           root certificates in the admin/user stores, and how
                          broadly each is trusted

The verdict is the correlation. A trusted root whose installing package has no
payload left is a certificate still vouching for a vendor otherwise gone from
the machine, and neither source produces that alone.

First live run on this Mac found exactly that: INNORIX.CA, self-signed,
CA:TRUE, 2048-bit, valid to 2028-07-07, trusted unconditionally in the admin
domain -- installed 2026-05-01 by a Korean file-transfer plugin whose app,
LaunchAgent, and processes are all gone. macOS trust semantics are the trap
here: an empty usage-constraint array reads like "nothing configured" and
means the opposite, trust as root for every policy, so it is named explicitly
and pinned by a test.

A root with no owning receipt is reported "unattributed", never accused --
MDM profiles, enterprise Wi-Fi, and hand-imported roots legitimately have no
installer receipt. Large packages are file-sampled and any sampling is
reported. Nothing is written and nothing deleted; removing a trust root is an
admin act that stays a human decision.

Prior art was read rather than assumed. AppCleaner's location list is genuinely
vetted (BundlePaths.plist plus ~70 directories in its binary), but it is
target-driven -- you drop an app on it -- and its binary carries no SecTrust or
SecCertificate symbols, so trust-store contents are outside a file sweep.
mole reads pkgutil receipts too, in lib/core/pkg_receipts.sh, but only to
locate app bundles under /usr/local and /opt, never to judge whether a payload
survived.

Also corrects a claim in the README. Orca's workspace cleanup runs the same
rev-list --count HEAD --not --remotes check scree does, against the worktrees
it manages, so "no other tool asks" was false for the unpushed-work verdict.
The README now names the prior art and states the actual difference: scree
sweeps every agent worktree on the machine whatever created it, plus primary
checkouts stranded off main and registry entries whose directory vanished.
The judgment itself stays -- it is what cleanup's safety rests on, and the
canary absorption rested on scree already having it, neither of which depends
on nobody else having it too.

Exposed through the MCP surface as moraine_report, which the registration-time
read-only contract gates like the other three.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant