Skip to content

METADATA: CUSTOM_PROPERTY scores HIGH 100 on the substring "confidential", so a Purview sensitivity label outranks a real secret #307

Description

@sectoramen

A custom document property is typed and scored by two mechanisms that have nothing to do with custom-property risk, and the purpose-built analyzer for it never runs.

What happens

inferMetadataType assigns CUSTOM_PROPERTY on the Custom_ name prefix alone (internal/validators/metadata/metadata_validator.go:1366). The confidence then comes from the generic metadata scorer CalculateConfidence, whose containsEnhancedCopyright check matches a bare substring anywhere in the line (metadata_validator.go:1596-1612):

copyrightPatterns := []string{
    "copyright", "©", "(c)", "all rights reserved", "proprietary",
    "confidential", "trade secret", "trademark", "patent",
}

A hit adds +0.4 (metadata_validator.go:396-399), which saturates the score at 100.

So the value Confidential — the standard Microsoft Purview / MSIP sensitivity label carried by a large fraction of enterprise documents — promotes the property to HIGH 100. That is the document stating its own classification marking, not a disclosure of sensitive data.

Measured on synthetic fixtures (one custom property each, identical benign body text):

custom property confidence validation_checks
MSIP_Label_<guid>_Name: Confidential 100 (HIGH) contains_enhanced_copyright
Notice: Proprietary 100 (HIGH) contains_enhanced_copyright
DocumentPurpose: Quarterly summary 60 (MEDIUM floor) none

Note (c) is also in that list as a three-character substring.

Why this matters

The confidence contract puts HIGH at >=90. A classification label reaching 100 sits above genuinely disclosed values: on a local corpus of 304 .docx files, CUSTOM_PROPERTY produced 292 HIGH findings out of 2,236 METADATA findings total — the single largest HIGH population in the run, ahead of AUTHOR_INFO (13 HIGH). HIGH is the band operators triage first, so the labels crowd out the findings that matter.

The analyzer that was written for this is dead on the live path

analyzeCustomPropertyRisk (metadata_validator.go:3065) already tiers custom properties properly — CRITICAL for classification, HIGH for PII/financial, MEDIUM for organizational — and stamps custom_prop_risk_level, custom_prop_name and custom_prop_risk_factors into the match metadata.

None of those three keys appear on any of the 506 CUSTOM_PROPERTY findings in the corpus run. The emitter that fires is a different code path, so the tiering never applies. This is the same dual-path split recorded in #251, which fixed the value-shape half and left the custom-property half on the generic scorer.

Suggested direction

  1. Route CUSTOM_PROPERTY scoring through analyzeCustomPropertyRisk so the tiering is load-bearing, and assert the custom_prop_* metadata keys are present — their absence is what makes this silently regress.
  2. Do not treat a classification label as a disclosure. A property whose value is only a label belongs in a distinct low-severity type, or should be demoted rather than vetoed (cf. the reserved-example-value reasoning).
  3. Word-bound the copyright patterns, and drop or gate (c).

Also, while reading: metadata_validator.go:1366-1377 contains the same if strings.HasPrefix(lineLower, "custom_") { return "CUSTOM_PROPERTY" } block twice, with an identical comment. The second copy is unreachable.

No customer content is quoted here; corpus figures are aggregate counts only and every value shown above is from a synthetic fixture.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions