💡 전체 작동 예제는 GitHub에서 확인할 수 있습니다:
sanitize-office-document-pii-python

전송하기 전에 아무도 검토하지 않는 데이터

분기별 이사회 보고서는 외부 감사인에게 전달됩니다. 텍스트는 완벽합니다; 세 차례의 검토 사이클이 이를 보장했습니다. 파일 자체는 또 다른 이야기입니다. 파일 속성에는 여전히 초안을 작성한 분석가, 재작업한 관리자, 템플릿을 소유한 회사 자회사, 마감 전날 밤의 LastPrinted 타임스탬프, 내부 승인 워크플로우의 SharePoint 승인자 ID가 남아 있습니다. 이러한 내용은 어느 페이지에도 나타나지 않으며, 파일과 함께 이동합니다.

PII 제거는 .NET을 통해 Python에서 사용할 수 있는 GroupDocs.Metadata 워크플로우로, Word, Excel, PowerPoint 파일에서 이러한 신원 정보를 담고 있는 속성을 프로그래밍 방식으로 제거합니다. 이 문서에서는 API가 제공하는 세 가지 접근 방식을 비교합니다: 신원 필드를 위한 태그 기반 제거, 주석 및 수정 기록과 같은 속성 그룹을 위한 이름‑패턴 제거, 그리고 모든 것을 한 번에 정리하는 sanitize() 호출. 또한 대부분의 정화 스크립트가 건너뛰는 단계인, 정리가 실제로 이루어졌는지 확인하는 검증 스캔도 보여드립니다.

메타데이터 PII가 자체 파이프라인을 가져야 하는 이유

콘텐츠 검토 도구는 사람들이 읽는 내용을 확인합니다. 파일 시스템이 저장하는 내용을 확인하지는 않으며, 바로 이 격차가 컴플라이언스 사고의 원인이 됩니다. GDPR 요청은 텍스트와 마찬가지로 Author와 Manager 필드에 있는 개인 데이터를 포함합니다. 법적 디스커버리는 수정 횟수와 편집 시간 총계를 읽어 위치 논문이 얼마나 오래 협상되었는지 재구성합니다. 입찰 검토자는 SharePoint 워크플로우 속성에서 조직 구조를 파악할 수 있고, 보도 자료의 주석 필드는 초안 단계 의견과 함께 검토자 이름을 보존합니다. 각각이 발견 항목이며, 문서 본문에는 보이지 않습니다.

전제 조건

시작하기 전에 다음을 확인하십시오:

  • Python 3와 pip
  • .NET을 통해 Python용 GroupDocs.Metadata, 샘플 저장소에서 버전 26.5로 고정
  • 실제 속성이 포함된 Office 파일 (연습용)

설치

pip install groupdocs-metadata-net==26.5

companion repository는 샘플 DOCX를 제공하고 아래 모든 스니펫을 검증된 파이프라인으로 실행합니다.

방법 1: 태그 기반 신원 제거

가장 민감한 네 필드인 Author, LastSavedBy, Manager, Company는 Office 형식마다 내부 이름이 다릅니다. 태그 시스템은 이를 해결합니다: 속성 이름을 지정하는 대신, 사람이나 회사를 나타내는 모든 태그가 달린 항목을 대상으로 합니다.

# Match identity properties by meaning, not by format-specific name
with Metadata("board-report.docx") as metadata:
    removed = metadata.remove_properties(lambda p:
        Tags.person.creator in list(p.tags)     # Author, LastSavedBy
        or Tags.person.editor in list(p.tags)
        or Tags.person.manager in list(p.tags)
        or Tags.corporate.company in list(p.tags))
    metadata.save("board-report-clean.docx")

print(f"{removed} identity properties removed")

핵심 포인트:

  • 형식 독립성: 동일한 람다가 DOCX, XLSX, PPTX를 모두 정리합니다. 태그가 역할에 따라 분류하기 때문입니다.
  • 카운트 가능한 결과: remove_properties는 일치한 속성 수를 반환하며, 이는 감사 로그에 기록됩니다.
  • 복사 의미론: 새 경로에 저장하면 원본을 기록 보관용으로 유지할 수 있습니다.

💡 팁: 이 단계는 Title, Subject 및 기타 설명 필드를 보존하므로 파일이 검색 및 DMS 인덱싱에 친화적입니다.

방법 2: 속성 그룹을 위한 이름 패턴 제거

태그는 분류된 개념을 다룹니다. 그러나 누수 필드가 많은 속성 그룹(주석 속성, 수정 카운터, SharePoint 워크플로우 스탬프 등)은 태그 분류 밖에 있습니다. 이러한 경우 속성 이름 자체를 매칭합니다.

# Comment fields often live in custom properties the tag system
# does not classify, so match them by name substring
with Metadata("board-report.docx") as metadata:
    removed = metadata.remove_properties(lambda p:
        p.name is not None and (
            "Comment" in p.name
            or "Reviewer" in p.name
            or "Reviewed" in p.name))
    metadata.save("board-report-no-comments.docx")

다른 두 그룹도 같은 형태로 처리하면 되며, 단지 서브스트링 목록만 바뀝니다:

그룹 매칭할 서브스트링
수정 기록 Revision, TrackedChange, LastPrinted, TotalEditingTime, EditTime
서버 / 워크플로우 Server, Workflow, Approver, ContentType, Template

정밀도보다 범위를 우선시합니다: "Comment"CommentsCommentCount도 잡아내며, 이는 일반적인 정화 단계에서 원하는 동작입니다. 넓은 서브스트링은 무해한 템플릿 필드까지 잡을 수 있으므로 반환된 카운트를 기대값과 비교해 검증하십시오.

💡 팁: 감사 로그에 카테고리별 카운트가 필요하면 각 그룹을 별도 패스로 실행하고, 필요 없으면 서브스트링을 하나의 프레디케이트에 합칩니다.

방법 3: 한 번 호출로 전체 정화

파일이 조직을 떠나고 메타데이터 레이어에 남아서는 안 될 때는 프레디케이트를 작성하지 말고 한 번에 정리합니다.

# One call, every detected metadata package
with Metadata("board-report.docx") as metadata:
    removed = metadata.sanitize()
    metadata.save("board-report-final.docx")

print(f"sanitize() removed {removed} properties")

sanitize()는 라이브러리가 감지하는 모든 패키지를 삭제합니다: 문서 정보 신원 필드, 주석, 수정 기록, 추적 변경 작성자, 사용자 정의 OOXML 파트 등. 동작은 Clean metadata 페이지에 문서화되어 있습니다. 강점이 곧 비용이기도 합니다. Title과 Subject도 PII와 함께 사라지므로, 협업 워크플로우 중간이 아니라 내보내기 단계에서 사용해야 합니다.

모든 네 가지 대상 패스를 모두 수행해야 할까요?

필요하지 않습니다. 각 패스는 서로 다른 팀이 위험을 담당하기 때문에 존재합니다. 신원 필드는 개인정보 보호 담당자를, 주석 흔적은 법무팀을, 수정 카운터는 협상 담당자를, 서버 필드는 보안 팀을 불편하게 합니다. 검토자가 필요로 하는 필드에 맞춰 원하는 순서대로 패스를 실행하십시오. 살아있는 문서에 필드가 필요 없으면 바로 sanitize()로 건너뛰고 검증만 하면 됩니다.

세 가지 접근 방식 비교

방법 최적 활용 상황 주요 장점 제한 사항
태그 기반 제거 작업 복사본, 다중 형식 파이프라인 형식 독립적, 설명 필드 보존 태그로 분류된 개념만 커버
이름 패턴 제거 주석, 수정 기록, 서버 필드 태그가 놓치는 사용자 정의 속성 도달 환경별 서브스트링 튜닝 필요
전체 sanitize() 조직 외부 최종 내보내기 놓치는 속성이 없음 무해한 필드까지 삭제

이 접근 방식은 자연스럽게 조합됩니다: 문서가 살아있는 동안 대상 패스로 정화하고, 배포 시 sanitize()를 호출합니다.

신뢰하기 전에 검증하기

제거 호출이 카운트를 반환한다고 해서 파일이 깨끗하다는 증거는 아닙니다. 저장소는 모든 실행이 끝난 뒤 정화된 출력을 다시 열어 find_properties로 스캔합니다. 이때 위 모든 패스의 태그 규칙과 이름 규칙을 결합한 프레디케이트를 사용합니다.

def is_pii(p):
    if p.name is None:
        return False
    return (
        Tags.person.creator in list(p.tags)
        or Tags.person.editor in list(p.tags)
        or Tags.person.manager in list(p.tags)
        or Tags.corporate.company in list(p.tags)
        or any(n in p.name for n in (
            "Comment", "Reviewer", "Revision", "TrackedChange",
            "Classification", "Department", "Server", "Workflow")))

with Metadata("board-report-final.docx") as metadata:
    for p in metadata.find_properties(is_pii):
        value = (str(p.interpreted_value) if p.interpreted_value is not None
                 else (str(p.value) if p.value is not None else ""))
        if value and value not in ("0", "0.0"):
            print(f"LEAK {p.name}={value}")

저장소의 전체 버전은 살아남은 항목을 두 개의 버킷으로 구분하며, 구분이 중요합니다. 메타데이터 누출은 0이어야 합니다. 콘텐츠 수준 잔여물(Word 주석 풍선 및 word/document.xml 내부의 추적 변경)은 메타데이터 API가 접근할 수 없는 본문 콘텐츠이므로, Aspose.Words와 같은 콘텐츠 편집 라이브러리로 제거해야 합니다. 정직한 보고서는 두 버킷을 모두 명시하고 첫 번째만 승리했다고 선언하지 않습니다. 제가 “깨끗한” 파일에 대해 이 스캔을 처음 실행했을 때, 몇 달 동안 조용히 재추가되던 템플릿의 Department 필드가 표시되었습니다.

모범 사례 및 팁

  • 복사본을 정화하고 원본은 절대 수정하지 않기: 여기의 모든 스니펫은 새 경로에 저장하므로 원본을 기록 보관 및 보존 규칙에 따라 유지할 수 있습니다.
  • 카운트 로그 남기기: remove_propertiessanitize()의 반환값이 감사 추적이 됩니다. 파일당, 패스당 저장하십시오.
  • CI에 검증 단계 삽입: 누출 검사는 빌드를 실패하게 하여 템플릿 회귀를 발생 즉시 포착합니다.
  • 메타데이터와 콘텐츠 경계 인식: 본문 수준 주석이 남아 있는 상태에서 파일을 깨끗하다고 보고하지 말고, 별도 발견 항목으로 보고하십시오.
  • 라이선스: 평가 모드는 이 문서의 모든 예제를 재현합니다. 프로덕션에서는 라이선스를 사용해 평가 표시가 외부 파일에 남지 않도록 하십시오.

결론

세 가지 접근 방식, 하나의 결정 규칙. 개념이 태그로 분류되고 파일이 계속 사용되어야 하면 태그 기반 매칭을 사용하고, 속성 그룹이 사용자 정의 속성에 있다면 이름 매칭을 사용합니다. 파일이 신뢰 경계를 넘을 때는 sanitize()를 호출하고, 어떤 경로를 택했든 읽어‑돌아오는 스캔으로 검증하십시오.

더 깊이 파고들 준비가 되셨나요? 다음 단계가 있습니다:

추가 리소스

질문이 있거나 구현을 공유하고 싶다면 support forum에 문의하십시오.