Chio/Docs
LOGIN · JOIN

Labels and clearances

Find how owners, readers, and compartments combine, and why the order permits or refuses a transfer.

Reference chio@5f63e6e

On this page

Every transfer of labeled data compares two values: the information label, which states the restrictions that must survive, and the destination’s clearance, which states the restrictions it accepts. Ordinary flow is permitted when the destination keeps every source owner’s policy and contains every source compartment.

The direction runs against intuition: a more restrictive destination has fewer allowed readers. Adding a reader broadens disclosure; removing one narrows the destination.

Label orderChoose a destination.
ℓ⊑c\htmlClass{role-seal}{\ell \sqsubseteq c}
ℓ⋢c\htmlClass{role-deny}{\ell \not\sqsubseteq c}
ℓsourcelegal: {legal, reviewer}contract-review
ℓ⊑c\htmlClass{role-seal}{\ell \sqsubseteq c}
ℓ⋢c\htmlClass{role-deny}{\ell \not\sqsubseteq c}
ℓsource

An added reader broadens disclosure and is refused

The destination lets outsider read data whose owner permits only legal and reviewer. The smallest counterexample is that one extra reader.

legal: {legal, outsider, reviewer} · contract-review

The contract-review label from the construct_label example and six destination clearances, each changing one obligation. Allowed flows run up to destinations at least as restrictive as the source; Top, missing clearances, and recipient authentication are left out.

Retained label

The pricing lab’s host returns this label after the fixer reads Harbor Goods’ negotiated terms. The same value serves as a destination clearance when the receiver accepts all of these obligations.

Retained labeljson
{
  "kind": "known",
  "owners": {"larkfield-pricing": ["larkfield-pricing"]},
  "compartments": ["harbor-goods-terms"]
}

The owner larkfield-pricing permits only its own reader identity, and the harbor-goods-terms compartment separates one customer’s terms from every other customer’s. larkfield-pricing is an owner identifier, distinct from the worker’s signing key. A public destination such as a pull-request comment accepts Bottom, which carries no obligations, so the terms label cannot flow there; an internal destination that keeps this owner and compartment can receive it, once a trusted host binds the receiver to that clearance.

Label fields

A known label combines the policies of the information’s owners. A clearance uses the same fields to describe what a destination accepts.

kind

"known"Variant

A known label carries owners and compartments. InformationLabel::bottom() is the known label with neither, for data established as public. InformationLabel::Top is restrictive uncertainty.

Example"known"

owners

map of owner to readersAt most 64 owners

Each owner is a principal whose information policy must survive the transfer, mapped to the readers that owner permits. Every owner belongs to its own reader set; a label that omits it fails to construct.

Example{"harbor-goods": ["harbor-goods", "fixer"]}

readers

set of principalsAt most 256 per owner

The principals one owner permits to receive the information, including the owner itself. Fewer readers make a destination more restrictive.

Example["harbor-goods", "fixer"]

compartments

set of compartmentsAt most 64

Separation requirements carried with the information. Every source compartment must be present at the destination.

Example["harbor-goods-terms"]

Choosing owners and readers

Start with who owns the information and which recipients each owner permits. Your pricing team may permit its agents to read a customer’s price sheet while Harbor Goods permits a smaller group to read its NDA; an analysis combining both keeps both policies, a distinction a single “confidential” tag would lose.

Choose identifiers with stable organizational meanings and bind their use through trusted configuration; a string such as larkfield-pricing authenticates no receiver by itself. Readers set too broadly permit unintended disclosure, and readers set too narrowly push routine internal work through an exception path, so review labels against who receives summaries and delegated results.

Flow relation

For every source owner, the destination keeps that owner and allows only readers the source already permits. Every source compartment is present at the destination. For Harbor Goods’ terms as the source, owner harbor-goods with readers harbor-goods and fixer and compartment harbor-goods-terms:

  • Same owners, readers, and compartments: allowed, every obligation survives.
  • A reader removed, owner kept: allowed, fewer principals receive the data.
  • An owner or compartment added: allowed, the destination is more restrictive.
  • A reader added to a source owner’s policy: refused, disclosure broadens.
  • A source owner dropped: refused, that owner’s policy disappears.
  • A source compartment dropped: refused, a required separation disappears.
In Rust: the known-label order
crates/security/chio-security-types/src/flow.rs401–425rust
    pub fn flows_to(&self, destination: &Self) -> bool {
        match (self, destination) {
            (_, Self::Top) => true,
            (Self::Top, Self::Known { .. }) => false,
            (
                Self::Known {
                    owners: source_owners,
                    compartments: source_compartments,
                },
                Self::Known {
                    owners: destination_owners,
                    compartments: destination_compartments,
                },
            ) => {
                source_compartments.is_subset(destination_compartments)
                    && source_owners.iter().all(|(owner, source_readers)| {
                        destination_owners
                            .get(owner)
                            .is_some_and(|destination_readers| {
                                destination_readers.is_subset(source_readers)
                            })
                    })
            }
        }
    }

Order and recipient authority

The order compares information restrictions. The destination obtains its clearance through the application’s trusted configuration, and the surrounding operation still passes capability and caller checks, so two compatible labels never make an arbitrary URL a trusted internal service. Adding a principal to a reader set issues it no capability: the data policy and the operation’s authority must agree, and a refused transfer is diagnosed against both.

Join

crates/security/chio-security-types/src/flow.rs362–398rust
    pub fn join_restrictions(&self, other: &Self) -> Result<Self, LabelValidationError> {
        let (
            Self::Known {
                owners: left_owners,
                compartments: left_compartments,
            },
            Self::Known {
                owners: right_owners,
                compartments: right_compartments,
            },
        ) = (self, other)
        else {
            return Ok(Self::Top);
        };

        let mut owners = left_owners.clone();
        for (owner, right_readers) in right_owners {
            match owners.entry(owner.clone()) {
                Entry::Vacant(entry) => {
                    entry.insert(right_readers.clone());
                }
                Entry::Occupied(mut entry) => {
                    let readers = entry
                        .get()
                        .intersection(right_readers)
                        .cloned()
                        .collect::<BTreeSet<_>>();
                    entry.insert(readers);
                }
            }
        }
        let compartments = left_compartments
            .union(right_compartments)
            .cloned()
            .collect();
        Self::try_known(owners, compartments)
    }

Joining your pricing team’s terms with Harbor Goods’ NDA keeps both owners. For an owner in both labels, the join intersects the allowed readers, and it unions the compartments. Joining an unrestricted label adds nothing, so a public input cannot reset a restricted session.

The default protocol limits are 64 owners, 256 readers per owner, and 64 compartments. try_known_with_limits constructs a label under tighter bounds; join_restrictions validates its result against the defaults. The enclosing wire input is byte-bounded before deserialization, because bounded collection cardinality alone leaves an incoming message unbounded.

Combined reader sets

Harbor Goods’ price sheet permits the pricing team, your pricing lead, and Harbor Goods’ contact; Ostrander’s permits the pricing team, the pricing lead, and Ostrander’s contact. After both reads, a combined analysis keeps both policies and the remaining readers are the pricing team and the pricing lead, so neither customer’s contact can receive a result drawn from both. Harbor Goods’ own NDA stays a separate obligation beside your team’s set, which makes a clearance that carries only your team’s obligation insufficient, and dropping either the terms or the NDA compartment broadens disclosure.

Calculate the source with join_restrictions, then check the destination with authorize_egress, which also rejects a missing clearance and a Top clearance. Here first, second, and expected_common_readers are labels with the reader sets above; the reader-intersection program builds them and runs the assertions.

examples/reader_intersection.rs · in mainrust
let combined = first.join_restrictions(&second)?;
assert_eq!(combined, expected_common_readers);
assert_eq!(authorize_egress(&combined, Some(&combined)), Ok(()));
assert_eq!(
    authorize_egress(&combined, Some(&first)),
    Err(EgressDenial::FlowViolation),
);

Join by hand

Take two documents owned by larkfield-pricing: the first permits larkfield-pricing, fixer, and reviewer, the second larkfield-pricing and fixer. A workflow that learns both satisfies both, so the readers are larkfield-pricing and fixer. A document owned by harbor-goods adds a separate owner entry beside your team’s policy, and different compartments are both required.

Input relationshipJoin operationExample result
The same owner appears twice
Intersect that owner’s readers
larkfield-pricing, fixer survives both policies
A new owner appears
Keep the new owner’s policy
Your team’s and Harbor Goods’ obligations remain
Different compartments appear
Union the compartments
harbor-goods-terms and harbor-goods-nda are required
One input is known public
Keep existing obligations
The restricted label is unchanged
One input is unknown Top
Keep Top
Ordinary egress is unavailable

These assertions use labels from the pricing lab and make no network request. The surrounding function returns a compatible error type, and first and second are validated InformationLabel values. A failed join refuses to construct the combined label; substituting bottom() would discard the obligations the join exists to keep.

src/main.rs · in a function returning Resultrust
let joined = first.join_restrictions(&second)?;
assert!(first.flows_to(&joined));
assert!(second.flows_to(&joined));
assert_eq!(
    joined.join_restrictions(&InformationLabel::bottom())?,
    joined,
);

Bottom and Top

InformationLabel::bottom() is known unrestricted data, for a source the application’s classification policy establishes as public. InformationLabel::Top is restrictive uncertainty, including history that verified evidence cannot reconstruct.

The lattice relation and the operational egress decision are separate APIs. The relation allows flow into Top; authorize_egress rejects Top as a destination clearance, so a catch-all unknown label never becomes an export permission.

Constructing a label

Harbor Goods permits itself and the fixer to receive its contract terms, and its destination keeps that owner and compartment. The program accepts the original policy and a narrower reader set, then refuses an outside buyer, basalt, as an extra reader, a missing compartment, and a public destination. Run it in the security-research project.

security-research/examples/construct_label.rsrust
use chio_security_types::{Compartment, InformationLabel, PrincipalId};
use std::collections::{BTreeMap, BTreeSet};

fn harbor_goods(readers: &[&str], compartments: &[&str]) -> InformationLabel {
    InformationLabel::try_known(
        BTreeMap::from([(
            PrincipalId::new("harbor-goods").unwrap(),
            readers.iter().map(|name| PrincipalId::new(*name).unwrap()).collect(),
        )]),
        compartments.iter().map(|name| Compartment::new(*name).unwrap()).collect(),
    ).unwrap()
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let source = harbor_goods(&["harbor-goods", "fixer"], &["harbor-goods-terms"]);
    let narrower = harbor_goods(&["harbor-goods"], &["harbor-goods-terms"]);
    let broader = harbor_goods(&["harbor-goods", "fixer", "basalt"], &["harbor-goods-terms"]);
    let missing_compartment = harbor_goods(&["harbor-goods", "fixer"], &[]);
    assert!(source.flows_to(&source));
    assert!(source.flows_to(&narrower));
    assert!(!source.flows_to(&broader));
    assert!(!source.flows_to(&missing_compartment));
    assert!(!source.flows_to(&InformationLabel::bottom()));
    assert_eq!(source.join_restrictions(&narrower)?, narrower);
    assert_eq!(source.join_restrictions(&InformationLabel::bottom())?, source);
    let owner = PrincipalId::new("harbor-goods")?;
    assert!(InformationLabel::try_known(
        BTreeMap::from([(owner, BTreeSet::from([PrincipalId::new("fixer")?]))]),
        BTreeSet::new(),
    ).is_err());
    println!("ALLOW same or narrower reader set: Harbor Goods obligation retained");
    println!("REFUSE Basalt as a reader, a missing compartment and public clearance");
    println!("JOIN same owner: readers intersect; public input adds no permission");
    println!("REFUSE malformed label: owner must belong to its own reader set");
    Ok(())
}
Terminalbash
cargo run --locked --quiet --example construct_label
Output
ALLOW same or narrower reader set: Harbor Goods obligation retained
REFUSE Basalt as a reader, a missing compartment and public clearance
JOIN same owner: readers intersect; public input adds no permission
REFUSE malformed label: owner must belong to its own reader set

The destination with fewer readers is more restrictive and compatible. Adding the outside buyer broadens disclosure, and removing the compartment loses an obligation even when the reader names match. The constructor check is separate: a known label whose owner is missing from its own readers fails to construct. Recipient authentication and the tool’s authority belong to the host.

Diagnosing a refused transfer

  1. Owners. For each source owner, find the matching destination owner. An absent owner is the reason for the refusal.
  2. Readers. For each owner, compare the destination’s readers with the source’s. One extra destination reader widens disclosure.
  3. Compartments. Check that every source compartment exists at the destination.
  4. Operational checks. If the order holds and egress still refuses, check whether the resolved source is Top, the clearance is absent, or the destination is Top. authorize_egress names each as its own EgressDenial.

A serving host keeps the EgressDenial out of the caller’s refusal; control egress shows where each host records it. Repair classification and policy at their trusted source, and leave the payload’s caller metadata alone.

Collection bounds matter as a workflow reads more sources. When a join exceeds the default limits, join_restrictions returns an error; the flow engine refuses an admission join as a state overflow and raises an output join to Top. Either way the application stops or redesigns the task’s information boundary, since truncating the owner set would discard the obligations the join keeps.

Next: send labeled data to a destination