What selective-disclosure invariants must survive cross-agent attribute proofs?
**Date of Record:** 2026 October 27 **Scope:** Applies when an agentic session proves an attribute about itself to a peer (e.g., "I am authorized for role X," "my data is from compliant source Y," "I have been audited for Z") without revealing the underlying evidence, using selective disclosure or zero-knowledge proofs. Does not apply to sessions with full transparency (no selective disclosure needed) or attributes that cannot be proven without full disclosure.
Core Distinction
The separation between **attribute possession** (the agent actually has the attribute) and **selective proof validity** (the cryptographic proof correctly demonstrates the attribute without revealing extra information). Relying on possession creates the **Selective Proof Over-Disclosure**: the proof is valid, but the proof itself leaks more than the attribute—perhaps revealing the underlying evidence, the exact value within a range, or the timestamps of attestations—and the receiver learns more than they needed to know.
What Was Observed
In privacy-preserving agent architectures, failing to control selective disclosure produces three primary failure modes:
- **Range Over-Specification:** The agent proves "my experience is >5 years," but the proof reveals the exact value (8 years, 3 months). The attribute is proven, but more information leaked than necessary.
- **Evidence Linkage:** The agent proves "my training data is GDPR-compliant," but the proof reveals the specific attestation ID. The receiver, correlating with other attestations, can identify the agent's training corpus.
- **Correlation Reconstruction:** Multiple selective proofs, individually minimal, allow the receiver to reconstruct information the agent did not intend to disclose (e.g., "has attribute A" + "has attribute B" + "not attribute C" → unique identification).
Recorded Elements
- **`attribute_scope_declaration_receipt`:** For each selective proof, the exact attribute scope (what is proven, and what is not proven), so the receiver cannot infer more than claimed.
- **`zero_knowledge_proof_parameters_signature`:** The cryptographic parameters of the proof (which ZK scheme, what information is provably hidden), so the receiver can verify the proof's privacy guarantees.
- **`proof_composition_safety_receipt`:** For multiple proofs, the cryptographic proof that composing them does not leak additional information beyond the sum of their individual disclosures.
- **`correlation_resistance_signature`:** Evidence that the specific combination of selective proofs does not enable unique identification or attribute reconstruction.
Condition of Validity
* **Applies when:** Agents prove attributes selectively, receivers may infer beyond what is proven, and privacy of the underlying evidence matters. * **Does not apply when:** Full disclosure is acceptable, or attributes cannot be selectively proven.
Decision Boundary
- **Is the proof a selective disclosure?** If no, full disclosure applies. If yes, proceed.
- **Does the `attribute_scope_declaration_receipt` clearly bound what is proven?** If yes, the receiver knows what they can infer. If no, the receiver may infer beyond the intent.
- **Is `proof_composition_safety_receipt` present for multi-attribute proofs?** If yes, the composition is verified. If no, the combination may leak.
- **Conversely, when can a selective proof be accepted without full privacy verification?** Only when the proof is for a single attribute with documented scope, and no other proofs will be combined with it.
Operational Effect
- **Over-Disclosure Prevention:** Attribute scope is bounded, preventing inference beyond intent.
- **Composition Safety:** Multi-attribute proofs are verified not to leak in combination.
- **Correlation Resistance:** Proofs are designed to resist reconstruction attacks.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json
class SelectiveDisclosureVerdict(Enum):
FULL_DISCLOSURE = auto()
PROOF_VALID_BOUNDED = auto()
OVER_DISCLOSURE_DETECTED = auto()
COMPOSITION_LEAK_RISK = auto()
@dataclass(frozen=True)
class SelectiveProof:
proof_id: str
proven_attribute: str
proven_scope: frozenset[str]
leaked_scope: frozenset[str]
zk_scheme: str
composition_safe: bool
@dataclass(frozen=True)
class ProofSet:
proofs: tuple[SelectiveProof, ...]
full_disclosure: bool
@dataclass(frozen=True)
class SelectivePolicy:
allow_composition: bool
max_acceptable_leakage: int
@dataclass(frozen=True)
class SelectiveManifest:
verdict: SelectiveDisclosureVerdict
directive: str
manifest_digest: str
class SelectiveDisclosureGuard:
@staticmethod
def _hash(data: str) -> str:
return hashlib.sha256(data.encode("utf-8")).hexdigest()
@classmethod
def evaluate_proof_set(
cls, proof_set: ProofSet, policy: SelectivePolicy
) -> SelectiveManifest:
# Gate 1: Full disclosure
if proof_set.full_disclosure:
digest = cls._hash(json.dumps({
"proofs": str(len(proof_set.proofs)),
"type": "FULL_DISCLOSURE"
}, sort_keys=True))
return SelectiveManifest(
verdict=SelectiveDisclosureVerdict.FULL_DISCLOSURE,
directive=f"PROCEED: Full disclosure, {len(proof_set.proofs)} proofs.",
manifest_digest=digest
)
# Gate 2: Over-disclosure check per proof
for proof in proof_set.proofs:
leaked_outside_scope = proof.leaked_scope - proof.proven_scope
if len(leaked_outside_scope) > policy.max_acceptable_leakage:
return SelectiveManifest(
verdict=SelectiveDisclosureVerdict.OVER_DISCLOSURE_DETECTED,
directive=f"HALT: Proof {proof.proof_id} leaks {sorted(leaked_outside_scope)} beyond scope.",
manifest_digest=cls._hash(f"OVER:{proof.proof_id}")
)
# Gate 3: Composition safety
if len(proof_set.proofs) > 1:
if policy.allow_composition:
unsafe = [p.proof_id for p in proof_set.proofs if not p.composition_safe]
if unsafe:
return SelectiveManifest(
verdict=SelectiveDisclosureVerdict.COMPOSITION_LEAK_RISK,
directive=f"HALT: {len(unsafe)} proofs unsafe for composition.",
manifest_digest=cls._hash(f"UNSAFE_COMP:{len(unsafe)}")
)
# Gate 4: Bounded
digest = cls._hash(json.dumps({
"proofs": tuple(p.proof_id for p in proof_set.proofs),
"attributes": tuple(p.proven_attribute for p in proof_set.proofs),
"scopes": tuple(sorted(str(sorted(p.proven_scope)) for p in proof_set.proofs))
}, sort_keys=True))
return SelectiveManifest(
verdict=SelectiveDisclosureVerdict.PROOF_VALID_BOUNDED,
directive=f"PROCEED: {len(proof_set.proofs)} selective proofs bounded and composition-safe.",
manifest_digest=digest
)