Every workbook touched by SharePoint, OneDrive for Business, or a Microsoft 365 tenant with Microsoft Information Protection enabled carries a folder almost nobody opens: xl/customXml/. Inside it sit one or more custom XML parts — arbitrary XML payloads attached to the workbook by the platform that produced it. The two most common payloads are SharePoint content type properties (the document-library column values that travel with the file when it leaves the library) and the Microsoft Information Protection sensitivity-label metadata that names the corporate tenant, the label GUID, the encryption protector, and the user who applied the classification. Document Inspector touches some of this and leaves the rest behind. This post walks through where custom XML parts live in the XLSX, how to decode the SharePoint and MIP payloads, what they reveal about your organisation, and how to actually strip every classification artefact before shipping a workbook externally.
The Office Open XML specification defines a generic extensibility hook called a custom XML part: any application can attach an arbitrary XML payload to a workbook, name it with a content type, and reference it through the package’s relationship graph. Excel itself uses very few custom XML parts — it has dedicated parts for nearly everything — but the surrounding Microsoft 365 platform uses them heavily. Two large families of metadata ride this hook into every workbook the platform touches:
Custom XML parts are designed to be opaque to Excel: the application stores them and ships them but does not interpret their contents. That design decision is exactly what makes them a metadata leak. The platform that wrote them knows what is inside; everyone downstream who unzips the file does too.
An XLSX is a ZIP archive. Custom XML parts cluster inside xl/customXml/ (Excel’s convention) and occasionally at the package root in customXml/ when written by older tools. Each payload comes as a triplet of files.
// XLSX layout fragment showing custom XML part storage
workbook.xlsx (zip)
├── [Content_Types].xml // declares customXml content types
├── _rels/.rels // package-root relationship to customXml/item1.xml
├── xl/
│ ├── workbook.xml
│ └── customXml/
│ ├── item1.xml // <-- the actual payload
│ ├── itemProps1.xml // <-- schema reference + datastoreItem ID
│ ├── _rels/
│ │ └── item1.xml.rels // links item1 to itemProps1
│ ├── item2.xml // <-- second payload (often the MIP label)
│ ├── itemProps2.xml
│ └── _rels/item2.xml.rels
└── docProps/
The triplet matters. itemN.xml carries the data. itemPropsN.xml carries a stable datastoreItem ID (a GUID that identifies the part across edits) and a list of namespace URIs that the data uses, which is how SharePoint figures out which schema the payload belongs to. The relationships file links the two, plus any embedded XSL or XSD if the platform shipped one. Strip item1.xml alone and you leave behind a dangling relationship; strip the props alone and SharePoint round-trips refuse to round-trip; strip neither and the whole metadata layer ships intact.
Each platform that wants to attach metadata grabs the next free itemN.xml slot. A workbook from a heavily integrated tenant routinely carries three or four custom XML parts: one for SharePoint content type properties, one for the MIP sensitivity label, one for a Purview retention label, and sometimes a fourth for an enterprise DLP scanner’s classification result. The numbering itself is meaningful only locally; the namespace URIs inside itemPropsN.xml are what disambiguate the payload type.
When a workbook is uploaded to a SharePoint or Teams document library and edited there, SharePoint serialises the values of every custom column on the file’s content type into a custom XML part. The namespace is consistent: it is rooted at http://schemas.microsoft.com/office/2006/metadata/properties, and the inner element is documentManagement.
// xl/customXml/item1.xml — SharePoint content type properties
<p:properties xmlns:p="http://schemas.microsoft.com/office/2006/metadata/properties">
<documentManagement>
<Department>Corporate Finance — M&A</Department>
<ProjectCode>PROJECT-ATLAS-2026</ProjectCode>
<Confidentiality>Restricted — Deal Team Only</Confidentiality>
<DealStage>LOI Drafted</DealStage>
<TargetCompany>Acme Industrial Holdings</TargetCompany>
<ContentTypeId>0x010100A1F2C3D4E5F60718293A4B5C6D7E8F9A</ContentTypeId>
<_dlc_DocId>CONTOSO-CFM-12849</_dlc_DocId>
<TaxKeywordTaxHTField0>L0|#valuation;diligence;synergy-model</TaxKeywordTaxHTField0>
</documentManagement>
</p:properties>
Every element name is a column on the document library. Every value is the column value at the moment the file was last saved into SharePoint. The internal column names are unedited — they retain the words the library administrator originally chose, including project codenames, deal nicknames, and classification taxonomies that your organisation may consider internal. Move the file to a USB stick and open it in a text editor; the column values are still there.
| Field | What it carries | What it reveals |
|---|---|---|
| ContentTypeId | Hexadecimal ID | Names the content type and its parent chain. The first 32 bytes after 0x0101 identify the site collection’s document content type, which lets a reader infer the SharePoint site the file came from. |
| _dlc_DocId | Document ID Service token | The site-collection prefix (here CONTOSO-CFM) names the SharePoint site collection. The trailing integer is a sequential document number that hints at total document volume. |
| TaxKeyword* | Managed metadata terms | The pipe-delimited string carries term-store IDs that resolve against the tenant’s managed metadata service. Frequently used for retention, jurisdiction, or matter-number tagging. |
| Custom columns | Free-text or choice-field values | Whatever the library admin decided to track: deal codenames, target company names, project IDs, confidentiality tiers, even “OwnerOfRecord” columns that name a specific employee. |
The element name itself is metadata. A library schema with columns called DealCodename, TargetCompany, and DealStage tells a reader they are looking at an M&A workbook even before they read the values. Renaming the file does nothing — the schema travels in the XML.
Microsoft Information Protection (MIP — the platform that powers Office Sensitivity labels and Microsoft Purview Information Protection) writes a separate custom XML part. The namespace is rooted at http://schemas.microsoft.com/office/2020/mipLabelMetadata, and inside is a single labelList element with one or more labels.
// xl/customXml/item2.xml — MIP sensitivity label
<clbl:labelList xmlns:clbl="http://schemas.microsoft.com/office/2020/mipLabelMetadata">
<clbl:label
id="{87867195-f2b8-4ac2-b0b6-6bb73cb33afd}"
enabled="1"
method="Privileged"
siteId="{3a1b6b0c-9e44-4a2d-9f17-4e1c8b8a6c01}"
contentBits="0"
removed="0"
/>
</clbl:labelList>
The label payload looks small but every attribute is meaningful, and several of them resolve to organisation-identifying values through public Microsoft Graph endpoints.
| Attribute | What it carries | What it reveals |
|---|---|---|
| id | Label GUID | A tenant-scoped identifier for the specific label (“Confidential — Deal Team”, “Internal”, etc.). Reusing the GUID across many files lets a reader cluster files by classification without reading any cell content. |
| siteId | Tenant ID GUID | The Microsoft 365 tenant ID. A single Graph call (/v1.0/tenantRelationships/findTenantInformationByTenantId) maps it to the company name and primary domain. |
| method | Application method | Common values: Standard (user picked), Privileged (admin/policy), Auto (DLP rule). Tells a reader whether the file was classified by hand, by policy, or by a scanner — which itself reveals organisational maturity and tooling. |
| contentBits | Visual-marking bitmask | Indicates which marking types are applied: header, footer, watermark. Even without seeing the document, a reader knows what visual markings should be on every page. |
| removed | Downgrade flag | Set to 1 if the label was removed, with a justification recorded in core.xml. The presence of a downgrade is itself a forensic event — someone reduced the protection. |
The MIP siteId attribute is, by design, your tenant’s Azure AD directory ID. It is the same GUID that appears in OAuth flows, Graph calls, and federation metadata for the tenant. A workbook with no author name, no UPN, and no SharePoint properties still names the corporate tenant by GUID the moment a sensitivity label is applied. Stripping core.xml alone does nothing.
MIP labels can do two things: classify (a metadata tag) or protect (encrypt the file with a tenant-controlled key). The classification half lives in the custom XML part above. The protection half adds a parallel set of custom document properties in docProps/custom.xml, with names that follow a strict template:
// docProps/custom.xml — MIP classification mirror in Custom Properties
<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/custom-properties">
<property fmtid="{D5CDD505-2E9C-101B-9397-08002B2CF9AE}"
pid="2" name="MSIP_Label_87867195-f2b8-4ac2-b0b6-6bb73cb33afd_Enabled">
<vt:lpwstr>true</vt:lpwstr>
</property>
<property pid="3" name="MSIP_Label_87867195-f2b8-4ac2-b0b6-6bb73cb33afd_SetDate">
<vt:lpwstr>2026-04-19T08:14:32Z</vt:lpwstr>
</property>
<property pid="4" name="MSIP_Label_87867195-f2b8-4ac2-b0b6-6bb73cb33afd_Method">
<vt:lpwstr>Privileged</vt:lpwstr>
</property>
<property pid="5" name="MSIP_Label_87867195-f2b8-4ac2-b0b6-6bb73cb33afd_Name">
<vt:lpwstr>Confidential — Deal Team Only</vt:lpwstr>
</property>
<property pid="6" name="MSIP_Label_87867195-f2b8-4ac2-b0b6-6bb73cb33afd_SiteId">
<vt:lpwstr>3a1b6b0c-9e44-4a2d-9f17-4e1c8b8a6c01</vt:lpwstr>
</property>
<property pid="7" name="MSIP_Label_87867195-f2b8-4ac2-b0b6-6bb73cb33afd_ActionId">
<vt:lpwstr>{d4e5f607-1829-3a4b-5c6d-7e8f9a0b1c2d}</vt:lpwstr>
</property>
</Properties>
The custom-properties mirror exists for backwards compatibility: older Office builds and third-party tools that do not understand the labelList namespace can still read the label name, the set date, the method, and the tenant ID through the well-known property naming convention. The label name is plain text. The label-applied date has second precision. The action ID is a per-application GUID that ties this specific label application to a record in the Microsoft Purview activity feed.
_Name is the human-readable label. Reading it gives you the organisation’s classification taxonomy verbatim — “Confidential — Deal Team Only”, “Restricted — Legal Hold”, “Public — Press Release”._SetDate records when the label was applied. If a workbook was downgraded, you see both the old label’s set date and the new label’s, allowing reconstruction of the classification history._SiteId is the tenant GUID. Same beacon as the custom XML payload, and now it lives in two places._ActionId ties to Purview audit logs. An adversary cannot read your audit log, but the GUID is itself unique enough to fingerprint a specific user-action event across files.Each itemN.xml has a sibling itemPropsN.xml that names the schemas the payload uses and assigns it a stable datastore ID.
// xl/customXml/itemProps1.xml
<ds:datastoreItem
ds:itemID="{c5b1e6a7-3f2d-4a8b-9e3a-4c5d6e7f8091}"
xmlns:ds="http://schemas.openxmlformats.org/officeDocument/2006/customXml">
<ds:schemaRefs>
<ds:schemaRef ds:uri="http://schemas.microsoft.com/office/2006/metadata/properties"/>
<ds:schemaRef ds:uri="http://schemas.microsoft.com/sharepoint/v3"/>
<ds:schemaRef ds:uri="http://schemas.microsoft.com/sharepoint/v3/contenttype/forms"/>
</ds:schemaRefs>
</ds:datastoreItem>
The datastore item ID is stable across SharePoint round-trips, which means a workbook that leaves the platform, gets edited, and is uploaded back can still be matched to its original library entry by GUID. From a privacy perspective the schema reference list is also informative: an analyst sees sharepoint/v3 URIs and immediately knows the file lived in SharePoint Online, regardless of whether any data values give it away.
A motivated reader who unzips an XLSX with custom XML parts can reconstruct, with no special tooling, an unusually complete picture of the file’s organisational origin.
siteId, the _SiteId custom property, or the SharePoint _dlc_DocId prefix names the tenant unambiguously.DealCodename, MatterNumber, ResearchProjectId reveal which library the file lived in and what kind of work the library tracks._Owner.contentBits bitmask names the watermark, header, and footer text the document is supposed to display, even if the file has no header at all.itemPropsN.xml identify which platforms and applications wrote the parts, including third-party systems like iManage or NetDocuments.Three quick recipes pull the SharePoint, MIP, and custom-property data out of a workbook without ever launching Office.
# 1. List every custom XML part and its schema reference
unzip -l workbook.xlsx | grep -E "customXml|docProps/custom"
# 2. Dump every custom XML payload
for f in $(unzip -l workbook.xlsx | awk '/customXml.item[0-9]+\.xml$/{print $4}'); do
echo "### $f ###"
unzip -p workbook.xlsx "$f"
done
# 3. Pull every MSIP_Label_* property
unzip -p workbook.xlsx "docProps/custom.xml" | \
xmlstarlet sel -t -m "//*[contains(@name,'MSIP_Label')]" -v "@name" -o " = " -v "." -n
For programmatic auditing, a small Python script enumerates every payload and decodes the SharePoint and MIP fields:
import zipfile, re
from xml.etree import ElementTree as ET
SP = "{http://schemas.microsoft.com/office/2006/metadata/properties}"
MIP = "{http://schemas.microsoft.com/office/2020/mipLabelMetadata}"
with zipfile.ZipFile("workbook.xlsx") as z:
for name in z.namelist():
if re.match(r"xl/customXml/item\d+\.xml", name):
root = ET.fromstring(z.read(name))
if root.tag.startswith(SP[:-1]):
print("SharePoint payload:", name)
for child in root.iter():
if child.text and child.text.strip():
print(" ", child.tag.split("}")[-1], "=", child.text)
elif root.tag.startswith(MIP[:-1]):
print("MIP label payload:", name)
for label in root.iter(MIP + "label"):
print(" label", dict(label.attrib))
The output is a flat dump of every SharePoint column value and every MIP label that ships with the workbook — the layer Document Inspector shows you nothing about.
Document Inspector exposes a checkbox called Custom XML Data and a separate one called Document Properties and Personal Information. In practice their behaviour is asymmetric.
xl/customXml/itemN.xml payloads, but only for parts whose schemaRefs do not match a list of Microsoft-blessed namespaces. SharePoint content type properties are typically caught; the MIP labelList namespace is treated as system metadata and frequently survives.MSIP_Label_* entries from docProps/custom.xml. Office’s logic treats them as system properties because removing them would also remove the encryption protection. So they ship intact.docProps/core.xml. Even after this, the historical _SetDate and _Justification remain.itemPropsN.xml are removed alongside their payloads, but [Content_Types].xml overrides occasionally remain, leaving a structural fingerprint that something was removed.A clean removal touches five artefacts: the xl/customXml/ folder, every MSIP_Label_* property in docProps/custom.xml, the package-root and folder-level .rels entries, and the relevant [Content_Types].xml overrides. Four approaches in increasing order of robustness.
Sensitivity dropdown > Remove (or pick a Public/None label), provide a justification, save. Then File > Info > Check for Issues > Inspect Document and tick both Document Properties and Personal Information and Custom XML Data. Verify by unzipping and confirming xl/customXml/ is empty and docProps/custom.xml contains no MSIP_Label_* entries.
Saving the workbook as Strict Open XML Spreadsheet (.xlsx with the strict conformance class) and then converting it back to transitional .xlsx drops most non-conformant custom XML parts. This is a useful sanity step but is not 100 % reliable across Office builds — treat it as a complement, not a replacement.
A short Python script removes every custom XML artefact and every MIP label custom property in a single pass and rewrites the relationship and content-type files:
import zipfile, re
from pathlib import Path
src, dst = Path("in.xlsx"), Path("out.xlsx")
DROP = ("xl/customXml/", "customXml/")
REL_PAT = rb"<Relationship[^/]*customXml[^/]*/>"
OVR_PAT = rb"<Override[^/]*customXml[^/]*/>"
MIP_PAT = rb"<property[^>]*name=\"MSIP_Label_[^\"]+\"[^>]*>.*?</property>"
with zipfile.ZipFile(src) as zin, \
zipfile.ZipFile(dst, "w", zipfile.ZIP_DEFLATED) as zout:
for item in zin.infolist():
if any(item.filename.startswith(p) for p in DROP):
continue
data = zin.read(item.filename)
if item.filename.endswith(".rels"):
data = re.sub(REL_PAT, b"", data)
if item.filename == "[Content_Types].xml":
data = re.sub(OVR_PAT, b"", data)
if item.filename == "docProps/custom.xml":
data = re.sub(MIP_PAT, b"", data, flags=re.DOTALL)
zout.writestr(item, data)
Excel reopens the resulting file cleanly. Because no worksheet XML element references custom XML payloads by ID, no further fix-up is required. If the workbook was protected by a MIP encryption template, this script will not remove the encryption itself — you must downgrade the label inside Office first or the file will not open.
For organisations sharing workbooks regularly, build a server-side step into the file-sharing pipeline that strips xl/customXml/, removes MSIP_Label_* properties, and verifies no SharePoint or MIP namespaces remain anywhere in the package. This composes well with parallel sanitisation steps for threaded comments and personas, external links, defined names, and printer settings — the same pipeline can clean every layer in one pass.
Programmatically removing MIP label metadata from a workbook circumvents the protection your tenant configured for it. Many organisations require the label to remain on the file at all times and instead control redistribution at the protection (encryption) layer. Before deploying a stripping pipeline, confirm with your information-security team that downgrading or removing labels is permitted for the specific use case — and prefer the supported “Remove label” flow inside Office, which records a justification, over a script that silently deletes the metadata.
Run this checklist against any workbook leaving your organisation, especially if it has lived in SharePoint or has been touched by a Microsoft 365 Sensitivity workflow.
xl/customXml/ contains any itemN.xml payloads?itemPropsN.xml and identified whether the source is SharePoint, MIP, a third-party DMS, or something custom?_dlc_DocId reveal the SharePoint site collection prefix, and is that prefix something I would not want a recipient to see?siteId attribute that names the corporate Microsoft 365 tenant GUID, even if the label name itself is innocuous?docProps/custom.xml contain MSIP_Label_* entries with label names that disclose the organisation’s sensitivity taxonomy?_Method attribute and noted whether the label was applied automatically, by policy, or by hand — and confirmed the implication of that channel is acceptable to disclose?[Content_Types].xml for orphan Override entries pointing at customXml content types that no longer exist in the package?xl/customXml/ and the MIP custom properties entirely, plus their .rels and content-type entries?Custom XML parts are the metadata layer that ride into your workbooks the moment SharePoint, OneDrive for Business, or a Microsoft 365 sensitivity workflow touches the file. They sit in xl/customXml/ as a triplet of payload, schema reference, and relationship. They are mirrored in docProps/custom.xml as MSIP_Label_* entries with the tenant GUID, the label name, and the application timestamp. Document Inspector handles the SharePoint half competently and the MIP half barely at all, with the result that the average enterprise workbook leaves the organisation carrying a complete classification beacon, the library schema it lived in, and a tenant-naming GUID that any reader can resolve through Microsoft Graph.
For workbooks staying inside an organisation, the layer is exactly what it is supposed to be: a useful classification record that drives DLP, retention, and downstream protection. For workbooks crossing the perimeter, it is a tenant fingerprint, a taxonomy disclosure, and a SharePoint provenance trail rolled into one. The only durable defence is to strip the custom XML folder and the MIP custom properties together — coordinated with the rest of the metadata-layer cleanup — before the workbook ever leaves the trusted boundary. A small ZIP-level operation closes a leak that most security teams never knew was open.
Use MetaData Analyzer to enumerate every custom XML payload, decode SharePoint content type properties, surface MIP sensitivity labels and the tenant GUIDs they encode, and confirm both layers are gone before your workbooks leave the organisation.
The other major identity-leak surface the Document Inspector misses, with parallel cleanup steps.
How SharePoint, OneDrive, and other cloud platforms layer their own metadata onto workbooks.
Where every metadata layer lives inside an XLSX, including the customXml and docProps folders.