How must an instance prevent latent feature leakage through correlated public features?
**Date of Record:** 2026 October 27 **Scope:** Applies when an agentic session publishes or shares feature representations (embeddings, statistical summaries, or metadata) where some features are intended to be private, but public features correlate strongly enough with private features to enable inference of the private features from public data. Does not apply to strictly public data or data where correlation-based inference is not possible.
Core Distinction
The separation between **feature-level privacy** (each private feature is not published) and **correlation-based privacy** (private features cannot be inferred from published features via their correlation). Relying on feature-level creates the **Latent Feature Leakage**: no private feature is explicitly published, but the published features' correlations with private features enable reconstruction of the private features with high accuracy, defeating the privacy intent.
What Was Observed
In feature-sharing agent architectures, failing to prevent correlated leakage produces three primary failure modes:
- **Correlation Inference:** The session publishes features A, B, C. Private feature X is not published. An adversary observes that X = f(A, B, C) with high R², and infers X.
- **Proxy Feature Exploitation:** The session publishes proxy features that correlate with private features without explicit names (e.g., "location cluster ID" correlates with "income bracket"), and adversaries use proxies to infer private features.
- **Aggregate Reconstruction:** The session publishes aggregate statistics (means, medians) that do not reveal individual records, but by querying aggregates with specific filters, adversaries reconstruct private records.
Recorded Elements
- **`private-feature-classification-receipt`:** The exact features classified as private, with the basis (regulatory, contractual, or policy), so the privacy boundary is explicit.
- **`public-feature-correlation-matrix-receipt`:** The pairwise correlation between public features and private features, computed on historical data, so correlation-based leakage is measurable.
- **`correlation-threshold-signature`:** The maximum acceptable correlation between public features and any private feature, beyond which the public feature must be suppressed, redacted, or decorrelated.
- **`decorrelation-receipt`:** For public features that exceed the correlation threshold, the decorrelation transformation applied (noise injection, binning, or suppression), so leakage is bounded.
Condition of Validity
* **Applies when:** The session publishes features, some features are private, and public features can correlate with private ones. * **Does not apply when:** All features are strictly public, or correlations between public and private are verified near-zero.
Decision Boundary
- **Does the session publish features with private counterparts?** If no, standard feature publishing applies. If yes, proceed.
- **Is `public-feature-correlation-matrix-receipt` computed?** If yes, correlations are measurable. If no, leakage is assumed.
- **Does any public feature exceed `correlation-threshold-signature`?** If yes, apply `decorrelation-receipt`. If no, publishing is safe.
- **Conversely, when can a session publish a high-correlation public feature?** Only when the correlation is verified to be spurious (causal confounding absent), or the private feature is explicitly authorized for correlated release, or the feature is aggregated with high noise.
Operational Effect
- **Correlation Inference Prevention:** Public features with private correlations are decorrelated or suppressed.
- **Proxy Detection:** Proxy features are caught before they enable inference.
- **Aggregate Reconstruction Prevention:** Aggregates are protected against filter-based reconstruction.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json
class LeakageVerdict(Enum):
NO_PRIVATE_FEATURES = auto()
WITHIN_CORRELATION_THRESHOLD = auto()
DECORRELATION_APPLIED = auto()
LEAKAGE_DETECTED = auto()
@dataclass(frozen=True)
class FeatureSet:
featureset_id: str
public_features: tuple[str, ...]
private_features: tuple[str, ...]
correlation_matrix: tuple[tuple[str, str, float], ...] # public, private, correlation
decorrelation_applied: tuple[str, ...]
@dataclass(frozen=True)
class LeakagePolicy:
max_acceptable_correlation: float
require_decorrelation_above_threshold: bool
@dataclass(frozen=True)
class LeakageManifest:
verdict: LeakageVerdict
directive: str
manifest_digest: str
class LatentFeatureLeakageGuard:
@staticmethod
def _hash(data: str) -> str:
return hashlib.sha256(data.encode("utf-8")).hexdigest()
@classmethod
def evaluate_featureset(
cls, featureset: FeatureSet, policy: LeakagePolicy
) -> LeakageManifest:
# Gate 1: No private features
if not featureset.private_features:
digest = cls._hash(json.dumps({
"featureset": featureset.featureset_id,
"private": "NONE"
}, sort_keys=True))
return LeakageManifest(
verdict=LeakageVerdict.NO_PRIVATE_FEATURES,
directive=f"PROCEED: FeatureSet {featureset.featureset_id} has no private features.",
manifest_digest=digest
)
# Gate 2: High correlations
high_corr = [
(pub, priv, corr)
for pub, priv, corr in featureset.correlation_matrix
if abs(corr) > policy.max_acceptable_correlation
]
# Gate 3: Decorrelation
if high_corr and policy.require_decorrelation_above_threshold:
undecorrelated = [
(pub, priv, corr)
for pub, priv, corr in high_corr
if pub not in featureset.decorrelation_applied
]
if undecorrelated:
return LeakageManifest(
verdict=LeakageVerdict.LEAKAGE_DETECTED,
directive=f"HALT: {len(undecorrelated)} public features correlate >{policy.max_acceptable_correlation:.2f} with private without decorrelation.",
manifest_digest=cls._hash(f"LEAKAGE:{featureset.featureset_id}")
)
digest = cls._hash(json.dumps({
"featureset": featureset.featureset_id,
"decorrelated": tuple(pub for pub, _, _ in high_corr)
}, sort_keys=True))
return LeakageManifest(
verdict=LeakageVerdict.DECORRELATION_APPLIED,
directive=f"PROCEED: {len(high_corr)} high-correlation features decorrelated.",
manifest_digest=digest
)
# Gate 4: Within threshold
max_corr = max((abs(c) for _, _, c in featureset.correlation_matrix), default=0)
digest = cls._hash(json.dumps({
"featureset": featureset.featureset_id,
"max_correlation": f"{max_corr:.3f}",
"threshold": f"{policy.max_acceptable_correlation:.3f}"
}, sort_keys=True))
return LeakageManifest(
verdict=LeakageVerdict.WITHIN_CORRELATION_THRESHOLD,
directive=f"PROCEED: Max correlation {max_corr:.2f} within threshold.",
manifest_digest=digest
)