SIFR tracks how sensitive data moves through a program at compile time. A value annotated as Secret cannot be passed to a logger, printed, or returned from a public API without an explicit label change; the compiler rejects the code. The same mechanism covers integrity: data from an untrusted source cannot silently flow into a context that requires trusted input.
The library models this with phantom type parameters on a wrapper type. No runtime cost is involved; the labels exist only in the type system.
Every value is wrapped in Labeled<T, C, I> where C is a confidentiality label and I is an integrity label. Both are zero-cost with no runtime overhead.
The crate ships two built-in label sets.
Confidentiality: Public ≤ Internal ≤ Secret. A Public value can be used where a Secret is expected; the reverse is a compile error.
Integrity: Trusted at the top, Untrusted at the bottom. A Trusted value flows freely to Untrusted. Going the other direction requires relabel!.
use sifr::{Labeled, Secret, Trusted};
let password: Labeled<String, Secret, Trusted> = Labeled::new("hunter2".into());relabel! changes the label of a value. Outside a labeled_branch! block it is unconditional. Inside one, the compiler checks that the program-counter label is no higher than the target; a violation is a compile error.
use sifr::{relabel, Labeled, Public, Secret, Trusted};
// raise: always allowed
let s: Labeled<i32, Secret, Trusted> = relabel!(Labeled::<i32, Public, Trusted>::new(1), Secret, Trusted);
// lower: allowed here because there is no enclosing branch
let p: Labeled<i32, Public, Trusted> = relabel!(Labeled::<i32, Secret, Trusted>::new(1), Public, Trusted);Branching on a Labeled value is an implicit flow. labeled_branch! tracks the program-counter label for the branch body and rejects any relabel! that the condition label would forbid.
use sifr::{labeled_branch, Labeled, Secret, Trusted};
let cond: Labeled<bool, Secret, Trusted> = Labeled::new(true);
labeled_branch!(if cond {
// pc = Secret.
// relabel!(val, Public, Trusted) is a compile error here.
});Supported constructs: if, if let, else if, while, while let, match. In an else if chain the pc is the join of all conditions evaluated so far.
#[derive(Label)] builds a label lattice from an enum. The derive macro computes the transitive closure and verifies that every pair of labels has a unique least upper bound.
use sifr::Label;
#[derive(Label)]
pub enum Clearance {
Public,
#[above(Public)]
Confidential,
#[above(Confidential)]
TopSecret,
}Diamond lattices:
#[derive(Label)]
pub enum Compartment {
Unclassified,
#[above(Unclassified)]
Alpha,
#[above(Unclassified)]
Beta,
#[above(Alpha, Beta)]
Full,
}Derived labels implement both ConfLabel and IntegLabel and can be used in either position.
labeled_call! and labeled_get! call ordinary functions with Labeled arguments. The result label is the join of all argument labels.
use sifr::{labeled_call, labeled_get, Labeled, Public, Secret, Trusted};
fn add(a: i32, b: i32) -> i32 { a + b }
let x: Labeled<i32, Public, Trusted> = Labeled::new(2);
let y: Labeled<i32, Secret, Trusted> = Labeled::new(40);
let sum = labeled_call!(add(x, y)); // Labeled<i32, Secret, Trusted>
let s: Labeled<String, Secret, Trusted> = Labeled::new("hello".into());
let len = labeled_get!(s.len()); // Labeled<usize, Secret, Trusted>The standard arithmetic and bitwise operators (+, -, *, /, %, &, |, ^, <<, >>, unary -, !) work on Labeled values. The result label is the join of the operand labels.
PartialEq is not implemented because it would return a plain bool, stripping the label. Use labeled_eq, labeled_ne, labeled_lt, labeled_le, labeled_gt, labeled_ge instead.
use sifr::{labeled_eq, Labeled, Public, Secret, Trusted};
let a: Labeled<i32, Public, Trusted> = Labeled::new(1);
let b: Labeled<i32, Secret, Trusted> = Labeled::new(1);
let eq: Labeled<bool, Secret, Trusted> = labeled_eq(&a, &b);Debug and Display are implemented only when C: CanFlowTo<Public>. Formatting a value whose confidentiality label is Internal or Secret is a compile error.