<!doctype html>
2026년 6월 23일 | 개발 깨알 상식_Tips
압축 파일은 열기 전에는 작은 묶음처럼 보이지만, 안쪽 경로가 잘못 들어 있으면 해제하는 순간부터 다른 문제가 된다. 파일 하나를 받았을 뿐인데 현재 디렉터리 바깥에 쓰거나, 기존 설정 파일을 덮거나, 심하면 심볼릭 링크를 따라가 엉뚱한 위치를 건드릴 수 있다. 나는 압축 파일을 디버깅할 때 용량이나 확장자보다 아카이브 안에 적힌 경로 문자열을 먼저 본다.
이 문제는 거창한 보안 사고에서만 나오는 이야기가 아니다. 실험 결과 묶음, 외부 데이터 샘플, CI 산출물, 로그 번들처럼 평범한 파일에서도 생긴다. 만든 쪽은 작업 디렉터리를 그대로 묶었고, 받는 쪽은 당연히 안전한 상대 경로라고 생각한다. 그 사이에 절대 경로, 상위 디렉터리 이동, 이상한 링크가 끼면 “압축 해제”라는 사소한 단계가 곧바로 덮어쓰기 버그가 된다.
목록이 먼저 로그에 남아야 한다
압축 파일을 받으면 나는 바로 해제하지 않고 목록부터 남긴다. tar면 tar -tf, zip이면 zipinfo -1이나 unzip -l 정도면 충분하다. 중요한 것은 예쁜 표가 아니라, 실제 멤버 이름이 어떤 문자열로 들어 있는지를 보는 것이다.
tar -tf artifact.tar | head -n 40
zipinfo -1 artifact.zip | head -n 40
이 출력은 나중에 꽤 좋은 증거가 된다. 해제 후 파일이 어디 갔는지 찾기 전에, 애초에 아카이브가 어떤 경로를 품고 있었는지 확인할 수 있기 때문이다. 특히 자동화 배치에서는 목록 출력이 없으면 “압축 해제 중 덮었다”와 “원래 파일이 없었다”를 구분하기가 어렵다. 그래서 작은 파일이라도 목록 확인을 해제 전 단계로 따로 둔다.
목록을 남길 때는 처음 몇 줄만 보는 것으로 끝내지 않는다. 압축 파일이 크면 앞부분은 정상이고 중간 어딘가에만 이상한 경로가 숨어 있을 수 있다. 그래서 자동 검사에서는 전체 멤버 이름을 훑고, 사람이 확인할 로그에는 위험 후보만 따로 줄여서 남기는 편이 좋다. 정상 파일 2천 개 사이에 ../../config 하나가 섞이면, 화면에 보이는 첫 40줄은 거의 도움이 되지 않는다.
위험 신호는 파일명보다 경로 모양이다
위험한 이름은 대개 화려하지 않다. /etc/passwd처럼 절대 경로로 시작하거나, ../app/config.yml처럼 상위 디렉터리로 올라가거나, Windows 드라이브 문자처럼 보이는 접두어가 붙어 있다. 파일명 자체는 평범해도 경로 모양이 해제 위치를 빠져나가면 문제가 된다.
심볼릭 링크도 같이 봐야 한다. 어떤 아카이브는 먼저 링크를 만들고, 뒤에서 그 링크 아래 파일을 쓰게 구성될 수 있다. 그러면 목록만 대충 봤을 때는 상대 경로처럼 보이지만 실제 쓰기 위치는 바깥으로 빠질 수 있다. 그래서 단순히 “이름에 ..가 없나”만 보는 검사보다, 링크 항목과 디렉터리 항목까지 나누어 읽는 편이 낫다.
또 하나는 최상위 폴더가 있는지다. 어떤 아카이브는 project-a/...처럼 한 디렉터리 아래에 모든 파일을 넣지만, 어떤 파일은 루트에 바로 여러 파일을 흩뿌린다. 루트에 바로 풀리는 구조가 늘 위험한 것은 아니지만, 기존 작업 디렉터리와 이름이 겹치기 쉬워진다. 나는 산출물 번들이라면 최상위 폴더 하나 아래로 묶여 있는지까지 확인한다.
해제 위치는 새 디렉터리로 고정한다
안전하다고 생각한 파일도 현재 작업 디렉터리에서 바로 풀지 않는다. 이미 중요한 파일이 있는 곳에서 해제하면, 정상 상대 경로라도 같은 이름의 파일을 덮을 수 있다. 가장 단순한 방어는 매번 비어 있는 디렉터리를 새로 만들고 그 안에서 푸는 것이다.
mkdir -p unpack/run-001
tar -xf artifact.tar -C unpack/run-001
이렇게 해 두면 실패해도 치울 범위가 작다. 해제 결과를 보고 필요한 파일만 옮길 수 있고, 이상한 경로가 생겼을 때도 작업 디렉터리 전체를 의심하지 않아도 된다. 자동화 코드에서는 임시 디렉터리 이름에 run id나 날짜를 붙여 두면, 나중에 어떤 입력에서 나온 결과인지 추적하기도 쉽다.
특히 CI 산출물을 받아서 다음 단계 입력으로 넘기는 흐름에서는 이 격리가 더 중요하다. 이전 실행의 파일이 남아 있으면 새 압축 파일이 덜 풀렸는데도 테스트가 통과할 수 있다. 빈 디렉터리에 풀고 나서 파일 개수와 필수 파일 목록을 맞추면, 압축 해제 실패와 후속 처리 실패가 덜 섞인다. 해제 위치를 고정하는 일은 보안만이 아니라 재현성 문제이기도 하다.
검사 실패는 조용히 고치지 않는다
가끔은 위험 경로를 그냥 잘라 내고 풀고 싶은 마음이 든다. 앞의 ../를 지우거나 절대 경로의 앞부분을 제거하면 당장 파일은 꺼낼 수 있다. 하지만 그렇게 조용히 고치면 입력이 잘못됐다는 사실이 사라진다. 다음번에도 같은 아카이브가 들어오고, 받는 쪽만 계속 임시 보정을 하게 된다.
나는 이런 경우에는 해제 실패를 정상 동작으로 본다. “상위 디렉터리 이동 포함”, “절대 경로 포함”, “링크 대상이 작업 디렉터리 밖”처럼 짧은 사유를 남기고 중단한다. 이 메시지가 있어야 만든 쪽에 다시 요청할 수 있다. 입력 파일을 고치는 일과 받는 쪽에서 안전하게 거절하는 일은 같은 작업이 아니다.
내가 남기는 작은 기준
압축 파일 해제 전에는 네 가지만 본다. 첫째, 모든 멤버 경로가 상대 경로인지 확인한다. 둘째, ..로 상위 디렉터리를 타지 않는지 본다. 셋째, 심볼릭 링크가 작업 디렉터리 밖을 가리키지 않는지 확인한다. 넷째, 해제 위치가 비어 있는 새 디렉터리인지 확인한다.
검사 코드를 넣는 위치도 중요하다. 파일을 내려받은 직후, 해제 명령을 부르기 전에 멈출 수 있어야 한다. 해제한 뒤 결과 폴더를 검사하면 이미 덮어쓰기나 링크 생성이 끝난 뒤일 수 있다. 나는 그래서 “다운로드 완료 → 멤버 목록 검사 → 격리 디렉터리 해제 → 필수 파일 확인”을 한 덩어리로 묶어 둔다. 순서가 한 번 정해지면 다음 사람도 같은 기준으로 실패를 읽을 수 있다.
이 기준은 복잡한 보안 프레임워크라기보다 손에 붙는 작업 순서에 가깝다. 압축 파일을 바로 풀면 문제는 해제 후에 보이지만, 목록을 먼저 보면 문제의 절반은 문자열 단계에서 드러난다. 작은 파일 하나를 푸는 일이라도 경로를 먼저 의심하면, 덮어쓰기와 경로 이탈 때문에 생기는 불필요한 디버깅을 꽤 줄일 수 있다.
반대로 사람이 직접 받은 일회성 파일이라도 이 순서를 한 번 거치면 불안이 줄어든다. 압축을 풀고 나서야 이상한 파일을 발견하는 것보다, 풀기 전에 위험 이름을 보는 편이 훨씬 싸다. 작은 번들일수록 이 습관은 더 가볍게 붙는다.
'[개발 깨알 상식_Tips]' 카테고리의 다른 글
| SQLite 백업에서 빠진 WAL 로그 (0) | 2026.06.24 |
|---|---|
| HTTP 429와 Retry-After 헤더 (0) | 2026.06.23 |
| CSV 헤더 공백이 만든 컬럼 선택 오류 (0) | 2026.06.22 |
| 이미 잡힌 포트가 만든 서버 실행 오류 (0) | 2026.06.19 |
| JSONL 빈 줄과 BOM이 만든 배치 파싱 오류 (0) | 2026.06.18 |