💡 전체 작동 예제는 GitHub에서 확인할 수 있습니다:
scrub-office-document-pii-dotnet
The Old Way Was Painful
일반적인 절차는 다음과 같습니다. 문서를 열고, 파일 → 정보 → 문제 확인 → 문서 검사, 체크박스를 선택하고, 모두 제거, 새 이름으로 저장, 닫기, 다음 파일을 엽니다. 40개의 파일을 처리한 뒤에 누군가가 문서 검사가 레코드 인덱스 키에 사용되는 제목까지 제거했으며, 한 시간 전에 보낸 복사본에는 SharePoint 승인자 ID가 남아 있었던 이유는 파일이 다른 애플리케이션에서 저장될 때 해당 필드가 다시 기록되었기 때문이라고 알아냅니다.
Metadata PII 제거는 .NET용 GroupDocs.Metadata 기능으로, Office 문서에서 신원 정보를 담고 있는 속성을 프로그래밍 방식으로 삭제하고 남은 내용을 보고합니다. 수동 절차는 세 가지 문제점이 있습니다. 파일 몇 개만 처리할 수 있을 정도로 확장성이 없고, 어떤 필드를 삭제할지 전부 혹은 전무 방식이며, 무엇이 제거됐는지 기록이 남지 않습니다. 이 문서에서는 동일한 작업을 .NET 버전으로, 속성 그룹별로 하나씩 수행하는 방법을 보여줍니다.
실제로 어떤 메타데이터가 들어 있는지 아는 것이 도움이 됩니다. 검토 라운드를 거친 Word 파일은 일반적으로 저장한 사람의 Windows 계정에서 온 Author와 LastSavedBy, 기업 템플릿에서 온 Manager와 Company, 수정 횟수, TotalEditingTime, LastPrinted 타임스탬프, 댓글 스레드 카운터 등을 포함합니다. SharePoint를 추가하면 승인자 식별자, 워크플로 경로, 콘텐츠 유형 URI, 문서가 생성된 템플릿 등도 포함됩니다. 이 모든 정보는 페이지에 보이지 않으며, 동일한 파일 안에 함께 저장됩니다.
There’s a Better Way
GroupDocs.Metadata for .NET의 모든 기능은 하나의 속성 검색 엔진을 통해 동작합니다. RemoveProperties는 MetadataProperty에 대한 람다를 받아 해당 람다에서 허용한 모든 속성을 삭제하고 삭제된 개수를 반환합니다. FindProperties는 같은 람다를 실행하지만 실제 삭제는 하지 않습니다. 속성에는 태그가 포함될 수 있어, Tags.Person.Creator는 형식과 패키지에 관계없이 저자와 같은 필드를 식별합니다(리터럴 이름이 애플리케이션마다 다를 때 유용).
이렇게 하면 하나의 버튼이 아니라 세 가지 형태의 정리가 가능합니다: 신원 필드에 대한 태그 패스, 댓글·수정 등과 같은 패밀리 이름 패스, 그리고 아무것도 남기지 않을 때 사용하는 Sanitize(). 세 가지 모두 개수를 반환하며, 이 개수가 각 패스를 감사 가능하게 만듭니다.
The New Way: A Predicate per Property Group
Step 1 - Clear the names
네 개의 태그 검사가 신원 그룹을 포괄합니다. 설명 필드는 건드리지 않으며, 이는 Document Inspector의 “Remove All”과 차별점입니다.
using (var metadata = new Metadata(inputPath))
{
if (metadata.FileFormat == FileFormat.Unknown) return 0;
var affected = metadata.RemoveProperties(p =>
p.Tags.Contains(Tags.Person.Creator) ||
p.Tags.Contains(Tags.Person.Editor) ||
p.Tags.Contains(Tags.Person.Manager) ||
p.Tags.Contains(Tags.Corporate.Company));
metadata.Save(outputPath);
return affected;
}
FileFormat.Unknown 검사는 0을 정직하게 반환하도록 보호합니다. 이 검사가 없으면 읽을 수 없는 파일과 정상 파일이 호출자에게 동일하게 보입니다.
Step 2 - Clear the families around them
댓글, 수정, 서버 필드에는 태그가 없으므로 이름으로 매칭합니다. 편집 타임라인은 가장 자주 놓치는 그룹이며, 문서가 어떻게 생성됐는지를 나타내는 중요한 정보입니다.
using (var metadata = new Metadata(inputPath))
{
if (metadata.FileFormat == FileFormat.Unknown) return 0;
var affected = metadata.RemoveProperties(p =>
p.Name != null && (
p.Name.Contains("Revision") ||
p.Name.Contains("TrackedChange") ||
p.Name.Contains("LastPrinted") ||
p.Name.Contains("TotalEditingTime") ||
p.Name.Contains("EditTime")));
metadata.Save(outputPath);
return affected;
}
부분 문자열 매칭은 의도적인 설계입니다. CommentsCount와 Comment, TotalEditingTime와 EditTime을 정확한 이름 리스트 없이도 포착합니다. SharePoint 패스는 동일한 호출에 Server, Workflow, Approver, ContentType, Template을 추가하면 됩니다.
Step 3 - Wipe everything, then check the result
신뢰 경계에서는 네 개의 패스를 하나의 호출로 대체합니다.
using (var metadata = new Metadata(inputPath))
{
if (metadata.FileFormat == FileFormat.Unknown) return 0;
var affected = metadata.Sanitize();
metadata.Save(outputPath);
return affected;
}
그 다음은 수동 절차에 없는 검증 단계입니다. FindProperties를 통해 제거용 람다를 재사용하고, 살아남은 속성을 두 리스트로 분류합니다.
foreach (var p in props)
{
var value = p.InterpretedValue?.ToString() ?? p.Value?.ToString() ?? string.Empty;
if (string.IsNullOrWhiteSpace(value)) continue;
if (value == "0" || value == "0.0") continue;
var entry = $"{p.Name}={value}";
var name = p.Name ?? string.Empty;
if (name.StartsWith("Comment") || name.StartsWith("Revision"))
report.ContentLevelLeaks.Add(entry);
else
report.MetadataLeaks.Add(entry);
}
MetadataLeaks가 비어 있어야 파일이 완전히 정리된 것으로 간주됩니다. ContentLevelLeaks는 정보용이며, Word 댓글 및 추적 변경 저자는 word/document.xml에 존재하므로 메타데이터 API가 아니라 Aspose.Words와 같은 콘텐츠 편집 라이브러리로 처리해야 합니다.
Why not just call Sanitize on everything?
대부분의 문서는 아직 사용 중이기 때문입니다. Sanitize()는 감지된 모든 패키지를 삭제하며, 여기에는 레코드 시스템과 검색 인덱스가 의존하는 Title, Subject, Keywords도 포함됩니다. 파일이 내부에서 순환할 때는 대상 패스를 사용해 설명 메타데이터는 유지하고, 조직을 떠나는 복사본에 대해서만 전체 삭제를 수행하십시오.
Side-by-Side: Before vs. After
| Manual inspection | GroupDocs.Metadata for .NET | |
|---|---|---|
| Selectivity | Remove All, descriptive fields included | one predicate per property group |
| Coverage | dialog에 표시되는 필드만 | 라이브러리가 감지하는 모든 패키지, 사용자 정의 OOXML 파트 포함 |
| Record | 없음 | 작업당 반환되는 affected count |
| Verification | 재열고 확인 | FindProperties 스캔으로 메타데이터와 콘텐츠 레벨 리스트 제공 |
| Batch of 200 files | 200번 클릭 | 하나의 루프, 다섯 번의 연산, 파일당 한 줄 로그 |
행동 변화를 일으키는 핵심은 Record 행입니다. 각 패스가 개수를 반환하면 정리 작업이 기억에 의존하는 단계가 아니라 파이프라인이 검증할 수 있는 데이터가 됩니다: 테스트 임계값, 감사 테이블 필드, 야간 작업 실패 조건 등. 수동 절차는 어느 정도 엄격히 관리해도 이런 기록을 만들 수 없습니다.
Real-World Example: The Pre-Send Hook
지원 포털에서는 직원이 고객 티켓에 문서를 첨부할 수 있습니다. 이제 첨부 파일 핸들러는 파일이 저장되기 전에 신원 패스와 서버 패스를 실행하고, 두 개의 카운트를 티켓에 기록한 뒤 저장된 복사본에 대해 누수 검사를 수행합니다. 메타데이터 누수 리스트가 비어 있지 않으면, 해당 속성을 명시한 메시지와 함께 업로드가 거부됩니다. 따라서 파일을 첨부한 사람은 고객에게 전달되기 전에 즉시 문제를 알 수 있습니다.
두 가지 세부 사항이 이 훅을 실용적으로 만듭니다. 패스가 새 경로에 저장되므로 원본은 직원 개인 저장소에 남아 자동 단계에 의해 파괴되지 않습니다. 또한 카운트가 첨부 파일 옆 티켓 레코드에 저장되므로 “이 문서에서 무엇이 제거됐는가”에 대한 답이 추정이 아니라 저장된 숫자가 됩니다.
첫 번째 템플릿에 이 검사를 적용했을 때, Manager 값이 정체성 패스에 의해 몇 초 전에 제거됐지만 기업 템플릿이 저장 시 다시 기록한 것을 발견했습니다. 제거 호출은 문서대로 동작했으며, 문제는 파이프라인 자체였으며, 읽어온 결과만이 이를 드러냈습니다.
What Else Can You Do with GroupDocs.Metadata?
같은 Predicate 엔진을 활용할 수 있습니다. 두 버전의 문서 속성을 비교하면 소유권 변경 및 리뷰 과정 외의 재작성 여부를 파악할 수 있으며, metadata scrubbing overview에서는 인터랙티브 도구가 API 기반 패스와 어떻게 보완되는지 설명합니다. 태그 시스템이 포맷을 초월하므로 여기서 작성한 신원 Predicate는 PDF, 이미지, 오디오 파일에도 수정 없이 적용됩니다.
이식성을 염두에 두고 설계하면 좋습니다. MetadataProperty에 대한 람다로 작성된 정리 규칙은 일반 C#이므로 공유 라이브러리에 두고, 고정 문서, 예약 레코드 작업, 릴리스 전 문서 첨부 파일 정리 등 필요한 서비스에서 재사용할 수 있습니다. 규칙은 한 곳에 유지되고 호출 위치만 바뀝니다.
Conclusion
네 개의 대상 패스, 하나의 전체 정리, 하나의 검증 스캔. 이 조합은 Office 문서에 실용적인 범위를 모두 커버합니다: 파일이 순환 중일 때는 설명 메타데이터를 유지하고, 조직을 떠날 때는 모든 것을 삭제하며, 결과를 언제든 증명할 수 있습니다. 샘플을 복제하고 실제 리뷰 라운드를 거친 문서에 실행해 보세요. 반환되는 affected count는 예상보다 높을 것이며, 이것이 바로 핵심 포인트입니다.