필요한 실습 파일만 ZIP에 넣고 내용 목록을 검산하기

이 패키지는 자기 소유의 통제된 연습 폴더에서 allowlist로 새 ZIP을 작성하는 실습입니다.
실제 Downloads, 업무 폴더, 동기화 폴더, 네트워크 드라이브에는 적용하지 마세요.
추가 패키지, API 키, 인터넷 연결은 필요 없습니다. Python 3.12를 사용합니다.
실제 확인 환경은 Python 3.12.14 / Linux입니다. Windows 실행은 확인하지 않았습니다.
하드 링크를 지원하는 로컬 파일시스템이 필요하며 미지원 오류 시 중단합니다.

실행 순서
1. 제공 ZIP을 새 빈 연습 폴더에 직접 풉니다. 프로그램은 압축 해제를 하지 않습니다.
2. README.txt가 있는 폴더를 터미널의 현재 폴더로 정합니다. 원본 다운로드는 보존합니다.
3. 다음 명령을 한 줄씩 실행합니다. python이 다른 버전이면 Python 3.12 실행 파일을 선택합니다.

python --version
python check_sha256.py
python -m unittest -v
python pack_allowlist.py --source source --allowlist allowlist.json --output practice.zip
python pack_allowlist.py --source source --allowlist allowlist.json --output practice.zip --check

정상 결과
- SHA256SUMS.txt의 모든 항목이 일치해야 합니다. 이 목록은 자기 자신을 제외합니다.
- unittest 22개가 OK여야 합니다. junction 검사는 Linux에서 분기만 실패 주입한 것이며 Windows 실측이 아닙니다.
- 새 practice.zip이 source 밖에 생깁니다. 작성 후 practice.zip.part는 남지 않습니다.
- ZIP 안 목록은 README.txt, input.csv, run.py 순서의 3개입니다. 폴더 항목은 없습니다.
- secret.txt 및 tmp/debug.txt는 결과 ZIP에 0개입니다. 두 파일의 문장은 가상 입력이며 실제 비밀이 아닙니다.
- 3파일의 합계는 347바이트입니다. JSON의 SHA-256을 아래 기준과 각각 대조하세요.
- source의 원래 5파일은 보존됩니다. 프로그램은 run.py를 실행하지 않습니다.
- --check는 기존 ZIP을 재열어 읽기만 하며 새 파일을 만들지 않습니다.
- expected/practice.zip은 실제 작성해 검산한 작은 예제 완성본입니다.
  expected/build_result.json과 expected/readback_result.json은 아래 기준과 같습니다.
- 이 전체 다운로드 ZIP에는 코드·테스트·제외용 가상 파일도 있습니다.
  배포 예제의 결과물은 직접 만드는 practice.zip이며 두 ZIP의 목적이 다릅니다.

고정 기대값 expected/package.json
{
  "count": 3,
  "members": [
    {
      "name": "README.txt",
      "bytes": 81,
      "sha256": "d67b7a006479a21af177e64748397cbc113c219fd4aa83122f335ee0fbb551ee"
    },
    {
      "name": "input.csv",
      "bytes": 23,
      "sha256": "ed09a12b651598dc1597fa551991a755c7d3dcaf98241c0e962c93f83b7d8554"
    },
    {
      "name": "run.py",
      "bytes": 243,
      "sha256": "ff69b00092d0858575246a9adeb957a1c8d87e2ce56cb0bd8a30780c93cfa4ea"
    }
  ],
  "extra_members": [],
  "crc": "OK",
  "source_unchanged": true
}


가상 입력과 허용 목록
source/README.txt: Synthetic practice package.로 시작하는 81바이트 텍스트
source/input.csv: item,qty 및 A100,3 / B200,2의 23바이트 CSV
source/run.py: input.csv 수량 합계를 출력하는 243바이트 선택 예제
source/secret.txt: 실제 비밀이 아닌 가상 제외 문장
source/tmp/debug.txt: 가상 제외용 디버그 문장
allowlist.json: ["README.txt", "input.csv", "run.py"]
원본은 UTF-8 / LF이며 마지막 줄바꿈도 해시에 포함됩니다. 편집기로 다시 저장하면 해시가 바뀔 수 있습니다.

새 파일 및 오류 복구 정책
- 모든 허용 입력의 이름·형식·링크·개수·크기 검증과 바이트 읽기를 마친 뒤에만 작업파일을 만듭니다.
- 파일 최대 16개, 각 1,048,576바이트, 합계 2,097,152바이트, allowlist 최대 16,384바이트입니다.
- ZIP 항목 이름은 / 구분의 상대경로만 허용합니다. .., 절대경로, 드라이브, 역슬래시,
  . 또는 빈 구성요소, 중복, casefold 충돌은 중단합니다.
  CON/PRN/AUX/NUL, COM1..9/LPT1..9, COM¹, COM², COM³, LPT¹, LPT², LPT³의 예약이름과 확장자도 거부합니다.
- 파일·원본폴더·상위폴더·allowlist·출력 상위폴더의 심볼릭 링크와 junction/reparse point를 거부합니다.
  디렉터리·FIFO·장치 등을 입력 파일로 읽지 않습니다.
- source 밖의 이미 있는 일반 폴더에 새 .zip만 작성합니다. 부모 폴더는 만들지 않습니다.
- 먼저 practice.zip.part를 배타적으로 만듭니다. 기존 .zip 및 .part는 절대 덮어쓰거나 지우지 않습니다.
- 작업파일을 닫고 재열어 목록·크기·CRC·바이트·SHA-256을 확인한 뒤에만 새 최종 이름을 만듭니다.
  같은 폴더의 작업파일을 os.link로 연결합니다. rename/replace 덮어쓰기 대체 경로는 없습니다.
- 잡을 수 있는 쓰기·검증·입력 재읽기 실패에는 이 실행이 만든 .part와 최종 ZIP을 정리하고 STOP합니다.
  정리 대상은 생성 당시 파일 식별자가 같은 파일뿐입니다. 다른 파일로 바뀌면 지우지 않습니다.
- 프로세스 강제 종료, 전원 차단, 권한 상실, 운영체제 정리 실패에는 파일이 남을 수 있습니다.
  STOP 또는 .part가 남으면 공유하지 마세요. 기존 파일은 보존하고 새 연습 폴더에 다시 풉니다.
- 같은 작성 명령의 재실행은 exit 1로 중단하고 이미 검증한 ZIP을 그대로 둡니다.
  기존 ZIP 재확인은 --check로 합니다. 다시 만들려면 --output practice_v2.zip처럼 새 이름을 쓰세요.
- source를 바꾸고 싶다면 별도 사본과 새 기대값을 검토해야 합니다. 실패를 숨기려고 해시 기준을 고치지 마세요.

범위
이 코드는 원본 폴더를 통째로 순회해 압축하거나, 파일을 이동·삭제하거나, 자동 추출·업로드하지 않습니다.
검산 통과는 이 허용목록과 읽은 바이트에 맞는 ZIP이라는 뜻입니다. 파일 내용의 배포 적합성·악성코드 여부,
작성자 인증, 제3자가 파일을 보지 않았다는 사실은 검사하지 않습니다.
여러 프로세스가 폴더를 바꾸는 race를 완전히 막는 보안 도구가 아닙니다. 다른 쓰기 작업을 멈춘 연습 사본만 사용하세요.
Windows junction 및 권한·잠금 동작은 실제 Windows 환경에서 별도 확인이 필요합니다.
이 이름 검사는 모든 Windows 경로 해석과 파일시스템 제약을 모사하지 않습니다.

파일 안내
pack_allowlist.py: 본문과 같은 전체 ZIP 작성 및 재검산 코드
check_sha256.py: 제공 패키지의 고정 SHA-256 목록 확인 코드
allowlist.json / source/: 가상 입력 5파일과 3개 허용 목록
expected/: 고정 기대값, 실제 작성한 예제 ZIP, 작성·재열기 JSON
version.txt / environment.json / test_log.txt: 실제 실행 환경과 22개 테스트 기록
SHA256SUMS.txt: 자기 자신과 실행 중 생기는 __pycache__·practice.zip을 제외한 제공 파일 SHA-256

공식 참고 자료 확인일 2026-10-05
https://docs.python.org/3.12/library/zipfile.html#zipfile.ZipFile
https://docs.python.org/3.12/library/zipfile.html#zipfile.ZipFile.writestr
https://docs.python.org/3.12/library/zipfile.html#zipfile.ZipFile.testzip
https://docs.python.org/3.12/library/hashlib.html
https://docs.python.org/3.12/library/pathlib.html#pathlib.Path.lstat
https://docs.python.org/3.12/library/pathlib.html#pathlib.Path.is_junction
https://docs.python.org/3.12/library/stat.html#stat.S_ISREG
https://docs.python.org/3.12/library/functions.html#open
https://docs.python.org/3.12/library/os.html#os.link
https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file
