본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 15. 15:23

파일 삭제를 실수하기 어렵게 만드는 방법: 안전을 최우선으로 하는 Windows 클리너, Sifty 제작기

요약

사용자의 실수와 보안 위협을 방지하기 위해 설계된 오픈 소스 Windows 유지 관리 도구 Sifty의 제작기를 소개합니다. 텔레메트리가 없고, 삭제 전 미리보기를 지원하며, 로컬 AI 어시스턴트 기능을 포함한 안전한 CLI/TUI 도구입니다.

핵심 포인트

  • 기존 클리너의 보안 취약점과 데이터 삭제 위험을 해결하기 위해 설계됨
  • MIT 라이선스의 오픈 소스이며 텔레메트리 없는 투명한 동작 보장
  • 임시 파일 정리, 중복 파일 탐색, winget 연동 등 다양한 관리 기능 제공
  • Ollama를 활용한 로컬 AI 어시스턴트 기능 탑재

파일 삭제를 실수하기 어렵게 만드는 방법: 안전을 최우선으로 하는 Windows 클리너, Sifty 제작기

터미널용 무료 오픈 소스 (open-source) Windows 유지 관리 도구 — 그리고 파일을 삭제하는 프로그램을 신뢰하기 위한 설계 결정들.

저는 정리 도구(cleanup tools)를 신뢰하지 않습니다. 제가 도구를 만들었다고 말씀드리기 직전에 이런 말을 하는 것이 어색할 수도 있지만, 이것이 바로 제가 이 도구를 만든 이유 그 자체이기도 합니다.

이 카테고리는 평판에 문제가 있습니다. 가장 유명한 Windows 클리너는 한 버전에서 번들로 포함된 크립토마이너 (cryptominer)를 배포했고, 다른 버전에서는 공급망 공격 (supply-chain attack)을 당해 침해되었습니다. 대부분의 도구는 데이터를 서버로 전송(phone home)하고, 자신들이 만들어낸 문제를 해결하기 위해 "Pro" 등급을 업셀링(upsell)하며, — 실제로 저를 무섭게 만드는 부분인데 — 파일을 영구적으로 삭제합니다. "Clean"을 클릭하면 진행 표시줄이 차오르고, 프로그램이 쓰레기라고 판단한 것은 무엇이든 사라집니다. 휴지통(Recycle Bin)도 없고, 되돌리기(undo)도 없습니다. 당신은 되돌릴 수 없는 상태에서 폐쇄 소스 바이너리 (closed-source binary)의 판단을 신뢰해야 합니다.

그래서 저는 Sifty를 작성했습니다. Sifty는 터미널에서 실행되는 Windows 10/11 유지 관리 도구로, MIT 라이선스이며, 텔레메트리 (telemetry)가 전혀 없고, 실수로 잘못된 것을 삭제하는 것조차 의도적으로 하기 어렵도록 처음부터 설계되었습니다. 이 포스트는 마지막 부분, 즉 설계와 코드에 관한 것입니다.

실제로 무엇을 하는가

나머지 내용을 이해하기 쉽게 빠르게 설명하겠습니다. Sifty는 다음과 같은 기능을 수행하는 스크립트 작성이 가능한 CLI (Command Line Interface) 및 전체 화면 TUI (Text User Interface)입니다:

  • 쓰레기 및 캐시 정리 (임시 파일, 브라우저 캐시, 크래시 덤프, 업데이트 잔여물 — 11개 이상의 카테고리)
  • 중복 파일 찾기 (SHA-256 사용, NTFS를 인식하여 하드링크 (hardlinks)가 중복 계산되지 않음) 및 용량을 가장 많이 차지하는 파일 찾기
  • 설치된 앱, 시작 프로그램, 서비스 및 업데이트 관리 (winget을 통해 수행)
  • 개발용 머신에 쌓이는 불필요한 요소 제거 — node_modules, dist, __pycache__, 고립된 git worktrees, 비대해진 WSL2 가상 디스크
  • Ollama를 통해 **로컬 (locally)**에서 실행되는 선택적 AI 어시스턴트 기능

Sifty demo

pipx install sifty
sifty checkup              # 모든 항목에 대한 1회성 읽기 전용 스캔
sifty junk clean           # 삭제될 항목 미리보기 (dry-run)
...

하지만 기능 목록이 흥미로운 부분은 아닙니다. 진짜 흥미로운 부분은 제가 스스로에게 부여한 제약 조건입니다. 파일을 삭제하는 도구는 "실수"가 허용될 여지가 없어야 합니다. 이 제약 조건이 코드를 어떻게 형성했는지 소개합니다.

원칙 1: 무언가를 삭제하는 방법은 단 하나뿐이다

전체 코드베이스에서 가장 중요한 단 하나의 설계 규칙은 이것입니다: Sifty의 그 어떤 것도 os.remove, os.unlink, shutil.rmtree 또는 Path.unlink를 호출하지 않습니다. 절대로 말이죠. 애플리케이션 전체의 모든 삭제 작업은 단 하나의 함수인 safety.trash()를 통과합니다:

def trash(
    path: str | Path,
    allow_subtrees: Sequence[str | Path] = (),
...

여기에는 세 가지 핵심적인 요소가 담겨 있으며, 이 모든 것이 저 몇 줄의 코드 안에 들어 있습니다:

  1. 망각이 아닌 휴지통으로 경로를 지정합니다. send_to_trash는 이 프로젝트에서 Send2Trash 라이브러리를 호출하는 유일한 지점입니다. 만약 Sifty가 실수를 하더라도, 파일은 휴지통에 남아 있어 다시 끌어올 수 있습니다. 영구 삭제는 코드 경로상에 존재하지 않습니다.

  2. dry_run=True파라미터의 기본값입니다. 단순히 전달하는 것을 잊지 말아야 할 플래그가 아닙니다. 아무것도 하지 않았을 때 얻게 되는 결과가 바로 안전한 동작입니다. 실제로 삭제하려면 호출자가 명시적으로 안전 모드에서 벗어나야(opt out) 합니다. 이는 일반적인 위험 요소를 역전시킵니다. 인자를 잊어버리면 Sifty는 덜 조심스러워지는 것이 아니라, 조심스러워집니다.

  3. 모든 실제 삭제 작업은 감사(audit)됩니다. audit() 함수는 %APPDATA%\sifty\audit.log에 타임스탬프가 찍힌 줄을 추가합니다. 만약 "이 도구가 무엇을 건드렸지?"라는 의문이 생긴다면, 기록(paper trail)이 남아 있습니다.

이것이 유일한 삭제 경로이기 때문에, 저는 단 하나의 함수를 논리적으로 검토함으로써 프로그램 전체에 대해 빈틈없는 보장을 제공할 수 있습니다. 그리고 저는 이를 아주 단순하고 확실한 방식으로 강제합니다. 즉, 테스트 코드가 소스 트리(source tree)를 검색(grep)하여, 만약 이 파일 외부의 어딘가에서 os.remove/rmtree/unlink가 발견되면 CI(지속적 통합)를 실패하게 만드는 방식입니다. 새로운 기능을 위한 PR(Pull Request)을 보낼 때 실수로 가공되지 않은(raw) 삭제 코드를 다시 도입할 수 없습니다. 빌드가 빨간색(실패)으로 변하기 때문입니다.

원칙 2: 어떤 상황에서도 거부되는 경로가 있다

휴지통(Recycle Bin)은 영구적인 손실로부터 당신을 구해 주지만, C:\Windows를 휴지통으로 보내는 것은 여전히 컴퓨터를 벽돌(brick)로 만듭니다. 따라서 무엇인가가 폐기되기 전에 assert_safe()를 거치게 되며, 이 함수는 is_protected()에 확인을 요청합니다. 실제 판단이 이루어지는 곳은 바로 여기이며, 이는 제가 올바르게 구현하기 위해 몇 번의 반복 과정을 거친 2단계 모델입니다.

그리고 두 루프 모두에 있는 _is_relative_to(root, target) 체크에 주목하세요. 이것이 바로 조상 보호 장치(ancestor guard)입니다. C:\를 대상으로 지정하면, 이 체크는 보호된 루트(C:\Windows)가 사용자의 대상 하위에 존재함을 감지하고 거부합니다. 이를 통해 "부모 디렉터리 삭제"라는 보안 허점을 차단합니다.

핵심적인 안전 속성: 이 체크는 --apply --yes 옵션을 사용하더라도 실행됩니다. 이를 무시할 수 있는 플래그나 --force, 혹은 "정말로 실행하겠다"는 식의 예외 옵션은 없습니다. 보호된 경로는 Sifty에 의해 단순히 삭제할 수 없습니다. 그것으로 끝입니다.

하지만 클리너는 시스템 폴더를 건드려야만 합니다

여기에 모순이 존재합니다. 클리너의 목적 자체가 콘텐츠 보호 루트(contents-protected root) 내부에 위치한 C:\Windows\Temp를 비우는 것이기 때문입니다. 무조건적인 거부는 기능을 차단하게 됩니다.

그 문제를 해결하는 것이 바로 allow_subtrees입니다. 이는 특정 모듈이 특정 서브트리(subtree)에 대해 **보증(vouches)**을 서는, 호출 단위의 예외 허용 방식입니다. 정크 클리닝(junk-cleaning) 모듈이 allow_subtrees=[r"C:\Windows\Temp"]를 전달하면, 그제서야 해당 폴더 하나만 허용되며 나머지 C:\Windows 영역은 잠긴 상태로 유지됩니다. 이 권한은 좁고 명시적이며, 전역 허용 목록(allowlist)에 묻혀 있는 것이 아니라 사람이 괜찮다고 결정한 호출 지점(call site)에 존재합니다. 즉, 감사가 가능한 예외를 허용하되 기본적으로는 거부(Default-deny)하는 방식입니다.

원칙 3: AI는 권고적이며, 로컬에서 작동하고, 파일 내용에는 관여하지 않습니다

수많은 "AI 기반" 도구들은 사실 "당신의 데이터를 클라우드 모델로 전송한다"는 의미입니다. 저는 모든 측면에서 그 반대를 원했습니다.

Sifty의 선택적 어시스턴트는 사용자의 기기에서 실행되는 로컬 모델인 **Ollama**에서 작동합니다. 어떤 데이터도 컴퓨터 외부로 나가지 않습니다. 하지만 "로컬"이라는 조건만으로는 충분하지 않았기에, 저는 세 가지 엄격한 제한 사항을 두었습니다:

  • 오직 메타데이터(metadata)만 확인합니다. 어드바이저(advisor)는 파일의 _이름, 크기, 경로_를 바탕으로 프롬프트(prompt)를 구성하며, 파일의 _내용(contents)_은 절대 확인하지 않습니다. 모델은 다운로드 폴더에 있는 40GB 용량의 .mp4 파일들이 아마도 삭제하여 공간을 확보할 수 있을 것이라고 추론할 수는 있지만, 문서의 내용은 프롬프트에 포함되지 않으므로 읽을 수 없습니다.
  • 그 어떤 것도 삭제할 수 없습니다. AI는 trash() 함수에 접근 권한이 없습니다. AI는 조언만 합니다. 설명하고 권장할 뿐, 그것이 AI 권한의 전부입니다.
  • 제안하는 모든 동작에는 사용자의 클릭이 필요합니다. 이 시스템은 에이전틱(agentic)합니다. 스캔이나 정리 작업을 _제안_할 수는 있지만, 제안된 각 도구 호출(tool call)은 대화창 내에 실행/건너뛰기(Run / Skip) 버튼으로 나타납니다. AI는 제안하고, 결정은 사용자가 합니다. AI가 스스로 행동하는 일은 절대 없습니다.

제가 유지한 사고 모델(mental model)은 다음과 같습니다: AI는 키보드 위에 놓인 손이 아니라, 당신 옆에 앉아 있는 박식한 조언자입니다.

이것이 CLI를 상단에 둔 라이브러리인 이유

안전성과 기여자(contributor) 모두에게 이득이 되는 결정이 하나 더 있습니다. Sifty는 계층화(layered)되어 있으며, 각 계층은 오직 한 방향으로만 가리킵니다.

cli/  tui/        <- 얇은 프론트엔드 (Typer, Textual)
        |
       core/      <- 엔진: junk, disk, apps, safety, ...  (UI 코드 없음)
...

프론트엔드는 core를 호출하고, corewindows/infra를 호출합니다. 상위 계층을 임포트(import)하는 것은 아무것도 없으며, OS별 호출은 windows/ 내에 격리되어 있습니다. CLI와 TUI는 의도적으로 단순하게 설계되었습니다. 이들은 인자(arguments)를 파싱하고 결과를 출력할 뿐입니다. 중요한 모든 로직은 junk.scan()이나 disk.find_duplicates()와 같이 일반적이고 테스트 가능한 함수 형태로 존재합니다.

두 가지 이점은 다음과 같습니다:

  • 안전 보장 기능은 독립적으로 테스트 가능합니다. 보호된 경로(protected paths)에 대한 테스트는 터미널 근처에도 가지 않습니다. 환경(SystemRoot, ProgramFiles, Path.home)을 monkeypatch하고 모든 것을 tmp_path 샌드박스로 지정하므로, 보호된 경로 로직이 결정론적(deterministic)으로 실행됩니다. 덕분에 Sifty가 Windows 도구임에도 불구하고 CI의 Linux 러너에서 테스트 스위트가 통과됩니다. 안전 계층(safety layer)은 저장소 내에서 압도적으로 가장 많이 테스트되는 코드입니다.
  • 미래의 GUI는 재작성이 아닌 프론트엔드 교체에 불과합니다. GUI는 동일한 core 함수를 호출하게 되며, 동일한 단일 삭제 경로와 동일한 보호 기능을 그대로 물려받게 됩니다.

삭제 기능을 만드는 모든 이들에게 전하고 싶은 말

전이 가능한 교훈이 하나 있다면 바로 이것입니다: 안전한 것을 기본값(default)으로 만들고, 위험한 것을 요란하게 만드세요. 구체적으로 효과가 있었던 조치들은 다음과 같습니다:

  1. 단일 초크 포인트(One choke point). 모든 파괴적인 동작을 단일 함수를 통하도록 집중시키고, 이를 기계적으로 강제하세요(원시 호출을 검색하는 테스트를 활용). 신뢰해야 할 백 개의 호출 지점보다, 감사(audit)할 수 있는 하나의 함수가 훨씬 낫습니다.
  2. 정확성보다 가역성(Reversibility). 저는

저장소는 **github.com/Vortrix5/sifty**이며, 저는 진심으로 이 안전 모델(safety model)을 검토해 주시기를 바랍니다. 특히 삭제해서는 안 될 무언가를 삭제할 수 있는 방법을 생각해낼 수 있는 분이라면 더욱 환영합니다 (이를 위한 SECURITY.md 파일이 마련되어 있습니다). 기여자(contributor)에게는 항상 열려 있으며, 테스트 스위트(test suite)는 빠르고 크로스 플랫폼(cross-platform)을 지원합니다. 또한 CONTRIBUTING.md를 통해 단 두 번의 명령어로 바로 시작할 수 있습니다.

사용자가 망가뜨리기 전에 먼저 망가뜨려 보세요. 유용했다면 ⭐를 눌러주세요.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0